Oven logo

Oven

cfgv3.4.0

Published

Validate configuration and produce human readable error messages.

pip install cfgv

Package Downloads

Weekly DownloadsMonthly Downloads

Project URLs

Requires Python

>=3.8

Dependencies

    build status pre-commit.ci status

    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 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:

    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:

    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
    • 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.

    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 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.

    • 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.
    • 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.

    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))