Adding upstream version 3.1.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
4ce492ac49
commit
3f4c8eaaf4
13 changed files with 1536 additions and 0 deletions
282
README.md
Normal file
282
README.md
Normal file
|
@ -0,0 +1,282 @@
|
|||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=24&branchName=master)
|
||||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=24&branchName=master)
|
||||
|
||||
cfgv
|
||||
====
|
||||
|
||||
Validate configuration and produce human readable error messages.
|
||||
|
||||
## Installation
|
||||
|
||||
`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))
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue