Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fastjsonschema/__init__.py: 41%
34 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 06:10 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 06:10 +0000
1# ___
2# \./ DANGER: This project implements some code generation
3# .--.O.--. techniques involving string concatenation.
4# \/ \/ If you look at it, you might die.
5#
7r"""
8Installation
9************
11.. code-block:: bash
13 pip install fastjsonschema
15Support only for Python 3.3 and higher.
17About
18*****
20``fastjsonschema`` implements validation of JSON documents by JSON schema.
21The library implements JSON schema drafts 04, 06, and 07. The main purpose is
22to have a really fast implementation. See some numbers:
24 * Probably the most popular, ``jsonschema``, can take up to 5 seconds for valid
25 inputs and 1.2 seconds for invalid inputs.
26 * Second most popular, ``json-spec``, is even worse with up to 7.2 and 1.7 seconds.
27 * Last ``validictory``, now deprecated, is much better with 370 or 23 milliseconds,
28 but it does not follow all standards, and it can be still slow for some purposes.
30With this library you can gain big improvements as ``fastjsonschema`` takes
31only about 25 milliseconds for valid inputs and 2 milliseconds for invalid ones.
32Pretty amazing, right? :-)
34Technically it works by generating the most stupid code on the fly, which is fast but
35is hard to write by hand. The best efficiency is achieved when a validator is compiled
36once and used many times, of course. It works similarly like regular expressions. But
37you can also generate the code to a file, which is even slightly faster.
39You can run the performance benchmarks on your computer or server with the included
40script:
42.. code-block:: bash
44 $ make performance
45 fast_compiled valid ==> 0.0464646
46 fast_compiled invalid ==> 0.0030227
47 fast_file valid ==> 0.0461219
48 fast_file invalid ==> 0.0030608
49 fast_not_compiled valid ==> 11.4627202
50 fast_not_compiled invalid ==> 2.5726230
51 jsonschema valid ==> 7.5844927
52 jsonschema invalid ==> 1.9204665
53 jsonschema_compiled valid ==> 0.6938364
54 jsonschema_compiled invalid ==> 0.0359244
55 jsonspec valid ==> 9.0715843
56 jsonspec invalid ==> 2.1650488
57 validictory valid ==> 0.4874793
58 validictory invalid ==> 0.0232244
60This library follows and implements `JSON schema draft-04, draft-06, and draft-07
61<http://json-schema.org>`_. Sometimes it's not perfectly clear, so I recommend also
62check out this `understanding JSON schema <https://spacetelescope.github.io/understanding-json-schema>`_.
64Note that there are some differences compared to JSON schema standard:
66 * Regular expressions are full Python ones, not only what JSON schema allows. It's easier
67 to allow everything, and also it's faster to compile without limits. So keep in mind that when
68 you will use a more advanced regular expression, it may not work with other libraries or in
69 other languages.
70 * Because Python matches new line for a dollar in regular expressions (``a$`` matches ``a`` and ``a\\n``),
71 instead of ``$`` is used ``\Z`` and all dollars in your regular expression are changed to ``\\Z``
72 as well. When you want to use dollar as regular character, you have to escape it (``\$``).
73 * JSON schema says you can use keyword ``default`` for providing default values. This implementation
74 uses that and always returns transformed input data.
76Usage
77*****
79.. code-block:: python
81 import fastjsonschema
83 point_schema = {
84 "type": "object",
85 "properties": {
86 "x": {
87 "type": "number",
88 },
89 "y": {
90 "type": "number",
91 },
92 },
93 "required": ["x", "y"],
94 "additionalProperties": False,
95 }
97 point_validator = fastjsonschema.compile(point_schema)
98 try:
99 point_validator({"x": 1.0, "y": 2.0})
100 except fastjsonschema.JsonSchemaException as e:
101 print(f"Data failed validation: {e}")
103API
104***
105"""
106from functools import partial, update_wrapper
108from .draft04 import CodeGeneratorDraft04
109from .draft06 import CodeGeneratorDraft06
110from .draft07 import CodeGeneratorDraft07
111from .exceptions import JsonSchemaException, JsonSchemaValueException, JsonSchemaDefinitionException
112from .ref_resolver import RefResolver
113from .version import VERSION
115__all__ = (
116 'VERSION',
117 'JsonSchemaException',
118 'JsonSchemaValueException',
119 'JsonSchemaDefinitionException',
120 'validate',
121 'compile',
122 'compile_to_code',
123)
126def validate(definition, data, handlers={}, formats={}, use_default=True):
127 """
128 Validation function for lazy programmers or for use cases when you need
129 to call validation only once, so you do not have to compile it first.
130 Use it only when you do not care about performance (even though it will
131 be still faster than alternative implementations).
133 .. code-block:: python
135 import fastjsonschema
137 fastjsonschema.validate({'type': 'string'}, 'hello')
138 # same as: compile({'type': 'string'})('hello')
140 Preferred is to use :any:`compile` function.
141 """
142 return compile(definition, handlers, formats, use_default)(data)
145#TODO: Change use_default to False when upgrading to version 3.
146# pylint: disable=redefined-builtin,dangerous-default-value,exec-used
147def compile(definition, handlers={}, formats={}, use_default=True):
148 """
149 Generates validation function for validating JSON schema passed in ``definition``.
150 Example:
152 .. code-block:: python
154 import fastjsonschema
156 validate = fastjsonschema.compile({'type': 'string'})
157 validate('hello')
159 This implementation supports keyword ``default`` (can be turned off
160 by passing `use_default=False`):
162 .. code-block:: python
164 validate = fastjsonschema.compile({
165 'type': 'object',
166 'properties': {
167 'a': {'type': 'number', 'default': 42},
168 },
169 })
171 data = validate({})
172 assert data == {'a': 42}
174 Supported implementations are draft-04, draft-06 and draft-07. Which version
175 should be used is determined by `$draft` in your ``definition``. When not
176 specified, the latest implementation is used (draft-07).
178 .. code-block:: python
180 validate = fastjsonschema.compile({
181 '$schema': 'http://json-schema.org/draft-04/schema',
182 'type': 'number',
183 })
185 You can pass mapping from URI to function that should be used to retrieve
186 remote schemes used in your ``definition`` in parameter ``handlers``.
188 Also, you can pass mapping for custom formats. Key is the name of your
189 formatter and value can be regular expression, which will be compiled or
190 callback returning `bool` (or you can raise your own exception).
192 .. code-block:: python
194 validate = fastjsonschema.compile(definition, formats={
195 'foo': r'foo|bar',
196 'bar': lambda value: value in ('foo', 'bar'),
197 })
199 Exception :any:`JsonSchemaDefinitionException` is raised when generating the
200 code fails (bad definition).
202 Exception :any:`JsonSchemaValueException` is raised from generated function when
203 validation fails (data do not follow the definition).
204 """
205 resolver, code_generator = _factory(definition, handlers, formats, use_default)
206 global_state = code_generator.global_state
207 # Do not pass local state so it can recursively call itself.
208 exec(code_generator.func_code, global_state)
209 func = global_state[resolver.get_scope_name()]
210 if formats:
211 return update_wrapper(partial(func, custom_formats=formats), func)
212 return func
215# pylint: disable=dangerous-default-value
216def compile_to_code(definition, handlers={}, formats={}, use_default=True):
217 """
218 Generates validation code for validating JSON schema passed in ``definition``.
219 Example:
221 .. code-block:: python
223 import fastjsonschema
225 code = fastjsonschema.compile_to_code({'type': 'string'})
226 with open('your_file.py', 'w') as f:
227 f.write(code)
229 You can also use it as a script:
231 .. code-block:: bash
233 echo "{'type': 'string'}" | python3 -m fastjsonschema > your_file.py
234 python3 -m fastjsonschema "{'type': 'string'}" > your_file.py
236 Exception :any:`JsonSchemaDefinitionException` is raised when generating the
237 code fails (bad definition).
238 """
239 _, code_generator = _factory(definition, handlers, formats, use_default)
240 return (
241 'VERSION = "' + VERSION + '"\n' +
242 code_generator.global_state_code + '\n' +
243 code_generator.func_code
244 )
247def _factory(definition, handlers, formats={}, use_default=True):
248 resolver = RefResolver.from_schema(definition, handlers=handlers, store={})
249 code_generator = _get_code_generator_class(definition)(
250 definition,
251 resolver=resolver,
252 formats=formats,
253 use_default=use_default,
254 )
255 return resolver, code_generator
258def _get_code_generator_class(schema):
259 # Schema in from draft-06 can be just the boolean value.
260 if isinstance(schema, dict):
261 schema_version = schema.get('$schema', '')
262 if 'draft-04' in schema_version:
263 return CodeGeneratorDraft04
264 if 'draft-06' in schema_version:
265 return CodeGeneratorDraft06
266 return CodeGeneratorDraft07