Convert Figma logo to code with AI

keleshev logoschema

Schema validation just got Pythonic

2,904
213
2,904
101

Top Related Projects

A lightweight library for converting complex objects to and from simple Python datatypes.

CONTRIBUTIONS ONLY: Voluptuous, despite the name, is a Python data validation library.

22,955

Data validation using Python type hints

Python Data Structures for Humans™.

An implementation of the JSON Schema specification for Python

Quick Overview

The keleshev/schema project is a lightweight and flexible data validation library for Python. It provides a simple and intuitive API for defining and validating data schemas, making it a useful tool for data-driven applications.

Pros

  • Simplicity: The library has a straightforward and easy-to-use API, making it accessible for developers of all skill levels.
  • Flexibility: schema supports a wide range of data types and validation rules, allowing for the creation of complex and customizable schemas.
  • Extensibility: The library can be easily extended with custom validators and data types, making it adaptable to a variety of use cases.
  • Performance: schema is designed to be efficient, with fast validation and minimal overhead.

Cons

  • Limited Documentation: The project's documentation could be more comprehensive, which may make it challenging for new users to get started.
  • Lack of Widespread Adoption: While schema is a well-designed library, it may not have the same level of community support and adoption as some other data validation libraries in the Python ecosystem.
  • Potential Complexity for Simple Use Cases: For very simple data validation needs, the library may be overkill, and a more lightweight solution might be more appropriate.
  • Python-specific: schema is a Python-specific library, which may limit its usefulness for developers working in other programming languages.

Code Examples

Here are a few examples of how to use the schema library:

  1. Defining a Simple Schema:
from schema import Schema, And, Use

schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), lambda n: 18 <= n <= 99),
    'email': And(str, lambda email: '@' in email),
})

data = {'name': 'John Doe', 'age': 30, 'email': 'john@example.com'}
validated_data = schema.validate(data)
print(validated_data)
  1. Nested Schemas:
from schema import Schema, And, Use

address_schema = Schema({
    'street': str,
    'city': str,
    'state': str,
    'zip': And(Use(int), lambda n: 10000 <= n <= 99999),
})

person_schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), lambda n: 18 <= n <= 99),
    'address': address_schema,
})

data = {
    'name': 'John Doe',
    'age': 30,
    'address': {
        'street': '123 Main St',
        'city': 'Anytown',
        'state': 'CA',
        'zip': 12345,
    },
}

validated_data = person_schema.validate(data)
print(validated_data)
  1. Custom Validators:
from schema import Schema, And, Use

def is_positive(n):
    return n > 0

schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), is_positive),
    'score': And(Use(float), is_positive),
})

data = {'name': 'John Doe', 'age': 30, 'score': 85.5}
validated_data = schema.validate(data)
print(validated_data)

Getting Started

To get started with the schema library, you can install it using pip:

pip install schema

Once installed, you can start defining and validating data schemas. Here's a simple example:

from schema import Schema, And, Use

# Define a schema
schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), lambda n: 18 <= n <= 99),
})

# Validate data against the schema
data = {'name': 'John Doe', 'age': 30}
validated_data = schema.validate(data)
print(validated_data)

In this example, we define a schema that requires a name field to be a non-empty

Competitor Comparisons

A lightweight library for converting complex objects to and from simple Python datatypes.

Pros of marshmallow

  • More comprehensive and feature-rich, offering advanced serialization and deserialization capabilities
  • Extensive documentation and larger community support
  • Integration with popular web frameworks like Flask and SQLAlchemy

Cons of marshmallow

  • Steeper learning curve due to more complex API and features
  • Slightly more verbose syntax for simple use cases
  • Potentially slower performance for basic schema validation tasks

Code Comparison

marshmallow:

from marshmallow import Schema, fields

class UserSchema(Schema):
    name = fields.Str(required=True)
    email = fields.Email()
    age = fields.Int(validate=lambda n: n > 0)

user_schema = UserSchema()
result = user_schema.load({"name": "John", "email": "john@example.com", "age": 30})

schema:

import schema

user_schema = schema.Schema({
    'name': str,
    'email': schema.And(str, lambda s: '@' in s),
    'age': schema.And(int, lambda n: n > 0)
})

result = user_schema.validate({"name": "John", "email": "john@example.com", "age": 30})

Both libraries provide schema validation, but marshmallow offers more built-in field types and validation options, while schema has a more concise syntax for simple cases.

CONTRIBUTIONS ONLY: Voluptuous, despite the name, is a Python data validation library.

