| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- # ___
- # \./ DANGER: This project implements some code generation
- # .--.O.--. techniques involving string concatenation.
- # \/ \/ If you look at it, you might die.
- #
- r"""
- Installation
- ************
- .. code-block:: bash
- pip install fastjsonschema
- Support only for Python 3.3 and higher.
- About
- *****
- ``fastjsonschema`` implements validation of JSON documents by JSON schema.
- The library implements JSON schema drafts 04, 06, and 07. The main purpose is
- to have a really fast implementation. See some numbers:
- * Probably the most popular, ``jsonschema``, can take up to 5 seconds for valid
- inputs and 1.2 seconds for invalid inputs.
- * Second most popular, ``json-spec``, is even worse with up to 7.2 and 1.7 seconds.
- * Last ``validictory``, now deprecated, is much better with 370 or 23 milliseconds,
- but it does not follow all standards, and it can be still slow for some purposes.
- With this library you can gain big improvements as ``fastjsonschema`` takes
- only about 25 milliseconds for valid inputs and 2 milliseconds for invalid ones.
- Pretty amazing, right? :-)
- Technically it works by generating the most stupid code on the fly, which is fast but
- is hard to write by hand. The best efficiency is achieved when a validator is compiled
- once and used many times, of course. It works similarly like regular expressions. But
- you can also generate the code to a file, which is even slightly faster.
- You can run the performance benchmarks on your computer or server with the included
- script:
- .. code-block:: bash
- $ make performance
- fast_compiled valid ==> 0.0993900
- fast_compiled invalid ==> 0.0041089
- fast_compiled_without_exc valid ==> 0.0465258
- fast_compiled_without_exc invalid ==> 0.0023688
- fast_file valid ==> 0.0989483
- fast_file invalid ==> 0.0041104
- fast_not_compiled valid ==> 11.9572681
- fast_not_compiled invalid ==> 2.9512092
- jsonschema valid ==> 5.2233240
- jsonschema invalid ==> 1.3227916
- jsonschema_compiled valid ==> 0.4447982
- jsonschema_compiled invalid ==> 0.0231333
- jsonspec valid ==> 4.1450569
- jsonspec invalid ==> 1.0485777
- validictory valid ==> 0.2730411
- validictory invalid ==> 0.0183669
- This library follows and implements `JSON schema draft-04, draft-06, and draft-07
- <http://json-schema.org>`_. Sometimes it's not perfectly clear, so I recommend also
- check out this `understanding JSON schema <https://spacetelescope.github.io/understanding-json-schema>`_.
- Note that there are some differences compared to JSON schema standard:
- * Regular expressions are full Python ones, not only what JSON schema allows. It's easier
- to allow everything, and also it's faster to compile without limits. So keep in mind that when
- you will use a more advanced regular expression, it may not work with other libraries or in
- other languages.
- * Because Python matches new line for a dollar in regular expressions (``a$`` matches ``a`` and ``a\\n``),
- instead of ``$`` is used ``\Z`` and all dollars in your regular expression are changed to ``\\Z``
- as well. When you want to use dollar as regular character, you have to escape it (``\$``).
- * JSON schema says you can use keyword ``default`` for providing default values. This implementation
- uses that and always returns transformed input data.
- Usage
- *****
- .. code-block:: python
- import fastjsonschema
- point_schema = {
- "type": "object",
- "properties": {
- "x": {
- "type": "number",
- },
- "y": {
- "type": "number",
- },
- },
- "required": ["x", "y"],
- "additionalProperties": False,
- }
- point_validator = fastjsonschema.compile(point_schema)
- try:
- point_validator({"x": 1.0, "y": 2.0})
- except fastjsonschema.JsonSchemaException as e:
- print(f"Data failed validation: {e}")
- API
- ***
- """
- from functools import partial, update_wrapper
- from .draft04 import CodeGeneratorDraft04
- from .draft06 import CodeGeneratorDraft06
- from .draft07 import CodeGeneratorDraft07
- from .exceptions import JsonSchemaException, JsonSchemaValueException, JsonSchemaDefinitionException
- from .ref_resolver import RefResolver
- from .version import VERSION
- __all__ = (
- 'VERSION',
- 'JsonSchemaException',
- 'JsonSchemaValueException',
- 'JsonSchemaDefinitionException',
- 'validate',
- 'compile',
- 'compile_to_code',
- )
- def validate(definition, data, handlers={}, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
- """
- Validation function for lazy programmers or for use cases when you need
- to call validation only once, so you do not have to compile it first.
- Use it only when you do not care about performance (even though it will
- be still faster than alternative implementations).
- .. code-block:: python
- import fastjsonschema
- fastjsonschema.validate({'type': 'string'}, 'hello')
- # same as: compile({'type': 'string'})('hello')
- Preferred is to use :any:`compile` function.
- """
- return compile(definition, handlers, formats, use_default, use_formats, detailed_exceptions)(data)
- #TODO: Change use_default to False when upgrading to version 3.
- # pylint: disable=redefined-builtin,dangerous-default-value,exec-used
- def compile(definition, handlers={}, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
- """
- Generates validation function for validating JSON schema passed in ``definition``.
- Example:
- .. code-block:: python
- import fastjsonschema
- validate = fastjsonschema.compile({'type': 'string'})
- validate('hello')
- This implementation supports keyword ``default`` (can be turned off
- by passing `use_default=False`):
- .. code-block:: python
- validate = fastjsonschema.compile({
- 'type': 'object',
- 'properties': {
- 'a': {'type': 'number', 'default': 42},
- },
- })
- data = validate({})
- assert data == {'a': 42}
- Supported implementations are draft-04, draft-06 and draft-07. Which version
- should be used is determined by `$draft` in your ``definition``. When not
- specified, the latest implementation is used (draft-07).
- .. code-block:: python
- validate = fastjsonschema.compile({
- '$schema': 'http://json-schema.org/draft-04/schema',
- 'type': 'number',
- })
- You can pass mapping from URI to function that should be used to retrieve
- remote schemes used in your ``definition`` in parameter ``handlers``.
- Also, you can pass mapping for custom formats. Key is the name of your
- formatter and value can be regular expression, which will be compiled or
- callback returning `bool` (or you can raise your own exception).
- .. code-block:: python
- validate = fastjsonschema.compile(definition, formats={
- 'foo': r'foo|bar',
- 'bar': lambda value: value in ('foo', 'bar'),
- })
- Note that formats are automatically used as assertions. It can be turned
- off by passing `use_formats=False`. When disabled, custom formats are
- disabled as well. (Added in 2.19.0.)
- If you don't need detailed exceptions, you can turn the details off and gain
- additional performance by passing `detailed_exceptions=False`.
- Exception :any:`JsonSchemaDefinitionException` is raised when generating the
- code fails (bad definition).
- Exception :any:`JsonSchemaValueException` is raised from generated function when
- validation fails (data do not follow the definition).
- """
- resolver, code_generator = _factory(definition, handlers, formats, use_default, use_formats, detailed_exceptions)
- global_state = code_generator.global_state
- # Do not pass local state so it can recursively call itself.
- exec(code_generator.func_code, global_state)
- func = global_state[resolver.get_scope_name()]
- if formats:
- return update_wrapper(partial(func, custom_formats=formats), func)
- return func
- # pylint: disable=dangerous-default-value
- def compile_to_code(definition, handlers={}, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
- """
- Generates validation code for validating JSON schema passed in ``definition``.
- Example:
- .. code-block:: python
- import fastjsonschema
- code = fastjsonschema.compile_to_code({'type': 'string'})
- with open('your_file.py', 'w') as f:
- f.write(code)
- You can also use it as a script:
- .. code-block:: bash
- echo "{'type': 'string'}" | python3 -m fastjsonschema > your_file.py
- python3 -m fastjsonschema "{'type': 'string'}" > your_file.py
- Exception :any:`JsonSchemaDefinitionException` is raised when generating the
- code fails (bad definition).
- """
- _, code_generator = _factory(definition, handlers, formats, use_default, use_formats, detailed_exceptions)
- return (
- 'VERSION = "' + VERSION + '"\n' +
- code_generator.global_state_code + '\n' +
- code_generator.func_code
- )
- def _factory(definition, handlers, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
- resolver = RefResolver.from_schema(definition, handlers=handlers, store={})
- code_generator = _get_code_generator_class(definition)(
- definition,
- resolver=resolver,
- formats=formats,
- use_default=use_default,
- use_formats=use_formats,
- detailed_exceptions=detailed_exceptions,
- )
- return resolver, code_generator
- def _get_code_generator_class(schema):
- # Schema in from draft-06 can be just the boolean value.
- if isinstance(schema, dict):
- schema_version = schema.get('$schema', '')
- if 'draft-04' in schema_version:
- return CodeGeneratorDraft04
- if 'draft-06' in schema_version:
- return CodeGeneratorDraft06
- return CodeGeneratorDraft07
|