Give AlbumentationsX a star on GitHub — it powers this leaderboard

Star on GitHub

schema

Simple data validation library

Downloads: 0 (30 days)

Description

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:: https://secure.travis-ci.org/keleshev/schema.svg?branch=master
    :target: https://travis-ci.org/keleshev/schema

.. image:: https://img.shields.io/codecov/c/github/keleshev/schema.svg
    :target: http://codecov.io/github/keleshev/schema

Example
----------------------------------------------------------------------------

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

.. code:: python
    
    from schema import Schema, And, Use, Optional, SchemaError
    
    schema = Schema(
        [
            {
                "name": And(str, len),
                "age": And(Use(int), lambda n: 18 <= n <= 99),
                Optional("gender"): And(
                    str,
                    Use(str.lower),
                    lambda s: s in ("squid", "kid"),
                ),
            }
        ]
    )
    
    data = [
        {"name": "Sue", "age": "28", "gender": "Squid"},
        {"name": "Sam", "age": "42"},
        {"name": "Sacha", "age": "20", "gender": "KID"},
    ]
    
    validated = schema.validate(data)
    
    assert validated == [
        {"name": "Sue", "age": 28, "gender": "squid"},
        {"name": "Sam", "age": 42},
        {"name": "Sacha", "age": 20, "gender": "kid"},
    ]



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 <http://pip-installer.org>`_ 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 <http://semver.org>`_.

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

.. code:: python

    >>> from schema import Schema

    >>> Schema(int).validate(123)
    123

    >>> Schema(int).validate('123')
    Traceback (most recent call last):
    ...
    schema.SchemaUnexpectedTypeError: '123' should be instance of 'int'

    >>> Schema(object).validate('hai')
    'hai'

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

.. code:: python

    >>> import os

    >>> Schema(os.path.exists).validate('./')
    './'

    >>> Schema(os.path.exists).validate('./non-existent/')
    Traceback (most recent call last):
    ...
    schema.SchemaError: exists('./non-existent/') should evaluate to True

    >>> Schema(lambda n: n > 0).validate(123)
    123

    >>> Schema(lambda n: n > 0).validate(-12)
    Traceback (most recent call last):
    ...
    schema.SchemaError: <lambda>(-12) should evaluate to True

"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``):

.. code:: python

    >>> from schema import Regex
    >>> import re

    >>> Regex(r'^foo').validate('foobar')
    'foobar'

    >>> Regex(r'^[A-Z]+$', flags=re.I).validate('those-dashes-dont-match')
    Traceback (most recent call last):
    ...
    schema.SchemaError: Regex('^[A-Z]+$', flags=re.IGNORECASE) does not match 'those-dashes-dont-match'

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:

.. code:: python

    >>> from schema import Use

    >>> Schema(Use(int)).validate('123')
    123

    >>> Schema(Use(lambda f: open(f, 'a'))).validate('LICENSE-MIT')
    <_io.TextIOWrapper name='LICENSE-MIT' mode='a' encoding='UTF-8'>

Dropping the details, ``Use`` is basically:

.. code:: python

    class Use(object):

        def __init__(self, callable_):
            self._callable = callable_

        def validate(self, data):
            try:
                return self._callable(data)
            except Exception as e:
                raise SchemaError('%r raised %r' % (self._callable.__name__, e))


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

.. code:: python

    >> from schema import Use, Const, And, Schema

    >> from datetime import datetime

    >> is_future = lambda date: datetime.now() > date

    >> to_json = lambda v: {"timestamp": v}

    >> Schema(And(Const(And(Use(datetime.fromtimestamp), is_future)), Use(to_json))).validate(1234567890)
    {"timestamp": 1234567890}

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:

.. code:: python

    >>> Schema([1, 0]).validate([1, 1, 0, 1])
    [1, 1, 0, 1]

    >>> Schema((int, float)).validate((5, 7, 8, 'not int or float here'))
    Traceback (most recent call last):
    ...
    schema.SchemaError: Or(<class 'int'>, <class 'float'>) did not validate 'not int or float here'
    'not int or float here' should be instance of 'int'
    'not int or float here' should be instance of 'float'

Dictionaries
~~~~~~~~~~~~

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

.. code:: python

    >>> d = Schema(
    ...     {"name": str, "age": lambda n: 18 <= n <= 99}
    ... ).validate(
    ...     {"name": "Sue", "age": 28}
    ... )

    >>> assert d == {'name': 'Sue', 'age': 28}

You can specify keys as schemas too:

.. code:: python

    >>> schema = Schema({
    ...     str: int,  # string keys should have integer values
    ...     int: None,  # int keys should be always None
    ... })

    >>> data = schema.validate({
    ...     "key1": 1,
    ...     "key2": 2,
    ...     10: None,
    ...     20: None,
    ... })

    >>> schema.validate({
    ...     "key1": 1,
    ...     10: "not None here",
    ... })
    Traceback (most recent call last):
    ...
    schema.SchemaError: Key '10' error:
    None does not match 'not None here'

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

.. code:: python

    >>> schema = Schema({
    ...     "<id>": int,
    ...     "<file>": Use(open),
    ...     str: object,  # don't care about other str keys
    ... })

    >>> data = schema.validate({
    ...     "<id>": 10,
    ...     "<file>": "README.rst",
    ...     "--verbose": True,
    ... })

You can mark a key as optional as follows:

.. code:: python

    >>> Schema({
    ...     "name": str,
    ...     Optional("occupation"): str,
    ... }).validate({"name": "Sam"})
    {'name': 'Sam'}

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

.. code:: python

    >>> Schema({
    ...     Optional("color", default="blue"): str,
    ...     str: str,
    ... }).validate({"texture": "furry"}) == {
    ...     "color": "blue",
    ...     "texture": "furry",
    ... }
    True

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

default can also be a callable:

.. code:: python

    >>> from schema import Schema, Optional
    >>> Schema({Optional('data', default=dict): {}}).validate({}) == {'data': {}}
    True

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

.. code:: python

    >>> Schema({int:int}).is_valid({})
    False

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:

.. code:: python

    >>> from schema import And, Or

    >>> Schema({'age': And(int, lambda n: 0 < n < 99)}).validate({'age': 7})
    {'age': 7}

    >>> Schema({'password': And(str, lambda s: len(s) > 6)}).validate({'password': 'hai'})
    Traceback (most recent call last):
    ...
    schema.SchemaError: Key 'password' error:
    <lambda>('hai') should evaluate to True

    >>> Schema(And(Or(int, float), lambda x: x > 0)).validate(3.1415)
    3.1415

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:

.. code:: python

    >>> from schema import Or, Schema
    >>> schema = Schema({
    ...    Or("key1", "key2", only_one=True): str
    ... })

    >>> schema.validate({"key1": "test"}) # Ok
    {'key1': 'test'}

    >>> schema.validate({"key1": "test", "key2": "test"}) # SchemaError
    Traceback (most rece