Pros of Voluptuous

  • More extensive and flexible validation options, including custom validators and coercion
  • Better support for nested structures and complex data types
  • More active development and larger community support

Cons of Voluptuous

  • Slightly more verbose syntax for defining schemas
  • Steeper learning curve due to more advanced features
  • Potentially slower performance for simple validation tasks

Code Comparison

Schema:

from schema import Schema, And, Use, Optional

schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), lambda n: 18 <= n <= 99),
    Optional('email'): str
})

Voluptuous:

from voluptuous import Schema, Required, All, Length, Range, Email

schema = Schema({
    Required('name'): All(str, Length(min=1)),
    Required('age'): All(int, Range(min=18, max=99)),
    'email': Email()
})

Both libraries provide similar functionality for data validation, but Voluptuous offers more built-in validators and a more expressive API. Schema has a simpler syntax and may be easier to get started with for basic use cases. The choice between the two depends on the complexity of your validation requirements and personal preference for API style.

22,955

Data validation using Python type hints

Pros of Pydantic

  • More comprehensive data validation and serialization capabilities
  • Better integration with modern Python features like type hints
  • Actively maintained with frequent updates and improvements

Cons of Pydantic

  • Steeper learning curve due to more complex API
  • Heavier dependency footprint compared to Schema

Code Comparison

Schema:

from schema import Schema, And, Use, Optional

schema = Schema({'name': And(str, len),
                 'age': And(Use(int), lambda n: 18 <= n <= 99),
                 Optional('gender'): str})

Pydantic:

from pydantic import BaseModel, Field

class Person(BaseModel):
    name: str
    age: int = Field(..., ge=18, le=99)
    gender: str | None = None

Both libraries provide data validation, but Pydantic offers a more Pythonic approach with type hints and built-in field validation. Schema uses a more explicit validation structure, which can be easier to understand for simpler use cases. Pydantic's model-based approach is generally more scalable for complex data structures and integrates better with modern Python practices.

Python Data Structures for Humans™.

Pros of Schematics

  • More extensive and feature-rich, offering a wider range of data types and validation options
  • Better suited for complex data structures and nested schemas
  • Actively maintained with regular updates and improvements

Cons of Schematics

  • Steeper learning curve due to its more comprehensive feature set
  • Potentially slower performance for simple use cases
  • Larger codebase and dependencies

Code Comparison

Schema:

from schema import Schema, And, Use, Optional

schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), lambda n: 18 <= n <= 99),
    Optional('email'): str
})

Schematics:

from schematics.models import Model
from schematics.types import StringType, IntType, EmailType

class Person(Model):
    name = StringType(required=True)
    age = IntType(required=True, min_value=18, max_value=99)
    email = EmailType()

Summary

Schema is simpler and more lightweight, making it ideal for quick validations and smaller projects. Schematics offers more powerful features and is better suited for complex data modeling in larger applications. The choice between the two depends on the specific requirements of your project and the level of complexity you need to handle.

An implementation of the JSON Schema specification for Python

Pros of jsonschema

  • More comprehensive JSON Schema support, including draft-07 and later versions
  • Extensive validation capabilities for complex JSON structures
  • Larger community and more frequent updates

Cons of jsonschema

  • Steeper learning curve due to its comprehensive feature set
  • Potentially slower performance for simple validation tasks
  • More complex setup and configuration for basic use cases

Code Comparison

schema:

from schema import Schema, And, Use, Optional

schema = Schema({
    'name': And(str, len),
    'age': And(Use(int), lambda n: 18 <= n <= 99),
    Optional('email'): str
})

jsonschema:

from jsonschema import validate

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string", "minLength": 1},
        "age": {"type": "integer", "minimum": 18, "maximum": 99},
        "email": {"type": "string"}
    },
    "required": ["name", "age"]
}

Both libraries offer data validation, but schema focuses on simplicity and readability, while jsonschema provides more comprehensive JSON Schema support with a more verbose syntax.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

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 recent call last):
    ...
    schema.SchemaOnlyOneAllowedError: There are multiple keys present from the Or('key1', 'key2') condition

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:

.. code:: python

    >>> from schema import Forbidden
    >>> Schema({Forbidden('age'): object}).validate({'age': 50})
    Traceback (most recent call last):
    ...
    schema.SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50}

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

.. code:: python

    >>> Schema({Forbidden('age'): str, 'age': int}).validate({'age': 50})
    {'age': 50}

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:

.. code:: python

    >>> Schema({Forbidden('age'): object, Optional(str): object}).validate({'age': 50})
    Traceback (most recent call last):
    ...
    schema.SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50}

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

