cfgv3.4.0
Published
Validate configuration and produce human readable error messages.
pip install cfgv
Package Downloads
Authors
Project URLs
Requires Python
>=3.8
Dependencies
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 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:
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:
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 messagesid_key
: will be used to identify the object in error messages. Set toNone
if there is no identifying key for the object.items
: validator objects such asRequired
orOptional
Consider the following schema:
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
: AMap
/Array
or other sub-schema.allow_empty
: whenFalse
,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:
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 checkself.check_fn
: the check functionself.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.
RequiredRecurse(key, schema)
Similar to Required
, but uses a schema.
Optional(key, check_fn, default)
If a key is present, check that it adheres to the check function.
apply_defaults
will set thedefault
if it is not present.remove_defaults
will remove the value if it is equal todefault
.
OptionalRecurse(key, schema, default)
Similar to Optional
but uses a schema.
apply_defaults
will set thedefault
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 todefault
.
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 thecondition_value
, the specifickey
will be checked using the check function. - If
ensure_absent
isTrue
and the condition check fails, thekey
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.
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.
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:
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:
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:
Required('args', check_array(check_string))
check_and(*fns)
Returns a function that performs multiple checks on a value.
For example:
Required('language', check_and(check_string, my_check_language))