Simple data validation library


Keywords
schema, json, validation
License
MIT
Install
pip install schema==0.7.6

Documentation

Schema validation just got Pythonic

schema is a library for validating Python data structures, such as those obtained from config-files, forms, external services or command-line parsing, converted from JSON/YAML (or something else) to Python data-types.

image

image

Example

Here is a quick example to get a feeling of schema, validating a list of entries with personal information:

If data is valid, Schema.validate will return the validated data (optionally converted with Use calls, see below).

If data is invalid, Schema will raise SchemaError exception. If you just want to check that the data is valid, schema.is_valid(data) will return True or False.

Installation

Use pip or easy_install:

pip install schema

Alternatively, you can just drop schema.py file into your project—it is self-contained.

  • schema is tested with Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9 and PyPy.
  • schema follows semantic versioning.

How Schema validates data

Types

If Schema(...) encounters a type (such as int, str, object, etc.), it will check if the corresponding piece of data is an instance of that type, otherwise it will raise SchemaError.

Callables

If Schema(...) encounters a callable (function, class, or object with __call__ method) it will call it, and if its return value evaluates to True it will continue validating, else—it will raise SchemaError.

"Validatables"

If Schema(...) encounters an object with method validate it will run this method on corresponding data as data = obj.validate(data). This method may raise SchemaError exception, which will tell Schema that that piece of data is invalid, otherwise—it will continue validating.

An example of "validatable" is Regex, that tries to match a string or a buffer with the given regular expression (itself as a string, buffer or compiled regex SRE_Pattern):

For a more general case, you can use Use for creating such objects. Use helps to use a function or type to convert a value while validating it:

Dropping the details, Use is basically:

Sometimes you need to transform and validate part of data, but keep original data unchanged. Const helps to keep your data safe:

Now you can write your own validation-aware classes and data types.

Lists, similar containers

If Schema(...) encounters an instance of list, tuple, set or frozenset, it will validate contents of corresponding data container against all schemas listed inside that container and aggregate all errors:

Dictionaries

If Schema(...) encounters an instance of dict, it will validate data key-value pairs:

You can specify keys as schemas too:

This is useful if you want to check certain key-values, but don't care about others:

You can mark a key as optional as follows:

Optional keys can also carry a default, to be used when no key in the data matches:

Defaults are used verbatim, not passed through any validators specified in the value.

default can also be a callable:

Also, a caveat: If you specify types, schema won't validate the empty dict:

To do that, you need Schema(Or({int:int}, {})). This is unlike what happens with lists, where Schema([int]).is_valid([]) will return True.

schema has classes And and Or that help validating several schemas for the same data:

In a dictionary, you can also combine two keys in a "one or the other" manner. To do so, use the Or class as a key:

Hooks

You can define hooks which are functions that are executed whenever a valid key:value is found. The Forbidden class is an example of this.

You can mark a key as forbidden as follows:

A few things are worth noting. First, the value paired with the forbidden key determines whether it will be rejected:

Note: if we hadn't supplied the 'age' key here, the call would have failed too, but with SchemaWrongKeyError, not SchemaForbiddenKeyError.

Second, Forbidden has a higher priority than standard keys, and consequently than Optional. This means we can do that:

You can also define your own hooks. The following hook will call _my_function if key is encountered.

Here's an example where a Deprecated class is added to log warnings whenever a key is encountered:

Extra Keys

The Schema(...) parameter ignore_extra_keys causes validation to ignore extra keys in a dictionary, and also to not return them after validating.

If you would like any extra keys returned, use object: object as one of the key/value pairs, which will match any key and any value. Otherwise, extra keys will raise a SchemaError.

Customized Validation

The Schema.validate method accepts additional keyword arguments. The keyword arguments will be propagated to the validate method of any child validatables (including any ad-hoc Schema objects), or the default value callable (if a callable is specified) for Optional keys.

This feature can be used together with inheritance of the Schema class for customized validation.

Here is an example where a "post-validation" hook that runs after validation against a sub-schema in a larger schema:

Note that the additional keyword argument _is_event_schema is necessary to limit the customized behavior to the EventSchema object itself so that it won't affect any recursive invoke of the self.__class__.validate for the child schemas (e.g., the call to Schema("capacity").validate("capacity")).

User-friendly error reporting

You can pass a keyword argument error to any of validatable classes (such as Schema, And, Or, Regex, Use) to report this error instead of a built-in one.

You can see all errors that occurred by accessing exception's exc.autos for auto-generated error messages, and exc.errors for errors which had error text passed to them.

You can exit with sys.exit(exc.code) if you want to show the messages to the user without traceback. error messages are given precedence in that case.

A JSON API example

Here is a quick example: validation of create a gist request from github API.

Using schema with docopt

Assume you are using docopt with the following usage-pattern:

Usage: my_program.py [--count=N] <path> <files>...

and you would like to validate that <files> are readable, and that <path> exists, and that --count is either integer from 0 to 5, or None.

Assuming docopt returns the following dict:

this is how you validate it using schema:

As you can see, schema validated data successfully, opened files and converted '3' to int.

JSON schema

You can also generate standard draft-07 JSON schema from a dict Schema. This can be used to add word completion, validation, and documentation directly in code editors. The output schema can also be used with JSON schema compatible libraries.

JSON: Generating

Just define your schema normally and call .json_schema() on it. The output is a Python dict, you need to dump it to JSON.