.. code:: python

    from schema import Hook
    def _my_function(key, scope, error):
        print(key, scope, error)

    Hook("key", handler=_my_function)

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

.. code:: python

    from schema import Hook, Schema
    class Deprecated(Hook):
        def __init__(self, *args, **kwargs):
            kwargs["handler"] = lambda key, *args: logging.warn(f"`{key}` is deprecated. " + (self._error or ""))
            super(Deprecated, self).__init__(*args, **kwargs)

    Schema({Deprecated("test", "custom error message."): object}, ignore_extra_keys=True).validate({"test": "value"})
    ...
    WARNING: `test` is deprecated. custom error message.

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.

.. code:: python

    >>> schema = Schema({'name': str}, ignore_extra_keys=True)
    >>> schema.validate({'name': 'Sam', 'age': '42'})
    {'name': 'Sam'}

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:

.. code:: python

class EventSchema(schema.Schema):

    def validate(self, data, _is_event_schema=True):
        data = super(EventSchema, self).validate(data, _is_event_schema=False)
        if _is_event_schema and data.get("minimum", None) is None:
            data["minimum"] = data["capacity"]
        return data


events_schema = schema.Schema(
    {
        str: EventSchema({
            "capacity": int,
            schema.Optional("minimum"): int,  # default to capacity
        })
    }
)


data = {'event1': {'capacity': 1}, 'event2': {'capacity': 2, 'minimum': 3}}
events = events_schema.validate(data)

assert events['event1']['minimum'] == 1  # == capacity
assert events['event2']['minimum'] == 3

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.

.. code:: python

>>> Schema(Use(int, error='Invalid year')).validate('XVII')
Traceback (most recent call last):
...
schema.SchemaError: Invalid year

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 <http://developer.github.com/v3/gists/>_ request from github API.

.. code:: python

>>> gist = '''{"description": "the description for this gist",
...            "public": true,
...            "files": {
...                "file1.txt": {"content": "String file contents"},
...                "other.txt": {"content": "Another file contents"}}}'''

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

>>> import json

>>> gist_schema = Schema(
...     And(
...         Use(json.loads),  # first convert from JSON
...         # use str since json returns unicode
...         {
...             Optional("description"): str,
...             "public": bool,
...             "files": {str: {"content": str}},
...         },
...     )
... )

>>> gist = gist_schema.validate(gist)

# gist:
{u'description': u'the description for this gist',
 u'files': {u'file1.txt': {u'content': u'String file contents'},
            u'other.txt': {u'content': u'Another file contents'}},
 u'public': True}

Using schema with docopt <http://github.com/docopt/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:

.. code:: python

>>> args = {
...     "<files>": ["LICENSE-MIT", "setup.py"],
...     "<path>": "../",
...     "--count": "3",
... }

this is how you validate it using schema:

.. code:: python

>>> from schema import Schema, And, Or, Use
>>> import os

>>> s = Schema({
...     "<files>": [Use(open)],
...     "<path>": os.path.exists,
...     "--count": Or(None, And(Use(int), lambda n: 0 < n < 5)),
... })


>>> args = s.validate(args)

>>> args['<files>']
[<_io.TextIOWrapper name='LICENSE-MIT' ...>, <_io.TextIOWrapper name='setup.py' ...]

>>> args['<path>']
'../'

>>> args['--count']
3

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 <https://json-schema.org/>_ 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.

.. code:: python

    >>> from schema import Optional, Schema
    >>> import json
    >>> s = Schema({
    ...     "test": str,
    ...     "nested": {Optional("other"): str},
    ... })
    >>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"))

    # json_schema
    {
        "type":"object",
        "properties": {
            "test": {"type": "string"},
            "nested": {
                "type":"object",
                "properties": {
                    "other": {"type": "string"}
                },
                "required": [],
                "additionalProperties": false
            }
        },
        "required":[
            "test",
            "nested"
        ],
        "additionalProperties":false,
        "$id":"https://example.com/my-schema.json",
        "$schema":"http://json-schema.org/draft-07/schema#"
    }

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.

.. code:: python

    >>> from schema import Literal, Schema
    >>> import json
    >>> s = Schema(
    ...     {Literal("project_name", description="Names must be unique"): str},
    ...     description="Project schema",
    ... )
    >>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"), indent=4)

    # json_schema
    {
        "type": "object",
        "properties": {
            "project_name": {
                "description": "Names must be unique",
                "type": "string"
            }
        },
        "required": [
            "project_name"
        ],
        "additionalProperties": false,
        "$id": "https://example.com/my-schema.json",
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Project schema"
    }


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 <https://json-schema.org/understanding-json-schema/reference/object.html#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:

- ``str`` -> `string <https://json-schema.org/understanding-json-schema/reference/string.html>`_
- ``int`` -> `integer <https://json-schema.org/understanding-json-schema/reference/numeric.html#integer>`_
- ``float`` -> `number <https://json-schema.org/understanding-json-schema/reference/numeric.html#number>`_
- ``bool`` -> `boolean <https://json-schema.org/understanding-json-schema/reference/boolean.html>`_
- ``list`` -> `array <https://json-schema.org/understanding-json-schema/reference/array.html>`_
- ``dict`` -> `object <https://json-schema.org/understanding-json-schema/reference/object.html>`_

Example:

``Schema(float)``

becomes

``{"type": "number"}``

Array items <https://json-schema.org/understanding-json-schema/reference/array.html#items>_ Surround a schema with [].

Example:

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

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

Enumerated values <https://json-schema.org/understanding-json-schema/reference/generic.html#enumerated-values>_ Use Or.

Example:

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

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

Constant values <https://json-schema.org/understanding-json-schema/reference/generic.html#constant-values>_ Use the value itself.

Example:

``Schema("name")`` becomes

``{"const": "name"}``

Regular expressions <https://json-schema.org/understanding-json-schema/reference/regular_expressions.html>_ Use Regex.

Example:

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

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

Annotations (title and description) <https://json-schema.org/understanding-json-schema/reference/generic.html#annotations>_ 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 <https://json-schema.org/understanding-json-schema/reference/combining.html#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 <https://json-schema.org/understanding-json-schema/reference/combining.html#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.

  • String length <https://json-schema.org/understanding-json-schema/reference/string.html#length>_ However, those can be implemented using Regex

  • String format <https://json-schema.org/understanding-json-schema/reference/string.html#format>_ However, those can be implemented using Regex

  • Object dependencies <https://json-schema.org/understanding-json-schema/reference/object.html#dependencies>_

  • Array length <https://json-schema.org/understanding-json-schema/reference/array.html#length>_

  • Array uniqueness <https://json-schema.org/understanding-json-schema/reference/array.html#uniqueness>_

  • Numeric multiples <https://json-schema.org/understanding-json-schema/reference/numeric.html#multiples>_

  • Numeric ranges <https://json-schema.org/understanding-json-schema/reference/numeric.html#range>_

  • Property Names <https://json-schema.org/understanding-json-schema/reference/object.html#property-names>_ Not implemented. We suggest listing the possible keys instead. As a tip, you can use Or as a dict key.

    Example:

    Schema({Or("name1", "name2"): str})

  • Annotations (default and examples) <https://json-schema.org/understanding-json-schema/reference/generic.html#annotations>_

  • Combining schemas with oneOf <https://json-schema.org/understanding-json-schema/reference/combining.html#oneof>_

  • Not <https://json-schema.org/understanding-json-schema/reference/combining.html#not>_

  • Object size <https://json-schema.org/understanding-json-schema/reference/object.html#size>_

  • additionalProperties having a different schema (true and false is supported)

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 <https://json-schema.org/understanding-json-schema/structuring.html#reuse>`_

.. code:: python

    >>> from schema import Optional, Schema
    >>> import json
    >>> s = Schema({
    ...     "test": str,
    ...     "nested": Schema({Optional("other"): str}, name="nested", as_reference=True)
    ... })
    >>> json_schema = json.dumps(s.json_schema("https://example.com/my-schema.json"), indent=4)

    # json_schema
    {
        "type": "object",
        "properties": {
            "test": {
                "type": "string"
            },
            "nested": {
                "$ref": "#/definitions/nested"
            }
        },
        "required": [
            "test",
            "nested"
        ],
        "additionalProperties": false,
        "$id": "https://example.com/my-schema.json",
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
            "nested": {
                "type": "object",
                "properties": {
                    "other": {
                        "type": "string"
                    }
                },
                "required": [],
                "additionalProperties": false
            }
        }
    }

This becomes really useful when using the same object several times

.. code:: python

    >>> from schema import Optional, Or, Schema
    >>> import json
    >>> language_configuration = Schema(
    ...     {"autocomplete": bool, "stop_words": [str]},
    ...     name="language",
    ...     as_reference=True,
    ... )
    >>> 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"), indent=4)

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

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.

.. code:: python

    >>> 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#"
    }