cfgv


Namecfgv JSON
Version 3.4.0 PyPI version JSON
download
home_pagehttps://github.com/asottile/cfgv
SummaryValidate configuration and produce human readable error messages.
upload_time2023-08-12 20:38:17
maintainer
docs_urlNone
authorAnthony Sottile
requires_python>=3.8
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![build status](https://github.com/asottile/cfgv/actions/workflows/main.yml/badge.svg)](https://github.com/asottile/cfgv/actions/workflows/main.yml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/cfgv/main.svg)](https://results.pre-commit.ci/latest/github/asottile/cfgv/main)

cfgv
====

Validate configuration and produce human readable error messages.

## Installation

```bash
pip install cfgv
```

## Sample error messages

These are easier to see by example.  Here's an example where I typo'd `true`
in a [pre-commit](https://pre-commit.com) configuration.

```
pre_commit.clientlib.InvalidConfigError:
==> File /home/asottile/workspace/pre-commit/.pre-commit-config.yaml
==> At Config()
==> At key: repos
==> At Repository(repo='https://github.com/pre-commit/pre-commit-hooks')
==> At key: hooks
==> At Hook(id='flake8')
==> At key: always_run
=====> Expected bool got str
```

## API

### `cfgv.validate(value, schema)`

Perform validation on the schema:
- raises `ValidationError` on failure
- returns the value on success (for convenience)

### `cfgv.apply_defaults(value, schema)`

Returns a new value which sets all missing optional values to their defaults.

### `cfgv.remove_defaults(value, schema)`

Returns a new value which removes all optional values that are set to their
defaults.

### `cfgv.load_from_filename(filename, schema, load_strategy, exc_tp=ValidationError)`

Load a file given the `load_strategy`.  Reraise any errors as `exc_tp`.  All
defaults will be populated in the resulting value.

Most useful when used with `functools.partial` as follows:

```python
load_my_cfg = functools.partial(
    cfgv.load_from_filename,
    schema=MY_SCHEMA,
    load_strategy=json.loads,
    exc_tp=MyError,
)
```

## Making a schema

A schema validates a container -- `cfgv` provides `Map` and `Array` for
most normal cases.

### writing your own schema container

If the built-in containers below don't quite satisfy your usecase, you can
always write your own.  Containers use the following interface:

```python
class Container(object):
    def check(self, v):
        """check the passed in value (do not modify `v`)"""

    def apply_defaults(self, v):
        """return a new value with defaults applied (do not modify `v`)"""

    def remove_defaults(self, v):
        """return a new value with defaults removed (do not modify `v`)"""
```

### `Map(object_name, id_key, *items)`

The most basic building block for creating a schema is a `Map`

- `object_name`: will be displayed in error messages
- `id_key`: will be used to identify the object in error messages.  Set to
  `None` if there is no identifying key for the object.
- `items`: validator objects such as `Required` or `Optional`

Consider the following schema:

```python
Map(
    'Repo', 'url',
    Required('url', check_any),
)
```

In an error message, the map may be displayed as:

- `Repo(url='https://github.com/pre-commit/pre-commit')`
- `Repo(url=MISSING)` (if the key is not present)

### `Array(of, allow_empty=True)`

Used to nest maps inside of arrays.  For arrays of scalars, see `check_array`.

- `of`: A `Map` / `Array` or other sub-schema.
- `allow_empty`: when `False`, `Array` will ensure at least one element.

When validated, this will check that each element adheres to the sub-schema.

## Validator objects

Validator objects are used to validate key-value-pairs of a `Map`.

### writing your own validator

If the built-in validators below don't quite satisfy your usecase, you can
always write your own.  Validators use the following interface:

```python
class Validator(object):
    def check(self, dct):
        """check that your specific key has the appropriate value in `dct`"""

    def apply_default(self, dct):
        """modify `dct` and set the default value if it is missing"""

    def remove_default(self, dct):
        """modify `dct` and remove the default value if it is present"""
```

It may make sense to _borrow_ functions from the built in validators.  They
additionally use the following interface(s):

- `self.key`: the key to check
- `self.check_fn`: the [check function](#check-functions)
- `self.default`: a default value to set.

### `Required(key, check_fn)`

Ensure that a key is present in a `Map` and adheres to the
[check function](#check-functions).

### `RequiredRecurse(key, schema)`

Similar to `Required`, but uses a [schema](#making-a-schema).

### `Optional(key, check_fn, default)`

If a key is present, check that it adheres to the
[check function](#check-functions).

- `apply_defaults` will set the `default` if it is not present.
- `remove_defaults` will remove the value if it is equal to `default`.

### `OptionalRecurse(key, schema, default)`

Similar to `Optional` but uses a [schema](#making-a-schema).

- `apply_defaults` will set the `default` if it is not present and then
  validate it with the schema.
- `remove_defaults` will remove defaults using the schema, and then remove the
  value it if it is equal to `default`.

### `OptionalNoDefault(key, check_fn)`

Like `Optional`, but does not `apply_defaults` or `remove_defaults`.

### `Conditional(key, check_fn, condition_key, condition_value, ensure_absent=False)`

- If `condition_key` is equal to the `condition_value`, the specific `key`
will be checked using the [check function](#check-functions).
- If `ensure_absent` is `True` and the condition check fails, the `key` will
be checked for absense.

Note that the `condition_value` is checked for equality, so any object
implementing `__eq__` may be used.  A few are provided out of the box
for this purpose, see [equality helpers](#equality-helpers).

### `ConditionalOptional(key, check_fn, default, condition_key, condition_value, ensure_absent=False)`

Similar to ``Conditional`` and ``Optional``.

### `ConditionalRecurse(key, schema, condition_key, condition_value, ensure_absent=True)`

Similar to `Conditional`, but uses a [schema](#making-a-schema).

### `NoAdditionalKeys(keys)`

Use in a mapping to ensure that only the `keys` specified are present.

## Equality helpers

Equality helpers at the very least implement `__eq__` for their behaviour.

They may also implement `def describe_opposite(self):` for use in the
`ensure_absent=True` error message (otherwise, the `__repr__` will be used).

### `Not(val)`

Returns `True` if the value is not equal to `val`.

### `In(*values)`

Returns `True` if the value is contained in `values`.

### `NotIn(*values)`

Returns `True` if the value is not contained in `values`.

## Check functions

A number of check functions are provided out of the box.

A check function takes a single parameter, the `value`, and either raises a
`ValidationError` or returns nothing.

### `check_any(_)`

A noop check function.

### `check_type(tp, typename=None)`

Returns a check function to check for a specific type.  Setting `typename`
will replace the type's name in the error message.

For example:

```python
Required('key', check_type(int))
# 'Expected bytes' in both python2 and python3.
Required('key', check_type(bytes, typename='bytes'))
```

Several type checking functions are provided out of the box:

- `check_bool`
- `check_bytes`
- `check_int`
- `check_string`
- `check_text`

### `check_one_of(possible)`

Returns a function that checks that the value is contained in `possible`.

For example:

```python
Required('language', check_one_of(('javascript', 'python', 'ruby')))
```

### `check_regex(v)`

Ensures that `v` is a valid python regular expression.

### `check_array(inner_check)`

Returns a function that checks that a value is a sequence and that each
value in that sequence adheres to the `inner_check`.

For example:

```python
Required('args', check_array(check_string))
```

### `check_and(*fns)`

Returns a function that performs multiple checks on a value.

For example:

```python
Required('language', check_and(check_string, my_check_language))
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/asottile/cfgv",
    "name": "cfgv",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "",
    "author": "Anthony Sottile",
    "author_email": "asottile@umich.edu",
    "download_url": "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz",
    "platform": null,
    "description": "[![build status](https://github.com/asottile/cfgv/actions/workflows/main.yml/badge.svg)](https://github.com/asottile/cfgv/actions/workflows/main.yml)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/cfgv/main.svg)](https://results.pre-commit.ci/latest/github/asottile/cfgv/main)\n\ncfgv\n====\n\nValidate configuration and produce human readable error messages.\n\n## Installation\n\n```bash\npip install cfgv\n```\n\n## Sample error messages\n\nThese are easier to see by example.  Here's an example where I typo'd `true`\nin a [pre-commit](https://pre-commit.com) configuration.\n\n```\npre_commit.clientlib.InvalidConfigError:\n==> File /home/asottile/workspace/pre-commit/.pre-commit-config.yaml\n==> At Config()\n==> At key: repos\n==> At Repository(repo='https://github.com/pre-commit/pre-commit-hooks')\n==> At key: hooks\n==> At Hook(id='flake8')\n==> At key: always_run\n=====> Expected bool got str\n```\n\n## API\n\n### `cfgv.validate(value, schema)`\n\nPerform validation on the schema:\n- raises `ValidationError` on failure\n- returns the value on success (for convenience)\n\n### `cfgv.apply_defaults(value, schema)`\n\nReturns a new value which sets all missing optional values to their defaults.\n\n### `cfgv.remove_defaults(value, schema)`\n\nReturns a new value which removes all optional values that are set to their\ndefaults.\n\n### `cfgv.load_from_filename(filename, schema, load_strategy, exc_tp=ValidationError)`\n\nLoad a file given the `load_strategy`.  Reraise any errors as `exc_tp`.  All\ndefaults will be populated in the resulting value.\n\nMost useful when used with `functools.partial` as follows:\n\n```python\nload_my_cfg = functools.partial(\n    cfgv.load_from_filename,\n    schema=MY_SCHEMA,\n    load_strategy=json.loads,\n    exc_tp=MyError,\n)\n```\n\n## Making a schema\n\nA schema validates a container -- `cfgv` provides `Map` and `Array` for\nmost normal cases.\n\n### writing your own schema container\n\nIf the built-in containers below don't quite satisfy your usecase, you can\nalways write your own.  Containers use the following interface:\n\n```python\nclass Container(object):\n    def check(self, v):\n        \"\"\"check the passed in value (do not modify `v`)\"\"\"\n\n    def apply_defaults(self, v):\n        \"\"\"return a new value with defaults applied (do not modify `v`)\"\"\"\n\n    def remove_defaults(self, v):\n        \"\"\"return a new value with defaults removed (do not modify `v`)\"\"\"\n```\n\n### `Map(object_name, id_key, *items)`\n\nThe most basic building block for creating a schema is a `Map`\n\n- `object_name`: will be displayed in error messages\n- `id_key`: will be used to identify the object in error messages.  Set to\n  `None` if there is no identifying key for the object.\n- `items`: validator objects such as `Required` or `Optional`\n\nConsider the following schema:\n\n```python\nMap(\n    'Repo', 'url',\n    Required('url', check_any),\n)\n```\n\nIn an error message, the map may be displayed as:\n\n- `Repo(url='https://github.com/pre-commit/pre-commit')`\n- `Repo(url=MISSING)` (if the key is not present)\n\n### `Array(of, allow_empty=True)`\n\nUsed to nest maps inside of arrays.  For arrays of scalars, see `check_array`.\n\n- `of`: A `Map` / `Array` or other sub-schema.\n- `allow_empty`: when `False`, `Array` will ensure at least one element.\n\nWhen validated, this will check that each element adheres to the sub-schema.\n\n## Validator objects\n\nValidator objects are used to validate key-value-pairs of a `Map`.\n\n### writing your own validator\n\nIf the built-in validators below don't quite satisfy your usecase, you can\nalways write your own.  Validators use the following interface:\n\n```python\nclass Validator(object):\n    def check(self, dct):\n        \"\"\"check that your specific key has the appropriate value in `dct`\"\"\"\n\n    def apply_default(self, dct):\n        \"\"\"modify `dct` and set the default value if it is missing\"\"\"\n\n    def remove_default(self, dct):\n        \"\"\"modify `dct` and remove the default value if it is present\"\"\"\n```\n\nIt may make sense to _borrow_ functions from the built in validators.  They\nadditionally use the following interface(s):\n\n- `self.key`: the key to check\n- `self.check_fn`: the [check function](#check-functions)\n- `self.default`: a default value to set.\n\n### `Required(key, check_fn)`\n\nEnsure that a key is present in a `Map` and adheres to the\n[check function](#check-functions).\n\n### `RequiredRecurse(key, schema)`\n\nSimilar to `Required`, but uses a [schema](#making-a-schema).\n\n### `Optional(key, check_fn, default)`\n\nIf a key is present, check that it adheres to the\n[check function](#check-functions).\n\n- `apply_defaults` will set the `default` if it is not present.\n- `remove_defaults` will remove the value if it is equal to `default`.\n\n### `OptionalRecurse(key, schema, default)`\n\nSimilar to `Optional` but uses a [schema](#making-a-schema).\n\n- `apply_defaults` will set the `default` if it is not present and then\n  validate it with the schema.\n- `remove_defaults` will remove defaults using the schema, and then remove the\n  value it if it is equal to `default`.\n\n### `OptionalNoDefault(key, check_fn)`\n\nLike `Optional`, but does not `apply_defaults` or `remove_defaults`.\n\n### `Conditional(key, check_fn, condition_key, condition_value, ensure_absent=False)`\n\n- If `condition_key` is equal to the `condition_value`, the specific `key`\nwill be checked using the [check function](#check-functions).\n- If `ensure_absent` is `True` and the condition check fails, the `key` will\nbe checked for absense.\n\nNote that the `condition_value` is checked for equality, so any object\nimplementing `__eq__` may be used.  A few are provided out of the box\nfor this purpose, see [equality helpers](#equality-helpers).\n\n### `ConditionalOptional(key, check_fn, default, condition_key, condition_value, ensure_absent=False)`\n\nSimilar to ``Conditional`` and ``Optional``.\n\n### `ConditionalRecurse(key, schema, condition_key, condition_value, ensure_absent=True)`\n\nSimilar to `Conditional`, but uses a [schema](#making-a-schema).\n\n### `NoAdditionalKeys(keys)`\n\nUse in a mapping to ensure that only the `keys` specified are present.\n\n## Equality helpers\n\nEquality helpers at the very least implement `__eq__` for their behaviour.\n\nThey may also implement `def describe_opposite(self):` for use in the\n`ensure_absent=True` error message (otherwise, the `__repr__` will be used).\n\n### `Not(val)`\n\nReturns `True` if the value is not equal to `val`.\n\n### `In(*values)`\n\nReturns `True` if the value is contained in `values`.\n\n### `NotIn(*values)`\n\nReturns `True` if the value is not contained in `values`.\n\n## Check functions\n\nA number of check functions are provided out of the box.\n\nA check function takes a single parameter, the `value`, and either raises a\n`ValidationError` or returns nothing.\n\n### `check_any(_)`\n\nA noop check function.\n\n### `check_type(tp, typename=None)`\n\nReturns a check function to check for a specific type.  Setting `typename`\nwill replace the type's name in the error message.\n\nFor example:\n\n```python\nRequired('key', check_type(int))\n# 'Expected bytes' in both python2 and python3.\nRequired('key', check_type(bytes, typename='bytes'))\n```\n\nSeveral type checking functions are provided out of the box:\n\n- `check_bool`\n- `check_bytes`\n- `check_int`\n- `check_string`\n- `check_text`\n\n### `check_one_of(possible)`\n\nReturns a function that checks that the value is contained in `possible`.\n\nFor example:\n\n```python\nRequired('language', check_one_of(('javascript', 'python', 'ruby')))\n```\n\n### `check_regex(v)`\n\nEnsures that `v` is a valid python regular expression.\n\n### `check_array(inner_check)`\n\nReturns a function that checks that a value is a sequence and that each\nvalue in that sequence adheres to the `inner_check`.\n\nFor example:\n\n```python\nRequired('args', check_array(check_string))\n```\n\n### `check_and(*fns)`\n\nReturns a function that performs multiple checks on a value.\n\nFor example:\n\n```python\nRequired('language', check_and(check_string, my_check_language))\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Validate configuration and produce human readable error messages.",
    "version": "3.4.0",
    "project_urls": {
        "Homepage": "https://github.com/asottile/cfgv"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c55551844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374",
                "md5": "86e6ed8c6f6bfc8eeb75c80bf1261612",
                "sha256": "b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"
            },
            "downloads": -1,
            "filename": "cfgv-3.4.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "86e6ed8c6f6bfc8eeb75c80bf1261612",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": ">=3.8",
            "size": 7249,
            "upload_time": "2023-08-12T20:38:16",
            "upload_time_iso_8601": "2023-08-12T20:38:16.269971Z",
            "url": "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1174539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94",
                "md5": "dea3acb4cd6df0bc4a00bdf44d72e0cd",
                "sha256": "e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"
            },
            "downloads": -1,
            "filename": "cfgv-3.4.0.tar.gz",
            "has_sig": false,
            "md5_digest": "dea3acb4cd6df0bc4a00bdf44d72e0cd",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 7114,
            "upload_time": "2023-08-12T20:38:17",
            "upload_time_iso_8601": "2023-08-12T20:38:17.776840Z",
            "url": "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-08-12 20:38:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "asottile",
    "github_project": "cfgv",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "cfgv"
}
        
Elapsed time: 0.10099s