You can add descriptions for the schema elements using the Literal object instead of a string. The main schema can also have a description.

These will appear in IDEs to help your users write a configuration.

JSON: Supported validations

The resulting JSON schema is not guaranteed to accept the same objects as the library would accept, since some validations are not implemented or have no JSON schema equivalent. This is the case of the Use and Hook objects for example.

Implemented

Object properties

Use a dict literal. The dict keys are the JSON schema properties.

Example:

Schema({"test": str})

becomes

{'type': 'object', 'properties': {'test': {'type': 'string'}}, 'required': ['test'], 'additionalProperties': False}.

Please note that attributes are required by default. To create optional attributes use Optional, like so:

Schema({Optional("test"): str})

becomes

{'type': 'object', 'properties': {'test': {'type': 'string'}}, 'required': [], 'additionalProperties': False}

additionalProperties is set to true when at least one of the conditions is met:
  • ignore_extra_keys is True
  • at least one key is str or object

For example:

Schema({str: str}) and Schema({}, ignore_extra_keys=True)

both becomes

{'type': 'object', 'properties' : {}, 'required': [], 'additionalProperties': True}

and

Schema({})

becomes

{'type': 'object', 'properties' : {}, 'required': [], 'additionalProperties': False}

Types

Use the Python type name directly. It will be converted to the JSON name:

Example:

Schema(float)

becomes

{"type": "number"}

Array items

Surround a schema with [].

Example:

Schema([str]) means an array of string and becomes:

{'type': 'array', 'items': {'type': 'string'}}

Enumerated values

Use Or.

Example:

Schema(Or(1, 2, 3)) becomes

{"enum": [1, 2, 3]}

Constant values

Use the value itself.

Example:

Schema("name") becomes

{"const": "name"}

Regular expressions

Use Regex.

Example:

Schema(Regex("^v\d+")) becomes

{'type': 'string', 'pattern': '^v\\d+'}

Annotations (title and description)

You can use the name and description parameters of the Schema object init method.

To add description to keys, replace a str with a Literal object.

Example:

Schema({Literal("test", description="A description"): str})

is equivalent to

Schema({"test": str})

with the description added to the resulting JSON schema.

Combining schemas with allOf

Use And

Example:

Schema(And(str, "value"))

becomes

{"allOf": [{"type": "string"}, {"const": "value"}]}

Note that this example is not really useful in the real world, since const already implies the type.

Combining schemas with anyOf

Use Or

Example:

Schema(Or(str, int))

becomes

{"anyOf": [{"type": "string"}, {"type": "integer"}]}

Not implemented

The following JSON schema validations cannot be generated from this library.

JSON: Minimizing output size

Explicit Reuse

If your JSON schema is big and has a lot of repetition, it can be made simpler and smaller by defining Schema objects as reference. These references will be placed in a "definitions" section in the main schema.

You can look at the JSON schema documentation for more information

This becomes really useful when using the same object several times

Automatic reuse

If you want to minimize the output size without using names explicitly, you can have the library generate hashes of parts of the output JSON schema and use them as references throughout.

Enable this behaviour by providing the parameter use_refs to the json_schema method.

Be aware that this method is less often compatible with IDEs and JSON schema libraries. It produces a JSON schema that is more difficult to read by humans.

>>> from schema import Optional, Or, Schema
>>> import json
>>> language_configuration = Schema({"autocomplete": bool, "stop_words": [str]})
>>> s = Schema({Or("ar", "cs", "de", "el", "eu", "en", "es", "fr"): language_configuration})
>>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json", use_refs=True), indent=4)

# json_schema
{
    "type": "object",
    "properties": {
        "ar": {
            "type": "object",
            "properties": {
                "autocomplete": {
                    "type": "boolean",
                    "$id": "#6456104181059880193"
                },
                "stop_words": {
                    "type": "array",
                    "items": {
                        "type": "string",
                        "$id": "#1856069563381977338"
                    }
                }
            },
            "required": [
                "autocomplete",
                "stop_words"
            ],
            "additionalProperties": false
        },
        "cs": {
            "type": "object",
            "properties": {
                "autocomplete": {
                    "$ref": "#6456104181059880193"
                },
                "stop_words": {
                    "type": "array",
                    "items": {
                        "$ref": "#1856069563381977338"
                    },
                    "$id": "#-5377945144312515805"
                }
            },
            "required": [
                "autocomplete",
                "stop_words"
            ],
            "additionalProperties": false
        },
        "de": {
            "type": "object",
            "properties": {
                "autocomplete": {
                    "$ref": "#6456104181059880193"
                },
                "stop_words": {
                    "$ref": "#-5377945144312515805"
                }
            },
            "required": [
                "autocomplete",
                "stop_words"
            ],
            "additionalProperties": false,
            "$id": "#-8142886105174600858"
        },
        "el": {
            "$ref": "#-8142886105174600858"
        },
        "eu": {
            "$ref": "#-8142886105174600858"
        },
        "en": {
            "$ref": "#-8142886105174600858"
        },
        "es": {
            "$ref": "#-8142886105174600858"
        },
        "fr": {
            "$ref": "#-8142886105174600858"
        }
    },
    "required": [],
    "additionalProperties": false,
    "$id": "https://example.com/my-schema.json",
    "$schema": "http://json-schema.org/draft-07/schema#"
}