summaryrefslogtreecommitdiffstats
path: root/contrib/python/jsonschema
diff options
context:
space:
mode:
authorshmel1k <[email protected]>2023-11-26 18:16:14 +0300
committershmel1k <[email protected]>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/jsonschema
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
add kikimr_configure
Diffstat (limited to 'contrib/python/jsonschema')
-rw-r--r--contrib/python/jsonschema/py2/.dist-info/METADATA224
-rw-r--r--contrib/python/jsonschema/py2/.dist-info/entry_points.txt3
-rw-r--r--contrib/python/jsonschema/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/jsonschema/py2/COPYING19
-rw-r--r--contrib/python/jsonschema/py2/README.rst179
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/__init__.py34
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/__main__.py2
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/_format.py425
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/_legacy_validators.py141
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/_reflect.py155
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/_types.py188
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/_utils.py212
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/_validators.py373
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/cli.py90
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/compat.py55
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/exceptions.py374
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/schemas/draft3.json199
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/schemas/draft4.json222
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/schemas/draft6.json153
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/schemas/draft7.json166
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/__init__.py0
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/_helpers.py5
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/test_cli.py143
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/test_exceptions.py462
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/test_format.py89
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/test_types.py190
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/tests/test_validators.py1762
-rw-r--r--contrib/python/jsonschema/py2/jsonschema/validators.py970
-rw-r--r--contrib/python/jsonschema/py2/tests/ya.make32
-rw-r--r--contrib/python/jsonschema/py2/ya.make51
-rw-r--r--contrib/python/jsonschema/py3/.dist-info/METADATA224
-rw-r--r--contrib/python/jsonschema/py3/.dist-info/entry_points.txt3
-rw-r--r--contrib/python/jsonschema/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/jsonschema/py3/COPYING19
-rw-r--r--contrib/python/jsonschema/py3/README.rst179
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/__init__.py34
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/__main__.py2
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/_format.py425
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/_legacy_validators.py141
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/_reflect.py155
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/_types.py188
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/_utils.py212
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/_validators.py373
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/cli.py90
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/compat.py55
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/exceptions.py374
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/schemas/draft3.json199
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/schemas/draft4.json222
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/schemas/draft6.json153
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/schemas/draft7.json166
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/__init__.py0
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/_helpers.py5
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/test_cli.py143
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/test_exceptions.py462
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/test_format.py89
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/test_types.py190
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/tests/test_validators.py1762
-rw-r--r--contrib/python/jsonschema/py3/jsonschema/validators.py970
-rw-r--r--contrib/python/jsonschema/py3/tests/ya.make32
-rw-r--r--contrib/python/jsonschema/py3/ya.make49
-rw-r--r--contrib/python/jsonschema/ya.make18
61 files changed, 13854 insertions, 0 deletions
diff --git a/contrib/python/jsonschema/py2/.dist-info/METADATA b/contrib/python/jsonschema/py2/.dist-info/METADATA
new file mode 100644
index 00000000000..aef9b18d586
--- /dev/null
+++ b/contrib/python/jsonschema/py2/.dist-info/METADATA
@@ -0,0 +1,224 @@
+Metadata-Version: 2.1
+Name: jsonschema
+Version: 3.2.0
+Summary: An implementation of JSON Schema validation for Python
+Home-page: https://github.com/Julian/jsonschema
+Author: Julian Berman
+Author-email: [email protected]
+License: UNKNOWN
+Project-URL: Docs, https://python-jsonschema.readthedocs.io/en/latest/
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Dist: attrs (>=17.4.0)
+Requires-Dist: pyrsistent (>=0.14.0)
+Requires-Dist: setuptools
+Requires-Dist: six (>=1.11.0)
+Requires-Dist: functools32 ; python_version < "3"
+Requires-Dist: importlib-metadata ; python_version < "3.8"
+Provides-Extra: format
+Requires-Dist: idna ; extra == 'format'
+Requires-Dist: jsonpointer (>1.13) ; extra == 'format'
+Requires-Dist: rfc3987 ; extra == 'format'
+Requires-Dist: strict-rfc3339 ; extra == 'format'
+Requires-Dist: webcolors ; extra == 'format'
+Provides-Extra: format_nongpl
+Requires-Dist: idna ; extra == 'format_nongpl'
+Requires-Dist: jsonpointer (>1.13) ; extra == 'format_nongpl'
+Requires-Dist: webcolors ; extra == 'format_nongpl'
+Requires-Dist: rfc3986-validator (>0.1.0) ; extra == 'format_nongpl'
+Requires-Dist: rfc3339-validator ; extra == 'format_nongpl'
+
+==========
+jsonschema
+==========
+
+|PyPI| |Pythons| |Travis| |AppVeyor| |Codecov| |ReadTheDocs|
+
+.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema.svg
+ :alt: PyPI version
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema.svg
+ :alt: Supported Python versions
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Travis| image:: https://travis-ci.com/Julian/jsonschema.svg?branch=master
+ :alt: Travis build status
+ :target: https://travis-ci.com/Julian/jsonschema
+
+.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/adtt0aiaihy6muyn/branch/master?svg=true
+ :alt: AppVeyor build status
+ :target: https://ci.appveyor.com/project/Julian/jsonschema
+
+.. |Codecov| image:: https://codecov.io/gh/Julian/jsonschema/branch/master/graph/badge.svg
+ :alt: Codecov Code coverage
+ :target: https://codecov.io/gh/Julian/jsonschema
+
+.. |ReadTheDocs| image:: https://readthedocs.org/projects/python-jsonschema/badge/?version=stable&style=flat
+ :alt: ReadTheDocs status
+ :target: https://python-jsonschema.readthedocs.io/en/stable/
+
+
+``jsonschema`` is an implementation of `JSON Schema <https://json-schema.org>`_
+for Python (supporting 2.7+ including Python 3).
+
+.. code-block:: python
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
+
+ >>> validate(
+ ... instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+It can also be used from console:
+
+.. code-block:: bash
+
+ $ jsonschema -i sample.json sample.schema
+
+Features
+--------
+
+* Full support for
+ `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator>`_,
+ `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft6Validator>`_,
+ `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft4Validator>`_
+ and
+ `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft3Validator>`_
+
+* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
+ that can iteratively report *all* validation errors.
+
+* `Programmatic querying <https://python-jsonschema.readthedocs.io/en/latest/errors/>`_
+ of which properties or items failed validation.
+
+
+Installation
+------------
+
+``jsonschema`` is available on `PyPI <https://pypi.org/project/jsonschema/>`_. You can install using `pip <https://pip.pypa.io/en/stable/>`_:
+
+.. code-block:: bash
+
+ $ pip install jsonschema
+
+
+Demo
+----
+
+Try ``jsonschema`` interactively in this online demo:
+
+.. image:: https://user-images.githubusercontent.com/1155573/56745335-8b158a00-6750-11e9-8776-83fa675939c4.png
+ :target: https://notebooks.ai/demo/gh/Julian/jsonschema
+ :alt: Open Live Demo
+
+
+Online demo Notebook will look similar to this:
+
+
+.. image:: https://user-images.githubusercontent.com/1155573/56820861-5c1c1880-6823-11e9-802a-ce01c5ec574f.gif
+ :alt: Open Live Demo
+ :width: 480 px
+
+
+Release Notes
+-------------
+
+v3.1 brings support for ECMA 262 dialect regular expressions
+throughout schemas, as recommended by the specification. Big
+thanks to @Zac-HD for authoring support in a new `js-regex
+<https://pypi.org/project/js-regex/>`_ library.
+
+
+Running the Test Suite
+----------------------
+
+If you have ``tox`` installed (perhaps via ``pip install tox`` or your
+package manager), running ``tox`` in the directory of your source
+checkout will run ``jsonschema``'s test suite on all of the versions
+of Python ``jsonschema`` supports. If you don't have all of the
+versions that ``jsonschema`` is tested under, you'll likely want to run
+using ``tox``'s ``--skip-missing-interpreters`` option.
+
+Of course you're also free to just run the tests on a single version with your
+favorite test runner. The tests live in the ``jsonschema.tests`` package.
+
+
+Benchmarks
+----------
+
+``jsonschema``'s benchmarks make use of `pyperf
+<https://pyperf.readthedocs.io>`_.
+
+Running them can be done via ``tox -e perf``, or by invoking the ``pyperf``
+commands externally (after ensuring that both it and ``jsonschema`` itself are
+installed)::
+
+ $ python -m pyperf jsonschema/benchmarks/test_suite.py --hist --output results.json
+
+To compare to a previous run, use::
+
+ $ python -m pyperf compare_to --table reference.json results.json
+
+See the ``pyperf`` documentation for more details.
+
+
+Community
+---------
+
+There's a `mailing list <https://groups.google.com/forum/#!forum/jsonschema>`_
+for this implementation on Google Groups.
+
+Please join, and feel free to send questions there.
+
+
+Contributing
+------------
+
+I'm Julian Berman.
+
+``jsonschema`` is on `GitHub <https://github.com/Julian/jsonschema>`_.
+
+Get in touch, via GitHub or otherwise, if you've got something to contribute,
+it'd be most welcome!
+
+You can also generally find me on Freenode (nick: ``tos9``) in various
+channels, including ``#python``.
+
+If you feel overwhelmingly grateful, you can also woo me with beer money
+via Google Pay with the email in my GitHub profile.
+
+And for companies who appreciate ``jsonschema`` and its continued support
+and growth, ``jsonschema`` is also now supportable via `TideLift
+<https://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-j
+sonschema&utm_medium=referral&utm_campaign=readme>`_.
+
+
diff --git a/contrib/python/jsonschema/py2/.dist-info/entry_points.txt b/contrib/python/jsonschema/py2/.dist-info/entry_points.txt
new file mode 100644
index 00000000000..c627b310cd0
--- /dev/null
+++ b/contrib/python/jsonschema/py2/.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+jsonschema = jsonschema.cli:main
+
diff --git a/contrib/python/jsonschema/py2/.dist-info/top_level.txt b/contrib/python/jsonschema/py2/.dist-info/top_level.txt
new file mode 100644
index 00000000000..d89304b1a89
--- /dev/null
+++ b/contrib/python/jsonschema/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+jsonschema
diff --git a/contrib/python/jsonschema/py2/COPYING b/contrib/python/jsonschema/py2/COPYING
new file mode 100644
index 00000000000..af9cfbdb134
--- /dev/null
+++ b/contrib/python/jsonschema/py2/COPYING
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Julian Berman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/contrib/python/jsonschema/py2/README.rst b/contrib/python/jsonschema/py2/README.rst
new file mode 100644
index 00000000000..ccfb55d02d4
--- /dev/null
+++ b/contrib/python/jsonschema/py2/README.rst
@@ -0,0 +1,179 @@
+==========
+jsonschema
+==========
+
+|PyPI| |Pythons| |Travis| |AppVeyor| |Codecov| |ReadTheDocs|
+
+.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema.svg
+ :alt: PyPI version
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema.svg
+ :alt: Supported Python versions
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Travis| image:: https://travis-ci.com/Julian/jsonschema.svg?branch=master
+ :alt: Travis build status
+ :target: https://travis-ci.com/Julian/jsonschema
+
+.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/adtt0aiaihy6muyn/branch/master?svg=true
+ :alt: AppVeyor build status
+ :target: https://ci.appveyor.com/project/Julian/jsonschema
+
+.. |Codecov| image:: https://codecov.io/gh/Julian/jsonschema/branch/master/graph/badge.svg
+ :alt: Codecov Code coverage
+ :target: https://codecov.io/gh/Julian/jsonschema
+
+.. |ReadTheDocs| image:: https://readthedocs.org/projects/python-jsonschema/badge/?version=stable&style=flat
+ :alt: ReadTheDocs status
+ :target: https://python-jsonschema.readthedocs.io/en/stable/
+
+
+``jsonschema`` is an implementation of `JSON Schema <https://json-schema.org>`_
+for Python (supporting 2.7+ including Python 3).
+
+.. code-block:: python
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
+
+ >>> validate(
+ ... instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+It can also be used from console:
+
+.. code-block:: bash
+
+ $ jsonschema -i sample.json sample.schema
+
+Features
+--------
+
+* Full support for
+ `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator>`_,
+ `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft6Validator>`_,
+ `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft4Validator>`_
+ and
+ `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft3Validator>`_
+
+* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
+ that can iteratively report *all* validation errors.
+
+* `Programmatic querying <https://python-jsonschema.readthedocs.io/en/latest/errors/>`_
+ of which properties or items failed validation.
+
+
+Installation
+------------
+
+``jsonschema`` is available on `PyPI <https://pypi.org/project/jsonschema/>`_. You can install using `pip <https://pip.pypa.io/en/stable/>`_:
+
+.. code-block:: bash
+
+ $ pip install jsonschema
+
+
+Demo
+----
+
+Try ``jsonschema`` interactively in this online demo:
+
+.. image:: https://user-images.githubusercontent.com/1155573/56745335-8b158a00-6750-11e9-8776-83fa675939c4.png
+ :target: https://notebooks.ai/demo/gh/Julian/jsonschema
+ :alt: Open Live Demo
+
+
+Online demo Notebook will look similar to this:
+
+
+.. image:: https://user-images.githubusercontent.com/1155573/56820861-5c1c1880-6823-11e9-802a-ce01c5ec574f.gif
+ :alt: Open Live Demo
+ :width: 480 px
+
+
+Release Notes
+-------------
+
+v3.1 brings support for ECMA 262 dialect regular expressions
+throughout schemas, as recommended by the specification. Big
+thanks to @Zac-HD for authoring support in a new `js-regex
+<https://pypi.org/project/js-regex/>`_ library.
+
+
+Running the Test Suite
+----------------------
+
+If you have ``tox`` installed (perhaps via ``pip install tox`` or your
+package manager), running ``tox`` in the directory of your source
+checkout will run ``jsonschema``'s test suite on all of the versions
+of Python ``jsonschema`` supports. If you don't have all of the
+versions that ``jsonschema`` is tested under, you'll likely want to run
+using ``tox``'s ``--skip-missing-interpreters`` option.
+
+Of course you're also free to just run the tests on a single version with your
+favorite test runner. The tests live in the ``jsonschema.tests`` package.
+
+
+Benchmarks
+----------
+
+``jsonschema``'s benchmarks make use of `pyperf
+<https://pyperf.readthedocs.io>`_.
+
+Running them can be done via ``tox -e perf``, or by invoking the ``pyperf``
+commands externally (after ensuring that both it and ``jsonschema`` itself are
+installed)::
+
+ $ python -m pyperf jsonschema/benchmarks/test_suite.py --hist --output results.json
+
+To compare to a previous run, use::
+
+ $ python -m pyperf compare_to --table reference.json results.json
+
+See the ``pyperf`` documentation for more details.
+
+
+Community
+---------
+
+There's a `mailing list <https://groups.google.com/forum/#!forum/jsonschema>`_
+for this implementation on Google Groups.
+
+Please join, and feel free to send questions there.
+
+
+Contributing
+------------
+
+I'm Julian Berman.
+
+``jsonschema`` is on `GitHub <https://github.com/Julian/jsonschema>`_.
+
+Get in touch, via GitHub or otherwise, if you've got something to contribute,
+it'd be most welcome!
+
+You can also generally find me on Freenode (nick: ``tos9``) in various
+channels, including ``#python``.
+
+If you feel overwhelmingly grateful, you can also woo me with beer money
+via Google Pay with the email in my GitHub profile.
+
+And for companies who appreciate ``jsonschema`` and its continued support
+and growth, ``jsonschema`` is also now supportable via `TideLift
+<https://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-j
+sonschema&utm_medium=referral&utm_campaign=readme>`_.
diff --git a/contrib/python/jsonschema/py2/jsonschema/__init__.py b/contrib/python/jsonschema/py2/jsonschema/__init__.py
new file mode 100644
index 00000000000..6b630cdfbbe
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/__init__.py
@@ -0,0 +1,34 @@
+"""
+An implementation of JSON Schema for Python
+
+The main functionality is provided by the validator classes for each of the
+supported JSON Schema versions.
+
+Most commonly, `validate` is the quickest way to simply validate a given
+instance under a schema, and will create a validator for you.
+"""
+
+from jsonschema.exceptions import (
+ ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError
+)
+from jsonschema._format import (
+ FormatChecker,
+ draft3_format_checker,
+ draft4_format_checker,
+ draft6_format_checker,
+ draft7_format_checker,
+)
+from jsonschema._types import TypeChecker
+from jsonschema.validators import (
+ Draft3Validator,
+ Draft4Validator,
+ Draft6Validator,
+ Draft7Validator,
+ RefResolver,
+ validate,
+)
+try:
+ from importlib import metadata
+except ImportError: # for Python<3.8
+ import importlib_metadata as metadata
+__version__ = metadata.version("jsonschema")
diff --git a/contrib/python/jsonschema/py2/jsonschema/__main__.py b/contrib/python/jsonschema/py2/jsonschema/__main__.py
new file mode 100644
index 00000000000..82c29fd39e7
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/__main__.py
@@ -0,0 +1,2 @@
+from jsonschema.cli import main
+main()
diff --git a/contrib/python/jsonschema/py2/jsonschema/_format.py b/contrib/python/jsonschema/py2/jsonschema/_format.py
new file mode 100644
index 00000000000..281a7cfcffe
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/_format.py
@@ -0,0 +1,425 @@
+import datetime
+import re
+import socket
+import struct
+
+from jsonschema.compat import str_types
+from jsonschema.exceptions import FormatError
+
+
+class FormatChecker(object):
+ """
+ A ``format`` property checker.
+
+ JSON Schema does not mandate that the ``format`` property actually do any
+ validation. If validation is desired however, instances of this class can
+ be hooked into validators to enable format validation.
+
+ `FormatChecker` objects always return ``True`` when asked about
+ formats that they do not know how to validate.
+
+ To check a custom format using a function that takes an instance and
+ returns a ``bool``, use the `FormatChecker.checks` or
+ `FormatChecker.cls_checks` decorators.
+
+ Arguments:
+
+ formats (~collections.Iterable):
+
+ The known formats to validate. This argument can be used to
+ limit which formats will be used during validation.
+ """
+
+ checkers = {}
+
+ def __init__(self, formats=None):
+ if formats is None:
+ self.checkers = self.checkers.copy()
+ else:
+ self.checkers = dict((k, self.checkers[k]) for k in formats)
+
+ def __repr__(self):
+ return "<FormatChecker checkers={}>".format(sorted(self.checkers))
+
+ def checks(self, format, raises=()):
+ """
+ Register a decorated function as validating a new format.
+
+ Arguments:
+
+ format (str):
+
+ The format that the decorated function will check.
+
+ raises (Exception):
+
+ The exception(s) raised by the decorated function when an
+ invalid instance is found.
+
+ The exception object will be accessible as the
+ `jsonschema.exceptions.ValidationError.cause` attribute of the
+ resulting validation error.
+ """
+
+ def _checks(func):
+ self.checkers[format] = (func, raises)
+ return func
+ return _checks
+
+ cls_checks = classmethod(checks)
+
+ def check(self, instance, format):
+ """
+ Check whether the instance conforms to the given format.
+
+ Arguments:
+
+ instance (*any primitive type*, i.e. str, number, bool):
+
+ The instance to check
+
+ format (str):
+
+ The format that instance should conform to
+
+
+ Raises:
+
+ FormatError: if the instance does not conform to ``format``
+ """
+
+ if format not in self.checkers:
+ return
+
+ func, raises = self.checkers[format]
+ result, cause = None, None
+ try:
+ result = func(instance)
+ except raises as e:
+ cause = e
+ if not result:
+ raise FormatError(
+ "%r is not a %r" % (instance, format), cause=cause,
+ )
+
+ def conforms(self, instance, format):
+ """
+ Check whether the instance conforms to the given format.
+
+ Arguments:
+
+ instance (*any primitive type*, i.e. str, number, bool):
+
+ The instance to check
+
+ format (str):
+
+ The format that instance should conform to
+
+ Returns:
+
+ bool: whether it conformed
+ """
+
+ try:
+ self.check(instance, format)
+ except FormatError:
+ return False
+ else:
+ return True
+
+
+draft3_format_checker = FormatChecker()
+draft4_format_checker = FormatChecker()
+draft6_format_checker = FormatChecker()
+draft7_format_checker = FormatChecker()
+
+
+_draft_checkers = dict(
+ draft3=draft3_format_checker,
+ draft4=draft4_format_checker,
+ draft6=draft6_format_checker,
+ draft7=draft7_format_checker,
+)
+
+
+def _checks_drafts(
+ name=None,
+ draft3=None,
+ draft4=None,
+ draft6=None,
+ draft7=None,
+ raises=(),
+):
+ draft3 = draft3 or name
+ draft4 = draft4 or name
+ draft6 = draft6 or name
+ draft7 = draft7 or name
+
+ def wrap(func):
+ if draft3:
+ func = _draft_checkers["draft3"].checks(draft3, raises)(func)
+ if draft4:
+ func = _draft_checkers["draft4"].checks(draft4, raises)(func)
+ if draft6:
+ func = _draft_checkers["draft6"].checks(draft6, raises)(func)
+ if draft7:
+ func = _draft_checkers["draft7"].checks(draft7, raises)(func)
+
+ # Oy. This is bad global state, but relied upon for now, until
+ # deprecation. See https://github.com/Julian/jsonschema/issues/519
+ # and test_format_checkers_come_with_defaults
+ FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)(
+ func,
+ )
+ return func
+ return wrap
+
+
+@_checks_drafts(name="idn-email")
+@_checks_drafts(name="email")
+def is_email(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return "@" in instance
+
+
+_ipv4_re = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
+
+
+@_checks_drafts(
+ draft3="ip-address", draft4="ipv4", draft6="ipv4", draft7="ipv4",
+)
+def is_ipv4(instance):
+ if not isinstance(instance, str_types):
+ return True
+ if not _ipv4_re.match(instance):
+ return False
+ return all(0 <= int(component) <= 255 for component in instance.split("."))
+
+
+if hasattr(socket, "inet_pton"):
+ # FIXME: Really this only should raise struct.error, but see the sadness
+ # that is https://twistedmatrix.com/trac/ticket/9409
+ @_checks_drafts(
+ name="ipv6", raises=(socket.error, struct.error, ValueError),
+ )
+ def is_ipv6(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return socket.inet_pton(socket.AF_INET6, instance)
+
+
+_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$")
+
+
+@_checks_drafts(
+ draft3="host-name",
+ draft4="hostname",
+ draft6="hostname",
+ draft7="hostname",
+)
+def is_host_name(instance):
+ if not isinstance(instance, str_types):
+ return True
+ if not _host_name_re.match(instance):
+ return False
+ components = instance.split(".")
+ for component in components:
+ if len(component) > 63:
+ return False
+ return True
+
+
+try:
+ # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
+ import idna
+except ImportError:
+ pass
+else:
+ @_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError)
+ def is_idn_host_name(instance):
+ if not isinstance(instance, str_types):
+ return True
+ idna.encode(instance)
+ return True
+
+
+try:
+ import rfc3987
+except ImportError:
+ try:
+ from rfc3986_validator import validate_rfc3986
+ except ImportError:
+ pass
+ else:
+ @_checks_drafts(name="uri")
+ def is_uri(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return validate_rfc3986(instance, rule="URI")
+
+ @_checks_drafts(
+ draft6="uri-reference",
+ draft7="uri-reference",
+ raises=ValueError,
+ )
+ def is_uri_reference(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return validate_rfc3986(instance, rule="URI_reference")
+
+else:
+ @_checks_drafts(draft7="iri", raises=ValueError)
+ def is_iri(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="IRI")
+
+ @_checks_drafts(draft7="iri-reference", raises=ValueError)
+ def is_iri_reference(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="IRI_reference")
+
+ @_checks_drafts(name="uri", raises=ValueError)
+ def is_uri(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="URI")
+
+ @_checks_drafts(
+ draft6="uri-reference",
+ draft7="uri-reference",
+ raises=ValueError,
+ )
+ def is_uri_reference(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="URI_reference")
+
+
+try:
+ from strict_rfc3339 import validate_rfc3339
+except ImportError:
+ try:
+ from rfc3339_validator import validate_rfc3339
+ except ImportError:
+ validate_rfc3339 = None
+
+if validate_rfc3339:
+ @_checks_drafts(name="date-time")
+ def is_datetime(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return validate_rfc3339(instance)
+
+ @_checks_drafts(draft7="time")
+ def is_time(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return is_datetime("1970-01-01T" + instance)
+
+
+@_checks_drafts(name="regex", raises=re.error)
+def is_regex(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return re.compile(instance)
+
+
+@_checks_drafts(draft3="date", draft7="date", raises=ValueError)
+def is_date(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return datetime.datetime.strptime(instance, "%Y-%m-%d")
+
+
+@_checks_drafts(draft3="time", raises=ValueError)
+def is_draft3_time(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return datetime.datetime.strptime(instance, "%H:%M:%S")
+
+
+try:
+ import webcolors
+except ImportError:
+ pass
+else:
+ def is_css_color_code(instance):
+ return webcolors.normalize_hex(instance)
+
+ @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
+ def is_css21_color(instance):
+ if (
+ not isinstance(instance, str_types) or
+ instance.lower() in webcolors.css21_names_to_hex
+ ):
+ return True
+ return is_css_color_code(instance)
+
+ def is_css3_color(instance):
+ if instance.lower() in webcolors.css3_names_to_hex:
+ return True
+ return is_css_color_code(instance)
+
+
+try:
+ import jsonpointer
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft6="json-pointer",
+ draft7="json-pointer",
+ raises=jsonpointer.JsonPointerException,
+ )
+ def is_json_pointer(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return jsonpointer.JsonPointer(instance)
+
+ # TODO: I don't want to maintain this, so it
+ # needs to go either into jsonpointer (pending
+ # https://github.com/stefankoegl/python-json-pointer/issues/34) or
+ # into a new external library.
+ @_checks_drafts(
+ draft7="relative-json-pointer",
+ raises=jsonpointer.JsonPointerException,
+ )
+ def is_relative_json_pointer(instance):
+ # Definition taken from:
+ # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
+ if not isinstance(instance, str_types):
+ return True
+ non_negative_integer, rest = [], ""
+ for i, character in enumerate(instance):
+ if character.isdigit():
+ non_negative_integer.append(character)
+ continue
+
+ if not non_negative_integer:
+ return False
+
+ rest = instance[i:]
+ break
+ return (rest == "#") or jsonpointer.JsonPointer(rest)
+
+
+try:
+ import uritemplate.exceptions
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft6="uri-template",
+ draft7="uri-template",
+ raises=uritemplate.exceptions.InvalidTemplate,
+ )
+ def is_uri_template(
+ instance,
+ template_validator=uritemplate.Validator().force_balanced_braces(),
+ ):
+ template = uritemplate.URITemplate(instance)
+ return template_validator.validate(template)
diff --git a/contrib/python/jsonschema/py2/jsonschema/_legacy_validators.py b/contrib/python/jsonschema/py2/jsonschema/_legacy_validators.py
new file mode 100644
index 00000000000..264ff7d7135
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/_legacy_validators.py
@@ -0,0 +1,141 @@
+from jsonschema import _utils
+from jsonschema.compat import iteritems
+from jsonschema.exceptions import ValidationError
+
+
+def dependencies_draft3(validator, dependencies, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, dependency in iteritems(dependencies):
+ if property not in instance:
+ continue
+
+ if validator.is_type(dependency, "object"):
+ for error in validator.descend(
+ instance, dependency, schema_path=property,
+ ):
+ yield error
+ elif validator.is_type(dependency, "string"):
+ if dependency not in instance:
+ yield ValidationError(
+ "%r is a dependency of %r" % (dependency, property)
+ )
+ else:
+ for each in dependency:
+ if each not in instance:
+ message = "%r is a dependency of %r"
+ yield ValidationError(message % (each, property))
+
+
+def disallow_draft3(validator, disallow, instance, schema):
+ for disallowed in _utils.ensure_list(disallow):
+ if validator.is_valid(instance, {"type": [disallowed]}):
+ yield ValidationError(
+ "%r is disallowed for %r" % (disallowed, instance)
+ )
+
+
+def extends_draft3(validator, extends, instance, schema):
+ if validator.is_type(extends, "object"):
+ for error in validator.descend(instance, extends):
+ yield error
+ return
+ for index, subschema in enumerate(extends):
+ for error in validator.descend(instance, subschema, schema_path=index):
+ yield error
+
+
+def items_draft3_draft4(validator, items, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if validator.is_type(items, "object"):
+ for index, item in enumerate(instance):
+ for error in validator.descend(item, items, path=index):
+ yield error
+ else:
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in validator.descend(
+ item, subschema, path=index, schema_path=index,
+ ):
+ yield error
+
+
+def minimum_draft3_draft4(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if schema.get("exclusiveMinimum", False):
+ failed = instance <= minimum
+ cmp = "less than or equal to"
+ else:
+ failed = instance < minimum
+ cmp = "less than"
+
+ if failed:
+ yield ValidationError(
+ "%r is %s the minimum of %r" % (instance, cmp, minimum)
+ )
+
+
+def maximum_draft3_draft4(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if schema.get("exclusiveMaximum", False):
+ failed = instance >= maximum
+ cmp = "greater than or equal to"
+ else:
+ failed = instance > maximum
+ cmp = "greater than"
+
+ if failed:
+ yield ValidationError(
+ "%r is %s the maximum of %r" % (instance, cmp, maximum)
+ )
+
+
+def properties_draft3(validator, properties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, subschema in iteritems(properties):
+ if property in instance:
+ for error in validator.descend(
+ instance[property],
+ subschema,
+ path=property,
+ schema_path=property,
+ ):
+ yield error
+ elif subschema.get("required", False):
+ error = ValidationError("%r is a required property" % property)
+ error._set(
+ validator="required",
+ validator_value=subschema["required"],
+ instance=instance,
+ schema=schema,
+ )
+ error.path.appendleft(property)
+ error.schema_path.extend([property, "required"])
+ yield error
+
+
+def type_draft3(validator, types, instance, schema):
+ types = _utils.ensure_list(types)
+
+ all_errors = []
+ for index, type in enumerate(types):
+ if validator.is_type(type, "object"):
+ errors = list(validator.descend(instance, type, schema_path=index))
+ if not errors:
+ return
+ all_errors.extend(errors)
+ else:
+ if validator.is_type(instance, type):
+ return
+ else:
+ yield ValidationError(
+ _utils.types_msg(instance, types), context=all_errors,
+ )
diff --git a/contrib/python/jsonschema/py2/jsonschema/_reflect.py b/contrib/python/jsonschema/py2/jsonschema/_reflect.py
new file mode 100644
index 00000000000..d09e38fbdcf
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/_reflect.py
@@ -0,0 +1,155 @@
+# -*- test-case-name: twisted.test.test_reflect -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Standardized versions of various cool and/or strange things that you can do
+with Python's reflection capabilities.
+"""
+
+import sys
+
+from jsonschema.compat import PY3
+
+
+class _NoModuleFound(Exception):
+ """
+ No module was found because none exists.
+ """
+
+
+
+class InvalidName(ValueError):
+ """
+ The given name is not a dot-separated list of Python objects.
+ """
+
+
+
+class ModuleNotFound(InvalidName):
+ """
+ The module associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+
+class ObjectNotFound(InvalidName):
+ """
+ The object associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+
+if PY3:
+ def reraise(exception, traceback):
+ raise exception.with_traceback(traceback)
+else:
+ exec("""def reraise(exception, traceback):
+ raise exception.__class__, exception, traceback""")
+
+reraise.__doc__ = """
+Re-raise an exception, with an optional traceback, in a way that is compatible
+with both Python 2 and Python 3.
+
+Note that on Python 3, re-raised exceptions will be mutated, with their
+C{__traceback__} attribute being set.
+
+@param exception: The exception instance.
+@param traceback: The traceback to use, or C{None} indicating a new traceback.
+"""
+
+
+def _importAndCheckStack(importName):
+ """
+ Import the given name as a module, then walk the stack to determine whether
+ the failure was the module not existing, or some code in the module (for
+ example a dependent import) failing. This can be helpful to determine
+ whether any actual application code was run. For example, to distiguish
+ administrative error (entering the wrong module name), from programmer
+ error (writing buggy code in a module that fails to import).
+
+ @param importName: The name of the module to import.
+ @type importName: C{str}
+ @raise Exception: if something bad happens. This can be any type of
+ exception, since nobody knows what loading some arbitrary code might
+ do.
+ @raise _NoModuleFound: if no module was found.
+ """
+ try:
+ return __import__(importName)
+ except ImportError:
+ excType, excValue, excTraceback = sys.exc_info()
+ while excTraceback:
+ execName = excTraceback.tb_frame.f_globals["__name__"]
+ # in Python 2 execName is None when an ImportError is encountered,
+ # where in Python 3 execName is equal to the importName.
+ if execName is None or execName == importName:
+ reraise(excValue, excTraceback)
+ excTraceback = excTraceback.tb_next
+ raise _NoModuleFound()
+
+
+
+def namedAny(name):
+ """
+ Retrieve a Python object by its fully qualified name from the global Python
+ module namespace. The first part of the name, that describes a module,
+ will be discovered and imported. Each subsequent part of the name is
+ treated as the name of an attribute of the object specified by all of the
+ name which came before it. For example, the fully-qualified name of this
+ object is 'twisted.python.reflect.namedAny'.
+
+ @type name: L{str}
+ @param name: The name of the object to return.
+
+ @raise InvalidName: If the name is an empty string, starts or ends with
+ a '.', or is otherwise syntactically incorrect.
+
+ @raise ModuleNotFound: If the name is syntactically correct but the
+ module it specifies cannot be imported because it does not appear to
+ exist.
+
+ @raise ObjectNotFound: If the name is syntactically correct, includes at
+ least one '.', but the module it specifies cannot be imported because
+ it does not appear to exist.
+
+ @raise AttributeError: If an attribute of an object along the way cannot be
+ accessed, or a module along the way is not found.
+
+ @return: the Python object identified by 'name'.
+ """
+ if not name:
+ raise InvalidName('Empty module name')
+
+ names = name.split('.')
+
+ # if the name starts or ends with a '.' or contains '..', the __import__
+ # will raise an 'Empty module name' error. This will provide a better error
+ # message.
+ if '' in names:
+ raise InvalidName(
+ "name must be a string giving a '.'-separated list of Python "
+ "identifiers, not %r" % (name,))
+
+ topLevelPackage = None
+ moduleNames = names[:]
+ while not topLevelPackage:
+ if moduleNames:
+ trialname = '.'.join(moduleNames)
+ try:
+ topLevelPackage = _importAndCheckStack(trialname)
+ except _NoModuleFound:
+ moduleNames.pop()
+ else:
+ if len(names) == 1:
+ raise ModuleNotFound("No module named %r" % (name,))
+ else:
+ raise ObjectNotFound('%r does not name an object' % (name,))
+
+ obj = topLevelPackage
+ for n in names[1:]:
+ obj = getattr(obj, n)
+
+ return obj
diff --git a/contrib/python/jsonschema/py2/jsonschema/_types.py b/contrib/python/jsonschema/py2/jsonschema/_types.py
new file mode 100644
index 00000000000..a71a4e34bdc
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/_types.py
@@ -0,0 +1,188 @@
+import numbers
+
+from pyrsistent import pmap
+import attr
+
+from jsonschema.compat import int_types, str_types
+from jsonschema.exceptions import UndefinedTypeCheck
+
+
+def is_array(checker, instance):
+ return isinstance(instance, list)
+
+
+def is_bool(checker, instance):
+ return isinstance(instance, bool)
+
+
+def is_integer(checker, instance):
+ # bool inherits from int, so ensure bools aren't reported as ints
+ if isinstance(instance, bool):
+ return False
+ return isinstance(instance, int_types)
+
+
+def is_null(checker, instance):
+ return instance is None
+
+
+def is_number(checker, instance):
+ # bool inherits from int, so ensure bools aren't reported as ints
+ if isinstance(instance, bool):
+ return False
+ return isinstance(instance, numbers.Number)
+
+
+def is_object(checker, instance):
+ return isinstance(instance, dict)
+
+
+def is_string(checker, instance):
+ return isinstance(instance, str_types)
+
+
+def is_any(checker, instance):
+ return True
+
+
[email protected](frozen=True)
+class TypeChecker(object):
+ """
+ A ``type`` property checker.
+
+ A `TypeChecker` performs type checking for an `IValidator`. Type
+ checks to perform are updated using `TypeChecker.redefine` or
+ `TypeChecker.redefine_many` and removed via `TypeChecker.remove`.
+ Each of these return a new `TypeChecker` object.
+
+ Arguments:
+
+ type_checkers (dict):
+
+ The initial mapping of types to their checking functions.
+ """
+ _type_checkers = attr.ib(default=pmap(), converter=pmap)
+
+ def is_type(self, instance, type):
+ """
+ Check if the instance is of the appropriate type.
+
+ Arguments:
+
+ instance (object):
+
+ The instance to check
+
+ type (str):
+
+ The name of the type that is expected.
+
+ Returns:
+
+ bool: Whether it conformed.
+
+
+ Raises:
+
+ `jsonschema.exceptions.UndefinedTypeCheck`:
+ if type is unknown to this object.
+ """
+ try:
+ fn = self._type_checkers[type]
+ except KeyError:
+ raise UndefinedTypeCheck(type)
+
+ return fn(self, instance)
+
+ def redefine(self, type, fn):
+ """
+ Produce a new checker with the given type redefined.
+
+ Arguments:
+
+ type (str):
+
+ The name of the type to check.
+
+ fn (collections.Callable):
+
+ A function taking exactly two parameters - the type
+ checker calling the function and the instance to check.
+ The function should return true if instance is of this
+ type and false otherwise.
+
+ Returns:
+
+ A new `TypeChecker` instance.
+ """
+ return self.redefine_many({type: fn})
+
+ def redefine_many(self, definitions=()):
+ """
+ Produce a new checker with the given types redefined.
+
+ Arguments:
+
+ definitions (dict):
+
+ A dictionary mapping types to their checking functions.
+
+ Returns:
+
+ A new `TypeChecker` instance.
+ """
+ return attr.evolve(
+ self, type_checkers=self._type_checkers.update(definitions),
+ )
+
+ def remove(self, *types):
+ """
+ Produce a new checker with the given types forgotten.
+
+ Arguments:
+
+ types (~collections.Iterable):
+
+ the names of the types to remove.
+
+ Returns:
+
+ A new `TypeChecker` instance
+
+ Raises:
+
+ `jsonschema.exceptions.UndefinedTypeCheck`:
+
+ if any given type is unknown to this object
+ """
+
+ checkers = self._type_checkers
+ for each in types:
+ try:
+ checkers = checkers.remove(each)
+ except KeyError:
+ raise UndefinedTypeCheck(each)
+ return attr.evolve(self, type_checkers=checkers)
+
+
+draft3_type_checker = TypeChecker(
+ {
+ u"any": is_any,
+ u"array": is_array,
+ u"boolean": is_bool,
+ u"integer": is_integer,
+ u"object": is_object,
+ u"null": is_null,
+ u"number": is_number,
+ u"string": is_string,
+ },
+)
+draft4_type_checker = draft3_type_checker.remove(u"any")
+draft6_type_checker = draft4_type_checker.redefine(
+ u"integer",
+ lambda checker, instance: (
+ is_integer(checker, instance) or
+ isinstance(instance, float) and instance.is_integer()
+ ),
+)
+draft7_type_checker = draft6_type_checker
diff --git a/contrib/python/jsonschema/py2/jsonschema/_utils.py b/contrib/python/jsonschema/py2/jsonschema/_utils.py
new file mode 100644
index 00000000000..ceb880198d1
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/_utils.py
@@ -0,0 +1,212 @@
+import itertools
+import json
+import pkgutil
+import re
+
+from jsonschema.compat import MutableMapping, str_types, urlsplit
+
+
+class URIDict(MutableMapping):
+ """
+ Dictionary which uses normalized URIs as keys.
+ """
+
+ def normalize(self, uri):
+ return urlsplit(uri).geturl()
+
+ def __init__(self, *args, **kwargs):
+ self.store = dict()
+ self.store.update(*args, **kwargs)
+
+ def __getitem__(self, uri):
+ return self.store[self.normalize(uri)]
+
+ def __setitem__(self, uri, value):
+ self.store[self.normalize(uri)] = value
+
+ def __delitem__(self, uri):
+ del self.store[self.normalize(uri)]
+
+ def __iter__(self):
+ return iter(self.store)
+
+ def __len__(self):
+ return len(self.store)
+
+ def __repr__(self):
+ return repr(self.store)
+
+
+class Unset(object):
+ """
+ An as-of-yet unset attribute or unprovided default parameter.
+ """
+
+ def __repr__(self):
+ return "<unset>"
+
+
+def load_schema(name):
+ """
+ Load a schema from ./schemas/``name``.json and return it.
+ """
+
+ data = pkgutil.get_data("jsonschema", "schemas/{0}.json".format(name))
+ return json.loads(data.decode("utf-8"))
+
+
+def indent(string, times=1):
+ """
+ A dumb version of `textwrap.indent` from Python 3.3.
+ """
+
+ return "\n".join(" " * (4 * times) + line for line in string.splitlines())
+
+
+def format_as_index(indices):
+ """
+ Construct a single string containing indexing operations for the indices.
+
+ For example, [1, 2, "foo"] -> [1][2]["foo"]
+
+ Arguments:
+
+ indices (sequence):
+
+ The indices to format.
+ """
+
+ if not indices:
+ return ""
+ return "[%s]" % "][".join(repr(index) for index in indices)
+
+
+def find_additional_properties(instance, schema):
+ """
+ Return the set of additional properties for the given ``instance``.
+
+ Weeds out properties that should have been validated by ``properties`` and
+ / or ``patternProperties``.
+
+ Assumes ``instance`` is dict-like already.
+ """
+
+ properties = schema.get("properties", {})
+ patterns = "|".join(schema.get("patternProperties", {}))
+ for property in instance:
+ if property not in properties:
+ if patterns and re.search(patterns, property):
+ continue
+ yield property
+
+
+def extras_msg(extras):
+ """
+ Create an error message for extra items or properties.
+ """
+
+ if len(extras) == 1:
+ verb = "was"
+ else:
+ verb = "were"
+ return ", ".join(repr(extra) for extra in extras), verb
+
+
+def types_msg(instance, types):
+ """
+ Create an error message for a failure to match the given types.
+
+ If the ``instance`` is an object and contains a ``name`` property, it will
+ be considered to be a description of that object and used as its type.
+
+ Otherwise the message is simply the reprs of the given ``types``.
+ """
+
+ reprs = []
+ for type in types:
+ try:
+ reprs.append(repr(type["name"]))
+ except Exception:
+ reprs.append(repr(type))
+ return "%r is not of type %s" % (instance, ", ".join(reprs))
+
+
+def flatten(suitable_for_isinstance):
+ """
+ isinstance() can accept a bunch of really annoying different types:
+ * a single type
+ * a tuple of types
+ * an arbitrary nested tree of tuples
+
+ Return a flattened tuple of the given argument.
+ """
+
+ types = set()
+
+ if not isinstance(suitable_for_isinstance, tuple):
+ suitable_for_isinstance = (suitable_for_isinstance,)
+ for thing in suitable_for_isinstance:
+ if isinstance(thing, tuple):
+ types.update(flatten(thing))
+ else:
+ types.add(thing)
+ return tuple(types)
+
+
+def ensure_list(thing):
+ """
+ Wrap ``thing`` in a list if it's a single str.
+
+ Otherwise, return it unchanged.
+ """
+
+ if isinstance(thing, str_types):
+ return [thing]
+ return thing
+
+
+def equal(one, two):
+ """
+ Check if two things are equal, but evade booleans and ints being equal.
+ """
+ return unbool(one) == unbool(two)
+
+
+def unbool(element, true=object(), false=object()):
+ """
+ A hack to make True and 1 and False and 0 unique for ``uniq``.
+ """
+
+ if element is True:
+ return true
+ elif element is False:
+ return false
+ return element
+
+
+def uniq(container):
+ """
+ Check if all of a container's elements are unique.
+
+ Successively tries first to rely that the elements are hashable, then
+ falls back on them being sortable, and finally falls back on brute
+ force.
+ """
+
+ try:
+ return len(set(unbool(i) for i in container)) == len(container)
+ except TypeError:
+ try:
+ sort = sorted(unbool(i) for i in container)
+ sliced = itertools.islice(sort, 1, None)
+ for i, j in zip(sort, sliced):
+ if i == j:
+ return False
+ except (NotImplementedError, TypeError):
+ seen = []
+ for e in container:
+ e = unbool(e)
+ if e in seen:
+ return False
+ seen.append(e)
+ return True
diff --git a/contrib/python/jsonschema/py2/jsonschema/_validators.py b/contrib/python/jsonschema/py2/jsonschema/_validators.py
new file mode 100644
index 00000000000..179fec09a94
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/_validators.py
@@ -0,0 +1,373 @@
+import re
+
+from jsonschema._utils import (
+ ensure_list,
+ equal,
+ extras_msg,
+ find_additional_properties,
+ types_msg,
+ unbool,
+ uniq,
+)
+from jsonschema.exceptions import FormatError, ValidationError
+from jsonschema.compat import iteritems
+
+
+def patternProperties(validator, patternProperties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for pattern, subschema in iteritems(patternProperties):
+ for k, v in iteritems(instance):
+ if re.search(pattern, k):
+ for error in validator.descend(
+ v, subschema, path=k, schema_path=pattern,
+ ):
+ yield error
+
+
+def propertyNames(validator, propertyNames, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property in instance:
+ for error in validator.descend(
+ instance=property,
+ schema=propertyNames,
+ ):
+ yield error
+
+
+def additionalProperties(validator, aP, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ extras = set(find_additional_properties(instance, schema))
+
+ if validator.is_type(aP, "object"):
+ for extra in extras:
+ for error in validator.descend(instance[extra], aP, path=extra):
+ yield error
+ elif not aP and extras:
+ if "patternProperties" in schema:
+ patterns = sorted(schema["patternProperties"])
+ if len(extras) == 1:
+ verb = "does"
+ else:
+ verb = "do"
+ error = "%s %s not match any of the regexes: %s" % (
+ ", ".join(map(repr, sorted(extras))),
+ verb,
+ ", ".join(map(repr, patterns)),
+ )
+ yield ValidationError(error)
+ else:
+ error = "Additional properties are not allowed (%s %s unexpected)"
+ yield ValidationError(error % extras_msg(extras))
+
+
+def items(validator, items, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if validator.is_type(items, "array"):
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in validator.descend(
+ item, subschema, path=index, schema_path=index,
+ ):
+ yield error
+ else:
+ for index, item in enumerate(instance):
+ for error in validator.descend(item, items, path=index):
+ yield error
+
+
+def additionalItems(validator, aI, instance, schema):
+ if (
+ not validator.is_type(instance, "array") or
+ validator.is_type(schema.get("items", {}), "object")
+ ):
+ return
+
+ len_items = len(schema.get("items", []))
+ if validator.is_type(aI, "object"):
+ for index, item in enumerate(instance[len_items:], start=len_items):
+ for error in validator.descend(item, aI, path=index):
+ yield error
+ elif not aI and len(instance) > len(schema.get("items", [])):
+ error = "Additional items are not allowed (%s %s unexpected)"
+ yield ValidationError(
+ error %
+ extras_msg(instance[len(schema.get("items", [])):])
+ )
+
+
+def const(validator, const, instance, schema):
+ if not equal(instance, const):
+ yield ValidationError("%r was expected" % (const,))
+
+
+def contains(validator, contains, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if not any(validator.is_valid(element, contains) for element in instance):
+ yield ValidationError(
+ "None of %r are valid under the given schema" % (instance,)
+ )
+
+
+def exclusiveMinimum(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance <= minimum:
+ yield ValidationError(
+ "%r is less than or equal to the minimum of %r" % (
+ instance, minimum,
+ ),
+ )
+
+
+def exclusiveMaximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance >= maximum:
+ yield ValidationError(
+ "%r is greater than or equal to the maximum of %r" % (
+ instance, maximum,
+ ),
+ )
+
+
+def minimum(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance < minimum:
+ yield ValidationError(
+ "%r is less than the minimum of %r" % (instance, minimum)
+ )
+
+
+def maximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance > maximum:
+ yield ValidationError(
+ "%r is greater than the maximum of %r" % (instance, maximum)
+ )
+
+
+def multipleOf(validator, dB, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if isinstance(dB, float):
+ quotient = instance / dB
+ failed = int(quotient) != quotient
+ else:
+ failed = instance % dB
+
+ if failed:
+ yield ValidationError("%r is not a multiple of %r" % (instance, dB))
+
+
+def minItems(validator, mI, instance, schema):
+ if validator.is_type(instance, "array") and len(instance) < mI:
+ yield ValidationError("%r is too short" % (instance,))
+
+
+def maxItems(validator, mI, instance, schema):
+ if validator.is_type(instance, "array") and len(instance) > mI:
+ yield ValidationError("%r is too long" % (instance,))
+
+
+def uniqueItems(validator, uI, instance, schema):
+ if (
+ uI and
+ validator.is_type(instance, "array") and
+ not uniq(instance)
+ ):
+ yield ValidationError("%r has non-unique elements" % (instance,))
+
+
+def pattern(validator, patrn, instance, schema):
+ if (
+ validator.is_type(instance, "string") and
+ not re.search(patrn, instance)
+ ):
+ yield ValidationError("%r does not match %r" % (instance, patrn))
+
+
+def format(validator, format, instance, schema):
+ if validator.format_checker is not None:
+ try:
+ validator.format_checker.check(instance, format)
+ except FormatError as error:
+ yield ValidationError(error.message, cause=error.cause)
+
+
+def minLength(validator, mL, instance, schema):
+ if validator.is_type(instance, "string") and len(instance) < mL:
+ yield ValidationError("%r is too short" % (instance,))
+
+
+def maxLength(validator, mL, instance, schema):
+ if validator.is_type(instance, "string") and len(instance) > mL:
+ yield ValidationError("%r is too long" % (instance,))
+
+
+def dependencies(validator, dependencies, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, dependency in iteritems(dependencies):
+ if property not in instance:
+ continue
+
+ if validator.is_type(dependency, "array"):
+ for each in dependency:
+ if each not in instance:
+ message = "%r is a dependency of %r"
+ yield ValidationError(message % (each, property))
+ else:
+ for error in validator.descend(
+ instance, dependency, schema_path=property,
+ ):
+ yield error
+
+
+def enum(validator, enums, instance, schema):
+ if instance == 0 or instance == 1:
+ unbooled = unbool(instance)
+ if all(unbooled != unbool(each) for each in enums):
+ yield ValidationError("%r is not one of %r" % (instance, enums))
+ elif instance not in enums:
+ yield ValidationError("%r is not one of %r" % (instance, enums))
+
+
+def ref(validator, ref, instance, schema):
+ resolve = getattr(validator.resolver, "resolve", None)
+ if resolve is None:
+ with validator.resolver.resolving(ref) as resolved:
+ for error in validator.descend(instance, resolved):
+ yield error
+ else:
+ scope, resolved = validator.resolver.resolve(ref)
+ validator.resolver.push_scope(scope)
+
+ try:
+ for error in validator.descend(instance, resolved):
+ yield error
+ finally:
+ validator.resolver.pop_scope()
+
+
+def type(validator, types, instance, schema):
+ types = ensure_list(types)
+
+ if not any(validator.is_type(instance, type) for type in types):
+ yield ValidationError(types_msg(instance, types))
+
+
+def properties(validator, properties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, subschema in iteritems(properties):
+ if property in instance:
+ for error in validator.descend(
+ instance[property],
+ subschema,
+ path=property,
+ schema_path=property,
+ ):
+ yield error
+
+
+def required(validator, required, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+ for property in required:
+ if property not in instance:
+ yield ValidationError("%r is a required property" % property)
+
+
+def minProperties(validator, mP, instance, schema):
+ if validator.is_type(instance, "object") and len(instance) < mP:
+ yield ValidationError(
+ "%r does not have enough properties" % (instance,)
+ )
+
+
+def maxProperties(validator, mP, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+ if validator.is_type(instance, "object") and len(instance) > mP:
+ yield ValidationError("%r has too many properties" % (instance,))
+
+
+def allOf(validator, allOf, instance, schema):
+ for index, subschema in enumerate(allOf):
+ for error in validator.descend(instance, subschema, schema_path=index):
+ yield error
+
+
+def anyOf(validator, anyOf, instance, schema):
+ all_errors = []
+ for index, subschema in enumerate(anyOf):
+ errs = list(validator.descend(instance, subschema, schema_path=index))
+ if not errs:
+ break
+ all_errors.extend(errs)
+ else:
+ yield ValidationError(
+ "%r is not valid under any of the given schemas" % (instance,),
+ context=all_errors,
+ )
+
+
+def oneOf(validator, oneOf, instance, schema):
+ subschemas = enumerate(oneOf)
+ all_errors = []
+ for index, subschema in subschemas:
+ errs = list(validator.descend(instance, subschema, schema_path=index))
+ if not errs:
+ first_valid = subschema
+ break
+ all_errors.extend(errs)
+ else:
+ yield ValidationError(
+ "%r is not valid under any of the given schemas" % (instance,),
+ context=all_errors,
+ )
+
+ more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)]
+ if more_valid:
+ more_valid.append(first_valid)
+ reprs = ", ".join(repr(schema) for schema in more_valid)
+ yield ValidationError(
+ "%r is valid under each of %s" % (instance, reprs)
+ )
+
+
+def not_(validator, not_schema, instance, schema):
+ if validator.is_valid(instance, not_schema):
+ yield ValidationError(
+ "%r is not allowed for %r" % (not_schema, instance)
+ )
+
+
+def if_(validator, if_schema, instance, schema):
+ if validator.is_valid(instance, if_schema):
+ if u"then" in schema:
+ then = schema[u"then"]
+ for error in validator.descend(instance, then, schema_path="then"):
+ yield error
+ elif u"else" in schema:
+ else_ = schema[u"else"]
+ for error in validator.descend(instance, else_, schema_path="else"):
+ yield error
diff --git a/contrib/python/jsonschema/py2/jsonschema/cli.py b/contrib/python/jsonschema/py2/jsonschema/cli.py
new file mode 100644
index 00000000000..ab3335b27c5
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/cli.py
@@ -0,0 +1,90 @@
+"""
+The ``jsonschema`` command line.
+"""
+from __future__ import absolute_import
+import argparse
+import json
+import sys
+
+from jsonschema import __version__
+from jsonschema._reflect import namedAny
+from jsonschema.validators import validator_for
+
+
+def _namedAnyWithDefault(name):
+ if "." not in name:
+ name = "jsonschema." + name
+ return namedAny(name)
+
+
+def _json_file(path):
+ with open(path) as file:
+ return json.load(file)
+
+
+parser = argparse.ArgumentParser(
+ description="JSON Schema Validation CLI",
+)
+parser.add_argument(
+ "-i", "--instance",
+ action="append",
+ dest="instances",
+ type=_json_file,
+ help=(
+ "a path to a JSON instance (i.e. filename.json) "
+ "to validate (may be specified multiple times)"
+ ),
+)
+parser.add_argument(
+ "-F", "--error-format",
+ default="{error.instance}: {error.message}\n",
+ help=(
+ "the format to use for each error output message, specified in "
+ "a form suitable for passing to str.format, which will be called "
+ "with 'error' for each error"
+ ),
+)
+parser.add_argument(
+ "-V", "--validator",
+ type=_namedAnyWithDefault,
+ help=(
+ "the fully qualified object name of a validator to use, or, for "
+ "validators that are registered with jsonschema, simply the name "
+ "of the class."
+ ),
+)
+parser.add_argument(
+ "--version",
+ action="version",
+ version=__version__,
+)
+parser.add_argument(
+ "schema",
+ help="the JSON Schema to validate with (i.e. schema.json)",
+ type=_json_file,
+)
+
+
+def parse_args(args):
+ arguments = vars(parser.parse_args(args=args or ["--help"]))
+ if arguments["validator"] is None:
+ arguments["validator"] = validator_for(arguments["schema"])
+ return arguments
+
+
+def main(args=sys.argv[1:]):
+ sys.exit(run(arguments=parse_args(args=args)))
+
+
+def run(arguments, stdout=sys.stdout, stderr=sys.stderr):
+ error_format = arguments["error_format"]
+ validator = arguments["validator"](schema=arguments["schema"])
+
+ validator.check_schema(arguments["schema"])
+
+ errored = False
+ for instance in arguments["instances"] or ():
+ for error in validator.iter_errors(instance):
+ stderr.write(error_format.format(error=error))
+ errored = True
+ return errored
diff --git a/contrib/python/jsonschema/py2/jsonschema/compat.py b/contrib/python/jsonschema/py2/jsonschema/compat.py
new file mode 100644
index 00000000000..47e09804551
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/compat.py
@@ -0,0 +1,55 @@
+"""
+Python 2/3 compatibility helpers.
+
+Note: This module is *not* public API.
+"""
+import contextlib
+import operator
+import sys
+
+
+try:
+ from collections.abc import MutableMapping, Sequence # noqa
+except ImportError:
+ from collections import MutableMapping, Sequence # noqa
+
+PY3 = sys.version_info[0] >= 3
+
+if PY3:
+ zip = zip
+ from functools import lru_cache
+ from io import StringIO as NativeIO
+ from urllib.parse import (
+ unquote, urljoin, urlunsplit, SplitResult, urlsplit
+ )
+ from urllib.request import pathname2url, urlopen
+ str_types = str,
+ int_types = int,
+ iteritems = operator.methodcaller("items")
+else:
+ from itertools import izip as zip # noqa
+ from io import BytesIO as NativeIO
+ from urlparse import urljoin, urlunsplit, SplitResult, urlsplit
+ from urllib import pathname2url, unquote # noqa
+ import urllib2 # noqa
+ def urlopen(*args, **kwargs):
+ return contextlib.closing(urllib2.urlopen(*args, **kwargs))
+
+ str_types = basestring
+ int_types = int, long
+ iteritems = operator.methodcaller("iteritems")
+
+ from functools32 import lru_cache
+
+
+def urldefrag(url):
+ if "#" in url:
+ s, n, p, q, frag = urlsplit(url)
+ defrag = urlunsplit((s, n, p, q, ""))
+ else:
+ defrag = url
+ frag = ""
+ return defrag, frag
+
+
+# flake8: noqa
diff --git a/contrib/python/jsonschema/py2/jsonschema/exceptions.py b/contrib/python/jsonschema/py2/jsonschema/exceptions.py
new file mode 100644
index 00000000000..691dcffe6c7
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/exceptions.py
@@ -0,0 +1,374 @@
+"""
+Validation errors, and some surrounding helpers.
+"""
+from collections import defaultdict, deque
+import itertools
+import pprint
+import textwrap
+
+import attr
+
+from jsonschema import _utils
+from jsonschema.compat import PY3, iteritems
+
+
+WEAK_MATCHES = frozenset(["anyOf", "oneOf"])
+STRONG_MATCHES = frozenset()
+
+_unset = _utils.Unset()
+
+
+class _Error(Exception):
+ def __init__(
+ self,
+ message,
+ validator=_unset,
+ path=(),
+ cause=None,
+ context=(),
+ validator_value=_unset,
+ instance=_unset,
+ schema=_unset,
+ schema_path=(),
+ parent=None,
+ ):
+ super(_Error, self).__init__(
+ message,
+ validator,
+ path,
+ cause,
+ context,
+ validator_value,
+ instance,
+ schema,
+ schema_path,
+ parent,
+ )
+ self.message = message
+ self.path = self.relative_path = deque(path)
+ self.schema_path = self.relative_schema_path = deque(schema_path)
+ self.context = list(context)
+ self.cause = self.__cause__ = cause
+ self.validator = validator
+ self.validator_value = validator_value
+ self.instance = instance
+ self.schema = schema
+ self.parent = parent
+
+ for error in context:
+ error.parent = self
+
+ def __repr__(self):
+ return "<%s: %r>" % (self.__class__.__name__, self.message)
+
+ def __unicode__(self):
+ essential_for_verbose = (
+ self.validator, self.validator_value, self.instance, self.schema,
+ )
+ if any(m is _unset for m in essential_for_verbose):
+ return self.message
+
+ pschema = pprint.pformat(self.schema, width=72)
+ pinstance = pprint.pformat(self.instance, width=72)
+ return self.message + textwrap.dedent("""
+
+ Failed validating %r in %s%s:
+ %s
+
+ On %s%s:
+ %s
+ """.rstrip()
+ ) % (
+ self.validator,
+ self._word_for_schema_in_error_message,
+ _utils.format_as_index(list(self.relative_schema_path)[:-1]),
+ _utils.indent(pschema),
+ self._word_for_instance_in_error_message,
+ _utils.format_as_index(self.relative_path),
+ _utils.indent(pinstance),
+ )
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return unicode(self).encode("utf-8")
+
+ @classmethod
+ def create_from(cls, other):
+ return cls(**other._contents())
+
+ @property
+ def absolute_path(self):
+ parent = self.parent
+ if parent is None:
+ return self.relative_path
+
+ path = deque(self.relative_path)
+ path.extendleft(reversed(parent.absolute_path))
+ return path
+
+ @property
+ def absolute_schema_path(self):
+ parent = self.parent
+ if parent is None:
+ return self.relative_schema_path
+
+ path = deque(self.relative_schema_path)
+ path.extendleft(reversed(parent.absolute_schema_path))
+ return path
+
+ def _set(self, **kwargs):
+ for k, v in iteritems(kwargs):
+ if getattr(self, k) is _unset:
+ setattr(self, k, v)
+
+ def _contents(self):
+ attrs = (
+ "message", "cause", "context", "validator", "validator_value",
+ "path", "schema_path", "instance", "schema", "parent",
+ )
+ return dict((attr, getattr(self, attr)) for attr in attrs)
+
+
+class ValidationError(_Error):
+ """
+ An instance was invalid under a provided schema.
+ """
+
+ _word_for_schema_in_error_message = "schema"
+ _word_for_instance_in_error_message = "instance"
+
+
+class SchemaError(_Error):
+ """
+ A schema was invalid under its corresponding metaschema.
+ """
+
+ _word_for_schema_in_error_message = "metaschema"
+ _word_for_instance_in_error_message = "schema"
+
+
+class RefResolutionError(Exception):
+ """
+ A ref could not be resolved.
+ """
+
+ _cause = attr.ib()
+
+ def __str__(self):
+ return str(self._cause)
+
+
+class UndefinedTypeCheck(Exception):
+ """
+ A type checker was asked to check a type it did not have registered.
+ """
+
+ def __init__(self, type):
+ self.type = type
+
+ def __unicode__(self):
+ return "Type %r is unknown to this type checker" % self.type
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return unicode(self).encode("utf-8")
+
+
+class UnknownType(Exception):
+ """
+ A validator was asked to validate an instance against an unknown type.
+ """
+
+ def __init__(self, type, instance, schema):
+ self.type = type
+ self.instance = instance
+ self.schema = schema
+
+ def __unicode__(self):
+ pschema = pprint.pformat(self.schema, width=72)
+ pinstance = pprint.pformat(self.instance, width=72)
+ return textwrap.dedent("""
+ Unknown type %r for validator with schema:
+ %s
+
+ While checking instance:
+ %s
+ """.rstrip()
+ ) % (self.type, _utils.indent(pschema), _utils.indent(pinstance))
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return unicode(self).encode("utf-8")
+
+
+class FormatError(Exception):
+ """
+ Validating a format failed.
+ """
+
+ def __init__(self, message, cause=None):
+ super(FormatError, self).__init__(message, cause)
+ self.message = message
+ self.cause = self.__cause__ = cause
+
+ def __unicode__(self):
+ return self.message
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return self.message.encode("utf-8")
+
+
+class ErrorTree(object):
+ """
+ ErrorTrees make it easier to check which validations failed.
+ """
+
+ _instance = _unset
+
+ def __init__(self, errors=()):
+ self.errors = {}
+ self._contents = defaultdict(self.__class__)
+
+ for error in errors:
+ container = self
+ for element in error.path:
+ container = container[element]
+ container.errors[error.validator] = error
+
+ container._instance = error.instance
+
+ def __contains__(self, index):
+ """
+ Check whether ``instance[index]`` has any errors.
+ """
+
+ return index in self._contents
+
+ def __getitem__(self, index):
+ """
+ Retrieve the child tree one level down at the given ``index``.
+
+ If the index is not in the instance that this tree corresponds to and
+ is not known by this tree, whatever error would be raised by
+ ``instance.__getitem__`` will be propagated (usually this is some
+ subclass of `exceptions.LookupError`.
+ """
+
+ if self._instance is not _unset and index not in self:
+ self._instance[index]
+ return self._contents[index]
+
+ def __setitem__(self, index, value):
+ """
+ Add an error to the tree at the given ``index``.
+ """
+ self._contents[index] = value
+
+ def __iter__(self):
+ """
+ Iterate (non-recursively) over the indices in the instance with errors.
+ """
+
+ return iter(self._contents)
+
+ def __len__(self):
+ """
+ Return the `total_errors`.
+ """
+ return self.total_errors
+
+ def __repr__(self):
+ return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
+
+ @property
+ def total_errors(self):
+ """
+ The total number of errors in the entire tree, including children.
+ """
+
+ child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
+ return len(self.errors) + child_errors
+
+
+def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):
+ """
+ Create a key function that can be used to sort errors by relevance.
+
+ Arguments:
+ weak (set):
+ a collection of validator names to consider to be "weak".
+ If there are two errors at the same level of the instance
+ and one is in the set of weak validator names, the other
+ error will take priority. By default, :validator:`anyOf` and
+ :validator:`oneOf` are considered weak validators and will
+ be superseded by other same-level validation errors.
+
+ strong (set):
+ a collection of validator names to consider to be "strong"
+ """
+ def relevance(error):
+ validator = error.validator
+ return -len(error.path), validator not in weak, validator in strong
+ return relevance
+
+
+relevance = by_relevance()
+
+
+def best_match(errors, key=relevance):
+ """
+ Try to find an error that appears to be the best match among given errors.
+
+ In general, errors that are higher up in the instance (i.e. for which
+ `ValidationError.path` is shorter) are considered better matches,
+ since they indicate "more" is wrong with the instance.
+
+ If the resulting match is either :validator:`oneOf` or :validator:`anyOf`,
+ the *opposite* assumption is made -- i.e. the deepest error is picked,
+ since these validators only need to match once, and any other errors may
+ not be relevant.
+
+ Arguments:
+ errors (collections.Iterable):
+
+ the errors to select from. Do not provide a mixture of
+ errors from different validation attempts (i.e. from
+ different instances or schemas), since it won't produce
+ sensical output.
+
+ key (collections.Callable):
+
+ the key to use when sorting errors. See `relevance` and
+ transitively `by_relevance` for more details (the default is
+ to sort with the defaults of that function). Changing the
+ default is only useful if you want to change the function
+ that rates errors but still want the error context descent
+ done by this function.
+
+ Returns:
+ the best matching error, or ``None`` if the iterable was empty
+
+ .. note::
+
+ This function is a heuristic. Its return value may change for a given
+ set of inputs from version to version if better heuristics are added.
+ """
+ errors = iter(errors)
+ best = next(errors, None)
+ if best is None:
+ return
+ best = max(itertools.chain([best], errors), key=key)
+
+ while best.context:
+ best = min(best.context, key=key)
+ return best
diff --git a/contrib/python/jsonschema/py2/jsonschema/schemas/draft3.json b/contrib/python/jsonschema/py2/jsonschema/schemas/draft3.json
new file mode 100644
index 00000000000..f8a09c563b4
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/schemas/draft3.json
@@ -0,0 +1,199 @@
+{
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "dependencies": {
+ "exclusiveMaximum": "maximum",
+ "exclusiveMinimum": "minimum"
+ },
+ "id": "http://json-schema.org/draft-03/schema#",
+ "properties": {
+ "$ref": {
+ "format": "uri",
+ "type": "string"
+ },
+ "$schema": {
+ "format": "uri",
+ "type": "string"
+ },
+ "additionalItems": {
+ "default": {},
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "boolean"
+ ]
+ },
+ "additionalProperties": {
+ "default": {},
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "boolean"
+ ]
+ },
+ "default": {
+ "type": "any"
+ },
+ "dependencies": {
+ "additionalProperties": {
+ "items": {
+ "type": "string"
+ },
+ "type": [
+ "string",
+ "array",
+ {
+ "$ref": "#"
+ }
+ ]
+ },
+ "default": {},
+ "type": [
+ "string",
+ "array",
+ "object"
+ ]
+ },
+ "description": {
+ "type": "string"
+ },
+ "disallow": {
+ "items": {
+ "type": [
+ "string",
+ {
+ "$ref": "#"
+ }
+ ]
+ },
+ "type": [
+ "string",
+ "array"
+ ],
+ "uniqueItems": true
+ },
+ "divisibleBy": {
+ "default": 1,
+ "exclusiveMinimum": true,
+ "minimum": 0,
+ "type": "number"
+ },
+ "enum": {
+ "type": "array"
+ },
+ "exclusiveMaximum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "exclusiveMinimum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "extends": {
+ "default": {},
+ "items": {
+ "$ref": "#"
+ },
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "id": {
+ "format": "uri",
+ "type": "string"
+ },
+ "items": {
+ "default": {},
+ "items": {
+ "$ref": "#"
+ },
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "array"
+ ]
+ },
+ "maxDecimal": {
+ "minimum": 0,
+ "type": "number"
+ },
+ "maxItems": {
+ "minimum": 0,
+ "type": "integer"
+ },
+ "maxLength": {
+ "type": "integer"
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "minItems": {
+ "default": 0,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "minLength": {
+ "default": 0,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "pattern": {
+ "format": "regex",
+ "type": "string"
+ },
+ "patternProperties": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "properties": {
+ "additionalProperties": {
+ "$ref": "#",
+ "type": "object"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "required": {
+ "default": false,
+ "type": "boolean"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "default": "any",
+ "items": {
+ "type": [
+ "string",
+ {
+ "$ref": "#"
+ }
+ ]
+ },
+ "type": [
+ "string",
+ "array"
+ ],
+ "uniqueItems": true
+ },
+ "uniqueItems": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+}
diff --git a/contrib/python/jsonschema/py2/jsonschema/schemas/draft4.json b/contrib/python/jsonschema/py2/jsonschema/schemas/draft4.json
new file mode 100644
index 00000000000..9b666cff88a
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/schemas/draft4.json
@@ -0,0 +1,222 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "default": {},
+ "definitions": {
+ "positiveInteger": {
+ "minimum": 0,
+ "type": "integer"
+ },
+ "positiveIntegerDefault0": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ {
+ "default": 0
+ }
+ ]
+ },
+ "schemaArray": {
+ "items": {
+ "$ref": "#"
+ },
+ "minItems": 1,
+ "type": "array"
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "type": "array",
+ "uniqueItems": true
+ }
+ },
+ "dependencies": {
+ "exclusiveMaximum": [
+ "maximum"
+ ],
+ "exclusiveMinimum": [
+ "minimum"
+ ]
+ },
+ "description": "Core schema meta-schema",
+ "id": "http://json-schema.org/draft-04/schema#",
+ "properties": {
+ "$schema": {
+ "format": "uri",
+ "type": "string"
+ },
+ "additionalItems": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "$ref": "#"
+ }
+ ],
+ "default": {}
+ },
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "$ref": "#"
+ }
+ ],
+ "default": {}
+ },
+ "allOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "anyOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "default": {},
+ "definitions": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "dependencies": {
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#"
+ },
+ {
+ "$ref": "#/definitions/stringArray"
+ }
+ ]
+ },
+ "type": "object"
+ },
+ "description": {
+ "type": "string"
+ },
+ "enum": {
+ "type": "array"
+ },
+ "exclusiveMaximum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "exclusiveMinimum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "format": {
+ "type": "string"
+ },
+ "id": {
+ "format": "uri",
+ "type": "string"
+ },
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#"
+ },
+ {
+ "$ref": "#/definitions/schemaArray"
+ }
+ ],
+ "default": {}
+ },
+ "maxItems": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "maxProperties": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "minItems": {
+ "$ref": "#/definitions/positiveIntegerDefault0"
+ },
+ "minLength": {
+ "$ref": "#/definitions/positiveIntegerDefault0"
+ },
+ "minProperties": {
+ "$ref": "#/definitions/positiveIntegerDefault0"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "multipleOf": {
+ "exclusiveMinimum": true,
+ "minimum": 0,
+ "type": "number"
+ },
+ "not": {
+ "$ref": "#"
+ },
+ "oneOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "pattern": {
+ "format": "regex",
+ "type": "string"
+ },
+ "patternProperties": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "properties": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "required": {
+ "$ref": "#/definitions/stringArray"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/simpleTypes"
+ },
+ {
+ "items": {
+ "$ref": "#/definitions/simpleTypes"
+ },
+ "minItems": 1,
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "uniqueItems": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+}
diff --git a/contrib/python/jsonschema/py2/jsonschema/schemas/draft6.json b/contrib/python/jsonschema/py2/jsonschema/schemas/draft6.json
new file mode 100644
index 00000000000..a0d2bf7896c
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/schemas/draft6.json
@@ -0,0 +1,153 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "$id": "http://json-schema.org/draft-06/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "examples": {
+ "type": "array",
+ "items": {}
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": {},
+ "enum": {
+ "type": "array"
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": {}
+}
diff --git a/contrib/python/jsonschema/py2/jsonschema/schemas/draft7.json b/contrib/python/jsonschema/py2/jsonschema/schemas/draft7.json
new file mode 100644
index 00000000000..746cde96901
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/schemas/draft7.json
@@ -0,0 +1,166 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": {"$ref": "#"},
+ "then": {"$ref": "#"},
+ "else": {"$ref": "#"},
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+}
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/__init__.py b/contrib/python/jsonschema/py2/jsonschema/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/__init__.py
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/_helpers.py b/contrib/python/jsonschema/py2/jsonschema/tests/_helpers.py
new file mode 100644
index 00000000000..70f291fe2ab
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/_helpers.py
@@ -0,0 +1,5 @@
+def bug(issue=None):
+ message = "A known bug."
+ if issue is not None:
+ message += " See issue #{issue}.".format(issue=issue)
+ return message
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/test_cli.py b/contrib/python/jsonschema/py2/jsonschema/tests/test_cli.py
new file mode 100644
index 00000000000..328c85106f1
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/test_cli.py
@@ -0,0 +1,143 @@
+from unittest import TestCase
+import json
+import subprocess
+import sys
+
+from jsonschema import Draft4Validator, ValidationError, cli, __version__
+from jsonschema.compat import NativeIO
+from jsonschema.exceptions import SchemaError
+
+
+def fake_validator(*errors):
+ errors = list(reversed(errors))
+
+ class FakeValidator(object):
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def iter_errors(self, instance):
+ if errors:
+ return errors.pop()
+ return []
+
+ def check_schema(self, schema):
+ pass
+
+ return FakeValidator
+
+
+class TestParser(TestCase):
+
+ FakeValidator = fake_validator()
+ instance_file = "foo.json"
+ schema_file = "schema.json"
+
+ def setUp(self):
+ cli.open = self.fake_open
+ self.addCleanup(delattr, cli, "open")
+
+ def fake_open(self, path):
+ if path == self.instance_file:
+ contents = ""
+ elif path == self.schema_file:
+ contents = {}
+ else: # pragma: no cover
+ self.fail("What is {!r}".format(path))
+ return NativeIO(json.dumps(contents))
+
+ def test_find_validator_by_fully_qualified_object_name(self):
+ arguments = cli.parse_args(
+ [
+ "--validator",
+ "__tests__.test_cli.TestParser.FakeValidator", # XXX Arcadia
+ "--instance", self.instance_file,
+ self.schema_file,
+ ]
+ )
+ self.assertIs(arguments["validator"], self.FakeValidator)
+
+ def test_find_validator_in_jsonschema(self):
+ arguments = cli.parse_args(
+ [
+ "--validator", "Draft4Validator",
+ "--instance", self.instance_file,
+ self.schema_file,
+ ]
+ )
+ self.assertIs(arguments["validator"], Draft4Validator)
+
+
+class TestCLI(TestCase):
+ def test_draft3_schema_draft4_validator(self):
+ stdout, stderr = NativeIO(), NativeIO()
+ with self.assertRaises(SchemaError):
+ cli.run(
+ {
+ "validator": Draft4Validator,
+ "schema": {
+ "anyOf": [
+ {"minimum": 20},
+ {"type": "string"},
+ {"required": True},
+ ],
+ },
+ "instances": [1],
+ "error_format": "{error.message}",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+
+ def test_successful_validation(self):
+ stdout, stderr = NativeIO(), NativeIO()
+ exit_code = cli.run(
+ {
+ "validator": fake_validator(),
+ "schema": {},
+ "instances": [1],
+ "error_format": "{error.message}",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+ self.assertFalse(stdout.getvalue())
+ self.assertFalse(stderr.getvalue())
+ self.assertEqual(exit_code, 0)
+
+ def test_unsuccessful_validation(self):
+ error = ValidationError("I am an error!", instance=1)
+ stdout, stderr = NativeIO(), NativeIO()
+ exit_code = cli.run(
+ {
+ "validator": fake_validator([error]),
+ "schema": {},
+ "instances": [1],
+ "error_format": "{error.instance} - {error.message}",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+ self.assertFalse(stdout.getvalue())
+ self.assertEqual(stderr.getvalue(), "1 - I am an error!")
+ self.assertEqual(exit_code, 1)
+
+ def test_unsuccessful_validation_multiple_instances(self):
+ first_errors = [
+ ValidationError("9", instance=1),
+ ValidationError("8", instance=1),
+ ]
+ second_errors = [ValidationError("7", instance=2)]
+ stdout, stderr = NativeIO(), NativeIO()
+ exit_code = cli.run(
+ {
+ "validator": fake_validator(first_errors, second_errors),
+ "schema": {},
+ "instances": [1, 2],
+ "error_format": "{error.instance} - {error.message}\t",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+ self.assertFalse(stdout.getvalue())
+ self.assertEqual(stderr.getvalue(), "1 - 9\t1 - 8\t2 - 7\t")
+ self.assertEqual(exit_code, 1)
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/test_exceptions.py b/contrib/python/jsonschema/py2/jsonschema/tests/test_exceptions.py
new file mode 100644
index 00000000000..eae00d76d77
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/test_exceptions.py
@@ -0,0 +1,462 @@
+from unittest import TestCase
+import textwrap
+
+from jsonschema import Draft4Validator, exceptions
+from jsonschema.compat import PY3
+
+
+class TestBestMatch(TestCase):
+ def best_match(self, errors):
+ errors = list(errors)
+ best = exceptions.best_match(errors)
+ reversed_best = exceptions.best_match(reversed(errors))
+ msg = "Didn't return a consistent best match!\nGot: {0}\n\nThen: {1}"
+ self.assertEqual(
+ best._contents(), reversed_best._contents(),
+ msg=msg.format(best, reversed_best),
+ )
+ return best
+
+ def test_shallower_errors_are_better_matches(self):
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "minProperties": 2,
+ "properties": {"bar": {"type": "object"}},
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": []}}))
+ self.assertEqual(best.validator, "minProperties")
+
+ def test_oneOf_and_anyOf_are_weak_matches(self):
+ """
+ A property you *must* match is probably better than one you have to
+ match a part of.
+ """
+
+ validator = Draft4Validator(
+ {
+ "minProperties": 2,
+ "anyOf": [{"type": "string"}, {"type": "number"}],
+ "oneOf": [{"type": "string"}, {"type": "number"}],
+ }
+ )
+ best = self.best_match(validator.iter_errors({}))
+ self.assertEqual(best.validator, "minProperties")
+
+ def test_if_the_most_relevant_error_is_anyOf_it_is_traversed(self):
+ """
+ If the most relevant error is an anyOf, then we traverse its context
+ and select the otherwise *least* relevant error, since in this case
+ that means the most specific, deep, error inside the instance.
+
+ I.e. since only one of the schemas must match, we look for the most
+ relevant one.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "anyOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_if_the_most_relevant_error_is_oneOf_it_is_traversed(self):
+ """
+ If the most relevant error is an oneOf, then we traverse its context
+ and select the otherwise *least* relevant error, since in this case
+ that means the most specific, deep, error inside the instance.
+
+ I.e. since only one of the schemas must match, we look for the most
+ relevant one.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_if_the_most_relevant_error_is_allOf_it_is_traversed(self):
+ """
+ Now, if the error is allOf, we traverse but select the *most* relevant
+ error from the context, because all schemas here must match anyways.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "allOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "string")
+
+ def test_nested_context_for_oneOf(self):
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {"type": "string"},
+ {
+ "oneOf": [
+ {"type": "string"},
+ {
+ "properties": {
+ "bar": {"type": "array"},
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_one_error(self):
+ validator = Draft4Validator({"minProperties": 2})
+ error, = validator.iter_errors({})
+ self.assertEqual(
+ exceptions.best_match(validator.iter_errors({})).validator,
+ "minProperties",
+ )
+
+ def test_no_errors(self):
+ validator = Draft4Validator({})
+ self.assertIsNone(exceptions.best_match(validator.iter_errors({})))
+
+
+class TestByRelevance(TestCase):
+ def test_short_paths_are_better_matches(self):
+ shallow = exceptions.ValidationError("Oh no!", path=["baz"])
+ deep = exceptions.ValidationError("Oh yes!", path=["foo", "bar"])
+ match = max([shallow, deep], key=exceptions.relevance)
+ self.assertIs(match, shallow)
+
+ match = max([deep, shallow], key=exceptions.relevance)
+ self.assertIs(match, shallow)
+
+ def test_global_errors_are_even_better_matches(self):
+ shallow = exceptions.ValidationError("Oh no!", path=[])
+ deep = exceptions.ValidationError("Oh yes!", path=["foo"])
+
+ errors = sorted([shallow, deep], key=exceptions.relevance)
+ self.assertEqual(
+ [list(error.path) for error in errors],
+ [["foo"], []],
+ )
+
+ errors = sorted([deep, shallow], key=exceptions.relevance)
+ self.assertEqual(
+ [list(error.path) for error in errors],
+ [["foo"], []],
+ )
+
+ def test_weak_validators_are_lower_priority(self):
+ weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
+ normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
+
+ best_match = exceptions.by_relevance(weak="a")
+
+ match = max([weak, normal], key=best_match)
+ self.assertIs(match, normal)
+
+ match = max([normal, weak], key=best_match)
+ self.assertIs(match, normal)
+
+ def test_strong_validators_are_higher_priority(self):
+ weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
+ normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
+ strong = exceptions.ValidationError("Oh fine!", path=[], validator="c")
+
+ best_match = exceptions.by_relevance(weak="a", strong="c")
+
+ match = max([weak, normal, strong], key=best_match)
+ self.assertIs(match, strong)
+
+ match = max([strong, normal, weak], key=best_match)
+ self.assertIs(match, strong)
+
+
+class TestErrorTree(TestCase):
+ def test_it_knows_how_many_total_errors_it_contains(self):
+ # FIXME: https://github.com/Julian/jsonschema/issues/442
+ errors = [
+ exceptions.ValidationError("Something", validator=i)
+ for i in range(8)
+ ]
+ tree = exceptions.ErrorTree(errors)
+ self.assertEqual(tree.total_errors, 8)
+
+ def test_it_contains_an_item_if_the_item_had_an_error(self):
+ errors = [exceptions.ValidationError("a message", path=["bar"])]
+ tree = exceptions.ErrorTree(errors)
+ self.assertIn("bar", tree)
+
+ def test_it_does_not_contain_an_item_if_the_item_had_no_error(self):
+ errors = [exceptions.ValidationError("a message", path=["bar"])]
+ tree = exceptions.ErrorTree(errors)
+ self.assertNotIn("foo", tree)
+
+ def test_validators_that_failed_appear_in_errors_dict(self):
+ error = exceptions.ValidationError("a message", validator="foo")
+ tree = exceptions.ErrorTree([error])
+ self.assertEqual(tree.errors, {"foo": error})
+
+ def test_it_creates_a_child_tree_for_each_nested_path(self):
+ errors = [
+ exceptions.ValidationError("a bar message", path=["bar"]),
+ exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]),
+ ]
+ tree = exceptions.ErrorTree(errors)
+ self.assertIn(0, tree["bar"])
+ self.assertNotIn(1, tree["bar"])
+
+ def test_children_have_their_errors_dicts_built(self):
+ e1, e2 = (
+ exceptions.ValidationError("1", validator="foo", path=["bar", 0]),
+ exceptions.ValidationError("2", validator="quux", path=["bar", 0]),
+ )
+ tree = exceptions.ErrorTree([e1, e2])
+ self.assertEqual(tree["bar"][0].errors, {"foo": e1, "quux": e2})
+
+ def test_multiple_errors_with_instance(self):
+ e1, e2 = (
+ exceptions.ValidationError(
+ "1",
+ validator="foo",
+ path=["bar", "bar2"],
+ instance="i1"),
+ exceptions.ValidationError(
+ "2",
+ validator="quux",
+ path=["foobar", 2],
+ instance="i2"),
+ )
+ exceptions.ErrorTree([e1, e2])
+
+ def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self):
+ error = exceptions.ValidationError("123", validator="foo", instance=[])
+ tree = exceptions.ErrorTree([error])
+
+ with self.assertRaises(IndexError):
+ tree[0]
+
+ def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self):
+ """
+ If a validator is dumb (like :validator:`required` in draft 3) and
+ refers to a path that isn't in the instance, the tree still properly
+ returns a subtree for that path.
+ """
+
+ error = exceptions.ValidationError(
+ "a message", validator="foo", instance={}, path=["foo"],
+ )
+ tree = exceptions.ErrorTree([error])
+ self.assertIsInstance(tree["foo"], exceptions.ErrorTree)
+
+
+class TestErrorInitReprStr(TestCase):
+ def make_error(self, **kwargs):
+ defaults = dict(
+ message=u"hello",
+ validator=u"type",
+ validator_value=u"string",
+ instance=5,
+ schema={u"type": u"string"},
+ )
+ defaults.update(kwargs)
+ return exceptions.ValidationError(**defaults)
+
+ def assertShows(self, expected, **kwargs):
+ if PY3: # pragma: no cover
+ expected = expected.replace("u'", "'")
+ expected = textwrap.dedent(expected).rstrip("\n")
+
+ error = self.make_error(**kwargs)
+ message_line, _, rest = str(error).partition("\n")
+ self.assertEqual(message_line, error.message)
+ self.assertEqual(rest, expected)
+
+ def test_it_calls_super_and_sets_args(self):
+ error = self.make_error()
+ self.assertGreater(len(error.args), 1)
+
+ def test_repr(self):
+ self.assertEqual(
+ repr(exceptions.ValidationError(message="Hello!")),
+ "<ValidationError: %r>" % "Hello!",
+ )
+
+ def test_unset_error(self):
+ error = exceptions.ValidationError("message")
+ self.assertEqual(str(error), "message")
+
+ kwargs = {
+ "validator": "type",
+ "validator_value": "string",
+ "instance": 5,
+ "schema": {"type": "string"},
+ }
+ # Just the message should show if any of the attributes are unset
+ for attr in kwargs:
+ k = dict(kwargs)
+ del k[attr]
+ error = exceptions.ValidationError("message", **k)
+ self.assertEqual(str(error), "message")
+
+ def test_empty_paths(self):
+ self.assertShows(
+ """
+ Failed validating u'type' in schema:
+ {u'type': u'string'}
+
+ On instance:
+ 5
+ """,
+ path=[],
+ schema_path=[],
+ )
+
+ def test_one_item_paths(self):
+ self.assertShows(
+ """
+ Failed validating u'type' in schema:
+ {u'type': u'string'}
+
+ On instance[0]:
+ 5
+ """,
+ path=[0],
+ schema_path=["items"],
+ )
+
+ def test_multiple_item_paths(self):
+ self.assertShows(
+ """
+ Failed validating u'type' in schema[u'items'][0]:
+ {u'type': u'string'}
+
+ On instance[0][u'a']:
+ 5
+ """,
+ path=[0, u"a"],
+ schema_path=[u"items", 0, 1],
+ )
+
+ def test_uses_pprint(self):
+ self.assertShows(
+ """
+ Failed validating u'maxLength' in schema:
+ {0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+ 4: 4,
+ 5: 5,
+ 6: 6,
+ 7: 7,
+ 8: 8,
+ 9: 9,
+ 10: 10,
+ 11: 11,
+ 12: 12,
+ 13: 13,
+ 14: 14,
+ 15: 15,
+ 16: 16,
+ 17: 17,
+ 18: 18,
+ 19: 19}
+
+ On instance:
+ [0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24]
+ """,
+ instance=list(range(25)),
+ schema=dict(zip(range(20), range(20))),
+ validator=u"maxLength",
+ )
+
+ def test_str_works_with_instances_having_overriden_eq_operator(self):
+ """
+ Check for https://github.com/Julian/jsonschema/issues/164 which
+ rendered exceptions unusable when a `ValidationError` involved
+ instances with an `__eq__` method that returned truthy values.
+ """
+
+ class DontEQMeBro(object):
+ def __eq__(this, other): # pragma: no cover
+ self.fail("Don't!")
+
+ def __ne__(this, other): # pragma: no cover
+ self.fail("Don't!")
+
+ instance = DontEQMeBro()
+ error = exceptions.ValidationError(
+ "a message",
+ validator="foo",
+ instance=instance,
+ validator_value="some",
+ schema="schema",
+ )
+ self.assertIn(repr(instance), str(error))
+
+
+class TestHashable(TestCase):
+ def test_hashable(self):
+ set([exceptions.ValidationError("")])
+ set([exceptions.SchemaError("")])
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/test_format.py b/contrib/python/jsonschema/py2/jsonschema/tests/test_format.py
new file mode 100644
index 00000000000..254985f6156
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/test_format.py
@@ -0,0 +1,89 @@
+"""
+Tests for the parts of jsonschema related to the :validator:`format` property.
+"""
+
+from unittest import TestCase
+
+from jsonschema import FormatError, ValidationError, FormatChecker
+from jsonschema.validators import Draft4Validator
+
+
+BOOM = ValueError("Boom!")
+BANG = ZeroDivisionError("Bang!")
+
+
+def boom(thing):
+ if thing == "bang":
+ raise BANG
+ raise BOOM
+
+
+class TestFormatChecker(TestCase):
+ def test_it_can_validate_no_formats(self):
+ checker = FormatChecker(formats=())
+ self.assertFalse(checker.checkers)
+
+ def test_it_raises_a_key_error_for_unknown_formats(self):
+ with self.assertRaises(KeyError):
+ FormatChecker(formats=["o noes"])
+
+ def test_it_can_register_cls_checkers(self):
+ original = dict(FormatChecker.checkers)
+ self.addCleanup(FormatChecker.checkers.pop, "boom")
+ FormatChecker.cls_checks("boom")(boom)
+ self.assertEqual(
+ FormatChecker.checkers,
+ dict(original, boom=(boom, ())),
+ )
+
+ def test_it_can_register_checkers(self):
+ checker = FormatChecker()
+ checker.checks("boom")(boom)
+ self.assertEqual(
+ checker.checkers,
+ dict(FormatChecker.checkers, boom=(boom, ()))
+ )
+
+ def test_it_catches_registered_errors(self):
+ checker = FormatChecker()
+ checker.checks("boom", raises=type(BOOM))(boom)
+
+ with self.assertRaises(FormatError) as cm:
+ checker.check(instance=12, format="boom")
+
+ self.assertIs(cm.exception.cause, BOOM)
+ self.assertIs(cm.exception.__cause__, BOOM)
+
+ # Unregistered errors should not be caught
+ with self.assertRaises(type(BANG)):
+ checker.check(instance="bang", format="boom")
+
+ def test_format_error_causes_become_validation_error_causes(self):
+ checker = FormatChecker()
+ checker.checks("boom", raises=ValueError)(boom)
+ validator = Draft4Validator({"format": "boom"}, format_checker=checker)
+
+ with self.assertRaises(ValidationError) as cm:
+ validator.validate("BOOM")
+
+ self.assertIs(cm.exception.cause, BOOM)
+ self.assertIs(cm.exception.__cause__, BOOM)
+
+ def test_format_checkers_come_with_defaults(self):
+ # This is bad :/ but relied upon.
+ # The docs for quite awhile recommended people do things like
+ # validate(..., format_checker=FormatChecker())
+ # We should change that, but we can't without deprecation...
+ checker = FormatChecker()
+ with self.assertRaises(FormatError):
+ checker.check(instance="not-an-ipv4", format="ipv4")
+
+ def test_repr(self):
+ checker = FormatChecker(formats=())
+ checker.checks("foo")(lambda thing: True)
+ checker.checks("bar")(lambda thing: True)
+ checker.checks("baz")(lambda thing: True)
+ self.assertEqual(
+ repr(checker),
+ "<FormatChecker checkers=['bar', 'baz', 'foo']>",
+ )
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/test_types.py b/contrib/python/jsonschema/py2/jsonschema/tests/test_types.py
new file mode 100644
index 00000000000..2280cc395b2
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/test_types.py
@@ -0,0 +1,190 @@
+"""
+Tests on the new type interface. The actual correctness of the type checking
+is handled in test_jsonschema_test_suite; these tests check that TypeChecker
+functions correctly and can facilitate extensions to type checking
+"""
+from collections import namedtuple
+from unittest import TestCase
+
+from jsonschema import ValidationError, _validators
+from jsonschema._types import TypeChecker
+from jsonschema.exceptions import UndefinedTypeCheck
+from jsonschema.validators import Draft4Validator, extend
+
+
+def equals_2(checker, instance):
+ return instance == 2
+
+
+def is_namedtuple(instance):
+ return isinstance(instance, tuple) and getattr(instance, "_fields", None)
+
+
+def is_object_or_named_tuple(checker, instance):
+ if Draft4Validator.TYPE_CHECKER.is_type(instance, "object"):
+ return True
+ return is_namedtuple(instance)
+
+
+def coerce_named_tuple(fn):
+ def coerced(validator, value, instance, schema):
+ if is_namedtuple(instance):
+ instance = instance._asdict()
+ return fn(validator, value, instance, schema)
+ return coerced
+
+
+required = coerce_named_tuple(_validators.required)
+properties = coerce_named_tuple(_validators.properties)
+
+
+class TestTypeChecker(TestCase):
+ def test_is_type(self):
+ checker = TypeChecker({"two": equals_2})
+ self.assertEqual(
+ (
+ checker.is_type(instance=2, type="two"),
+ checker.is_type(instance="bar", type="two"),
+ ),
+ (True, False),
+ )
+
+ def test_is_unknown_type(self):
+ with self.assertRaises(UndefinedTypeCheck) as context:
+ TypeChecker().is_type(4, "foobar")
+ self.assertIn("foobar", str(context.exception))
+
+ def test_checks_can_be_added_at_init(self):
+ checker = TypeChecker({"two": equals_2})
+ self.assertEqual(checker, TypeChecker().redefine("two", equals_2))
+
+ def test_redefine_existing_type(self):
+ self.assertEqual(
+ TypeChecker().redefine("two", object()).redefine("two", equals_2),
+ TypeChecker().redefine("two", equals_2),
+ )
+
+ def test_remove(self):
+ self.assertEqual(
+ TypeChecker({"two": equals_2}).remove("two"),
+ TypeChecker(),
+ )
+
+ def test_remove_unknown_type(self):
+ with self.assertRaises(UndefinedTypeCheck) as context:
+ TypeChecker().remove("foobar")
+ self.assertIn("foobar", str(context.exception))
+
+ def test_redefine_many(self):
+ self.assertEqual(
+ TypeChecker().redefine_many({"foo": int, "bar": str}),
+ TypeChecker().redefine("foo", int).redefine("bar", str),
+ )
+
+ def test_remove_multiple(self):
+ self.assertEqual(
+ TypeChecker({"foo": int, "bar": str}).remove("foo", "bar"),
+ TypeChecker(),
+ )
+
+ def test_type_check_can_raise_key_error(self):
+ """
+ Make sure no one writes:
+
+ try:
+ self._type_checkers[type](...)
+ except KeyError:
+
+ ignoring the fact that the function itself can raise that.
+ """
+
+ error = KeyError("Stuff")
+
+ def raises_keyerror(checker, instance):
+ raise error
+
+ with self.assertRaises(KeyError) as context:
+ TypeChecker({"foo": raises_keyerror}).is_type(4, "foo")
+
+ self.assertIs(context.exception, error)
+
+
+class TestCustomTypes(TestCase):
+ def test_simple_type_can_be_extended(self):
+ def int_or_str_int(checker, instance):
+ if not isinstance(instance, (int, str)):
+ return False
+ try:
+ int(instance)
+ except ValueError:
+ return False
+ return True
+
+ CustomValidator = extend(
+ Draft4Validator,
+ type_checker=Draft4Validator.TYPE_CHECKER.redefine(
+ "integer", int_or_str_int,
+ ),
+ )
+ validator = CustomValidator({"type": "integer"})
+
+ validator.validate(4)
+ validator.validate("4")
+
+ with self.assertRaises(ValidationError):
+ validator.validate(4.4)
+
+ def test_object_can_be_extended(self):
+ schema = {"type": "object"}
+
+ Point = namedtuple("Point", ["x", "y"])
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(Draft4Validator, type_checker=type_checker)
+ validator = CustomValidator(schema)
+
+ validator.validate(Point(x=4, y=5))
+
+ def test_object_extensions_require_custom_validators(self):
+ schema = {"type": "object", "required": ["x"]}
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(Draft4Validator, type_checker=type_checker)
+ validator = CustomValidator(schema)
+
+ Point = namedtuple("Point", ["x", "y"])
+ # Cannot handle required
+ with self.assertRaises(ValidationError):
+ validator.validate(Point(x=4, y=5))
+
+ def test_object_extensions_can_handle_custom_validators(self):
+ schema = {
+ "type": "object",
+ "required": ["x"],
+ "properties": {"x": {"type": "integer"}},
+ }
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(
+ Draft4Validator,
+ type_checker=type_checker,
+ validators={"required": required, "properties": properties},
+ )
+
+ validator = CustomValidator(schema)
+
+ Point = namedtuple("Point", ["x", "y"])
+ # Can now process required and properties
+ validator.validate(Point(x=4, y=5))
+
+ with self.assertRaises(ValidationError):
+ validator.validate(Point(x="not an integer", y=5))
diff --git a/contrib/python/jsonschema/py2/jsonschema/tests/test_validators.py b/contrib/python/jsonschema/py2/jsonschema/tests/test_validators.py
new file mode 100644
index 00000000000..07be4f08bc2
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/tests/test_validators.py
@@ -0,0 +1,1762 @@
+from collections import deque
+from contextlib import contextmanager
+from decimal import Decimal
+from io import BytesIO
+from unittest import TestCase
+import json
+import os
+import sys
+import tempfile
+import unittest
+
+from twisted.trial.unittest import SynchronousTestCase
+import attr
+
+from jsonschema import FormatChecker, TypeChecker, exceptions, validators
+from jsonschema.compat import PY3, pathname2url
+from jsonschema.tests._helpers import bug
+
+
+def startswith(validator, startswith, instance, schema):
+ if not instance.startswith(startswith):
+ yield exceptions.ValidationError(u"Whoops!")
+
+
+class TestCreateAndExtend(SynchronousTestCase):
+ def setUp(self):
+ self.addCleanup(
+ self.assertEqual,
+ validators.meta_schemas,
+ dict(validators.meta_schemas),
+ )
+
+ self.meta_schema = {u"$id": "some://meta/schema"}
+ self.validators = {u"startswith": startswith}
+ self.type_checker = TypeChecker()
+ self.Validator = validators.create(
+ meta_schema=self.meta_schema,
+ validators=self.validators,
+ type_checker=self.type_checker,
+ )
+
+ def test_attrs(self):
+ self.assertEqual(
+ (
+ self.Validator.VALIDATORS,
+ self.Validator.META_SCHEMA,
+ self.Validator.TYPE_CHECKER,
+ ), (
+ self.validators,
+ self.meta_schema,
+ self.type_checker,
+ ),
+ )
+
+ def test_init(self):
+ schema = {u"startswith": u"foo"}
+ self.assertEqual(self.Validator(schema).schema, schema)
+
+ def test_iter_errors(self):
+ schema = {u"startswith": u"hel"}
+ iter_errors = self.Validator(schema).iter_errors
+
+ errors = list(iter_errors(u"hello"))
+ self.assertEqual(errors, [])
+
+ expected_error = exceptions.ValidationError(
+ u"Whoops!",
+ instance=u"goodbye",
+ schema=schema,
+ validator=u"startswith",
+ validator_value=u"hel",
+ schema_path=deque([u"startswith"]),
+ )
+
+ errors = list(iter_errors(u"goodbye"))
+ self.assertEqual(len(errors), 1)
+ self.assertEqual(errors[0]._contents(), expected_error._contents())
+
+ def test_if_a_version_is_provided_it_is_registered(self):
+ Validator = validators.create(
+ meta_schema={u"$id": "something"},
+ version="my version",
+ )
+ self.addCleanup(validators.meta_schemas.pop, "something")
+ self.assertEqual(Validator.__name__, "MyVersionValidator")
+
+ def test_if_a_version_is_not_provided_it_is_not_registered(self):
+ original = dict(validators.meta_schemas)
+ validators.create(meta_schema={u"id": "id"})
+ self.assertEqual(validators.meta_schemas, original)
+
+ def test_validates_registers_meta_schema_id(self):
+ meta_schema_key = "meta schema id"
+ my_meta_schema = {u"id": meta_schema_key}
+
+ validators.create(
+ meta_schema=my_meta_schema,
+ version="my version",
+ id_of=lambda s: s.get("id", ""),
+ )
+ self.addCleanup(validators.meta_schemas.pop, meta_schema_key)
+
+ self.assertIn(meta_schema_key, validators.meta_schemas)
+
+ def test_validates_registers_meta_schema_draft6_id(self):
+ meta_schema_key = "meta schema $id"
+ my_meta_schema = {u"$id": meta_schema_key}
+
+ validators.create(
+ meta_schema=my_meta_schema,
+ version="my version",
+ )
+ self.addCleanup(validators.meta_schemas.pop, meta_schema_key)
+
+ self.assertIn(meta_schema_key, validators.meta_schemas)
+
+ def test_create_default_types(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ self.assertTrue(
+ all(
+ Validator({}).is_type(instance=instance, type=type)
+ for type, instance in [
+ (u"array", []),
+ (u"boolean", True),
+ (u"integer", 12),
+ (u"null", None),
+ (u"number", 12.0),
+ (u"object", {}),
+ (u"string", u"foo"),
+ ]
+ ),
+ )
+
+ def test_extend(self):
+ original = dict(self.Validator.VALIDATORS)
+ new = object()
+
+ Extended = validators.extend(
+ self.Validator,
+ validators={u"new": new},
+ )
+ self.assertEqual(
+ (
+ Extended.VALIDATORS,
+ Extended.META_SCHEMA,
+ Extended.TYPE_CHECKER,
+ self.Validator.VALIDATORS,
+ ), (
+ dict(original, new=new),
+ self.Validator.META_SCHEMA,
+ self.Validator.TYPE_CHECKER,
+ original,
+ ),
+ )
+
+ def test_extend_idof(self):
+ """
+ Extending a validator preserves its notion of schema IDs.
+ """
+ def id_of(schema):
+ return schema.get(u"__test__", self.Validator.ID_OF(schema))
+ correct_id = "the://correct/id/"
+ meta_schema = {
+ u"$id": "the://wrong/id/",
+ u"__test__": correct_id,
+ }
+ Original = validators.create(
+ meta_schema=meta_schema,
+ validators=self.validators,
+ type_checker=self.type_checker,
+ id_of=id_of,
+ )
+ self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id)
+
+ Derived = validators.extend(Original)
+ self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id)
+
+
+class TestLegacyTypeChecking(SynchronousTestCase):
+ def test_create_default_types(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ self.assertEqual(
+ set(Validator.DEFAULT_TYPES), {
+ u"array",
+ u"boolean",
+ u"integer",
+ u"null",
+ u"number",
+ u"object", u"string",
+ },
+ )
+ self.flushWarnings()
+
+ def test_extend(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ original = dict(Validator.VALIDATORS)
+ new = object()
+
+ Extended = validators.extend(
+ Validator,
+ validators={u"new": new},
+ )
+ self.assertEqual(
+ (
+ Extended.VALIDATORS,
+ Extended.META_SCHEMA,
+ Extended.TYPE_CHECKER,
+ Validator.VALIDATORS,
+
+ Extended.DEFAULT_TYPES,
+ Extended({}).DEFAULT_TYPES,
+ self.flushWarnings()[0]["message"],
+ ), (
+ dict(original, new=new),
+ Validator.META_SCHEMA,
+ Validator.TYPE_CHECKER,
+ original,
+
+ Validator.DEFAULT_TYPES,
+ Validator.DEFAULT_TYPES,
+ self.flushWarnings()[0]["message"],
+ ),
+ )
+
+ def test_types_redefines_the_validators_type_checker(self):
+ schema = {"type": "string"}
+ self.assertFalse(validators.Draft7Validator(schema).is_valid(12))
+
+ validator = validators.Draft7Validator(
+ schema,
+ types={"string": (str, int)},
+ )
+ self.assertTrue(validator.is_valid(12))
+ self.flushWarnings()
+
+ def test_providing_default_types_warns(self):
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The default_types argument is deprecated. "
+ "Use the type_checker argument instead."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=validators.create,
+ meta_schema={},
+ validators={},
+ default_types={"foo": object},
+ )
+
+ def test_cannot_ask_for_default_types_with_non_default_type_checker(self):
+ """
+ We raise an error when you ask a validator with non-default
+ type checker for its DEFAULT_TYPES.
+
+ The type checker argument is new, so no one but this library
+ itself should be trying to use it, and doing so while then
+ asking for DEFAULT_TYPES makes no sense (not to mention is
+ deprecated), since type checkers are not strictly about Python
+ type.
+ """
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ type_checker=TypeChecker(),
+ )
+ with self.assertRaises(validators._DontDoThat) as e:
+ Validator.DEFAULT_TYPES
+
+ self.assertIn(
+ "DEFAULT_TYPES cannot be used on Validators using TypeCheckers",
+ str(e.exception),
+ )
+ with self.assertRaises(validators._DontDoThat):
+ Validator({}).DEFAULT_TYPES
+
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_explicit_type_checker_does_not_warn(self):
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ type_checker=TypeChecker(),
+ )
+ self.assertFalse(self.flushWarnings())
+
+ Validator({})
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_neither_does_not_warn(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ Validator({})
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_default_types_with_type_checker_errors(self):
+ with self.assertRaises(TypeError) as e:
+ validators.create(
+ meta_schema={},
+ validators={},
+ default_types={"foo": object},
+ type_checker=TypeChecker(),
+ )
+
+ self.assertIn(
+ "Do not specify default_types when providing a type checker",
+ str(e.exception),
+ )
+ self.assertFalse(self.flushWarnings())
+
+ def test_extending_a_legacy_validator_with_a_type_checker_errors(self):
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ default_types={u"array": list}
+ )
+ with self.assertRaises(TypeError) as e:
+ validators.extend(
+ Validator,
+ validators={},
+ type_checker=TypeChecker(),
+ )
+
+ self.assertIn(
+ (
+ "Cannot extend a validator created with default_types "
+ "with a type_checker. Update the validator to use a "
+ "type_checker when created."
+ ),
+ str(e.exception),
+ )
+ self.flushWarnings()
+
+ def test_extending_a_legacy_validator_does_not_rewarn(self):
+ Validator = validators.create(meta_schema={}, default_types={})
+ self.assertTrue(self.flushWarnings())
+
+ validators.extend(Validator)
+ self.assertFalse(self.flushWarnings())
+
+ def test_accessing_default_types_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ DeprecationWarning,
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ # https://tm.tl/9363 :'(
+ sys.modules[self.assertWarns.__module__].__file__,
+
+ getattr,
+ Validator,
+ "DEFAULT_TYPES",
+ )
+
+ def test_accessing_default_types_on_the_instance_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ DeprecationWarning,
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ # https://tm.tl/9363 :'(
+ sys.modules[self.assertWarns.__module__].__file__,
+
+ getattr,
+ Validator({}),
+ "DEFAULT_TYPES",
+ )
+
+ def test_providing_types_to_init_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The types argument is deprecated. "
+ "Provide a type_checker to jsonschema.validators.extend "
+ "instead."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=Validator,
+ schema={},
+ types={"bar": object},
+ )
+
+
+class TestIterErrors(TestCase):
+ def setUp(self):
+ self.validator = validators.Draft3Validator({})
+
+ def test_iter_errors(self):
+ instance = [1, 2]
+ schema = {
+ u"disallow": u"array",
+ u"enum": [["a", "b", "c"], ["d", "e", "f"]],
+ u"minItems": 3,
+ }
+
+ got = (e.message for e in self.validator.iter_errors(instance, schema))
+ expected = [
+ "%r is disallowed for [1, 2]" % (schema["disallow"],),
+ "[1, 2] is too short",
+ "[1, 2] is not one of %r" % (schema["enum"],),
+ ]
+ self.assertEqual(sorted(got), sorted(expected))
+
+ def test_iter_errors_multiple_failures_one_validator(self):
+ instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
+ schema = {
+ u"properties": {
+ "foo": {u"type": "string"},
+ "bar": {u"minItems": 2},
+ "baz": {u"maximum": 10, u"enum": [2, 4, 6, 8]},
+ },
+ }
+
+ errors = list(self.validator.iter_errors(instance, schema))
+ self.assertEqual(len(errors), 4)
+
+
+class TestValidationErrorMessages(TestCase):
+ def message_for(self, instance, schema, *args, **kwargs):
+ kwargs.setdefault("cls", validators.Draft3Validator)
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(instance, schema, *args, **kwargs)
+ return e.exception.message
+
+ def test_single_type_failure(self):
+ message = self.message_for(instance=1, schema={u"type": u"string"})
+ self.assertEqual(message, "1 is not of type %r" % u"string")
+
+ def test_single_type_list_failure(self):
+ message = self.message_for(instance=1, schema={u"type": [u"string"]})
+ self.assertEqual(message, "1 is not of type %r" % u"string")
+
+ def test_multiple_type_failure(self):
+ types = u"string", u"object"
+ message = self.message_for(instance=1, schema={u"type": list(types)})
+ self.assertEqual(message, "1 is not of type %r, %r" % types)
+
+ def test_object_without_title_type_failure(self):
+ type = {u"type": [{u"minimum": 3}]}
+ message = self.message_for(instance=1, schema={u"type": [type]})
+ self.assertEqual(message, "1 is less than the minimum of 3")
+
+ def test_object_with_named_type_failure(self):
+ schema = {u"type": [{u"name": "Foo", u"minimum": 3}]}
+ message = self.message_for(instance=1, schema=schema)
+ self.assertEqual(message, "1 is less than the minimum of 3")
+
+ def test_minimum(self):
+ message = self.message_for(instance=1, schema={"minimum": 2})
+ self.assertEqual(message, "1 is less than the minimum of 2")
+
+ def test_maximum(self):
+ message = self.message_for(instance=1, schema={"maximum": 0})
+ self.assertEqual(message, "1 is greater than the maximum of 0")
+
+ def test_dependencies_single_element(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: on}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft3Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_dependencies_list_draft3(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: [on]}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft3Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_dependencies_list_draft7(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: [on]}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft7Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_additionalItems_single_failure(self):
+ message = self.message_for(
+ instance=[2],
+ schema={u"items": [], u"additionalItems": False},
+ )
+ self.assertIn("(2 was unexpected)", message)
+
+ def test_additionalItems_multiple_failures(self):
+ message = self.message_for(
+ instance=[1, 2, 3],
+ schema={u"items": [], u"additionalItems": False}
+ )
+ self.assertIn("(1, 2, 3 were unexpected)", message)
+
+ def test_additionalProperties_single_failure(self):
+ additional = "foo"
+ schema = {u"additionalProperties": False}
+ message = self.message_for(instance={additional: 2}, schema=schema)
+ self.assertIn("(%r was unexpected)" % (additional,), message)
+
+ def test_additionalProperties_multiple_failures(self):
+ schema = {u"additionalProperties": False}
+ message = self.message_for(
+ instance=dict.fromkeys(["foo", "bar"]),
+ schema=schema,
+ )
+
+ self.assertIn(repr("foo"), message)
+ self.assertIn(repr("bar"), message)
+ self.assertIn("were unexpected)", message)
+
+ def test_const(self):
+ schema = {u"const": 12}
+ message = self.message_for(
+ instance={"foo": "bar"},
+ schema=schema,
+ cls=validators.Draft6Validator,
+ )
+ self.assertIn("12 was expected", message)
+
+ def test_contains(self):
+ schema = {u"contains": {u"const": 12}}
+ message = self.message_for(
+ instance=[2, {}, []],
+ schema=schema,
+ cls=validators.Draft6Validator,
+ )
+ self.assertIn(
+ "None of [2, {}, []] are valid under the given schema",
+ message,
+ )
+
+ def test_invalid_format_default_message(self):
+ checker = FormatChecker(formats=())
+ checker.checks(u"thing")(lambda value: False)
+
+ schema = {u"format": u"thing"}
+ message = self.message_for(
+ instance="bla",
+ schema=schema,
+ format_checker=checker,
+ )
+
+ self.assertIn(repr("bla"), message)
+ self.assertIn(repr("thing"), message)
+ self.assertIn("is not a", message)
+
+ def test_additionalProperties_false_patternProperties(self):
+ schema = {u"type": u"object",
+ u"additionalProperties": False,
+ u"patternProperties": {
+ u"^abc$": {u"type": u"string"},
+ u"^def$": {u"type": u"string"},
+ }}
+ message = self.message_for(
+ instance={u"zebra": 123},
+ schema=schema,
+ cls=validators.Draft4Validator,
+ )
+ self.assertEqual(
+ message,
+ "{} does not match any of the regexes: {}, {}".format(
+ repr(u"zebra"), repr(u"^abc$"), repr(u"^def$"),
+ ),
+ )
+ message = self.message_for(
+ instance={u"zebra": 123, u"fish": 456},
+ schema=schema,
+ cls=validators.Draft4Validator,
+ )
+ self.assertEqual(
+ message,
+ "{}, {} do not match any of the regexes: {}, {}".format(
+ repr(u"fish"), repr(u"zebra"), repr(u"^abc$"), repr(u"^def$")
+ ),
+ )
+
+ def test_False_schema(self):
+ message = self.message_for(
+ instance="something",
+ schema=False,
+ cls=validators.Draft7Validator,
+ )
+ self.assertIn("False schema does not allow 'something'", message)
+
+
+class TestValidationErrorDetails(TestCase):
+ # TODO: These really need unit tests for each individual validator, rather
+ # than just these higher level tests.
+ def test_anyOf(self):
+ instance = 5
+ schema = {
+ "anyOf": [
+ {"minimum": 20},
+ {"type": "string"},
+ ],
+ }
+
+ validator = validators.Draft4Validator(schema)
+ errors = list(validator.iter_errors(instance))
+ self.assertEqual(len(errors), 1)
+ e = errors[0]
+
+ self.assertEqual(e.validator, "anyOf")
+ self.assertEqual(e.validator_value, schema["anyOf"])
+ self.assertEqual(e.instance, instance)
+ self.assertEqual(e.schema, schema)
+ self.assertIsNone(e.parent)
+
+ self.assertEqual(e.path, deque([]))
+ self.assertEqual(e.relative_path, deque([]))
+ self.assertEqual(e.absolute_path, deque([]))
+
+ self.assertEqual(e.schema_path, deque(["anyOf"]))
+ self.assertEqual(e.relative_schema_path, deque(["anyOf"]))
+ self.assertEqual(e.absolute_schema_path, deque(["anyOf"]))
+
+ self.assertEqual(len(e.context), 2)
+
+ e1, e2 = sorted_errors(e.context)
+
+ self.assertEqual(e1.validator, "minimum")
+ self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"])
+ self.assertEqual(e1.instance, instance)
+ self.assertEqual(e1.schema, schema["anyOf"][0])
+ self.assertIs(e1.parent, e)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e1.absolute_path, deque([]))
+ self.assertEqual(e1.relative_path, deque([]))
+
+ self.assertEqual(e1.schema_path, deque([0, "minimum"]))
+ self.assertEqual(e1.relative_schema_path, deque([0, "minimum"]))
+ self.assertEqual(
+ e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]),
+ )
+
+ self.assertFalse(e1.context)
+
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"])
+ self.assertEqual(e2.instance, instance)
+ self.assertEqual(e2.schema, schema["anyOf"][1])
+ self.assertIs(e2.parent, e)
+
+ self.assertEqual(e2.path, deque([]))
+ self.assertEqual(e2.relative_path, deque([]))
+ self.assertEqual(e2.absolute_path, deque([]))
+
+ self.assertEqual(e2.schema_path, deque([1, "type"]))
+ self.assertEqual(e2.relative_schema_path, deque([1, "type"]))
+ self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"]))
+
+ self.assertEqual(len(e2.context), 0)
+
+ def test_type(self):
+ instance = {"foo": 1}
+ schema = {
+ "type": [
+ {"type": "integer"},
+ {
+ "type": "object",
+ "properties": {"foo": {"enum": [2]}},
+ },
+ ],
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = list(validator.iter_errors(instance))
+ self.assertEqual(len(errors), 1)
+ e = errors[0]
+
+ self.assertEqual(e.validator, "type")
+ self.assertEqual(e.validator_value, schema["type"])
+ self.assertEqual(e.instance, instance)
+ self.assertEqual(e.schema, schema)
+ self.assertIsNone(e.parent)
+
+ self.assertEqual(e.path, deque([]))
+ self.assertEqual(e.relative_path, deque([]))
+ self.assertEqual(e.absolute_path, deque([]))
+
+ self.assertEqual(e.schema_path, deque(["type"]))
+ self.assertEqual(e.relative_schema_path, deque(["type"]))
+ self.assertEqual(e.absolute_schema_path, deque(["type"]))
+
+ self.assertEqual(len(e.context), 2)
+
+ e1, e2 = sorted_errors(e.context)
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e1.validator_value, schema["type"][0]["type"])
+ self.assertEqual(e1.instance, instance)
+ self.assertEqual(e1.schema, schema["type"][0])
+ self.assertIs(e1.parent, e)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e1.relative_path, deque([]))
+ self.assertEqual(e1.absolute_path, deque([]))
+
+ self.assertEqual(e1.schema_path, deque([0, "type"]))
+ self.assertEqual(e1.relative_schema_path, deque([0, "type"]))
+ self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"]))
+
+ self.assertFalse(e1.context)
+
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e2.validator_value, [2])
+ self.assertEqual(e2.instance, 1)
+ self.assertEqual(e2.schema, {u"enum": [2]})
+ self.assertIs(e2.parent, e)
+
+ self.assertEqual(e2.path, deque(["foo"]))
+ self.assertEqual(e2.relative_path, deque(["foo"]))
+ self.assertEqual(e2.absolute_path, deque(["foo"]))
+
+ self.assertEqual(
+ e2.schema_path, deque([1, "properties", "foo", "enum"]),
+ )
+ self.assertEqual(
+ e2.relative_schema_path, deque([1, "properties", "foo", "enum"]),
+ )
+ self.assertEqual(
+ e2.absolute_schema_path,
+ deque(["type", 1, "properties", "foo", "enum"]),
+ )
+
+ self.assertFalse(e2.context)
+
+ def test_single_nesting(self):
+ instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
+ schema = {
+ "properties": {
+ "foo": {"type": "string"},
+ "bar": {"minItems": 2},
+ "baz": {"maximum": 10, "enum": [2, 4, 6, 8]},
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2, e3, e4 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["baz"]))
+ self.assertEqual(e3.path, deque(["baz"]))
+ self.assertEqual(e4.path, deque(["foo"]))
+
+ self.assertEqual(e1.relative_path, deque(["bar"]))
+ self.assertEqual(e2.relative_path, deque(["baz"]))
+ self.assertEqual(e3.relative_path, deque(["baz"]))
+ self.assertEqual(e4.relative_path, deque(["foo"]))
+
+ self.assertEqual(e1.absolute_path, deque(["bar"]))
+ self.assertEqual(e2.absolute_path, deque(["baz"]))
+ self.assertEqual(e3.absolute_path, deque(["baz"]))
+ self.assertEqual(e4.absolute_path, deque(["foo"]))
+
+ self.assertEqual(e1.validator, "minItems")
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e3.validator, "maximum")
+ self.assertEqual(e4.validator, "type")
+
+ def test_multiple_nesting(self):
+ instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"]
+ schema = {
+ "type": "string",
+ "items": {
+ "type": ["string", "object"],
+ "properties": {
+ "foo": {"enum": [1, 3]},
+ "bar": {
+ "type": "array",
+ "properties": {
+ "bar": {"required": True},
+ "baz": {"minItems": 2},
+ },
+ },
+ },
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e2.path, deque([0]))
+ self.assertEqual(e3.path, deque([1, "bar"]))
+ self.assertEqual(e4.path, deque([1, "bar", "bar"]))
+ self.assertEqual(e5.path, deque([1, "bar", "baz"]))
+ self.assertEqual(e6.path, deque([1, "foo"]))
+
+ self.assertEqual(e1.schema_path, deque(["type"]))
+ self.assertEqual(e2.schema_path, deque(["items", "type"]))
+ self.assertEqual(
+ list(e3.schema_path), ["items", "properties", "bar", "type"],
+ )
+ self.assertEqual(
+ list(e4.schema_path),
+ ["items", "properties", "bar", "properties", "bar", "required"],
+ )
+ self.assertEqual(
+ list(e5.schema_path),
+ ["items", "properties", "bar", "properties", "baz", "minItems"]
+ )
+ self.assertEqual(
+ list(e6.schema_path), ["items", "properties", "foo", "enum"],
+ )
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e3.validator, "type")
+ self.assertEqual(e4.validator, "required")
+ self.assertEqual(e5.validator, "minItems")
+ self.assertEqual(e6.validator, "enum")
+
+ def test_recursive(self):
+ schema = {
+ "definitions": {
+ "node": {
+ "anyOf": [{
+ "type": "object",
+ "required": ["name", "children"],
+ "properties": {
+ "name": {
+ "type": "string",
+ },
+ "children": {
+ "type": "object",
+ "patternProperties": {
+ "^.*$": {
+ "$ref": "#/definitions/node",
+ },
+ },
+ },
+ },
+ }],
+ },
+ },
+ "type": "object",
+ "required": ["root"],
+ "properties": {"root": {"$ref": "#/definitions/node"}},
+ }
+
+ instance = {
+ "root": {
+ "name": "root",
+ "children": {
+ "a": {
+ "name": "a",
+ "children": {
+ "ab": {
+ "name": "ab",
+ # missing "children"
+ },
+ },
+ },
+ },
+ },
+ }
+ validator = validators.Draft4Validator(schema)
+
+ e, = validator.iter_errors(instance)
+ self.assertEqual(e.absolute_path, deque(["root"]))
+ self.assertEqual(
+ e.absolute_schema_path, deque(["properties", "root", "anyOf"]),
+ )
+
+ e1, = e.context
+ self.assertEqual(e1.absolute_path, deque(["root", "children", "a"]))
+ self.assertEqual(
+ e1.absolute_schema_path, deque(
+ [
+ "properties",
+ "root",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ ],
+ ),
+ )
+
+ e2, = e1.context
+ self.assertEqual(
+ e2.absolute_path, deque(
+ ["root", "children", "a", "children", "ab"],
+ ),
+ )
+ self.assertEqual(
+ e2.absolute_schema_path, deque(
+ [
+ "properties",
+ "root",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ ],
+ ),
+ )
+
+ def test_additionalProperties(self):
+ instance = {"bar": "bar", "foo": 2}
+ schema = {"additionalProperties": {"type": "integer", "minimum": 5}}
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["foo"]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_patternProperties(self):
+ instance = {"bar": 1, "foo": 2}
+ schema = {
+ "patternProperties": {
+ "bar": {"type": "string"},
+ "foo": {"minimum": 5},
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["foo"]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_additionalItems(self):
+ instance = ["foo", 1]
+ schema = {
+ "items": [],
+ "additionalItems": {"type": "integer", "minimum": 5},
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([0]))
+ self.assertEqual(e2.path, deque([1]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_additionalItems_with_items(self):
+ instance = ["foo", "bar", 1]
+ schema = {
+ "items": [{}],
+ "additionalItems": {"type": "integer", "minimum": 5},
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([1]))
+ self.assertEqual(e2.path, deque([2]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_propertyNames(self):
+ instance = {"foo": 12}
+ schema = {"propertyNames": {"not": {"const": "foo"}}}
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(instance)
+
+ self.assertEqual(error.validator, "not")
+ self.assertEqual(
+ error.message,
+ "%r is not allowed for %r" % ({"const": "foo"}, "foo"),
+ )
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.schema_path, deque(["propertyNames", "not"]))
+
+ def test_if_then(self):
+ schema = {
+ "if": {"const": 12},
+ "then": {"const": 13},
+ }
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(12)
+
+ self.assertEqual(error.validator, "const")
+ self.assertEqual(error.message, "13 was expected")
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.schema_path, deque(["if", "then", "const"]))
+
+ def test_if_else(self):
+ schema = {
+ "if": {"const": 12},
+ "else": {"const": 13},
+ }
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(15)
+
+ self.assertEqual(error.validator, "const")
+ self.assertEqual(error.message, "13 was expected")
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.schema_path, deque(["if", "else", "const"]))
+
+ def test_boolean_schema_False(self):
+ validator = validators.Draft7Validator(False)
+ error, = validator.iter_errors(12)
+
+ self.assertEqual(
+ (
+ error.message,
+ error.validator,
+ error.validator_value,
+ error.instance,
+ error.schema,
+ error.schema_path,
+ ),
+ (
+ "False schema does not allow 12",
+ None,
+ None,
+ 12,
+ False,
+ deque([]),
+ ),
+ )
+
+ def test_ref(self):
+ ref, schema = "someRef", {"additionalProperties": {"type": "integer"}}
+ validator = validators.Draft7Validator(
+ {"$ref": ref},
+ resolver=validators.RefResolver("", {}, store={ref: schema}),
+ )
+ error, = validator.iter_errors({"foo": "notAnInteger"})
+
+ self.assertEqual(
+ (
+ error.message,
+ error.validator,
+ error.validator_value,
+ error.instance,
+ error.absolute_path,
+ error.schema,
+ error.schema_path,
+ ),
+ (
+ "'notAnInteger' is not of type 'integer'",
+ "type",
+ "integer",
+ "notAnInteger",
+ deque(["foo"]),
+ {"type": "integer"},
+ deque(["additionalProperties", "type"]),
+ ),
+ )
+
+
+class MetaSchemaTestsMixin(object):
+ # TODO: These all belong upstream
+ def test_invalid_properties(self):
+ with self.assertRaises(exceptions.SchemaError):
+ self.Validator.check_schema({"properties": {"test": object()}})
+
+ def test_minItems_invalid_string(self):
+ with self.assertRaises(exceptions.SchemaError):
+ # needs to be an integer
+ self.Validator.check_schema({"minItems": "1"})
+
+ def test_enum_allows_empty_arrays(self):
+ """
+ Technically, all the spec says is they SHOULD have elements, not MUST.
+
+ See https://github.com/Julian/jsonschema/issues/529.
+ """
+ self.Validator.check_schema({"enum": []})
+
+ def test_enum_allows_non_unique_items(self):
+ """
+ Technically, all the spec says is they SHOULD be unique, not MUST.
+
+ See https://github.com/Julian/jsonschema/issues/529.
+ """
+ self.Validator.check_schema({"enum": [12, 12]})
+
+
+class ValidatorTestMixin(MetaSchemaTestsMixin, object):
+ def test_valid_instances_are_valid(self):
+ schema, instance = self.valid
+ self.assertTrue(self.Validator(schema).is_valid(instance))
+
+ def test_invalid_instances_are_not_valid(self):
+ schema, instance = self.invalid
+ self.assertFalse(self.Validator(schema).is_valid(instance))
+
+ def test_non_existent_properties_are_ignored(self):
+ self.Validator({object(): object()}).validate(instance=object())
+
+ def test_it_creates_a_ref_resolver_if_not_provided(self):
+ self.assertIsInstance(
+ self.Validator({}).resolver,
+ validators.RefResolver,
+ )
+
+ def test_it_delegates_to_a_ref_resolver(self):
+ ref, schema = "someCoolRef", {"type": "integer"}
+ resolver = validators.RefResolver("", {}, store={ref: schema})
+ validator = self.Validator({"$ref": ref}, resolver=resolver)
+
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate(None)
+
+ def test_it_delegates_to_a_legacy_ref_resolver(self):
+ """
+ Legacy RefResolvers support only the context manager form of
+ resolution.
+ """
+
+ class LegacyRefResolver(object):
+ @contextmanager
+ def resolving(this, ref):
+ self.assertEqual(ref, "the ref")
+ yield {"type": "integer"}
+
+ resolver = LegacyRefResolver()
+ schema = {"$ref": "the ref"}
+
+ with self.assertRaises(exceptions.ValidationError):
+ self.Validator(schema, resolver=resolver).validate(None)
+
+ def test_is_type_is_true_for_valid_type(self):
+ self.assertTrue(self.Validator({}).is_type("foo", "string"))
+
+ def test_is_type_is_false_for_invalid_type(self):
+ self.assertFalse(self.Validator({}).is_type("foo", "array"))
+
+ def test_is_type_evades_bool_inheriting_from_int(self):
+ self.assertFalse(self.Validator({}).is_type(True, "integer"))
+ self.assertFalse(self.Validator({}).is_type(True, "number"))
+
+ @unittest.skipIf(PY3, "In Python 3 json.load always produces unicode")
+ def test_string_a_bytestring_is_a_string(self):
+ self.Validator({"type": "string"}).validate(b"foo")
+
+ def test_patterns_can_be_native_strings(self):
+ """
+ See https://github.com/Julian/jsonschema/issues/611.
+ """
+ self.Validator({"pattern": "foo"}).validate("foo")
+
+ def test_it_can_validate_with_decimals(self):
+ schema = {"items": {"type": "number"}}
+ Validator = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "number",
+ lambda checker, thing: isinstance(
+ thing, (int, float, Decimal),
+ ) and not isinstance(thing, bool),
+ )
+ )
+
+ validator = Validator(schema)
+ validator.validate([1, 1.1, Decimal(1) / Decimal(8)])
+
+ invalid = ["foo", {}, [], True, None]
+ self.assertEqual(
+ [error.instance for error in validator.iter_errors(invalid)],
+ invalid,
+ )
+
+ def test_it_returns_true_for_formats_it_does_not_know_about(self):
+ validator = self.Validator(
+ {"format": "carrot"}, format_checker=FormatChecker(),
+ )
+ validator.validate("bugs")
+
+ def test_it_does_not_validate_formats_by_default(self):
+ validator = self.Validator({})
+ self.assertIsNone(validator.format_checker)
+
+ def test_it_validates_formats_if_a_checker_is_provided(self):
+ checker = FormatChecker()
+ bad = ValueError("Bad!")
+
+ @checker.checks("foo", raises=ValueError)
+ def check(value):
+ if value == "good":
+ return True
+ elif value == "bad":
+ raise bad
+ else: # pragma: no cover
+ self.fail("What is {}? [Baby Don't Hurt Me]".format(value))
+
+ validator = self.Validator(
+ {"format": "foo"}, format_checker=checker,
+ )
+
+ validator.validate("good")
+ with self.assertRaises(exceptions.ValidationError) as cm:
+ validator.validate("bad")
+
+ # Make sure original cause is attached
+ self.assertIs(cm.exception.cause, bad)
+
+ def test_non_string_custom_type(self):
+ non_string_type = object()
+ schema = {"type": [non_string_type]}
+ Crazy = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ non_string_type,
+ lambda checker, thing: isinstance(thing, int),
+ )
+ )
+ Crazy(schema).validate(15)
+
+ def test_it_properly_formats_tuples_in_errors(self):
+ """
+ A tuple instance properly formats validation errors for uniqueItems.
+
+ See https://github.com/Julian/jsonschema/pull/224
+ """
+ TupleValidator = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "array",
+ lambda checker, thing: isinstance(thing, tuple),
+ )
+ )
+ with self.assertRaises(exceptions.ValidationError) as e:
+ TupleValidator({"uniqueItems": True}).validate((1, 1))
+ self.assertIn("(1, 1) has non-unique elements", str(e.exception))
+
+
+class AntiDraft6LeakMixin(object):
+ """
+ Make sure functionality from draft 6 doesn't leak backwards in time.
+ """
+
+ def test_True_is_not_a_schema(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ self.Validator.check_schema(True)
+ self.assertIn("True is not of type", str(e.exception))
+
+ def test_False_is_not_a_schema(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ self.Validator.check_schema(False)
+ self.assertIn("False is not of type", str(e.exception))
+
+ @unittest.skip(bug(523))
+ def test_True_is_not_a_schema_even_if_you_forget_to_check(self):
+ resolver = validators.RefResolver("", {})
+ with self.assertRaises(Exception) as e:
+ self.Validator(True, resolver=resolver).validate(12)
+ self.assertNotIsInstance(e.exception, exceptions.ValidationError)
+
+ @unittest.skip(bug(523))
+ def test_False_is_not_a_schema_even_if_you_forget_to_check(self):
+ resolver = validators.RefResolver("", {})
+ with self.assertRaises(Exception) as e:
+ self.Validator(False, resolver=resolver).validate(12)
+ self.assertNotIsInstance(e.exception, exceptions.ValidationError)
+
+
+class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
+ Validator = validators.Draft3Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+ def test_any_type_is_valid_for_type_any(self):
+ validator = self.Validator({"type": "any"})
+ validator.validate(object())
+
+ def test_any_type_is_redefinable(self):
+ """
+ Sigh, because why not.
+ """
+ Crazy = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "any", lambda checker, thing: isinstance(thing, int),
+ )
+ )
+ validator = Crazy({"type": "any"})
+ validator.validate(12)
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate("foo")
+
+ def test_is_type_is_true_for_any_type(self):
+ self.assertTrue(self.Validator({}).is_valid(object(), {"type": "any"}))
+
+ def test_is_type_does_not_evade_bool_if_it_is_being_tested(self):
+ self.assertTrue(self.Validator({}).is_type(True, "boolean"))
+ self.assertTrue(self.Validator({}).is_valid(True, {"type": "any"}))
+
+
+class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
+ Validator = validators.Draft4Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestDraft6Validator(ValidatorTestMixin, TestCase):
+ Validator = validators.Draft6Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestDraft7Validator(ValidatorTestMixin, TestCase):
+ Validator = validators.Draft7Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestValidatorFor(SynchronousTestCase):
+ def test_draft_3(self):
+ schema = {"$schema": "http://json-schema.org/draft-03/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft3Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-03/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft3Validator,
+ )
+
+ def test_draft_4(self):
+ schema = {"$schema": "http://json-schema.org/draft-04/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft4Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-04/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft4Validator,
+ )
+
+ def test_draft_6(self):
+ schema = {"$schema": "http://json-schema.org/draft-06/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft6Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-06/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft6Validator,
+ )
+
+ def test_draft_7(self):
+ schema = {"$schema": "http://json-schema.org/draft-07/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-07/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ def test_True(self):
+ self.assertIs(
+ validators.validator_for(True),
+ validators._LATEST_VERSION,
+ )
+
+ def test_False(self):
+ self.assertIs(
+ validators.validator_for(False),
+ validators._LATEST_VERSION,
+ )
+
+ def test_custom_validator(self):
+ Validator = validators.create(
+ meta_schema={"id": "meta schema id"},
+ version="12",
+ id_of=lambda s: s.get("id", ""),
+ )
+ schema = {"$schema": "meta schema id"}
+ self.assertIs(
+ validators.validator_for(schema),
+ Validator,
+ )
+
+ def test_custom_validator_draft6(self):
+ Validator = validators.create(
+ meta_schema={"$id": "meta schema $id"},
+ version="13",
+ )
+ schema = {"$schema": "meta schema $id"}
+ self.assertIs(
+ validators.validator_for(schema),
+ Validator,
+ )
+
+ def test_validator_for_jsonschema_default(self):
+ self.assertIs(validators.validator_for({}), validators._LATEST_VERSION)
+
+ def test_validator_for_custom_default(self):
+ self.assertIs(validators.validator_for({}, default=None), None)
+
+ def test_warns_if_meta_schema_specified_was_not_found(self):
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The metaschema specified by $schema was not found. "
+ "Using the latest draft to validate, but this will raise "
+ "an error in the future."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=validators.validator_for,
+ schema={u"$schema": "unknownSchema"},
+ default={},
+ )
+
+ def test_does_not_warn_if_meta_schema_is_unspecified(self):
+ validators.validator_for(schema={}, default={}),
+ self.assertFalse(self.flushWarnings())
+
+
+class TestValidate(SynchronousTestCase):
+ def assertUses(self, schema, Validator):
+ result = []
+ self.patch(Validator, "check_schema", result.append)
+ validators.validate({}, schema)
+ self.assertEqual(result, [schema])
+
+ def test_draft3_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-03/schema#"},
+ Validator=validators.Draft3Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-03/schema"},
+ Validator=validators.Draft3Validator,
+ )
+
+ def test_draft4_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-04/schema#"},
+ Validator=validators.Draft4Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-04/schema"},
+ Validator=validators.Draft4Validator,
+ )
+
+ def test_draft6_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-06/schema#"},
+ Validator=validators.Draft6Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-06/schema"},
+ Validator=validators.Draft6Validator,
+ )
+
+ def test_draft7_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema#"},
+ Validator=validators.Draft7Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema"},
+ Validator=validators.Draft7Validator,
+ )
+
+ def test_draft7_validator_is_the_default(self):
+ self.assertUses(schema={}, Validator=validators.Draft7Validator)
+
+ def test_validation_error_message(self):
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(12, {"type": "string"})
+ self.assertRegexpMatches(
+ str(e.exception),
+ "(?s)Failed validating u?'.*' in schema.*On instance",
+ )
+
+ def test_schema_error_message(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ validators.validate(12, {"type": 12})
+ self.assertRegexpMatches(
+ str(e.exception),
+ "(?s)Failed validating u?'.*' in metaschema.*On schema",
+ )
+
+ def test_it_uses_best_match(self):
+ # This is a schema that best_match will recurse into
+ schema = {"oneOf": [{"type": "string"}, {"type": "array"}]}
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(12, schema)
+ self.assertIn("12 is not of type", str(e.exception))
+
+
+class TestRefResolver(SynchronousTestCase):
+
+ base_uri = ""
+ stored_uri = "foo://stored"
+ stored_schema = {"stored": "schema"}
+
+ def setUp(self):
+ self.referrer = {}
+ self.store = {self.stored_uri: self.stored_schema}
+ self.resolver = validators.RefResolver(
+ self.base_uri, self.referrer, self.store,
+ )
+
+ def test_it_does_not_retrieve_schema_urls_from_the_network(self):
+ ref = validators.Draft3Validator.META_SCHEMA["id"]
+ self.patch(
+ self.resolver,
+ "resolve_remote",
+ lambda *args, **kwargs: self.fail("Should not have been called!"),
+ )
+ with self.resolver.resolving(ref) as resolved:
+ pass
+ self.assertEqual(resolved, validators.Draft3Validator.META_SCHEMA)
+
+ def test_it_resolves_local_refs(self):
+ ref = "#/properties/foo"
+ self.referrer["properties"] = {"foo": object()}
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, self.referrer["properties"]["foo"])
+
+ def test_it_resolves_local_refs_with_id(self):
+ schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}}
+ resolver = validators.RefResolver.from_schema(
+ schema,
+ id_of=lambda schema: schema.get(u"id", u""),
+ )
+ with resolver.resolving("#/a") as resolved:
+ self.assertEqual(resolved, schema["a"])
+ with resolver.resolving("http://bar/schema#/a") as resolved:
+ self.assertEqual(resolved, schema["a"])
+
+ def test_it_retrieves_stored_refs(self):
+ with self.resolver.resolving(self.stored_uri) as resolved:
+ self.assertIs(resolved, self.stored_schema)
+
+ self.resolver.store["cached_ref"] = {"foo": 12}
+ with self.resolver.resolving("cached_ref#/foo") as resolved:
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_unstored_refs_via_requests(self):
+ ref = "http://bar#baz"
+ schema = {"baz": 12}
+
+ if "requests" in sys.modules:
+ self.addCleanup(
+ sys.modules.__setitem__, "requests", sys.modules["requests"],
+ )
+ sys.modules["requests"] = ReallyFakeRequests({"http://bar": schema})
+
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_unstored_refs_via_urlopen(self):
+ ref = "http://bar#baz"
+ schema = {"baz": 12}
+
+ if "requests" in sys.modules:
+ self.addCleanup(
+ sys.modules.__setitem__, "requests", sys.modules["requests"],
+ )
+ sys.modules["requests"] = None
+
+ @contextmanager
+ def fake_urlopen(url):
+ self.assertEqual(url, "http://bar")
+ yield BytesIO(json.dumps(schema).encode("utf8"))
+
+ self.addCleanup(setattr, validators, "urlopen", validators.urlopen)
+ validators.urlopen = fake_urlopen
+
+ with self.resolver.resolving(ref) as resolved:
+ pass
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_local_refs_via_urlopen(self):
+ with tempfile.NamedTemporaryFile(delete=False, mode="wt") as tempf:
+ self.addCleanup(os.remove, tempf.name)
+ json.dump({"foo": "bar"}, tempf)
+
+ ref = "file://{}#foo".format(pathname2url(tempf.name))
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, "bar")
+
+ def test_it_can_construct_a_base_uri_from_a_schema(self):
+ schema = {"id": "foo"}
+ resolver = validators.RefResolver.from_schema(
+ schema,
+ id_of=lambda schema: schema.get(u"id", u""),
+ )
+ self.assertEqual(resolver.base_uri, "foo")
+ self.assertEqual(resolver.resolution_scope, "foo")
+ with resolver.resolving("") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("#") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("foo") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("foo#") as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
+ schema = {}
+ resolver = validators.RefResolver.from_schema(schema)
+ self.assertEqual(resolver.base_uri, "")
+ self.assertEqual(resolver.resolution_scope, "")
+ with resolver.resolving("") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("#") as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_custom_uri_scheme_handlers(self):
+ def handler(url):
+ self.assertEqual(url, ref)
+ return schema
+
+ schema = {"foo": "bar"}
+ ref = "foo://bar"
+ resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ with resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_cache_remote_on(self):
+ response = [object()]
+
+ def handler(url):
+ try:
+ return response.pop()
+ except IndexError: # pragma: no cover
+ self.fail("Response must not have been cached!")
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver(
+ "", {}, cache_remote=True, handlers={"foo": handler},
+ )
+ with resolver.resolving(ref):
+ pass
+ with resolver.resolving(ref):
+ pass
+
+ def test_cache_remote_off(self):
+ response = [object()]
+
+ def handler(url):
+ try:
+ return response.pop()
+ except IndexError: # pragma: no cover
+ self.fail("Handler called twice!")
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver(
+ "", {}, cache_remote=False, handlers={"foo": handler},
+ )
+ with resolver.resolving(ref):
+ pass
+
+ def test_if_you_give_it_junk_you_get_a_resolution_error(self):
+ error = ValueError("Oh no! What's this?")
+
+ def handler(url):
+ raise error
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ with self.assertRaises(exceptions.RefResolutionError) as err:
+ with resolver.resolving(ref):
+ self.fail("Shouldn't get this far!") # pragma: no cover
+ self.assertEqual(err.exception, exceptions.RefResolutionError(error))
+
+ def test_helpful_error_message_on_failed_pop_scope(self):
+ resolver = validators.RefResolver("", {})
+ resolver.pop_scope()
+ with self.assertRaises(exceptions.RefResolutionError) as exc:
+ resolver.pop_scope()
+ self.assertIn("Failed to pop the scope", str(exc.exception))
+
+
+def sorted_errors(errors):
+ def key(error):
+ return (
+ [str(e) for e in error.path],
+ [str(e) for e in error.schema_path],
+ )
+ return sorted(errors, key=key)
+
+
+class ReallyFakeRequests(object):
+
+ _responses = attr.ib()
+
+ def get(self, url):
+ response = self._responses.get(url)
+ if url is None: # pragma: no cover
+ raise ValueError("Unknown URL: " + repr(url))
+ return _ReallyFakeJSONResponse(json.dumps(response))
+
+
+class _ReallyFakeJSONResponse(object):
+
+ _response = attr.ib()
+
+ def json(self):
+ return json.loads(self._response)
diff --git a/contrib/python/jsonschema/py2/jsonschema/validators.py b/contrib/python/jsonschema/py2/jsonschema/validators.py
new file mode 100644
index 00000000000..1dc420c70d2
--- /dev/null
+++ b/contrib/python/jsonschema/py2/jsonschema/validators.py
@@ -0,0 +1,970 @@
+"""
+Creation and extension of validators, with implementations for existing drafts.
+"""
+from __future__ import division
+
+from warnings import warn
+import contextlib
+import json
+import numbers
+
+from six import add_metaclass
+
+from jsonschema import (
+ _legacy_validators,
+ _types,
+ _utils,
+ _validators,
+ exceptions,
+)
+from jsonschema.compat import (
+ Sequence,
+ int_types,
+ iteritems,
+ lru_cache,
+ str_types,
+ unquote,
+ urldefrag,
+ urljoin,
+ urlopen,
+ urlsplit,
+)
+
+# Sigh. https://gitlab.com/pycqa/flake8/issues/280
+# https://github.com/pyga/ebb-lint/issues/7
+# Imported for backwards compatibility.
+from jsonschema.exceptions import ErrorTree
+ErrorTree
+
+
+class _DontDoThat(Exception):
+ """
+ Raised when a Validators with non-default type checker is misused.
+
+ Asking one for DEFAULT_TYPES doesn't make sense, since type checkers
+ exist for the unrepresentable cases where DEFAULT_TYPES can't
+ represent the type relationship.
+ """
+
+ def __str__(self):
+ return "DEFAULT_TYPES cannot be used on Validators using TypeCheckers"
+
+
+validators = {}
+meta_schemas = _utils.URIDict()
+
+
+def _generate_legacy_type_checks(types=()):
+ """
+ Generate newer-style type checks out of JSON-type-name-to-type mappings.
+
+ Arguments:
+
+ types (dict):
+
+ A mapping of type names to their Python types
+
+ Returns:
+
+ A dictionary of definitions to pass to `TypeChecker`
+ """
+ types = dict(types)
+
+ def gen_type_check(pytypes):
+ pytypes = _utils.flatten(pytypes)
+
+ def type_check(checker, instance):
+ if isinstance(instance, bool):
+ if bool not in pytypes:
+ return False
+ return isinstance(instance, pytypes)
+
+ return type_check
+
+ definitions = {}
+ for typename, pytypes in iteritems(types):
+ definitions[typename] = gen_type_check(pytypes)
+
+ return definitions
+
+
+_DEPRECATED_DEFAULT_TYPES = {
+ u"array": list,
+ u"boolean": bool,
+ u"integer": int_types,
+ u"null": type(None),
+ u"number": numbers.Number,
+ u"object": dict,
+ u"string": str_types,
+}
+_TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES = _types.TypeChecker(
+ type_checkers=_generate_legacy_type_checks(_DEPRECATED_DEFAULT_TYPES),
+)
+
+
+def validates(version):
+ """
+ Register the decorated validator for a ``version`` of the specification.
+
+ Registered validators and their meta schemas will be considered when
+ parsing ``$schema`` properties' URIs.
+
+ Arguments:
+
+ version (str):
+
+ An identifier to use as the version's name
+
+ Returns:
+
+ collections.Callable:
+
+ a class decorator to decorate the validator with the version
+ """
+
+ def _validates(cls):
+ validators[version] = cls
+ meta_schema_id = cls.ID_OF(cls.META_SCHEMA)
+ if meta_schema_id:
+ meta_schemas[meta_schema_id] = cls
+ return cls
+ return _validates
+
+
+def _DEFAULT_TYPES(self):
+ if self._CREATED_WITH_DEFAULT_TYPES is None:
+ raise _DontDoThat()
+
+ warn(
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self._DEFAULT_TYPES
+
+
+class _DefaultTypesDeprecatingMetaClass(type):
+ DEFAULT_TYPES = property(_DEFAULT_TYPES)
+
+
+def _id_of(schema):
+ if schema is True or schema is False:
+ return u""
+ return schema.get(u"$id", u"")
+
+
+def create(
+ meta_schema,
+ validators=(),
+ version=None,
+ default_types=None,
+ type_checker=None,
+ id_of=_id_of,
+):
+ """
+ Create a new validator class.
+
+ Arguments:
+
+ meta_schema (collections.Mapping):
+
+ the meta schema for the new validator class
+
+ validators (collections.Mapping):
+
+ a mapping from names to callables, where each callable will
+ validate the schema property with the given name.
+
+ Each callable should take 4 arguments:
+
+ 1. a validator instance,
+ 2. the value of the property being validated within the
+ instance
+ 3. the instance
+ 4. the schema
+
+ version (str):
+
+ an identifier for the version that this validator class will
+ validate. If provided, the returned validator class will
+ have its ``__name__`` set to include the version, and also
+ will have `jsonschema.validators.validates` automatically
+ called for the given version.
+
+ type_checker (jsonschema.TypeChecker):
+
+ a type checker, used when applying the :validator:`type` validator.
+
+ If unprovided, a `jsonschema.TypeChecker` will be created
+ with a set of default types typical of JSON Schema drafts.
+
+ default_types (collections.Mapping):
+
+ .. deprecated:: 3.0.0
+
+ Please use the type_checker argument instead.
+
+ If set, it provides mappings of JSON types to Python types
+ that will be converted to functions and redefined in this
+ object's `jsonschema.TypeChecker`.
+
+ id_of (collections.Callable):
+
+ A function that given a schema, returns its ID.
+
+ Returns:
+
+ a new `jsonschema.IValidator` class
+ """
+
+ if default_types is not None:
+ if type_checker is not None:
+ raise TypeError(
+ "Do not specify default_types when providing a type checker.",
+ )
+ _created_with_default_types = True
+ warn(
+ (
+ "The default_types argument is deprecated. "
+ "Use the type_checker argument instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ type_checker = _types.TypeChecker(
+ type_checkers=_generate_legacy_type_checks(default_types),
+ )
+ else:
+ default_types = _DEPRECATED_DEFAULT_TYPES
+ if type_checker is None:
+ _created_with_default_types = False
+ type_checker = _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES
+ elif type_checker is _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES:
+ _created_with_default_types = False
+ else:
+ _created_with_default_types = None
+
+ @add_metaclass(_DefaultTypesDeprecatingMetaClass)
+ class Validator(object):
+
+ VALIDATORS = dict(validators)
+ META_SCHEMA = dict(meta_schema)
+ TYPE_CHECKER = type_checker
+ ID_OF = staticmethod(id_of)
+
+ DEFAULT_TYPES = property(_DEFAULT_TYPES)
+ _DEFAULT_TYPES = dict(default_types)
+ _CREATED_WITH_DEFAULT_TYPES = _created_with_default_types
+
+ def __init__(
+ self,
+ schema,
+ types=(),
+ resolver=None,
+ format_checker=None,
+ ):
+ if types:
+ warn(
+ (
+ "The types argument is deprecated. Provide "
+ "a type_checker to jsonschema.validators.extend "
+ "instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ self.TYPE_CHECKER = self.TYPE_CHECKER.redefine_many(
+ _generate_legacy_type_checks(types),
+ )
+
+ if resolver is None:
+ resolver = RefResolver.from_schema(schema, id_of=id_of)
+
+ self.resolver = resolver
+ self.format_checker = format_checker
+ self.schema = schema
+
+ @classmethod
+ def check_schema(cls, schema):
+ for error in cls(cls.META_SCHEMA).iter_errors(schema):
+ raise exceptions.SchemaError.create_from(error)
+
+ def iter_errors(self, instance, _schema=None):
+ if _schema is None:
+ _schema = self.schema
+
+ if _schema is True:
+ return
+ elif _schema is False:
+ yield exceptions.ValidationError(
+ "False schema does not allow %r" % (instance,),
+ validator=None,
+ validator_value=None,
+ instance=instance,
+ schema=_schema,
+ )
+ return
+
+ scope = id_of(_schema)
+ if scope:
+ self.resolver.push_scope(scope)
+ try:
+ ref = _schema.get(u"$ref")
+ if ref is not None:
+ validators = [(u"$ref", ref)]
+ else:
+ validators = iteritems(_schema)
+
+ for k, v in validators:
+ validator = self.VALIDATORS.get(k)
+ if validator is None:
+ continue
+
+ errors = validator(self, v, instance, _schema) or ()
+ for error in errors:
+ # set details if not already set by the called fn
+ error._set(
+ validator=k,
+ validator_value=v,
+ instance=instance,
+ schema=_schema,
+ )
+ if k != u"$ref":
+ error.schema_path.appendleft(k)
+ yield error
+ finally:
+ if scope:
+ self.resolver.pop_scope()
+
+ def descend(self, instance, schema, path=None, schema_path=None):
+ for error in self.iter_errors(instance, schema):
+ if path is not None:
+ error.path.appendleft(path)
+ if schema_path is not None:
+ error.schema_path.appendleft(schema_path)
+ yield error
+
+ def validate(self, *args, **kwargs):
+ for error in self.iter_errors(*args, **kwargs):
+ raise error
+
+ def is_type(self, instance, type):
+ try:
+ return self.TYPE_CHECKER.is_type(instance, type)
+ except exceptions.UndefinedTypeCheck:
+ raise exceptions.UnknownType(type, instance, self.schema)
+
+ def is_valid(self, instance, _schema=None):
+ error = next(self.iter_errors(instance, _schema), None)
+ return error is None
+
+ if version is not None:
+ Validator = validates(version)(Validator)
+ Validator.__name__ = version.title().replace(" ", "") + "Validator"
+
+ return Validator
+
+
+def extend(validator, validators=(), version=None, type_checker=None):
+ """
+ Create a new validator class by extending an existing one.
+
+ Arguments:
+
+ validator (jsonschema.IValidator):
+
+ an existing validator class
+
+ validators (collections.Mapping):
+
+ a mapping of new validator callables to extend with, whose
+ structure is as in `create`.
+
+ .. note::
+
+ Any validator callables with the same name as an
+ existing one will (silently) replace the old validator
+ callable entirely, effectively overriding any validation
+ done in the "parent" validator class.
+
+ If you wish to instead extend the behavior of a parent's
+ validator callable, delegate and call it directly in
+ the new validator function by retrieving it using
+ ``OldValidator.VALIDATORS["validator_name"]``.
+
+ version (str):
+
+ a version for the new validator class
+
+ type_checker (jsonschema.TypeChecker):
+
+ a type checker, used when applying the :validator:`type` validator.
+
+ If unprovided, the type checker of the extended
+ `jsonschema.IValidator` will be carried along.`
+
+ Returns:
+
+ a new `jsonschema.IValidator` class extending the one provided
+
+ .. note:: Meta Schemas
+
+ The new validator class will have its parent's meta schema.
+
+ If you wish to change or extend the meta schema in the new
+ validator class, modify ``META_SCHEMA`` directly on the returned
+ class. Note that no implicit copying is done, so a copy should
+ likely be made before modifying it, in order to not affect the
+ old validator.
+ """
+
+ all_validators = dict(validator.VALIDATORS)
+ all_validators.update(validators)
+
+ if type_checker is None:
+ type_checker = validator.TYPE_CHECKER
+ elif validator._CREATED_WITH_DEFAULT_TYPES:
+ raise TypeError(
+ "Cannot extend a validator created with default_types "
+ "with a type_checker. Update the validator to use a "
+ "type_checker when created."
+ )
+ return create(
+ meta_schema=validator.META_SCHEMA,
+ validators=all_validators,
+ version=version,
+ type_checker=type_checker,
+ id_of=validator.ID_OF,
+ )
+
+
+Draft3Validator = create(
+ meta_schema=_utils.load_schema("draft3"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"dependencies": _legacy_validators.dependencies_draft3,
+ u"disallow": _legacy_validators.disallow_draft3,
+ u"divisibleBy": _validators.multipleOf,
+ u"enum": _validators.enum,
+ u"extends": _legacy_validators.extends_draft3,
+ u"format": _validators.format,
+ u"items": _legacy_validators.items_draft3_draft4,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maximum": _legacy_validators.maximum_draft3_draft4,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minimum": _legacy_validators.minimum_draft3_draft4,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _legacy_validators.properties_draft3,
+ u"type": _legacy_validators.type_draft3,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft3_type_checker,
+ version="draft3",
+ id_of=lambda schema: schema.get(u"id", ""),
+)
+
+Draft4Validator = create(
+ meta_schema=_utils.load_schema("draft4"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"format": _validators.format,
+ u"items": _legacy_validators.items_draft3_draft4,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _legacy_validators.maximum_draft3_draft4,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _legacy_validators.minimum_draft3_draft4,
+ u"multipleOf": _validators.multipleOf,
+ u"not": _validators.not_,
+ u"oneOf": _validators.oneOf,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft4_type_checker,
+ version="draft4",
+ id_of=lambda schema: schema.get(u"id", ""),
+)
+
+Draft6Validator = create(
+ meta_schema=_utils.load_schema("draft6"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"not": _validators.not_,
+ u"oneOf": _validators.oneOf,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft6_type_checker,
+ version="draft6",
+)
+
+Draft7Validator = create(
+ meta_schema=_utils.load_schema("draft7"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"if": _validators.if_,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"oneOf": _validators.oneOf,
+ u"not": _validators.not_,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft7_type_checker,
+ version="draft7",
+)
+
+_LATEST_VERSION = Draft7Validator
+
+
+class RefResolver(object):
+ """
+ Resolve JSON References.
+
+ Arguments:
+
+ base_uri (str):
+
+ The URI of the referring document
+
+ referrer:
+
+ The actual referring document
+
+ store (dict):
+
+ A mapping from URIs to documents to cache
+
+ cache_remote (bool):
+
+ Whether remote refs should be cached after first resolution
+
+ handlers (dict):
+
+ A mapping from URI schemes to functions that should be used
+ to retrieve them
+
+ urljoin_cache (:func:`functools.lru_cache`):
+
+ A cache that will be used for caching the results of joining
+ the resolution scope to subscopes.
+
+ remote_cache (:func:`functools.lru_cache`):
+
+ A cache that will be used for caching the results of
+ resolved remote URLs.
+
+ Attributes:
+
+ cache_remote (bool):
+
+ Whether remote refs should be cached after first resolution
+ """
+
+ def __init__(
+ self,
+ base_uri,
+ referrer,
+ store=(),
+ cache_remote=True,
+ handlers=(),
+ urljoin_cache=None,
+ remote_cache=None,
+ ):
+ if urljoin_cache is None:
+ urljoin_cache = lru_cache(1024)(urljoin)
+ if remote_cache is None:
+ remote_cache = lru_cache(1024)(self.resolve_from_url)
+
+ self.referrer = referrer
+ self.cache_remote = cache_remote
+ self.handlers = dict(handlers)
+
+ self._scopes_stack = [base_uri]
+ self.store = _utils.URIDict(
+ (id, validator.META_SCHEMA)
+ for id, validator in iteritems(meta_schemas)
+ )
+ self.store.update(store)
+ self.store[base_uri] = referrer
+
+ self._urljoin_cache = urljoin_cache
+ self._remote_cache = remote_cache
+
+ @classmethod
+ def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
+ """
+ Construct a resolver from a JSON schema object.
+
+ Arguments:
+
+ schema:
+
+ the referring schema
+
+ Returns:
+
+ `RefResolver`
+ """
+
+ return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs)
+
+ def push_scope(self, scope):
+ """
+ Enter a given sub-scope.
+
+ Treats further dereferences as being performed underneath the
+ given scope.
+ """
+ self._scopes_stack.append(
+ self._urljoin_cache(self.resolution_scope, scope),
+ )
+
+ def pop_scope(self):
+ """
+ Exit the most recent entered scope.
+
+ Treats further dereferences as being performed underneath the
+ original scope.
+
+ Don't call this method more times than `push_scope` has been
+ called.
+ """
+ try:
+ self._scopes_stack.pop()
+ except IndexError:
+ raise exceptions.RefResolutionError(
+ "Failed to pop the scope from an empty stack. "
+ "`pop_scope()` should only be called once for every "
+ "`push_scope()`"
+ )
+
+ @property
+ def resolution_scope(self):
+ """
+ Retrieve the current resolution scope.
+ """
+ return self._scopes_stack[-1]
+
+ @property
+ def base_uri(self):
+ """
+ Retrieve the current base URI, not including any fragment.
+ """
+ uri, _ = urldefrag(self.resolution_scope)
+ return uri
+
+ @contextlib.contextmanager
+ def in_scope(self, scope):
+ """
+ Temporarily enter the given scope for the duration of the context.
+ """
+ self.push_scope(scope)
+ try:
+ yield
+ finally:
+ self.pop_scope()
+
+ @contextlib.contextmanager
+ def resolving(self, ref):
+ """
+ Resolve the given ``ref`` and enter its resolution scope.
+
+ Exits the scope on exit of this context manager.
+
+ Arguments:
+
+ ref (str):
+
+ The reference to resolve
+ """
+
+ url, resolved = self.resolve(ref)
+ self.push_scope(url)
+ try:
+ yield resolved
+ finally:
+ self.pop_scope()
+
+ def resolve(self, ref):
+ """
+ Resolve the given reference.
+ """
+ url = self._urljoin_cache(self.resolution_scope, ref)
+ return url, self._remote_cache(url)
+
+ def resolve_from_url(self, url):
+ """
+ Resolve the given remote URL.
+ """
+ url, fragment = urldefrag(url)
+ try:
+ document = self.store[url]
+ except KeyError:
+ try:
+ document = self.resolve_remote(url)
+ except Exception as exc:
+ raise exceptions.RefResolutionError(exc)
+
+ return self.resolve_fragment(document, fragment)
+
+ def resolve_fragment(self, document, fragment):
+ """
+ Resolve a ``fragment`` within the referenced ``document``.
+
+ Arguments:
+
+ document:
+
+ The referent document
+
+ fragment (str):
+
+ a URI fragment to resolve within it
+ """
+
+ fragment = fragment.lstrip(u"/")
+ parts = unquote(fragment).split(u"/") if fragment else []
+
+ for part in parts:
+ part = part.replace(u"~1", u"/").replace(u"~0", u"~")
+
+ if isinstance(document, Sequence):
+ # Array indexes should be turned into integers
+ try:
+ part = int(part)
+ except ValueError:
+ pass
+ try:
+ document = document[part]
+ except (TypeError, LookupError):
+ raise exceptions.RefResolutionError(
+ "Unresolvable JSON pointer: %r" % fragment
+ )
+
+ return document
+
+ def resolve_remote(self, uri):
+ """
+ Resolve a remote ``uri``.
+
+ If called directly, does not check the store first, but after
+ retrieving the document at the specified URI it will be saved in
+ the store if :attr:`cache_remote` is True.
+
+ .. note::
+
+ If the requests_ library is present, ``jsonschema`` will use it to
+ request the remote ``uri``, so that the correct encoding is
+ detected and used.
+
+ If it isn't, or if the scheme of the ``uri`` is not ``http`` or
+ ``https``, UTF-8 is assumed.
+
+ Arguments:
+
+ uri (str):
+
+ The URI to resolve
+
+ Returns:
+
+ The retrieved document
+
+ .. _requests: https://pypi.org/project/requests/
+ """
+ try:
+ import requests
+ except ImportError:
+ requests = None
+
+ scheme = urlsplit(uri).scheme
+
+ if scheme in self.handlers:
+ result = self.handlers[scheme](uri)
+ elif scheme in [u"http", u"https"] and requests:
+ # Requests has support for detecting the correct encoding of
+ # json over http
+ result = requests.get(uri).json()
+ else:
+ # Otherwise, pass off to urllib and assume utf-8
+ with urlopen(uri) as url:
+ result = json.loads(url.read().decode("utf-8"))
+
+ if self.cache_remote:
+ self.store[uri] = result
+ return result
+
+
+def validate(instance, schema, cls=None, *args, **kwargs):
+ """
+ Validate an instance under the given schema.
+
+ >>> validate([2, 3, 4], {"maxItems": 2})
+ Traceback (most recent call last):
+ ...
+ ValidationError: [2, 3, 4] is too long
+
+ :func:`validate` will first verify that the provided schema is
+ itself valid, since not doing so can lead to less obvious error
+ messages and fail in less obvious or consistent ways.
+
+ If you know you have a valid schema already, especially if you
+ intend to validate multiple instances with the same schema, you
+ likely would prefer using the `IValidator.validate` method directly
+ on a specific validator (e.g. ``Draft7Validator.validate``).
+
+
+ Arguments:
+
+ instance:
+
+ The instance to validate
+
+ schema:
+
+ The schema to validate with
+
+ cls (IValidator):
+
+ The class that will be used to validate the instance.
+
+ If the ``cls`` argument is not provided, two things will happen
+ in accordance with the specification. First, if the schema has a
+ :validator:`$schema` property containing a known meta-schema [#]_
+ then the proper validator will be used. The specification recommends
+ that all schemas contain :validator:`$schema` properties for this
+ reason. If no :validator:`$schema` property is found, the default
+ validator class is the latest released draft.
+
+ Any other provided positional and keyword arguments will be passed
+ on when instantiating the ``cls``.
+
+ Raises:
+
+ `jsonschema.exceptions.ValidationError` if the instance
+ is invalid
+
+ `jsonschema.exceptions.SchemaError` if the schema itself
+ is invalid
+
+ .. rubric:: Footnotes
+ .. [#] known by a validator registered with
+ `jsonschema.validators.validates`
+ """
+ if cls is None:
+ cls = validator_for(schema)
+
+ cls.check_schema(schema)
+ validator = cls(schema, *args, **kwargs)
+ error = exceptions.best_match(validator.iter_errors(instance))
+ if error is not None:
+ raise error
+
+
+def validator_for(schema, default=_LATEST_VERSION):
+ """
+ Retrieve the validator class appropriate for validating the given schema.
+
+ Uses the :validator:`$schema` property that should be present in the
+ given schema to look up the appropriate validator class.
+
+ Arguments:
+
+ schema (collections.Mapping or bool):
+
+ the schema to look at
+
+ default:
+
+ the default to return if the appropriate validator class
+ cannot be determined.
+
+ If unprovided, the default is to return the latest supported
+ draft.
+ """
+ if schema is True or schema is False or u"$schema" not in schema:
+ return default
+ if schema[u"$schema"] not in meta_schemas:
+ warn(
+ (
+ "The metaschema specified by $schema was not found. "
+ "Using the latest draft to validate, but this will raise "
+ "an error in the future."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return meta_schemas.get(schema[u"$schema"], _LATEST_VERSION)
diff --git a/contrib/python/jsonschema/py2/tests/ya.make b/contrib/python/jsonschema/py2/tests/ya.make
new file mode 100644
index 00000000000..198984175b0
--- /dev/null
+++ b/contrib/python/jsonschema/py2/tests/ya.make
@@ -0,0 +1,32 @@
+PY2TEST()
+
+PEERDIR(
+ contrib/python/jsonschema
+ contrib/python/Twisted
+)
+
+IF (PYTHON2)
+ PEERDIR(
+ contrib/python/mock
+ )
+ENDIF()
+
+SRCDIR(contrib/python/jsonschema/py2/jsonschema/tests)
+
+PY_SRCS(
+ NAMESPACE jsonschema.tests
+ _helpers.py
+)
+
+TEST_SRCS(
+ __init__.py
+ test_cli.py
+ test_exceptions.py
+ test_format.py
+ test_types.py
+ test_validators.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/jsonschema/py2/ya.make b/contrib/python/jsonschema/py2/ya.make
new file mode 100644
index 00000000000..6860467224d
--- /dev/null
+++ b/contrib/python/jsonschema/py2/ya.make
@@ -0,0 +1,51 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+VERSION(3.2.0)
+
+LICENSE(MIT)
+
+PEERDIR(
+ contrib/deprecated/python/functools32
+ contrib/python/attrs
+ contrib/python/importlib-metadata
+ contrib/python/pyrsistent
+ contrib/python/setuptools
+ contrib/python/six
+)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ jsonschema/__init__.py
+ jsonschema/__main__.py
+ jsonschema/_format.py
+ jsonschema/_legacy_validators.py
+ jsonschema/_reflect.py
+ jsonschema/_types.py
+ jsonschema/_utils.py
+ jsonschema/_validators.py
+ jsonschema/cli.py
+ jsonschema/compat.py
+ jsonschema/exceptions.py
+ jsonschema/validators.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/jsonschema/py2/
+ .dist-info/METADATA
+ .dist-info/entry_points.txt
+ .dist-info/top_level.txt
+ jsonschema/schemas/draft3.json
+ jsonschema/schemas/draft4.json
+ jsonschema/schemas/draft6.json
+ jsonschema/schemas/draft7.json
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)
diff --git a/contrib/python/jsonschema/py3/.dist-info/METADATA b/contrib/python/jsonschema/py3/.dist-info/METADATA
new file mode 100644
index 00000000000..aef9b18d586
--- /dev/null
+++ b/contrib/python/jsonschema/py3/.dist-info/METADATA
@@ -0,0 +1,224 @@
+Metadata-Version: 2.1
+Name: jsonschema
+Version: 3.2.0
+Summary: An implementation of JSON Schema validation for Python
+Home-page: https://github.com/Julian/jsonschema
+Author: Julian Berman
+Author-email: [email protected]
+License: UNKNOWN
+Project-URL: Docs, https://python-jsonschema.readthedocs.io/en/latest/
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Dist: attrs (>=17.4.0)
+Requires-Dist: pyrsistent (>=0.14.0)
+Requires-Dist: setuptools
+Requires-Dist: six (>=1.11.0)
+Requires-Dist: functools32 ; python_version < "3"
+Requires-Dist: importlib-metadata ; python_version < "3.8"
+Provides-Extra: format
+Requires-Dist: idna ; extra == 'format'
+Requires-Dist: jsonpointer (>1.13) ; extra == 'format'
+Requires-Dist: rfc3987 ; extra == 'format'
+Requires-Dist: strict-rfc3339 ; extra == 'format'
+Requires-Dist: webcolors ; extra == 'format'
+Provides-Extra: format_nongpl
+Requires-Dist: idna ; extra == 'format_nongpl'
+Requires-Dist: jsonpointer (>1.13) ; extra == 'format_nongpl'
+Requires-Dist: webcolors ; extra == 'format_nongpl'
+Requires-Dist: rfc3986-validator (>0.1.0) ; extra == 'format_nongpl'
+Requires-Dist: rfc3339-validator ; extra == 'format_nongpl'
+
+==========
+jsonschema
+==========
+
+|PyPI| |Pythons| |Travis| |AppVeyor| |Codecov| |ReadTheDocs|
+
+.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema.svg
+ :alt: PyPI version
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema.svg
+ :alt: Supported Python versions
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Travis| image:: https://travis-ci.com/Julian/jsonschema.svg?branch=master
+ :alt: Travis build status
+ :target: https://travis-ci.com/Julian/jsonschema
+
+.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/adtt0aiaihy6muyn/branch/master?svg=true
+ :alt: AppVeyor build status
+ :target: https://ci.appveyor.com/project/Julian/jsonschema
+
+.. |Codecov| image:: https://codecov.io/gh/Julian/jsonschema/branch/master/graph/badge.svg
+ :alt: Codecov Code coverage
+ :target: https://codecov.io/gh/Julian/jsonschema
+
+.. |ReadTheDocs| image:: https://readthedocs.org/projects/python-jsonschema/badge/?version=stable&style=flat
+ :alt: ReadTheDocs status
+ :target: https://python-jsonschema.readthedocs.io/en/stable/
+
+
+``jsonschema`` is an implementation of `JSON Schema <https://json-schema.org>`_
+for Python (supporting 2.7+ including Python 3).
+
+.. code-block:: python
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
+
+ >>> validate(
+ ... instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+It can also be used from console:
+
+.. code-block:: bash
+
+ $ jsonschema -i sample.json sample.schema
+
+Features
+--------
+
+* Full support for
+ `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator>`_,
+ `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft6Validator>`_,
+ `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft4Validator>`_
+ and
+ `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft3Validator>`_
+
+* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
+ that can iteratively report *all* validation errors.
+
+* `Programmatic querying <https://python-jsonschema.readthedocs.io/en/latest/errors/>`_
+ of which properties or items failed validation.
+
+
+Installation
+------------
+
+``jsonschema`` is available on `PyPI <https://pypi.org/project/jsonschema/>`_. You can install using `pip <https://pip.pypa.io/en/stable/>`_:
+
+.. code-block:: bash
+
+ $ pip install jsonschema
+
+
+Demo
+----
+
+Try ``jsonschema`` interactively in this online demo:
+
+.. image:: https://user-images.githubusercontent.com/1155573/56745335-8b158a00-6750-11e9-8776-83fa675939c4.png
+ :target: https://notebooks.ai/demo/gh/Julian/jsonschema
+ :alt: Open Live Demo
+
+
+Online demo Notebook will look similar to this:
+
+
+.. image:: https://user-images.githubusercontent.com/1155573/56820861-5c1c1880-6823-11e9-802a-ce01c5ec574f.gif
+ :alt: Open Live Demo
+ :width: 480 px
+
+
+Release Notes
+-------------
+
+v3.1 brings support for ECMA 262 dialect regular expressions
+throughout schemas, as recommended by the specification. Big
+thanks to @Zac-HD for authoring support in a new `js-regex
+<https://pypi.org/project/js-regex/>`_ library.
+
+
+Running the Test Suite
+----------------------
+
+If you have ``tox`` installed (perhaps via ``pip install tox`` or your
+package manager), running ``tox`` in the directory of your source
+checkout will run ``jsonschema``'s test suite on all of the versions
+of Python ``jsonschema`` supports. If you don't have all of the
+versions that ``jsonschema`` is tested under, you'll likely want to run
+using ``tox``'s ``--skip-missing-interpreters`` option.
+
+Of course you're also free to just run the tests on a single version with your
+favorite test runner. The tests live in the ``jsonschema.tests`` package.
+
+
+Benchmarks
+----------
+
+``jsonschema``'s benchmarks make use of `pyperf
+<https://pyperf.readthedocs.io>`_.
+
+Running them can be done via ``tox -e perf``, or by invoking the ``pyperf``
+commands externally (after ensuring that both it and ``jsonschema`` itself are
+installed)::
+
+ $ python -m pyperf jsonschema/benchmarks/test_suite.py --hist --output results.json
+
+To compare to a previous run, use::
+
+ $ python -m pyperf compare_to --table reference.json results.json
+
+See the ``pyperf`` documentation for more details.
+
+
+Community
+---------
+
+There's a `mailing list <https://groups.google.com/forum/#!forum/jsonschema>`_
+for this implementation on Google Groups.
+
+Please join, and feel free to send questions there.
+
+
+Contributing
+------------
+
+I'm Julian Berman.
+
+``jsonschema`` is on `GitHub <https://github.com/Julian/jsonschema>`_.
+
+Get in touch, via GitHub or otherwise, if you've got something to contribute,
+it'd be most welcome!
+
+You can also generally find me on Freenode (nick: ``tos9``) in various
+channels, including ``#python``.
+
+If you feel overwhelmingly grateful, you can also woo me with beer money
+via Google Pay with the email in my GitHub profile.
+
+And for companies who appreciate ``jsonschema`` and its continued support
+and growth, ``jsonschema`` is also now supportable via `TideLift
+<https://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-j
+sonschema&utm_medium=referral&utm_campaign=readme>`_.
+
+
diff --git a/contrib/python/jsonschema/py3/.dist-info/entry_points.txt b/contrib/python/jsonschema/py3/.dist-info/entry_points.txt
new file mode 100644
index 00000000000..c627b310cd0
--- /dev/null
+++ b/contrib/python/jsonschema/py3/.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+jsonschema = jsonschema.cli:main
+
diff --git a/contrib/python/jsonschema/py3/.dist-info/top_level.txt b/contrib/python/jsonschema/py3/.dist-info/top_level.txt
new file mode 100644
index 00000000000..d89304b1a89
--- /dev/null
+++ b/contrib/python/jsonschema/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+jsonschema
diff --git a/contrib/python/jsonschema/py3/COPYING b/contrib/python/jsonschema/py3/COPYING
new file mode 100644
index 00000000000..af9cfbdb134
--- /dev/null
+++ b/contrib/python/jsonschema/py3/COPYING
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Julian Berman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/contrib/python/jsonschema/py3/README.rst b/contrib/python/jsonschema/py3/README.rst
new file mode 100644
index 00000000000..ccfb55d02d4
--- /dev/null
+++ b/contrib/python/jsonschema/py3/README.rst
@@ -0,0 +1,179 @@
+==========
+jsonschema
+==========
+
+|PyPI| |Pythons| |Travis| |AppVeyor| |Codecov| |ReadTheDocs|
+
+.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema.svg
+ :alt: PyPI version
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema.svg
+ :alt: Supported Python versions
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Travis| image:: https://travis-ci.com/Julian/jsonschema.svg?branch=master
+ :alt: Travis build status
+ :target: https://travis-ci.com/Julian/jsonschema
+
+.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/adtt0aiaihy6muyn/branch/master?svg=true
+ :alt: AppVeyor build status
+ :target: https://ci.appveyor.com/project/Julian/jsonschema
+
+.. |Codecov| image:: https://codecov.io/gh/Julian/jsonschema/branch/master/graph/badge.svg
+ :alt: Codecov Code coverage
+ :target: https://codecov.io/gh/Julian/jsonschema
+
+.. |ReadTheDocs| image:: https://readthedocs.org/projects/python-jsonschema/badge/?version=stable&style=flat
+ :alt: ReadTheDocs status
+ :target: https://python-jsonschema.readthedocs.io/en/stable/
+
+
+``jsonschema`` is an implementation of `JSON Schema <https://json-schema.org>`_
+for Python (supporting 2.7+ including Python 3).
+
+.. code-block:: python
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
+
+ >>> validate(
+ ... instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+It can also be used from console:
+
+.. code-block:: bash
+
+ $ jsonschema -i sample.json sample.schema
+
+Features
+--------
+
+* Full support for
+ `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator>`_,
+ `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft6Validator>`_,
+ `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft4Validator>`_
+ and
+ `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft3Validator>`_
+
+* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
+ that can iteratively report *all* validation errors.
+
+* `Programmatic querying <https://python-jsonschema.readthedocs.io/en/latest/errors/>`_
+ of which properties or items failed validation.
+
+
+Installation
+------------
+
+``jsonschema`` is available on `PyPI <https://pypi.org/project/jsonschema/>`_. You can install using `pip <https://pip.pypa.io/en/stable/>`_:
+
+.. code-block:: bash
+
+ $ pip install jsonschema
+
+
+Demo
+----
+
+Try ``jsonschema`` interactively in this online demo:
+
+.. image:: https://user-images.githubusercontent.com/1155573/56745335-8b158a00-6750-11e9-8776-83fa675939c4.png
+ :target: https://notebooks.ai/demo/gh/Julian/jsonschema
+ :alt: Open Live Demo
+
+
+Online demo Notebook will look similar to this:
+
+
+.. image:: https://user-images.githubusercontent.com/1155573/56820861-5c1c1880-6823-11e9-802a-ce01c5ec574f.gif
+ :alt: Open Live Demo
+ :width: 480 px
+
+
+Release Notes
+-------------
+
+v3.1 brings support for ECMA 262 dialect regular expressions
+throughout schemas, as recommended by the specification. Big
+thanks to @Zac-HD for authoring support in a new `js-regex
+<https://pypi.org/project/js-regex/>`_ library.
+
+
+Running the Test Suite
+----------------------
+
+If you have ``tox`` installed (perhaps via ``pip install tox`` or your
+package manager), running ``tox`` in the directory of your source
+checkout will run ``jsonschema``'s test suite on all of the versions
+of Python ``jsonschema`` supports. If you don't have all of the
+versions that ``jsonschema`` is tested under, you'll likely want to run
+using ``tox``'s ``--skip-missing-interpreters`` option.
+
+Of course you're also free to just run the tests on a single version with your
+favorite test runner. The tests live in the ``jsonschema.tests`` package.
+
+
+Benchmarks
+----------
+
+``jsonschema``'s benchmarks make use of `pyperf
+<https://pyperf.readthedocs.io>`_.
+
+Running them can be done via ``tox -e perf``, or by invoking the ``pyperf``
+commands externally (after ensuring that both it and ``jsonschema`` itself are
+installed)::
+
+ $ python -m pyperf jsonschema/benchmarks/test_suite.py --hist --output results.json
+
+To compare to a previous run, use::
+
+ $ python -m pyperf compare_to --table reference.json results.json
+
+See the ``pyperf`` documentation for more details.
+
+
+Community
+---------
+
+There's a `mailing list <https://groups.google.com/forum/#!forum/jsonschema>`_
+for this implementation on Google Groups.
+
+Please join, and feel free to send questions there.
+
+
+Contributing
+------------
+
+I'm Julian Berman.
+
+``jsonschema`` is on `GitHub <https://github.com/Julian/jsonschema>`_.
+
+Get in touch, via GitHub or otherwise, if you've got something to contribute,
+it'd be most welcome!
+
+You can also generally find me on Freenode (nick: ``tos9``) in various
+channels, including ``#python``.
+
+If you feel overwhelmingly grateful, you can also woo me with beer money
+via Google Pay with the email in my GitHub profile.
+
+And for companies who appreciate ``jsonschema`` and its continued support
+and growth, ``jsonschema`` is also now supportable via `TideLift
+<https://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-j
+sonschema&utm_medium=referral&utm_campaign=readme>`_.
diff --git a/contrib/python/jsonschema/py3/jsonschema/__init__.py b/contrib/python/jsonschema/py3/jsonschema/__init__.py
new file mode 100644
index 00000000000..6b630cdfbbe
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/__init__.py
@@ -0,0 +1,34 @@
+"""
+An implementation of JSON Schema for Python
+
+The main functionality is provided by the validator classes for each of the
+supported JSON Schema versions.
+
+Most commonly, `validate` is the quickest way to simply validate a given
+instance under a schema, and will create a validator for you.
+"""
+
+from jsonschema.exceptions import (
+ ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError
+)
+from jsonschema._format import (
+ FormatChecker,
+ draft3_format_checker,
+ draft4_format_checker,
+ draft6_format_checker,
+ draft7_format_checker,
+)
+from jsonschema._types import TypeChecker
+from jsonschema.validators import (
+ Draft3Validator,
+ Draft4Validator,
+ Draft6Validator,
+ Draft7Validator,
+ RefResolver,
+ validate,
+)
+try:
+ from importlib import metadata
+except ImportError: # for Python<3.8
+ import importlib_metadata as metadata
+__version__ = metadata.version("jsonschema")
diff --git a/contrib/python/jsonschema/py3/jsonschema/__main__.py b/contrib/python/jsonschema/py3/jsonschema/__main__.py
new file mode 100644
index 00000000000..82c29fd39e7
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/__main__.py
@@ -0,0 +1,2 @@
+from jsonschema.cli import main
+main()
diff --git a/contrib/python/jsonschema/py3/jsonschema/_format.py b/contrib/python/jsonschema/py3/jsonschema/_format.py
new file mode 100644
index 00000000000..281a7cfcffe
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/_format.py
@@ -0,0 +1,425 @@
+import datetime
+import re
+import socket
+import struct
+
+from jsonschema.compat import str_types
+from jsonschema.exceptions import FormatError
+
+
+class FormatChecker(object):
+ """
+ A ``format`` property checker.
+
+ JSON Schema does not mandate that the ``format`` property actually do any
+ validation. If validation is desired however, instances of this class can
+ be hooked into validators to enable format validation.
+
+ `FormatChecker` objects always return ``True`` when asked about
+ formats that they do not know how to validate.
+
+ To check a custom format using a function that takes an instance and
+ returns a ``bool``, use the `FormatChecker.checks` or
+ `FormatChecker.cls_checks` decorators.
+
+ Arguments:
+
+ formats (~collections.Iterable):
+
+ The known formats to validate. This argument can be used to
+ limit which formats will be used during validation.
+ """
+
+ checkers = {}
+
+ def __init__(self, formats=None):
+ if formats is None:
+ self.checkers = self.checkers.copy()
+ else:
+ self.checkers = dict((k, self.checkers[k]) for k in formats)
+
+ def __repr__(self):
+ return "<FormatChecker checkers={}>".format(sorted(self.checkers))
+
+ def checks(self, format, raises=()):
+ """
+ Register a decorated function as validating a new format.
+
+ Arguments:
+
+ format (str):
+
+ The format that the decorated function will check.
+
+ raises (Exception):
+
+ The exception(s) raised by the decorated function when an
+ invalid instance is found.
+
+ The exception object will be accessible as the
+ `jsonschema.exceptions.ValidationError.cause` attribute of the
+ resulting validation error.
+ """
+
+ def _checks(func):
+ self.checkers[format] = (func, raises)
+ return func
+ return _checks
+
+ cls_checks = classmethod(checks)
+
+ def check(self, instance, format):
+ """
+ Check whether the instance conforms to the given format.
+
+ Arguments:
+
+ instance (*any primitive type*, i.e. str, number, bool):
+
+ The instance to check
+
+ format (str):
+
+ The format that instance should conform to
+
+
+ Raises:
+
+ FormatError: if the instance does not conform to ``format``
+ """
+
+ if format not in self.checkers:
+ return
+
+ func, raises = self.checkers[format]
+ result, cause = None, None
+ try:
+ result = func(instance)
+ except raises as e:
+ cause = e
+ if not result:
+ raise FormatError(
+ "%r is not a %r" % (instance, format), cause=cause,
+ )
+
+ def conforms(self, instance, format):
+ """
+ Check whether the instance conforms to the given format.
+
+ Arguments:
+
+ instance (*any primitive type*, i.e. str, number, bool):
+
+ The instance to check
+
+ format (str):
+
+ The format that instance should conform to
+
+ Returns:
+
+ bool: whether it conformed
+ """
+
+ try:
+ self.check(instance, format)
+ except FormatError:
+ return False
+ else:
+ return True
+
+
+draft3_format_checker = FormatChecker()
+draft4_format_checker = FormatChecker()
+draft6_format_checker = FormatChecker()
+draft7_format_checker = FormatChecker()
+
+
+_draft_checkers = dict(
+ draft3=draft3_format_checker,
+ draft4=draft4_format_checker,
+ draft6=draft6_format_checker,
+ draft7=draft7_format_checker,
+)
+
+
+def _checks_drafts(
+ name=None,
+ draft3=None,
+ draft4=None,
+ draft6=None,
+ draft7=None,
+ raises=(),
+):
+ draft3 = draft3 or name
+ draft4 = draft4 or name
+ draft6 = draft6 or name
+ draft7 = draft7 or name
+
+ def wrap(func):
+ if draft3:
+ func = _draft_checkers["draft3"].checks(draft3, raises)(func)
+ if draft4:
+ func = _draft_checkers["draft4"].checks(draft4, raises)(func)
+ if draft6:
+ func = _draft_checkers["draft6"].checks(draft6, raises)(func)
+ if draft7:
+ func = _draft_checkers["draft7"].checks(draft7, raises)(func)
+
+ # Oy. This is bad global state, but relied upon for now, until
+ # deprecation. See https://github.com/Julian/jsonschema/issues/519
+ # and test_format_checkers_come_with_defaults
+ FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)(
+ func,
+ )
+ return func
+ return wrap
+
+
+@_checks_drafts(name="idn-email")
+@_checks_drafts(name="email")
+def is_email(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return "@" in instance
+
+
+_ipv4_re = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
+
+
+@_checks_drafts(
+ draft3="ip-address", draft4="ipv4", draft6="ipv4", draft7="ipv4",
+)
+def is_ipv4(instance):
+ if not isinstance(instance, str_types):
+ return True
+ if not _ipv4_re.match(instance):
+ return False
+ return all(0 <= int(component) <= 255 for component in instance.split("."))
+
+
+if hasattr(socket, "inet_pton"):
+ # FIXME: Really this only should raise struct.error, but see the sadness
+ # that is https://twistedmatrix.com/trac/ticket/9409
+ @_checks_drafts(
+ name="ipv6", raises=(socket.error, struct.error, ValueError),
+ )
+ def is_ipv6(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return socket.inet_pton(socket.AF_INET6, instance)
+
+
+_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$")
+
+
+@_checks_drafts(
+ draft3="host-name",
+ draft4="hostname",
+ draft6="hostname",
+ draft7="hostname",
+)
+def is_host_name(instance):
+ if not isinstance(instance, str_types):
+ return True
+ if not _host_name_re.match(instance):
+ return False
+ components = instance.split(".")
+ for component in components:
+ if len(component) > 63:
+ return False
+ return True
+
+
+try:
+ # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
+ import idna
+except ImportError:
+ pass
+else:
+ @_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError)
+ def is_idn_host_name(instance):
+ if not isinstance(instance, str_types):
+ return True
+ idna.encode(instance)
+ return True
+
+
+try:
+ import rfc3987
+except ImportError:
+ try:
+ from rfc3986_validator import validate_rfc3986
+ except ImportError:
+ pass
+ else:
+ @_checks_drafts(name="uri")
+ def is_uri(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return validate_rfc3986(instance, rule="URI")
+
+ @_checks_drafts(
+ draft6="uri-reference",
+ draft7="uri-reference",
+ raises=ValueError,
+ )
+ def is_uri_reference(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return validate_rfc3986(instance, rule="URI_reference")
+
+else:
+ @_checks_drafts(draft7="iri", raises=ValueError)
+ def is_iri(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="IRI")
+
+ @_checks_drafts(draft7="iri-reference", raises=ValueError)
+ def is_iri_reference(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="IRI_reference")
+
+ @_checks_drafts(name="uri", raises=ValueError)
+ def is_uri(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="URI")
+
+ @_checks_drafts(
+ draft6="uri-reference",
+ draft7="uri-reference",
+ raises=ValueError,
+ )
+ def is_uri_reference(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return rfc3987.parse(instance, rule="URI_reference")
+
+
+try:
+ from strict_rfc3339 import validate_rfc3339
+except ImportError:
+ try:
+ from rfc3339_validator import validate_rfc3339
+ except ImportError:
+ validate_rfc3339 = None
+
+if validate_rfc3339:
+ @_checks_drafts(name="date-time")
+ def is_datetime(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return validate_rfc3339(instance)
+
+ @_checks_drafts(draft7="time")
+ def is_time(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return is_datetime("1970-01-01T" + instance)
+
+
+@_checks_drafts(name="regex", raises=re.error)
+def is_regex(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return re.compile(instance)
+
+
+@_checks_drafts(draft3="date", draft7="date", raises=ValueError)
+def is_date(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return datetime.datetime.strptime(instance, "%Y-%m-%d")
+
+
+@_checks_drafts(draft3="time", raises=ValueError)
+def is_draft3_time(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return datetime.datetime.strptime(instance, "%H:%M:%S")
+
+
+try:
+ import webcolors
+except ImportError:
+ pass
+else:
+ def is_css_color_code(instance):
+ return webcolors.normalize_hex(instance)
+
+ @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
+ def is_css21_color(instance):
+ if (
+ not isinstance(instance, str_types) or
+ instance.lower() in webcolors.css21_names_to_hex
+ ):
+ return True
+ return is_css_color_code(instance)
+
+ def is_css3_color(instance):
+ if instance.lower() in webcolors.css3_names_to_hex:
+ return True
+ return is_css_color_code(instance)
+
+
+try:
+ import jsonpointer
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft6="json-pointer",
+ draft7="json-pointer",
+ raises=jsonpointer.JsonPointerException,
+ )
+ def is_json_pointer(instance):
+ if not isinstance(instance, str_types):
+ return True
+ return jsonpointer.JsonPointer(instance)
+
+ # TODO: I don't want to maintain this, so it
+ # needs to go either into jsonpointer (pending
+ # https://github.com/stefankoegl/python-json-pointer/issues/34) or
+ # into a new external library.
+ @_checks_drafts(
+ draft7="relative-json-pointer",
+ raises=jsonpointer.JsonPointerException,
+ )
+ def is_relative_json_pointer(instance):
+ # Definition taken from:
+ # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
+ if not isinstance(instance, str_types):
+ return True
+ non_negative_integer, rest = [], ""
+ for i, character in enumerate(instance):
+ if character.isdigit():
+ non_negative_integer.append(character)
+ continue
+
+ if not non_negative_integer:
+ return False
+
+ rest = instance[i:]
+ break
+ return (rest == "#") or jsonpointer.JsonPointer(rest)
+
+
+try:
+ import uritemplate.exceptions
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft6="uri-template",
+ draft7="uri-template",
+ raises=uritemplate.exceptions.InvalidTemplate,
+ )
+ def is_uri_template(
+ instance,
+ template_validator=uritemplate.Validator().force_balanced_braces(),
+ ):
+ template = uritemplate.URITemplate(instance)
+ return template_validator.validate(template)
diff --git a/contrib/python/jsonschema/py3/jsonschema/_legacy_validators.py b/contrib/python/jsonschema/py3/jsonschema/_legacy_validators.py
new file mode 100644
index 00000000000..264ff7d7135
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/_legacy_validators.py
@@ -0,0 +1,141 @@
+from jsonschema import _utils
+from jsonschema.compat import iteritems
+from jsonschema.exceptions import ValidationError
+
+
+def dependencies_draft3(validator, dependencies, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, dependency in iteritems(dependencies):
+ if property not in instance:
+ continue
+
+ if validator.is_type(dependency, "object"):
+ for error in validator.descend(
+ instance, dependency, schema_path=property,
+ ):
+ yield error
+ elif validator.is_type(dependency, "string"):
+ if dependency not in instance:
+ yield ValidationError(
+ "%r is a dependency of %r" % (dependency, property)
+ )
+ else:
+ for each in dependency:
+ if each not in instance:
+ message = "%r is a dependency of %r"
+ yield ValidationError(message % (each, property))
+
+
+def disallow_draft3(validator, disallow, instance, schema):
+ for disallowed in _utils.ensure_list(disallow):
+ if validator.is_valid(instance, {"type": [disallowed]}):
+ yield ValidationError(
+ "%r is disallowed for %r" % (disallowed, instance)
+ )
+
+
+def extends_draft3(validator, extends, instance, schema):
+ if validator.is_type(extends, "object"):
+ for error in validator.descend(instance, extends):
+ yield error
+ return
+ for index, subschema in enumerate(extends):
+ for error in validator.descend(instance, subschema, schema_path=index):
+ yield error
+
+
+def items_draft3_draft4(validator, items, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if validator.is_type(items, "object"):
+ for index, item in enumerate(instance):
+ for error in validator.descend(item, items, path=index):
+ yield error
+ else:
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in validator.descend(
+ item, subschema, path=index, schema_path=index,
+ ):
+ yield error
+
+
+def minimum_draft3_draft4(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if schema.get("exclusiveMinimum", False):
+ failed = instance <= minimum
+ cmp = "less than or equal to"
+ else:
+ failed = instance < minimum
+ cmp = "less than"
+
+ if failed:
+ yield ValidationError(
+ "%r is %s the minimum of %r" % (instance, cmp, minimum)
+ )
+
+
+def maximum_draft3_draft4(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if schema.get("exclusiveMaximum", False):
+ failed = instance >= maximum
+ cmp = "greater than or equal to"
+ else:
+ failed = instance > maximum
+ cmp = "greater than"
+
+ if failed:
+ yield ValidationError(
+ "%r is %s the maximum of %r" % (instance, cmp, maximum)
+ )
+
+
+def properties_draft3(validator, properties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, subschema in iteritems(properties):
+ if property in instance:
+ for error in validator.descend(
+ instance[property],
+ subschema,
+ path=property,
+ schema_path=property,
+ ):
+ yield error
+ elif subschema.get("required", False):
+ error = ValidationError("%r is a required property" % property)
+ error._set(
+ validator="required",
+ validator_value=subschema["required"],
+ instance=instance,
+ schema=schema,
+ )
+ error.path.appendleft(property)
+ error.schema_path.extend([property, "required"])
+ yield error
+
+
+def type_draft3(validator, types, instance, schema):
+ types = _utils.ensure_list(types)
+
+ all_errors = []
+ for index, type in enumerate(types):
+ if validator.is_type(type, "object"):
+ errors = list(validator.descend(instance, type, schema_path=index))
+ if not errors:
+ return
+ all_errors.extend(errors)
+ else:
+ if validator.is_type(instance, type):
+ return
+ else:
+ yield ValidationError(
+ _utils.types_msg(instance, types), context=all_errors,
+ )
diff --git a/contrib/python/jsonschema/py3/jsonschema/_reflect.py b/contrib/python/jsonschema/py3/jsonschema/_reflect.py
new file mode 100644
index 00000000000..d09e38fbdcf
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/_reflect.py
@@ -0,0 +1,155 @@
+# -*- test-case-name: twisted.test.test_reflect -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Standardized versions of various cool and/or strange things that you can do
+with Python's reflection capabilities.
+"""
+
+import sys
+
+from jsonschema.compat import PY3
+
+
+class _NoModuleFound(Exception):
+ """
+ No module was found because none exists.
+ """
+
+
+
+class InvalidName(ValueError):
+ """
+ The given name is not a dot-separated list of Python objects.
+ """
+
+
+
+class ModuleNotFound(InvalidName):
+ """
+ The module associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+
+class ObjectNotFound(InvalidName):
+ """
+ The object associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+
+if PY3:
+ def reraise(exception, traceback):
+ raise exception.with_traceback(traceback)
+else:
+ exec("""def reraise(exception, traceback):
+ raise exception.__class__, exception, traceback""")
+
+reraise.__doc__ = """
+Re-raise an exception, with an optional traceback, in a way that is compatible
+with both Python 2 and Python 3.
+
+Note that on Python 3, re-raised exceptions will be mutated, with their
+C{__traceback__} attribute being set.
+
+@param exception: The exception instance.
+@param traceback: The traceback to use, or C{None} indicating a new traceback.
+"""
+
+
+def _importAndCheckStack(importName):
+ """
+ Import the given name as a module, then walk the stack to determine whether
+ the failure was the module not existing, or some code in the module (for
+ example a dependent import) failing. This can be helpful to determine
+ whether any actual application code was run. For example, to distiguish
+ administrative error (entering the wrong module name), from programmer
+ error (writing buggy code in a module that fails to import).
+
+ @param importName: The name of the module to import.
+ @type importName: C{str}
+ @raise Exception: if something bad happens. This can be any type of
+ exception, since nobody knows what loading some arbitrary code might
+ do.
+ @raise _NoModuleFound: if no module was found.
+ """
+ try:
+ return __import__(importName)
+ except ImportError:
+ excType, excValue, excTraceback = sys.exc_info()
+ while excTraceback:
+ execName = excTraceback.tb_frame.f_globals["__name__"]
+ # in Python 2 execName is None when an ImportError is encountered,
+ # where in Python 3 execName is equal to the importName.
+ if execName is None or execName == importName:
+ reraise(excValue, excTraceback)
+ excTraceback = excTraceback.tb_next
+ raise _NoModuleFound()
+
+
+
+def namedAny(name):
+ """
+ Retrieve a Python object by its fully qualified name from the global Python
+ module namespace. The first part of the name, that describes a module,
+ will be discovered and imported. Each subsequent part of the name is
+ treated as the name of an attribute of the object specified by all of the
+ name which came before it. For example, the fully-qualified name of this
+ object is 'twisted.python.reflect.namedAny'.
+
+ @type name: L{str}
+ @param name: The name of the object to return.
+
+ @raise InvalidName: If the name is an empty string, starts or ends with
+ a '.', or is otherwise syntactically incorrect.
+
+ @raise ModuleNotFound: If the name is syntactically correct but the
+ module it specifies cannot be imported because it does not appear to
+ exist.
+
+ @raise ObjectNotFound: If the name is syntactically correct, includes at
+ least one '.', but the module it specifies cannot be imported because
+ it does not appear to exist.
+
+ @raise AttributeError: If an attribute of an object along the way cannot be
+ accessed, or a module along the way is not found.
+
+ @return: the Python object identified by 'name'.
+ """
+ if not name:
+ raise InvalidName('Empty module name')
+
+ names = name.split('.')
+
+ # if the name starts or ends with a '.' or contains '..', the __import__
+ # will raise an 'Empty module name' error. This will provide a better error
+ # message.
+ if '' in names:
+ raise InvalidName(
+ "name must be a string giving a '.'-separated list of Python "
+ "identifiers, not %r" % (name,))
+
+ topLevelPackage = None
+ moduleNames = names[:]
+ while not topLevelPackage:
+ if moduleNames:
+ trialname = '.'.join(moduleNames)
+ try:
+ topLevelPackage = _importAndCheckStack(trialname)
+ except _NoModuleFound:
+ moduleNames.pop()
+ else:
+ if len(names) == 1:
+ raise ModuleNotFound("No module named %r" % (name,))
+ else:
+ raise ObjectNotFound('%r does not name an object' % (name,))
+
+ obj = topLevelPackage
+ for n in names[1:]:
+ obj = getattr(obj, n)
+
+ return obj
diff --git a/contrib/python/jsonschema/py3/jsonschema/_types.py b/contrib/python/jsonschema/py3/jsonschema/_types.py
new file mode 100644
index 00000000000..a71a4e34bdc
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/_types.py
@@ -0,0 +1,188 @@
+import numbers
+
+from pyrsistent import pmap
+import attr
+
+from jsonschema.compat import int_types, str_types
+from jsonschema.exceptions import UndefinedTypeCheck
+
+
+def is_array(checker, instance):
+ return isinstance(instance, list)
+
+
+def is_bool(checker, instance):
+ return isinstance(instance, bool)
+
+
+def is_integer(checker, instance):
+ # bool inherits from int, so ensure bools aren't reported as ints
+ if isinstance(instance, bool):
+ return False
+ return isinstance(instance, int_types)
+
+
+def is_null(checker, instance):
+ return instance is None
+
+
+def is_number(checker, instance):
+ # bool inherits from int, so ensure bools aren't reported as ints
+ if isinstance(instance, bool):
+ return False
+ return isinstance(instance, numbers.Number)
+
+
+def is_object(checker, instance):
+ return isinstance(instance, dict)
+
+
+def is_string(checker, instance):
+ return isinstance(instance, str_types)
+
+
+def is_any(checker, instance):
+ return True
+
+
[email protected](frozen=True)
+class TypeChecker(object):
+ """
+ A ``type`` property checker.
+
+ A `TypeChecker` performs type checking for an `IValidator`. Type
+ checks to perform are updated using `TypeChecker.redefine` or
+ `TypeChecker.redefine_many` and removed via `TypeChecker.remove`.
+ Each of these return a new `TypeChecker` object.
+
+ Arguments:
+
+ type_checkers (dict):
+
+ The initial mapping of types to their checking functions.
+ """
+ _type_checkers = attr.ib(default=pmap(), converter=pmap)
+
+ def is_type(self, instance, type):
+ """
+ Check if the instance is of the appropriate type.
+
+ Arguments:
+
+ instance (object):
+
+ The instance to check
+
+ type (str):
+
+ The name of the type that is expected.
+
+ Returns:
+
+ bool: Whether it conformed.
+
+
+ Raises:
+
+ `jsonschema.exceptions.UndefinedTypeCheck`:
+ if type is unknown to this object.
+ """
+ try:
+ fn = self._type_checkers[type]
+ except KeyError:
+ raise UndefinedTypeCheck(type)
+
+ return fn(self, instance)
+
+ def redefine(self, type, fn):
+ """
+ Produce a new checker with the given type redefined.
+
+ Arguments:
+
+ type (str):
+
+ The name of the type to check.
+
+ fn (collections.Callable):
+
+ A function taking exactly two parameters - the type
+ checker calling the function and the instance to check.
+ The function should return true if instance is of this
+ type and false otherwise.
+
+ Returns:
+
+ A new `TypeChecker` instance.
+ """
+ return self.redefine_many({type: fn})
+
+ def redefine_many(self, definitions=()):
+ """
+ Produce a new checker with the given types redefined.
+
+ Arguments:
+
+ definitions (dict):
+
+ A dictionary mapping types to their checking functions.
+
+ Returns:
+
+ A new `TypeChecker` instance.
+ """
+ return attr.evolve(
+ self, type_checkers=self._type_checkers.update(definitions),
+ )
+
+ def remove(self, *types):
+ """
+ Produce a new checker with the given types forgotten.
+
+ Arguments:
+
+ types (~collections.Iterable):
+
+ the names of the types to remove.
+
+ Returns:
+
+ A new `TypeChecker` instance
+
+ Raises:
+
+ `jsonschema.exceptions.UndefinedTypeCheck`:
+
+ if any given type is unknown to this object
+ """
+
+ checkers = self._type_checkers
+ for each in types:
+ try:
+ checkers = checkers.remove(each)
+ except KeyError:
+ raise UndefinedTypeCheck(each)
+ return attr.evolve(self, type_checkers=checkers)
+
+
+draft3_type_checker = TypeChecker(
+ {
+ u"any": is_any,
+ u"array": is_array,
+ u"boolean": is_bool,
+ u"integer": is_integer,
+ u"object": is_object,
+ u"null": is_null,
+ u"number": is_number,
+ u"string": is_string,
+ },
+)
+draft4_type_checker = draft3_type_checker.remove(u"any")
+draft6_type_checker = draft4_type_checker.redefine(
+ u"integer",
+ lambda checker, instance: (
+ is_integer(checker, instance) or
+ isinstance(instance, float) and instance.is_integer()
+ ),
+)
+draft7_type_checker = draft6_type_checker
diff --git a/contrib/python/jsonschema/py3/jsonschema/_utils.py b/contrib/python/jsonschema/py3/jsonschema/_utils.py
new file mode 100644
index 00000000000..ceb880198d1
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/_utils.py
@@ -0,0 +1,212 @@
+import itertools
+import json
+import pkgutil
+import re
+
+from jsonschema.compat import MutableMapping, str_types, urlsplit
+
+
+class URIDict(MutableMapping):
+ """
+ Dictionary which uses normalized URIs as keys.
+ """
+
+ def normalize(self, uri):
+ return urlsplit(uri).geturl()
+
+ def __init__(self, *args, **kwargs):
+ self.store = dict()
+ self.store.update(*args, **kwargs)
+
+ def __getitem__(self, uri):
+ return self.store[self.normalize(uri)]
+
+ def __setitem__(self, uri, value):
+ self.store[self.normalize(uri)] = value
+
+ def __delitem__(self, uri):
+ del self.store[self.normalize(uri)]
+
+ def __iter__(self):
+ return iter(self.store)
+
+ def __len__(self):
+ return len(self.store)
+
+ def __repr__(self):
+ return repr(self.store)
+
+
+class Unset(object):
+ """
+ An as-of-yet unset attribute or unprovided default parameter.
+ """
+
+ def __repr__(self):
+ return "<unset>"
+
+
+def load_schema(name):
+ """
+ Load a schema from ./schemas/``name``.json and return it.
+ """
+
+ data = pkgutil.get_data("jsonschema", "schemas/{0}.json".format(name))
+ return json.loads(data.decode("utf-8"))
+
+
+def indent(string, times=1):
+ """
+ A dumb version of `textwrap.indent` from Python 3.3.
+ """
+
+ return "\n".join(" " * (4 * times) + line for line in string.splitlines())
+
+
+def format_as_index(indices):
+ """
+ Construct a single string containing indexing operations for the indices.
+
+ For example, [1, 2, "foo"] -> [1][2]["foo"]
+
+ Arguments:
+
+ indices (sequence):
+
+ The indices to format.
+ """
+
+ if not indices:
+ return ""
+ return "[%s]" % "][".join(repr(index) for index in indices)
+
+
+def find_additional_properties(instance, schema):
+ """
+ Return the set of additional properties for the given ``instance``.
+
+ Weeds out properties that should have been validated by ``properties`` and
+ / or ``patternProperties``.
+
+ Assumes ``instance`` is dict-like already.
+ """
+
+ properties = schema.get("properties", {})
+ patterns = "|".join(schema.get("patternProperties", {}))
+ for property in instance:
+ if property not in properties:
+ if patterns and re.search(patterns, property):
+ continue
+ yield property
+
+
+def extras_msg(extras):
+ """
+ Create an error message for extra items or properties.
+ """
+
+ if len(extras) == 1:
+ verb = "was"
+ else:
+ verb = "were"
+ return ", ".join(repr(extra) for extra in extras), verb
+
+
+def types_msg(instance, types):
+ """
+ Create an error message for a failure to match the given types.
+
+ If the ``instance`` is an object and contains a ``name`` property, it will
+ be considered to be a description of that object and used as its type.
+
+ Otherwise the message is simply the reprs of the given ``types``.
+ """
+
+ reprs = []
+ for type in types:
+ try:
+ reprs.append(repr(type["name"]))
+ except Exception:
+ reprs.append(repr(type))
+ return "%r is not of type %s" % (instance, ", ".join(reprs))
+
+
+def flatten(suitable_for_isinstance):
+ """
+ isinstance() can accept a bunch of really annoying different types:
+ * a single type
+ * a tuple of types
+ * an arbitrary nested tree of tuples
+
+ Return a flattened tuple of the given argument.
+ """
+
+ types = set()
+
+ if not isinstance(suitable_for_isinstance, tuple):
+ suitable_for_isinstance = (suitable_for_isinstance,)
+ for thing in suitable_for_isinstance:
+ if isinstance(thing, tuple):
+ types.update(flatten(thing))
+ else:
+ types.add(thing)
+ return tuple(types)
+
+
+def ensure_list(thing):
+ """
+ Wrap ``thing`` in a list if it's a single str.
+
+ Otherwise, return it unchanged.
+ """
+
+ if isinstance(thing, str_types):
+ return [thing]
+ return thing
+
+
+def equal(one, two):
+ """
+ Check if two things are equal, but evade booleans and ints being equal.
+ """
+ return unbool(one) == unbool(two)
+
+
+def unbool(element, true=object(), false=object()):
+ """
+ A hack to make True and 1 and False and 0 unique for ``uniq``.
+ """
+
+ if element is True:
+ return true
+ elif element is False:
+ return false
+ return element
+
+
+def uniq(container):
+ """
+ Check if all of a container's elements are unique.
+
+ Successively tries first to rely that the elements are hashable, then
+ falls back on them being sortable, and finally falls back on brute
+ force.
+ """
+
+ try:
+ return len(set(unbool(i) for i in container)) == len(container)
+ except TypeError:
+ try:
+ sort = sorted(unbool(i) for i in container)
+ sliced = itertools.islice(sort, 1, None)
+ for i, j in zip(sort, sliced):
+ if i == j:
+ return False
+ except (NotImplementedError, TypeError):
+ seen = []
+ for e in container:
+ e = unbool(e)
+ if e in seen:
+ return False
+ seen.append(e)
+ return True
diff --git a/contrib/python/jsonschema/py3/jsonschema/_validators.py b/contrib/python/jsonschema/py3/jsonschema/_validators.py
new file mode 100644
index 00000000000..179fec09a94
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/_validators.py
@@ -0,0 +1,373 @@
+import re
+
+from jsonschema._utils import (
+ ensure_list,
+ equal,
+ extras_msg,
+ find_additional_properties,
+ types_msg,
+ unbool,
+ uniq,
+)
+from jsonschema.exceptions import FormatError, ValidationError
+from jsonschema.compat import iteritems
+
+
+def patternProperties(validator, patternProperties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for pattern, subschema in iteritems(patternProperties):
+ for k, v in iteritems(instance):
+ if re.search(pattern, k):
+ for error in validator.descend(
+ v, subschema, path=k, schema_path=pattern,
+ ):
+ yield error
+
+
+def propertyNames(validator, propertyNames, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property in instance:
+ for error in validator.descend(
+ instance=property,
+ schema=propertyNames,
+ ):
+ yield error
+
+
+def additionalProperties(validator, aP, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ extras = set(find_additional_properties(instance, schema))
+
+ if validator.is_type(aP, "object"):
+ for extra in extras:
+ for error in validator.descend(instance[extra], aP, path=extra):
+ yield error
+ elif not aP and extras:
+ if "patternProperties" in schema:
+ patterns = sorted(schema["patternProperties"])
+ if len(extras) == 1:
+ verb = "does"
+ else:
+ verb = "do"
+ error = "%s %s not match any of the regexes: %s" % (
+ ", ".join(map(repr, sorted(extras))),
+ verb,
+ ", ".join(map(repr, patterns)),
+ )
+ yield ValidationError(error)
+ else:
+ error = "Additional properties are not allowed (%s %s unexpected)"
+ yield ValidationError(error % extras_msg(extras))
+
+
+def items(validator, items, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if validator.is_type(items, "array"):
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in validator.descend(
+ item, subschema, path=index, schema_path=index,
+ ):
+ yield error
+ else:
+ for index, item in enumerate(instance):
+ for error in validator.descend(item, items, path=index):
+ yield error
+
+
+def additionalItems(validator, aI, instance, schema):
+ if (
+ not validator.is_type(instance, "array") or
+ validator.is_type(schema.get("items", {}), "object")
+ ):
+ return
+
+ len_items = len(schema.get("items", []))
+ if validator.is_type(aI, "object"):
+ for index, item in enumerate(instance[len_items:], start=len_items):
+ for error in validator.descend(item, aI, path=index):
+ yield error
+ elif not aI and len(instance) > len(schema.get("items", [])):
+ error = "Additional items are not allowed (%s %s unexpected)"
+ yield ValidationError(
+ error %
+ extras_msg(instance[len(schema.get("items", [])):])
+ )
+
+
+def const(validator, const, instance, schema):
+ if not equal(instance, const):
+ yield ValidationError("%r was expected" % (const,))
+
+
+def contains(validator, contains, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if not any(validator.is_valid(element, contains) for element in instance):
+ yield ValidationError(
+ "None of %r are valid under the given schema" % (instance,)
+ )
+
+
+def exclusiveMinimum(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance <= minimum:
+ yield ValidationError(
+ "%r is less than or equal to the minimum of %r" % (
+ instance, minimum,
+ ),
+ )
+
+
+def exclusiveMaximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance >= maximum:
+ yield ValidationError(
+ "%r is greater than or equal to the maximum of %r" % (
+ instance, maximum,
+ ),
+ )
+
+
+def minimum(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance < minimum:
+ yield ValidationError(
+ "%r is less than the minimum of %r" % (instance, minimum)
+ )
+
+
+def maximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance > maximum:
+ yield ValidationError(
+ "%r is greater than the maximum of %r" % (instance, maximum)
+ )
+
+
+def multipleOf(validator, dB, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if isinstance(dB, float):
+ quotient = instance / dB
+ failed = int(quotient) != quotient
+ else:
+ failed = instance % dB
+
+ if failed:
+ yield ValidationError("%r is not a multiple of %r" % (instance, dB))
+
+
+def minItems(validator, mI, instance, schema):
+ if validator.is_type(instance, "array") and len(instance) < mI:
+ yield ValidationError("%r is too short" % (instance,))
+
+
+def maxItems(validator, mI, instance, schema):
+ if validator.is_type(instance, "array") and len(instance) > mI:
+ yield ValidationError("%r is too long" % (instance,))
+
+
+def uniqueItems(validator, uI, instance, schema):
+ if (
+ uI and
+ validator.is_type(instance, "array") and
+ not uniq(instance)
+ ):
+ yield ValidationError("%r has non-unique elements" % (instance,))
+
+
+def pattern(validator, patrn, instance, schema):
+ if (
+ validator.is_type(instance, "string") and
+ not re.search(patrn, instance)
+ ):
+ yield ValidationError("%r does not match %r" % (instance, patrn))
+
+
+def format(validator, format, instance, schema):
+ if validator.format_checker is not None:
+ try:
+ validator.format_checker.check(instance, format)
+ except FormatError as error:
+ yield ValidationError(error.message, cause=error.cause)
+
+
+def minLength(validator, mL, instance, schema):
+ if validator.is_type(instance, "string") and len(instance) < mL:
+ yield ValidationError("%r is too short" % (instance,))
+
+
+def maxLength(validator, mL, instance, schema):
+ if validator.is_type(instance, "string") and len(instance) > mL:
+ yield ValidationError("%r is too long" % (instance,))
+
+
+def dependencies(validator, dependencies, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, dependency in iteritems(dependencies):
+ if property not in instance:
+ continue
+
+ if validator.is_type(dependency, "array"):
+ for each in dependency:
+ if each not in instance:
+ message = "%r is a dependency of %r"
+ yield ValidationError(message % (each, property))
+ else:
+ for error in validator.descend(
+ instance, dependency, schema_path=property,
+ ):
+ yield error
+
+
+def enum(validator, enums, instance, schema):
+ if instance == 0 or instance == 1:
+ unbooled = unbool(instance)
+ if all(unbooled != unbool(each) for each in enums):
+ yield ValidationError("%r is not one of %r" % (instance, enums))
+ elif instance not in enums:
+ yield ValidationError("%r is not one of %r" % (instance, enums))
+
+
+def ref(validator, ref, instance, schema):
+ resolve = getattr(validator.resolver, "resolve", None)
+ if resolve is None:
+ with validator.resolver.resolving(ref) as resolved:
+ for error in validator.descend(instance, resolved):
+ yield error
+ else:
+ scope, resolved = validator.resolver.resolve(ref)
+ validator.resolver.push_scope(scope)
+
+ try:
+ for error in validator.descend(instance, resolved):
+ yield error
+ finally:
+ validator.resolver.pop_scope()
+
+
+def type(validator, types, instance, schema):
+ types = ensure_list(types)
+
+ if not any(validator.is_type(instance, type) for type in types):
+ yield ValidationError(types_msg(instance, types))
+
+
+def properties(validator, properties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, subschema in iteritems(properties):
+ if property in instance:
+ for error in validator.descend(
+ instance[property],
+ subschema,
+ path=property,
+ schema_path=property,
+ ):
+ yield error
+
+
+def required(validator, required, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+ for property in required:
+ if property not in instance:
+ yield ValidationError("%r is a required property" % property)
+
+
+def minProperties(validator, mP, instance, schema):
+ if validator.is_type(instance, "object") and len(instance) < mP:
+ yield ValidationError(
+ "%r does not have enough properties" % (instance,)
+ )
+
+
+def maxProperties(validator, mP, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+ if validator.is_type(instance, "object") and len(instance) > mP:
+ yield ValidationError("%r has too many properties" % (instance,))
+
+
+def allOf(validator, allOf, instance, schema):
+ for index, subschema in enumerate(allOf):
+ for error in validator.descend(instance, subschema, schema_path=index):
+ yield error
+
+
+def anyOf(validator, anyOf, instance, schema):
+ all_errors = []
+ for index, subschema in enumerate(anyOf):
+ errs = list(validator.descend(instance, subschema, schema_path=index))
+ if not errs:
+ break
+ all_errors.extend(errs)
+ else:
+ yield ValidationError(
+ "%r is not valid under any of the given schemas" % (instance,),
+ context=all_errors,
+ )
+
+
+def oneOf(validator, oneOf, instance, schema):
+ subschemas = enumerate(oneOf)
+ all_errors = []
+ for index, subschema in subschemas:
+ errs = list(validator.descend(instance, subschema, schema_path=index))
+ if not errs:
+ first_valid = subschema
+ break
+ all_errors.extend(errs)
+ else:
+ yield ValidationError(
+ "%r is not valid under any of the given schemas" % (instance,),
+ context=all_errors,
+ )
+
+ more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)]
+ if more_valid:
+ more_valid.append(first_valid)
+ reprs = ", ".join(repr(schema) for schema in more_valid)
+ yield ValidationError(
+ "%r is valid under each of %s" % (instance, reprs)
+ )
+
+
+def not_(validator, not_schema, instance, schema):
+ if validator.is_valid(instance, not_schema):
+ yield ValidationError(
+ "%r is not allowed for %r" % (not_schema, instance)
+ )
+
+
+def if_(validator, if_schema, instance, schema):
+ if validator.is_valid(instance, if_schema):
+ if u"then" in schema:
+ then = schema[u"then"]
+ for error in validator.descend(instance, then, schema_path="then"):
+ yield error
+ elif u"else" in schema:
+ else_ = schema[u"else"]
+ for error in validator.descend(instance, else_, schema_path="else"):
+ yield error
diff --git a/contrib/python/jsonschema/py3/jsonschema/cli.py b/contrib/python/jsonschema/py3/jsonschema/cli.py
new file mode 100644
index 00000000000..ab3335b27c5
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/cli.py
@@ -0,0 +1,90 @@
+"""
+The ``jsonschema`` command line.
+"""
+from __future__ import absolute_import
+import argparse
+import json
+import sys
+
+from jsonschema import __version__
+from jsonschema._reflect import namedAny
+from jsonschema.validators import validator_for
+
+
+def _namedAnyWithDefault(name):
+ if "." not in name:
+ name = "jsonschema." + name
+ return namedAny(name)
+
+
+def _json_file(path):
+ with open(path) as file:
+ return json.load(file)
+
+
+parser = argparse.ArgumentParser(
+ description="JSON Schema Validation CLI",
+)
+parser.add_argument(
+ "-i", "--instance",
+ action="append",
+ dest="instances",
+ type=_json_file,
+ help=(
+ "a path to a JSON instance (i.e. filename.json) "
+ "to validate (may be specified multiple times)"
+ ),
+)
+parser.add_argument(
+ "-F", "--error-format",
+ default="{error.instance}: {error.message}\n",
+ help=(
+ "the format to use for each error output message, specified in "
+ "a form suitable for passing to str.format, which will be called "
+ "with 'error' for each error"
+ ),
+)
+parser.add_argument(
+ "-V", "--validator",
+ type=_namedAnyWithDefault,
+ help=(
+ "the fully qualified object name of a validator to use, or, for "
+ "validators that are registered with jsonschema, simply the name "
+ "of the class."
+ ),
+)
+parser.add_argument(
+ "--version",
+ action="version",
+ version=__version__,
+)
+parser.add_argument(
+ "schema",
+ help="the JSON Schema to validate with (i.e. schema.json)",
+ type=_json_file,
+)
+
+
+def parse_args(args):
+ arguments = vars(parser.parse_args(args=args or ["--help"]))
+ if arguments["validator"] is None:
+ arguments["validator"] = validator_for(arguments["schema"])
+ return arguments
+
+
+def main(args=sys.argv[1:]):
+ sys.exit(run(arguments=parse_args(args=args)))
+
+
+def run(arguments, stdout=sys.stdout, stderr=sys.stderr):
+ error_format = arguments["error_format"]
+ validator = arguments["validator"](schema=arguments["schema"])
+
+ validator.check_schema(arguments["schema"])
+
+ errored = False
+ for instance in arguments["instances"] or ():
+ for error in validator.iter_errors(instance):
+ stderr.write(error_format.format(error=error))
+ errored = True
+ return errored
diff --git a/contrib/python/jsonschema/py3/jsonschema/compat.py b/contrib/python/jsonschema/py3/jsonschema/compat.py
new file mode 100644
index 00000000000..47e09804551
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/compat.py
@@ -0,0 +1,55 @@
+"""
+Python 2/3 compatibility helpers.
+
+Note: This module is *not* public API.
+"""
+import contextlib
+import operator
+import sys
+
+
+try:
+ from collections.abc import MutableMapping, Sequence # noqa
+except ImportError:
+ from collections import MutableMapping, Sequence # noqa
+
+PY3 = sys.version_info[0] >= 3
+
+if PY3:
+ zip = zip
+ from functools import lru_cache
+ from io import StringIO as NativeIO
+ from urllib.parse import (
+ unquote, urljoin, urlunsplit, SplitResult, urlsplit
+ )
+ from urllib.request import pathname2url, urlopen
+ str_types = str,
+ int_types = int,
+ iteritems = operator.methodcaller("items")
+else:
+ from itertools import izip as zip # noqa
+ from io import BytesIO as NativeIO
+ from urlparse import urljoin, urlunsplit, SplitResult, urlsplit
+ from urllib import pathname2url, unquote # noqa
+ import urllib2 # noqa
+ def urlopen(*args, **kwargs):
+ return contextlib.closing(urllib2.urlopen(*args, **kwargs))
+
+ str_types = basestring
+ int_types = int, long
+ iteritems = operator.methodcaller("iteritems")
+
+ from functools32 import lru_cache
+
+
+def urldefrag(url):
+ if "#" in url:
+ s, n, p, q, frag = urlsplit(url)
+ defrag = urlunsplit((s, n, p, q, ""))
+ else:
+ defrag = url
+ frag = ""
+ return defrag, frag
+
+
+# flake8: noqa
diff --git a/contrib/python/jsonschema/py3/jsonschema/exceptions.py b/contrib/python/jsonschema/py3/jsonschema/exceptions.py
new file mode 100644
index 00000000000..691dcffe6c7
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/exceptions.py
@@ -0,0 +1,374 @@
+"""
+Validation errors, and some surrounding helpers.
+"""
+from collections import defaultdict, deque
+import itertools
+import pprint
+import textwrap
+
+import attr
+
+from jsonschema import _utils
+from jsonschema.compat import PY3, iteritems
+
+
+WEAK_MATCHES = frozenset(["anyOf", "oneOf"])
+STRONG_MATCHES = frozenset()
+
+_unset = _utils.Unset()
+
+
+class _Error(Exception):
+ def __init__(
+ self,
+ message,
+ validator=_unset,
+ path=(),
+ cause=None,
+ context=(),
+ validator_value=_unset,
+ instance=_unset,
+ schema=_unset,
+ schema_path=(),
+ parent=None,
+ ):
+ super(_Error, self).__init__(
+ message,
+ validator,
+ path,
+ cause,
+ context,
+ validator_value,
+ instance,
+ schema,
+ schema_path,
+ parent,
+ )
+ self.message = message
+ self.path = self.relative_path = deque(path)
+ self.schema_path = self.relative_schema_path = deque(schema_path)
+ self.context = list(context)
+ self.cause = self.__cause__ = cause
+ self.validator = validator
+ self.validator_value = validator_value
+ self.instance = instance
+ self.schema = schema
+ self.parent = parent
+
+ for error in context:
+ error.parent = self
+
+ def __repr__(self):
+ return "<%s: %r>" % (self.__class__.__name__, self.message)
+
+ def __unicode__(self):
+ essential_for_verbose = (
+ self.validator, self.validator_value, self.instance, self.schema,
+ )
+ if any(m is _unset for m in essential_for_verbose):
+ return self.message
+
+ pschema = pprint.pformat(self.schema, width=72)
+ pinstance = pprint.pformat(self.instance, width=72)
+ return self.message + textwrap.dedent("""
+
+ Failed validating %r in %s%s:
+ %s
+
+ On %s%s:
+ %s
+ """.rstrip()
+ ) % (
+ self.validator,
+ self._word_for_schema_in_error_message,
+ _utils.format_as_index(list(self.relative_schema_path)[:-1]),
+ _utils.indent(pschema),
+ self._word_for_instance_in_error_message,
+ _utils.format_as_index(self.relative_path),
+ _utils.indent(pinstance),
+ )
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return unicode(self).encode("utf-8")
+
+ @classmethod
+ def create_from(cls, other):
+ return cls(**other._contents())
+
+ @property
+ def absolute_path(self):
+ parent = self.parent
+ if parent is None:
+ return self.relative_path
+
+ path = deque(self.relative_path)
+ path.extendleft(reversed(parent.absolute_path))
+ return path
+
+ @property
+ def absolute_schema_path(self):
+ parent = self.parent
+ if parent is None:
+ return self.relative_schema_path
+
+ path = deque(self.relative_schema_path)
+ path.extendleft(reversed(parent.absolute_schema_path))
+ return path
+
+ def _set(self, **kwargs):
+ for k, v in iteritems(kwargs):
+ if getattr(self, k) is _unset:
+ setattr(self, k, v)
+
+ def _contents(self):
+ attrs = (
+ "message", "cause", "context", "validator", "validator_value",
+ "path", "schema_path", "instance", "schema", "parent",
+ )
+ return dict((attr, getattr(self, attr)) for attr in attrs)
+
+
+class ValidationError(_Error):
+ """
+ An instance was invalid under a provided schema.
+ """
+
+ _word_for_schema_in_error_message = "schema"
+ _word_for_instance_in_error_message = "instance"
+
+
+class SchemaError(_Error):
+ """
+ A schema was invalid under its corresponding metaschema.
+ """
+
+ _word_for_schema_in_error_message = "metaschema"
+ _word_for_instance_in_error_message = "schema"
+
+
+class RefResolutionError(Exception):
+ """
+ A ref could not be resolved.
+ """
+
+ _cause = attr.ib()
+
+ def __str__(self):
+ return str(self._cause)
+
+
+class UndefinedTypeCheck(Exception):
+ """
+ A type checker was asked to check a type it did not have registered.
+ """
+
+ def __init__(self, type):
+ self.type = type
+
+ def __unicode__(self):
+ return "Type %r is unknown to this type checker" % self.type
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return unicode(self).encode("utf-8")
+
+
+class UnknownType(Exception):
+ """
+ A validator was asked to validate an instance against an unknown type.
+ """
+
+ def __init__(self, type, instance, schema):
+ self.type = type
+ self.instance = instance
+ self.schema = schema
+
+ def __unicode__(self):
+ pschema = pprint.pformat(self.schema, width=72)
+ pinstance = pprint.pformat(self.instance, width=72)
+ return textwrap.dedent("""
+ Unknown type %r for validator with schema:
+ %s
+
+ While checking instance:
+ %s
+ """.rstrip()
+ ) % (self.type, _utils.indent(pschema), _utils.indent(pinstance))
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return unicode(self).encode("utf-8")
+
+
+class FormatError(Exception):
+ """
+ Validating a format failed.
+ """
+
+ def __init__(self, message, cause=None):
+ super(FormatError, self).__init__(message, cause)
+ self.message = message
+ self.cause = self.__cause__ = cause
+
+ def __unicode__(self):
+ return self.message
+
+ if PY3:
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return self.message.encode("utf-8")
+
+
+class ErrorTree(object):
+ """
+ ErrorTrees make it easier to check which validations failed.
+ """
+
+ _instance = _unset
+
+ def __init__(self, errors=()):
+ self.errors = {}
+ self._contents = defaultdict(self.__class__)
+
+ for error in errors:
+ container = self
+ for element in error.path:
+ container = container[element]
+ container.errors[error.validator] = error
+
+ container._instance = error.instance
+
+ def __contains__(self, index):
+ """
+ Check whether ``instance[index]`` has any errors.
+ """
+
+ return index in self._contents
+
+ def __getitem__(self, index):
+ """
+ Retrieve the child tree one level down at the given ``index``.
+
+ If the index is not in the instance that this tree corresponds to and
+ is not known by this tree, whatever error would be raised by
+ ``instance.__getitem__`` will be propagated (usually this is some
+ subclass of `exceptions.LookupError`.
+ """
+
+ if self._instance is not _unset and index not in self:
+ self._instance[index]
+ return self._contents[index]
+
+ def __setitem__(self, index, value):
+ """
+ Add an error to the tree at the given ``index``.
+ """
+ self._contents[index] = value
+
+ def __iter__(self):
+ """
+ Iterate (non-recursively) over the indices in the instance with errors.
+ """
+
+ return iter(self._contents)
+
+ def __len__(self):
+ """
+ Return the `total_errors`.
+ """
+ return self.total_errors
+
+ def __repr__(self):
+ return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
+
+ @property
+ def total_errors(self):
+ """
+ The total number of errors in the entire tree, including children.
+ """
+
+ child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
+ return len(self.errors) + child_errors
+
+
+def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):
+ """
+ Create a key function that can be used to sort errors by relevance.
+
+ Arguments:
+ weak (set):
+ a collection of validator names to consider to be "weak".
+ If there are two errors at the same level of the instance
+ and one is in the set of weak validator names, the other
+ error will take priority. By default, :validator:`anyOf` and
+ :validator:`oneOf` are considered weak validators and will
+ be superseded by other same-level validation errors.
+
+ strong (set):
+ a collection of validator names to consider to be "strong"
+ """
+ def relevance(error):
+ validator = error.validator
+ return -len(error.path), validator not in weak, validator in strong
+ return relevance
+
+
+relevance = by_relevance()
+
+
+def best_match(errors, key=relevance):
+ """
+ Try to find an error that appears to be the best match among given errors.
+
+ In general, errors that are higher up in the instance (i.e. for which
+ `ValidationError.path` is shorter) are considered better matches,
+ since they indicate "more" is wrong with the instance.
+
+ If the resulting match is either :validator:`oneOf` or :validator:`anyOf`,
+ the *opposite* assumption is made -- i.e. the deepest error is picked,
+ since these validators only need to match once, and any other errors may
+ not be relevant.
+
+ Arguments:
+ errors (collections.Iterable):
+
+ the errors to select from. Do not provide a mixture of
+ errors from different validation attempts (i.e. from
+ different instances or schemas), since it won't produce
+ sensical output.
+
+ key (collections.Callable):
+
+ the key to use when sorting errors. See `relevance` and
+ transitively `by_relevance` for more details (the default is
+ to sort with the defaults of that function). Changing the
+ default is only useful if you want to change the function
+ that rates errors but still want the error context descent
+ done by this function.
+
+ Returns:
+ the best matching error, or ``None`` if the iterable was empty
+
+ .. note::
+
+ This function is a heuristic. Its return value may change for a given
+ set of inputs from version to version if better heuristics are added.
+ """
+ errors = iter(errors)
+ best = next(errors, None)
+ if best is None:
+ return
+ best = max(itertools.chain([best], errors), key=key)
+
+ while best.context:
+ best = min(best.context, key=key)
+ return best
diff --git a/contrib/python/jsonschema/py3/jsonschema/schemas/draft3.json b/contrib/python/jsonschema/py3/jsonschema/schemas/draft3.json
new file mode 100644
index 00000000000..f8a09c563b4
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/schemas/draft3.json
@@ -0,0 +1,199 @@
+{
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "dependencies": {
+ "exclusiveMaximum": "maximum",
+ "exclusiveMinimum": "minimum"
+ },
+ "id": "http://json-schema.org/draft-03/schema#",
+ "properties": {
+ "$ref": {
+ "format": "uri",
+ "type": "string"
+ },
+ "$schema": {
+ "format": "uri",
+ "type": "string"
+ },
+ "additionalItems": {
+ "default": {},
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "boolean"
+ ]
+ },
+ "additionalProperties": {
+ "default": {},
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "boolean"
+ ]
+ },
+ "default": {
+ "type": "any"
+ },
+ "dependencies": {
+ "additionalProperties": {
+ "items": {
+ "type": "string"
+ },
+ "type": [
+ "string",
+ "array",
+ {
+ "$ref": "#"
+ }
+ ]
+ },
+ "default": {},
+ "type": [
+ "string",
+ "array",
+ "object"
+ ]
+ },
+ "description": {
+ "type": "string"
+ },
+ "disallow": {
+ "items": {
+ "type": [
+ "string",
+ {
+ "$ref": "#"
+ }
+ ]
+ },
+ "type": [
+ "string",
+ "array"
+ ],
+ "uniqueItems": true
+ },
+ "divisibleBy": {
+ "default": 1,
+ "exclusiveMinimum": true,
+ "minimum": 0,
+ "type": "number"
+ },
+ "enum": {
+ "type": "array"
+ },
+ "exclusiveMaximum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "exclusiveMinimum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "extends": {
+ "default": {},
+ "items": {
+ "$ref": "#"
+ },
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "id": {
+ "format": "uri",
+ "type": "string"
+ },
+ "items": {
+ "default": {},
+ "items": {
+ "$ref": "#"
+ },
+ "type": [
+ {
+ "$ref": "#"
+ },
+ "array"
+ ]
+ },
+ "maxDecimal": {
+ "minimum": 0,
+ "type": "number"
+ },
+ "maxItems": {
+ "minimum": 0,
+ "type": "integer"
+ },
+ "maxLength": {
+ "type": "integer"
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "minItems": {
+ "default": 0,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "minLength": {
+ "default": 0,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "pattern": {
+ "format": "regex",
+ "type": "string"
+ },
+ "patternProperties": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "properties": {
+ "additionalProperties": {
+ "$ref": "#",
+ "type": "object"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "required": {
+ "default": false,
+ "type": "boolean"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "default": "any",
+ "items": {
+ "type": [
+ "string",
+ {
+ "$ref": "#"
+ }
+ ]
+ },
+ "type": [
+ "string",
+ "array"
+ ],
+ "uniqueItems": true
+ },
+ "uniqueItems": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+}
diff --git a/contrib/python/jsonschema/py3/jsonschema/schemas/draft4.json b/contrib/python/jsonschema/py3/jsonschema/schemas/draft4.json
new file mode 100644
index 00000000000..9b666cff88a
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/schemas/draft4.json
@@ -0,0 +1,222 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "default": {},
+ "definitions": {
+ "positiveInteger": {
+ "minimum": 0,
+ "type": "integer"
+ },
+ "positiveIntegerDefault0": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ {
+ "default": 0
+ }
+ ]
+ },
+ "schemaArray": {
+ "items": {
+ "$ref": "#"
+ },
+ "minItems": 1,
+ "type": "array"
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "type": "array",
+ "uniqueItems": true
+ }
+ },
+ "dependencies": {
+ "exclusiveMaximum": [
+ "maximum"
+ ],
+ "exclusiveMinimum": [
+ "minimum"
+ ]
+ },
+ "description": "Core schema meta-schema",
+ "id": "http://json-schema.org/draft-04/schema#",
+ "properties": {
+ "$schema": {
+ "format": "uri",
+ "type": "string"
+ },
+ "additionalItems": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "$ref": "#"
+ }
+ ],
+ "default": {}
+ },
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "$ref": "#"
+ }
+ ],
+ "default": {}
+ },
+ "allOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "anyOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "default": {},
+ "definitions": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "dependencies": {
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#"
+ },
+ {
+ "$ref": "#/definitions/stringArray"
+ }
+ ]
+ },
+ "type": "object"
+ },
+ "description": {
+ "type": "string"
+ },
+ "enum": {
+ "type": "array"
+ },
+ "exclusiveMaximum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "exclusiveMinimum": {
+ "default": false,
+ "type": "boolean"
+ },
+ "format": {
+ "type": "string"
+ },
+ "id": {
+ "format": "uri",
+ "type": "string"
+ },
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#"
+ },
+ {
+ "$ref": "#/definitions/schemaArray"
+ }
+ ],
+ "default": {}
+ },
+ "maxItems": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "maxProperties": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "minItems": {
+ "$ref": "#/definitions/positiveIntegerDefault0"
+ },
+ "minLength": {
+ "$ref": "#/definitions/positiveIntegerDefault0"
+ },
+ "minProperties": {
+ "$ref": "#/definitions/positiveIntegerDefault0"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "multipleOf": {
+ "exclusiveMinimum": true,
+ "minimum": 0,
+ "type": "number"
+ },
+ "not": {
+ "$ref": "#"
+ },
+ "oneOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "pattern": {
+ "format": "regex",
+ "type": "string"
+ },
+ "patternProperties": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "properties": {
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {},
+ "type": "object"
+ },
+ "required": {
+ "$ref": "#/definitions/stringArray"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/simpleTypes"
+ },
+ {
+ "items": {
+ "$ref": "#/definitions/simpleTypes"
+ },
+ "minItems": 1,
+ "type": "array",
+ "uniqueItems": true
+ }
+ ]
+ },
+ "uniqueItems": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+}
diff --git a/contrib/python/jsonschema/py3/jsonschema/schemas/draft6.json b/contrib/python/jsonschema/py3/jsonschema/schemas/draft6.json
new file mode 100644
index 00000000000..a0d2bf7896c
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/schemas/draft6.json
@@ -0,0 +1,153 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "$id": "http://json-schema.org/draft-06/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "examples": {
+ "type": "array",
+ "items": {}
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": {},
+ "enum": {
+ "type": "array"
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": {}
+}
diff --git a/contrib/python/jsonschema/py3/jsonschema/schemas/draft7.json b/contrib/python/jsonschema/py3/jsonschema/schemas/draft7.json
new file mode 100644
index 00000000000..746cde96901
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/schemas/draft7.json
@@ -0,0 +1,166 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": {"$ref": "#"},
+ "then": {"$ref": "#"},
+ "else": {"$ref": "#"},
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+}
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/__init__.py b/contrib/python/jsonschema/py3/jsonschema/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/__init__.py
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/_helpers.py b/contrib/python/jsonschema/py3/jsonschema/tests/_helpers.py
new file mode 100644
index 00000000000..70f291fe2ab
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/_helpers.py
@@ -0,0 +1,5 @@
+def bug(issue=None):
+ message = "A known bug."
+ if issue is not None:
+ message += " See issue #{issue}.".format(issue=issue)
+ return message
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/test_cli.py b/contrib/python/jsonschema/py3/jsonschema/tests/test_cli.py
new file mode 100644
index 00000000000..328c85106f1
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/test_cli.py
@@ -0,0 +1,143 @@
+from unittest import TestCase
+import json
+import subprocess
+import sys
+
+from jsonschema import Draft4Validator, ValidationError, cli, __version__
+from jsonschema.compat import NativeIO
+from jsonschema.exceptions import SchemaError
+
+
+def fake_validator(*errors):
+ errors = list(reversed(errors))
+
+ class FakeValidator(object):
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def iter_errors(self, instance):
+ if errors:
+ return errors.pop()
+ return []
+
+ def check_schema(self, schema):
+ pass
+
+ return FakeValidator
+
+
+class TestParser(TestCase):
+
+ FakeValidator = fake_validator()
+ instance_file = "foo.json"
+ schema_file = "schema.json"
+
+ def setUp(self):
+ cli.open = self.fake_open
+ self.addCleanup(delattr, cli, "open")
+
+ def fake_open(self, path):
+ if path == self.instance_file:
+ contents = ""
+ elif path == self.schema_file:
+ contents = {}
+ else: # pragma: no cover
+ self.fail("What is {!r}".format(path))
+ return NativeIO(json.dumps(contents))
+
+ def test_find_validator_by_fully_qualified_object_name(self):
+ arguments = cli.parse_args(
+ [
+ "--validator",
+ "__tests__.test_cli.TestParser.FakeValidator", # XXX Arcadia
+ "--instance", self.instance_file,
+ self.schema_file,
+ ]
+ )
+ self.assertIs(arguments["validator"], self.FakeValidator)
+
+ def test_find_validator_in_jsonschema(self):
+ arguments = cli.parse_args(
+ [
+ "--validator", "Draft4Validator",
+ "--instance", self.instance_file,
+ self.schema_file,
+ ]
+ )
+ self.assertIs(arguments["validator"], Draft4Validator)
+
+
+class TestCLI(TestCase):
+ def test_draft3_schema_draft4_validator(self):
+ stdout, stderr = NativeIO(), NativeIO()
+ with self.assertRaises(SchemaError):
+ cli.run(
+ {
+ "validator": Draft4Validator,
+ "schema": {
+ "anyOf": [
+ {"minimum": 20},
+ {"type": "string"},
+ {"required": True},
+ ],
+ },
+ "instances": [1],
+ "error_format": "{error.message}",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+
+ def test_successful_validation(self):
+ stdout, stderr = NativeIO(), NativeIO()
+ exit_code = cli.run(
+ {
+ "validator": fake_validator(),
+ "schema": {},
+ "instances": [1],
+ "error_format": "{error.message}",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+ self.assertFalse(stdout.getvalue())
+ self.assertFalse(stderr.getvalue())
+ self.assertEqual(exit_code, 0)
+
+ def test_unsuccessful_validation(self):
+ error = ValidationError("I am an error!", instance=1)
+ stdout, stderr = NativeIO(), NativeIO()
+ exit_code = cli.run(
+ {
+ "validator": fake_validator([error]),
+ "schema": {},
+ "instances": [1],
+ "error_format": "{error.instance} - {error.message}",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+ self.assertFalse(stdout.getvalue())
+ self.assertEqual(stderr.getvalue(), "1 - I am an error!")
+ self.assertEqual(exit_code, 1)
+
+ def test_unsuccessful_validation_multiple_instances(self):
+ first_errors = [
+ ValidationError("9", instance=1),
+ ValidationError("8", instance=1),
+ ]
+ second_errors = [ValidationError("7", instance=2)]
+ stdout, stderr = NativeIO(), NativeIO()
+ exit_code = cli.run(
+ {
+ "validator": fake_validator(first_errors, second_errors),
+ "schema": {},
+ "instances": [1, 2],
+ "error_format": "{error.instance} - {error.message}\t",
+ },
+ stdout=stdout,
+ stderr=stderr,
+ )
+ self.assertFalse(stdout.getvalue())
+ self.assertEqual(stderr.getvalue(), "1 - 9\t1 - 8\t2 - 7\t")
+ self.assertEqual(exit_code, 1)
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/test_exceptions.py b/contrib/python/jsonschema/py3/jsonschema/tests/test_exceptions.py
new file mode 100644
index 00000000000..eae00d76d77
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/test_exceptions.py
@@ -0,0 +1,462 @@
+from unittest import TestCase
+import textwrap
+
+from jsonschema import Draft4Validator, exceptions
+from jsonschema.compat import PY3
+
+
+class TestBestMatch(TestCase):
+ def best_match(self, errors):
+ errors = list(errors)
+ best = exceptions.best_match(errors)
+ reversed_best = exceptions.best_match(reversed(errors))
+ msg = "Didn't return a consistent best match!\nGot: {0}\n\nThen: {1}"
+ self.assertEqual(
+ best._contents(), reversed_best._contents(),
+ msg=msg.format(best, reversed_best),
+ )
+ return best
+
+ def test_shallower_errors_are_better_matches(self):
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "minProperties": 2,
+ "properties": {"bar": {"type": "object"}},
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": []}}))
+ self.assertEqual(best.validator, "minProperties")
+
+ def test_oneOf_and_anyOf_are_weak_matches(self):
+ """
+ A property you *must* match is probably better than one you have to
+ match a part of.
+ """
+
+ validator = Draft4Validator(
+ {
+ "minProperties": 2,
+ "anyOf": [{"type": "string"}, {"type": "number"}],
+ "oneOf": [{"type": "string"}, {"type": "number"}],
+ }
+ )
+ best = self.best_match(validator.iter_errors({}))
+ self.assertEqual(best.validator, "minProperties")
+
+ def test_if_the_most_relevant_error_is_anyOf_it_is_traversed(self):
+ """
+ If the most relevant error is an anyOf, then we traverse its context
+ and select the otherwise *least* relevant error, since in this case
+ that means the most specific, deep, error inside the instance.
+
+ I.e. since only one of the schemas must match, we look for the most
+ relevant one.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "anyOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_if_the_most_relevant_error_is_oneOf_it_is_traversed(self):
+ """
+ If the most relevant error is an oneOf, then we traverse its context
+ and select the otherwise *least* relevant error, since in this case
+ that means the most specific, deep, error inside the instance.
+
+ I.e. since only one of the schemas must match, we look for the most
+ relevant one.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_if_the_most_relevant_error_is_allOf_it_is_traversed(self):
+ """
+ Now, if the error is allOf, we traverse but select the *most* relevant
+ error from the context, because all schemas here must match anyways.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "allOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "string")
+
+ def test_nested_context_for_oneOf(self):
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {"type": "string"},
+ {
+ "oneOf": [
+ {"type": "string"},
+ {
+ "properties": {
+ "bar": {"type": "array"},
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_one_error(self):
+ validator = Draft4Validator({"minProperties": 2})
+ error, = validator.iter_errors({})
+ self.assertEqual(
+ exceptions.best_match(validator.iter_errors({})).validator,
+ "minProperties",
+ )
+
+ def test_no_errors(self):
+ validator = Draft4Validator({})
+ self.assertIsNone(exceptions.best_match(validator.iter_errors({})))
+
+
+class TestByRelevance(TestCase):
+ def test_short_paths_are_better_matches(self):
+ shallow = exceptions.ValidationError("Oh no!", path=["baz"])
+ deep = exceptions.ValidationError("Oh yes!", path=["foo", "bar"])
+ match = max([shallow, deep], key=exceptions.relevance)
+ self.assertIs(match, shallow)
+
+ match = max([deep, shallow], key=exceptions.relevance)
+ self.assertIs(match, shallow)
+
+ def test_global_errors_are_even_better_matches(self):
+ shallow = exceptions.ValidationError("Oh no!", path=[])
+ deep = exceptions.ValidationError("Oh yes!", path=["foo"])
+
+ errors = sorted([shallow, deep], key=exceptions.relevance)
+ self.assertEqual(
+ [list(error.path) for error in errors],
+ [["foo"], []],
+ )
+
+ errors = sorted([deep, shallow], key=exceptions.relevance)
+ self.assertEqual(
+ [list(error.path) for error in errors],
+ [["foo"], []],
+ )
+
+ def test_weak_validators_are_lower_priority(self):
+ weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
+ normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
+
+ best_match = exceptions.by_relevance(weak="a")
+
+ match = max([weak, normal], key=best_match)
+ self.assertIs(match, normal)
+
+ match = max([normal, weak], key=best_match)
+ self.assertIs(match, normal)
+
+ def test_strong_validators_are_higher_priority(self):
+ weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
+ normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
+ strong = exceptions.ValidationError("Oh fine!", path=[], validator="c")
+
+ best_match = exceptions.by_relevance(weak="a", strong="c")
+
+ match = max([weak, normal, strong], key=best_match)
+ self.assertIs(match, strong)
+
+ match = max([strong, normal, weak], key=best_match)
+ self.assertIs(match, strong)
+
+
+class TestErrorTree(TestCase):
+ def test_it_knows_how_many_total_errors_it_contains(self):
+ # FIXME: https://github.com/Julian/jsonschema/issues/442
+ errors = [
+ exceptions.ValidationError("Something", validator=i)
+ for i in range(8)
+ ]
+ tree = exceptions.ErrorTree(errors)
+ self.assertEqual(tree.total_errors, 8)
+
+ def test_it_contains_an_item_if_the_item_had_an_error(self):
+ errors = [exceptions.ValidationError("a message", path=["bar"])]
+ tree = exceptions.ErrorTree(errors)
+ self.assertIn("bar", tree)
+
+ def test_it_does_not_contain_an_item_if_the_item_had_no_error(self):
+ errors = [exceptions.ValidationError("a message", path=["bar"])]
+ tree = exceptions.ErrorTree(errors)
+ self.assertNotIn("foo", tree)
+
+ def test_validators_that_failed_appear_in_errors_dict(self):
+ error = exceptions.ValidationError("a message", validator="foo")
+ tree = exceptions.ErrorTree([error])
+ self.assertEqual(tree.errors, {"foo": error})
+
+ def test_it_creates_a_child_tree_for_each_nested_path(self):
+ errors = [
+ exceptions.ValidationError("a bar message", path=["bar"]),
+ exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]),
+ ]
+ tree = exceptions.ErrorTree(errors)
+ self.assertIn(0, tree["bar"])
+ self.assertNotIn(1, tree["bar"])
+
+ def test_children_have_their_errors_dicts_built(self):
+ e1, e2 = (
+ exceptions.ValidationError("1", validator="foo", path=["bar", 0]),
+ exceptions.ValidationError("2", validator="quux", path=["bar", 0]),
+ )
+ tree = exceptions.ErrorTree([e1, e2])
+ self.assertEqual(tree["bar"][0].errors, {"foo": e1, "quux": e2})
+
+ def test_multiple_errors_with_instance(self):
+ e1, e2 = (
+ exceptions.ValidationError(
+ "1",
+ validator="foo",
+ path=["bar", "bar2"],
+ instance="i1"),
+ exceptions.ValidationError(
+ "2",
+ validator="quux",
+ path=["foobar", 2],
+ instance="i2"),
+ )
+ exceptions.ErrorTree([e1, e2])
+
+ def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self):
+ error = exceptions.ValidationError("123", validator="foo", instance=[])
+ tree = exceptions.ErrorTree([error])
+
+ with self.assertRaises(IndexError):
+ tree[0]
+
+ def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self):
+ """
+ If a validator is dumb (like :validator:`required` in draft 3) and
+ refers to a path that isn't in the instance, the tree still properly
+ returns a subtree for that path.
+ """
+
+ error = exceptions.ValidationError(
+ "a message", validator="foo", instance={}, path=["foo"],
+ )
+ tree = exceptions.ErrorTree([error])
+ self.assertIsInstance(tree["foo"], exceptions.ErrorTree)
+
+
+class TestErrorInitReprStr(TestCase):
+ def make_error(self, **kwargs):
+ defaults = dict(
+ message=u"hello",
+ validator=u"type",
+ validator_value=u"string",
+ instance=5,
+ schema={u"type": u"string"},
+ )
+ defaults.update(kwargs)
+ return exceptions.ValidationError(**defaults)
+
+ def assertShows(self, expected, **kwargs):
+ if PY3: # pragma: no cover
+ expected = expected.replace("u'", "'")
+ expected = textwrap.dedent(expected).rstrip("\n")
+
+ error = self.make_error(**kwargs)
+ message_line, _, rest = str(error).partition("\n")
+ self.assertEqual(message_line, error.message)
+ self.assertEqual(rest, expected)
+
+ def test_it_calls_super_and_sets_args(self):
+ error = self.make_error()
+ self.assertGreater(len(error.args), 1)
+
+ def test_repr(self):
+ self.assertEqual(
+ repr(exceptions.ValidationError(message="Hello!")),
+ "<ValidationError: %r>" % "Hello!",
+ )
+
+ def test_unset_error(self):
+ error = exceptions.ValidationError("message")
+ self.assertEqual(str(error), "message")
+
+ kwargs = {
+ "validator": "type",
+ "validator_value": "string",
+ "instance": 5,
+ "schema": {"type": "string"},
+ }
+ # Just the message should show if any of the attributes are unset
+ for attr in kwargs:
+ k = dict(kwargs)
+ del k[attr]
+ error = exceptions.ValidationError("message", **k)
+ self.assertEqual(str(error), "message")
+
+ def test_empty_paths(self):
+ self.assertShows(
+ """
+ Failed validating u'type' in schema:
+ {u'type': u'string'}
+
+ On instance:
+ 5
+ """,
+ path=[],
+ schema_path=[],
+ )
+
+ def test_one_item_paths(self):
+ self.assertShows(
+ """
+ Failed validating u'type' in schema:
+ {u'type': u'string'}
+
+ On instance[0]:
+ 5
+ """,
+ path=[0],
+ schema_path=["items"],
+ )
+
+ def test_multiple_item_paths(self):
+ self.assertShows(
+ """
+ Failed validating u'type' in schema[u'items'][0]:
+ {u'type': u'string'}
+
+ On instance[0][u'a']:
+ 5
+ """,
+ path=[0, u"a"],
+ schema_path=[u"items", 0, 1],
+ )
+
+ def test_uses_pprint(self):
+ self.assertShows(
+ """
+ Failed validating u'maxLength' in schema:
+ {0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+ 4: 4,
+ 5: 5,
+ 6: 6,
+ 7: 7,
+ 8: 8,
+ 9: 9,
+ 10: 10,
+ 11: 11,
+ 12: 12,
+ 13: 13,
+ 14: 14,
+ 15: 15,
+ 16: 16,
+ 17: 17,
+ 18: 18,
+ 19: 19}
+
+ On instance:
+ [0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24]
+ """,
+ instance=list(range(25)),
+ schema=dict(zip(range(20), range(20))),
+ validator=u"maxLength",
+ )
+
+ def test_str_works_with_instances_having_overriden_eq_operator(self):
+ """
+ Check for https://github.com/Julian/jsonschema/issues/164 which
+ rendered exceptions unusable when a `ValidationError` involved
+ instances with an `__eq__` method that returned truthy values.
+ """
+
+ class DontEQMeBro(object):
+ def __eq__(this, other): # pragma: no cover
+ self.fail("Don't!")
+
+ def __ne__(this, other): # pragma: no cover
+ self.fail("Don't!")
+
+ instance = DontEQMeBro()
+ error = exceptions.ValidationError(
+ "a message",
+ validator="foo",
+ instance=instance,
+ validator_value="some",
+ schema="schema",
+ )
+ self.assertIn(repr(instance), str(error))
+
+
+class TestHashable(TestCase):
+ def test_hashable(self):
+ set([exceptions.ValidationError("")])
+ set([exceptions.SchemaError("")])
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/test_format.py b/contrib/python/jsonschema/py3/jsonschema/tests/test_format.py
new file mode 100644
index 00000000000..254985f6156
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/test_format.py
@@ -0,0 +1,89 @@
+"""
+Tests for the parts of jsonschema related to the :validator:`format` property.
+"""
+
+from unittest import TestCase
+
+from jsonschema import FormatError, ValidationError, FormatChecker
+from jsonschema.validators import Draft4Validator
+
+
+BOOM = ValueError("Boom!")
+BANG = ZeroDivisionError("Bang!")
+
+
+def boom(thing):
+ if thing == "bang":
+ raise BANG
+ raise BOOM
+
+
+class TestFormatChecker(TestCase):
+ def test_it_can_validate_no_formats(self):
+ checker = FormatChecker(formats=())
+ self.assertFalse(checker.checkers)
+
+ def test_it_raises_a_key_error_for_unknown_formats(self):
+ with self.assertRaises(KeyError):
+ FormatChecker(formats=["o noes"])
+
+ def test_it_can_register_cls_checkers(self):
+ original = dict(FormatChecker.checkers)
+ self.addCleanup(FormatChecker.checkers.pop, "boom")
+ FormatChecker.cls_checks("boom")(boom)
+ self.assertEqual(
+ FormatChecker.checkers,
+ dict(original, boom=(boom, ())),
+ )
+
+ def test_it_can_register_checkers(self):
+ checker = FormatChecker()
+ checker.checks("boom")(boom)
+ self.assertEqual(
+ checker.checkers,
+ dict(FormatChecker.checkers, boom=(boom, ()))
+ )
+
+ def test_it_catches_registered_errors(self):
+ checker = FormatChecker()
+ checker.checks("boom", raises=type(BOOM))(boom)
+
+ with self.assertRaises(FormatError) as cm:
+ checker.check(instance=12, format="boom")
+
+ self.assertIs(cm.exception.cause, BOOM)
+ self.assertIs(cm.exception.__cause__, BOOM)
+
+ # Unregistered errors should not be caught
+ with self.assertRaises(type(BANG)):
+ checker.check(instance="bang", format="boom")
+
+ def test_format_error_causes_become_validation_error_causes(self):
+ checker = FormatChecker()
+ checker.checks("boom", raises=ValueError)(boom)
+ validator = Draft4Validator({"format": "boom"}, format_checker=checker)
+
+ with self.assertRaises(ValidationError) as cm:
+ validator.validate("BOOM")
+
+ self.assertIs(cm.exception.cause, BOOM)
+ self.assertIs(cm.exception.__cause__, BOOM)
+
+ def test_format_checkers_come_with_defaults(self):
+ # This is bad :/ but relied upon.
+ # The docs for quite awhile recommended people do things like
+ # validate(..., format_checker=FormatChecker())
+ # We should change that, but we can't without deprecation...
+ checker = FormatChecker()
+ with self.assertRaises(FormatError):
+ checker.check(instance="not-an-ipv4", format="ipv4")
+
+ def test_repr(self):
+ checker = FormatChecker(formats=())
+ checker.checks("foo")(lambda thing: True)
+ checker.checks("bar")(lambda thing: True)
+ checker.checks("baz")(lambda thing: True)
+ self.assertEqual(
+ repr(checker),
+ "<FormatChecker checkers=['bar', 'baz', 'foo']>",
+ )
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/test_types.py b/contrib/python/jsonschema/py3/jsonschema/tests/test_types.py
new file mode 100644
index 00000000000..2280cc395b2
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/test_types.py
@@ -0,0 +1,190 @@
+"""
+Tests on the new type interface. The actual correctness of the type checking
+is handled in test_jsonschema_test_suite; these tests check that TypeChecker
+functions correctly and can facilitate extensions to type checking
+"""
+from collections import namedtuple
+from unittest import TestCase
+
+from jsonschema import ValidationError, _validators
+from jsonschema._types import TypeChecker
+from jsonschema.exceptions import UndefinedTypeCheck
+from jsonschema.validators import Draft4Validator, extend
+
+
+def equals_2(checker, instance):
+ return instance == 2
+
+
+def is_namedtuple(instance):
+ return isinstance(instance, tuple) and getattr(instance, "_fields", None)
+
+
+def is_object_or_named_tuple(checker, instance):
+ if Draft4Validator.TYPE_CHECKER.is_type(instance, "object"):
+ return True
+ return is_namedtuple(instance)
+
+
+def coerce_named_tuple(fn):
+ def coerced(validator, value, instance, schema):
+ if is_namedtuple(instance):
+ instance = instance._asdict()
+ return fn(validator, value, instance, schema)
+ return coerced
+
+
+required = coerce_named_tuple(_validators.required)
+properties = coerce_named_tuple(_validators.properties)
+
+
+class TestTypeChecker(TestCase):
+ def test_is_type(self):
+ checker = TypeChecker({"two": equals_2})
+ self.assertEqual(
+ (
+ checker.is_type(instance=2, type="two"),
+ checker.is_type(instance="bar", type="two"),
+ ),
+ (True, False),
+ )
+
+ def test_is_unknown_type(self):
+ with self.assertRaises(UndefinedTypeCheck) as context:
+ TypeChecker().is_type(4, "foobar")
+ self.assertIn("foobar", str(context.exception))
+
+ def test_checks_can_be_added_at_init(self):
+ checker = TypeChecker({"two": equals_2})
+ self.assertEqual(checker, TypeChecker().redefine("two", equals_2))
+
+ def test_redefine_existing_type(self):
+ self.assertEqual(
+ TypeChecker().redefine("two", object()).redefine("two", equals_2),
+ TypeChecker().redefine("two", equals_2),
+ )
+
+ def test_remove(self):
+ self.assertEqual(
+ TypeChecker({"two": equals_2}).remove("two"),
+ TypeChecker(),
+ )
+
+ def test_remove_unknown_type(self):
+ with self.assertRaises(UndefinedTypeCheck) as context:
+ TypeChecker().remove("foobar")
+ self.assertIn("foobar", str(context.exception))
+
+ def test_redefine_many(self):
+ self.assertEqual(
+ TypeChecker().redefine_many({"foo": int, "bar": str}),
+ TypeChecker().redefine("foo", int).redefine("bar", str),
+ )
+
+ def test_remove_multiple(self):
+ self.assertEqual(
+ TypeChecker({"foo": int, "bar": str}).remove("foo", "bar"),
+ TypeChecker(),
+ )
+
+ def test_type_check_can_raise_key_error(self):
+ """
+ Make sure no one writes:
+
+ try:
+ self._type_checkers[type](...)
+ except KeyError:
+
+ ignoring the fact that the function itself can raise that.
+ """
+
+ error = KeyError("Stuff")
+
+ def raises_keyerror(checker, instance):
+ raise error
+
+ with self.assertRaises(KeyError) as context:
+ TypeChecker({"foo": raises_keyerror}).is_type(4, "foo")
+
+ self.assertIs(context.exception, error)
+
+
+class TestCustomTypes(TestCase):
+ def test_simple_type_can_be_extended(self):
+ def int_or_str_int(checker, instance):
+ if not isinstance(instance, (int, str)):
+ return False
+ try:
+ int(instance)
+ except ValueError:
+ return False
+ return True
+
+ CustomValidator = extend(
+ Draft4Validator,
+ type_checker=Draft4Validator.TYPE_CHECKER.redefine(
+ "integer", int_or_str_int,
+ ),
+ )
+ validator = CustomValidator({"type": "integer"})
+
+ validator.validate(4)
+ validator.validate("4")
+
+ with self.assertRaises(ValidationError):
+ validator.validate(4.4)
+
+ def test_object_can_be_extended(self):
+ schema = {"type": "object"}
+
+ Point = namedtuple("Point", ["x", "y"])
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(Draft4Validator, type_checker=type_checker)
+ validator = CustomValidator(schema)
+
+ validator.validate(Point(x=4, y=5))
+
+ def test_object_extensions_require_custom_validators(self):
+ schema = {"type": "object", "required": ["x"]}
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(Draft4Validator, type_checker=type_checker)
+ validator = CustomValidator(schema)
+
+ Point = namedtuple("Point", ["x", "y"])
+ # Cannot handle required
+ with self.assertRaises(ValidationError):
+ validator.validate(Point(x=4, y=5))
+
+ def test_object_extensions_can_handle_custom_validators(self):
+ schema = {
+ "type": "object",
+ "required": ["x"],
+ "properties": {"x": {"type": "integer"}},
+ }
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(
+ Draft4Validator,
+ type_checker=type_checker,
+ validators={"required": required, "properties": properties},
+ )
+
+ validator = CustomValidator(schema)
+
+ Point = namedtuple("Point", ["x", "y"])
+ # Can now process required and properties
+ validator.validate(Point(x=4, y=5))
+
+ with self.assertRaises(ValidationError):
+ validator.validate(Point(x="not an integer", y=5))
diff --git a/contrib/python/jsonschema/py3/jsonschema/tests/test_validators.py b/contrib/python/jsonschema/py3/jsonschema/tests/test_validators.py
new file mode 100644
index 00000000000..07be4f08bc2
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/tests/test_validators.py
@@ -0,0 +1,1762 @@
+from collections import deque
+from contextlib import contextmanager
+from decimal import Decimal
+from io import BytesIO
+from unittest import TestCase
+import json
+import os
+import sys
+import tempfile
+import unittest
+
+from twisted.trial.unittest import SynchronousTestCase
+import attr
+
+from jsonschema import FormatChecker, TypeChecker, exceptions, validators
+from jsonschema.compat import PY3, pathname2url
+from jsonschema.tests._helpers import bug
+
+
+def startswith(validator, startswith, instance, schema):
+ if not instance.startswith(startswith):
+ yield exceptions.ValidationError(u"Whoops!")
+
+
+class TestCreateAndExtend(SynchronousTestCase):
+ def setUp(self):
+ self.addCleanup(
+ self.assertEqual,
+ validators.meta_schemas,
+ dict(validators.meta_schemas),
+ )
+
+ self.meta_schema = {u"$id": "some://meta/schema"}
+ self.validators = {u"startswith": startswith}
+ self.type_checker = TypeChecker()
+ self.Validator = validators.create(
+ meta_schema=self.meta_schema,
+ validators=self.validators,
+ type_checker=self.type_checker,
+ )
+
+ def test_attrs(self):
+ self.assertEqual(
+ (
+ self.Validator.VALIDATORS,
+ self.Validator.META_SCHEMA,
+ self.Validator.TYPE_CHECKER,
+ ), (
+ self.validators,
+ self.meta_schema,
+ self.type_checker,
+ ),
+ )
+
+ def test_init(self):
+ schema = {u"startswith": u"foo"}
+ self.assertEqual(self.Validator(schema).schema, schema)
+
+ def test_iter_errors(self):
+ schema = {u"startswith": u"hel"}
+ iter_errors = self.Validator(schema).iter_errors
+
+ errors = list(iter_errors(u"hello"))
+ self.assertEqual(errors, [])
+
+ expected_error = exceptions.ValidationError(
+ u"Whoops!",
+ instance=u"goodbye",
+ schema=schema,
+ validator=u"startswith",
+ validator_value=u"hel",
+ schema_path=deque([u"startswith"]),
+ )
+
+ errors = list(iter_errors(u"goodbye"))
+ self.assertEqual(len(errors), 1)
+ self.assertEqual(errors[0]._contents(), expected_error._contents())
+
+ def test_if_a_version_is_provided_it_is_registered(self):
+ Validator = validators.create(
+ meta_schema={u"$id": "something"},
+ version="my version",
+ )
+ self.addCleanup(validators.meta_schemas.pop, "something")
+ self.assertEqual(Validator.__name__, "MyVersionValidator")
+
+ def test_if_a_version_is_not_provided_it_is_not_registered(self):
+ original = dict(validators.meta_schemas)
+ validators.create(meta_schema={u"id": "id"})
+ self.assertEqual(validators.meta_schemas, original)
+
+ def test_validates_registers_meta_schema_id(self):
+ meta_schema_key = "meta schema id"
+ my_meta_schema = {u"id": meta_schema_key}
+
+ validators.create(
+ meta_schema=my_meta_schema,
+ version="my version",
+ id_of=lambda s: s.get("id", ""),
+ )
+ self.addCleanup(validators.meta_schemas.pop, meta_schema_key)
+
+ self.assertIn(meta_schema_key, validators.meta_schemas)
+
+ def test_validates_registers_meta_schema_draft6_id(self):
+ meta_schema_key = "meta schema $id"
+ my_meta_schema = {u"$id": meta_schema_key}
+
+ validators.create(
+ meta_schema=my_meta_schema,
+ version="my version",
+ )
+ self.addCleanup(validators.meta_schemas.pop, meta_schema_key)
+
+ self.assertIn(meta_schema_key, validators.meta_schemas)
+
+ def test_create_default_types(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ self.assertTrue(
+ all(
+ Validator({}).is_type(instance=instance, type=type)
+ for type, instance in [
+ (u"array", []),
+ (u"boolean", True),
+ (u"integer", 12),
+ (u"null", None),
+ (u"number", 12.0),
+ (u"object", {}),
+ (u"string", u"foo"),
+ ]
+ ),
+ )
+
+ def test_extend(self):
+ original = dict(self.Validator.VALIDATORS)
+ new = object()
+
+ Extended = validators.extend(
+ self.Validator,
+ validators={u"new": new},
+ )
+ self.assertEqual(
+ (
+ Extended.VALIDATORS,
+ Extended.META_SCHEMA,
+ Extended.TYPE_CHECKER,
+ self.Validator.VALIDATORS,
+ ), (
+ dict(original, new=new),
+ self.Validator.META_SCHEMA,
+ self.Validator.TYPE_CHECKER,
+ original,
+ ),
+ )
+
+ def test_extend_idof(self):
+ """
+ Extending a validator preserves its notion of schema IDs.
+ """
+ def id_of(schema):
+ return schema.get(u"__test__", self.Validator.ID_OF(schema))
+ correct_id = "the://correct/id/"
+ meta_schema = {
+ u"$id": "the://wrong/id/",
+ u"__test__": correct_id,
+ }
+ Original = validators.create(
+ meta_schema=meta_schema,
+ validators=self.validators,
+ type_checker=self.type_checker,
+ id_of=id_of,
+ )
+ self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id)
+
+ Derived = validators.extend(Original)
+ self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id)
+
+
+class TestLegacyTypeChecking(SynchronousTestCase):
+ def test_create_default_types(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ self.assertEqual(
+ set(Validator.DEFAULT_TYPES), {
+ u"array",
+ u"boolean",
+ u"integer",
+ u"null",
+ u"number",
+ u"object", u"string",
+ },
+ )
+ self.flushWarnings()
+
+ def test_extend(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ original = dict(Validator.VALIDATORS)
+ new = object()
+
+ Extended = validators.extend(
+ Validator,
+ validators={u"new": new},
+ )
+ self.assertEqual(
+ (
+ Extended.VALIDATORS,
+ Extended.META_SCHEMA,
+ Extended.TYPE_CHECKER,
+ Validator.VALIDATORS,
+
+ Extended.DEFAULT_TYPES,
+ Extended({}).DEFAULT_TYPES,
+ self.flushWarnings()[0]["message"],
+ ), (
+ dict(original, new=new),
+ Validator.META_SCHEMA,
+ Validator.TYPE_CHECKER,
+ original,
+
+ Validator.DEFAULT_TYPES,
+ Validator.DEFAULT_TYPES,
+ self.flushWarnings()[0]["message"],
+ ),
+ )
+
+ def test_types_redefines_the_validators_type_checker(self):
+ schema = {"type": "string"}
+ self.assertFalse(validators.Draft7Validator(schema).is_valid(12))
+
+ validator = validators.Draft7Validator(
+ schema,
+ types={"string": (str, int)},
+ )
+ self.assertTrue(validator.is_valid(12))
+ self.flushWarnings()
+
+ def test_providing_default_types_warns(self):
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The default_types argument is deprecated. "
+ "Use the type_checker argument instead."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=validators.create,
+ meta_schema={},
+ validators={},
+ default_types={"foo": object},
+ )
+
+ def test_cannot_ask_for_default_types_with_non_default_type_checker(self):
+ """
+ We raise an error when you ask a validator with non-default
+ type checker for its DEFAULT_TYPES.
+
+ The type checker argument is new, so no one but this library
+ itself should be trying to use it, and doing so while then
+ asking for DEFAULT_TYPES makes no sense (not to mention is
+ deprecated), since type checkers are not strictly about Python
+ type.
+ """
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ type_checker=TypeChecker(),
+ )
+ with self.assertRaises(validators._DontDoThat) as e:
+ Validator.DEFAULT_TYPES
+
+ self.assertIn(
+ "DEFAULT_TYPES cannot be used on Validators using TypeCheckers",
+ str(e.exception),
+ )
+ with self.assertRaises(validators._DontDoThat):
+ Validator({}).DEFAULT_TYPES
+
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_explicit_type_checker_does_not_warn(self):
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ type_checker=TypeChecker(),
+ )
+ self.assertFalse(self.flushWarnings())
+
+ Validator({})
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_neither_does_not_warn(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ Validator({})
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_default_types_with_type_checker_errors(self):
+ with self.assertRaises(TypeError) as e:
+ validators.create(
+ meta_schema={},
+ validators={},
+ default_types={"foo": object},
+ type_checker=TypeChecker(),
+ )
+
+ self.assertIn(
+ "Do not specify default_types when providing a type checker",
+ str(e.exception),
+ )
+ self.assertFalse(self.flushWarnings())
+
+ def test_extending_a_legacy_validator_with_a_type_checker_errors(self):
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ default_types={u"array": list}
+ )
+ with self.assertRaises(TypeError) as e:
+ validators.extend(
+ Validator,
+ validators={},
+ type_checker=TypeChecker(),
+ )
+
+ self.assertIn(
+ (
+ "Cannot extend a validator created with default_types "
+ "with a type_checker. Update the validator to use a "
+ "type_checker when created."
+ ),
+ str(e.exception),
+ )
+ self.flushWarnings()
+
+ def test_extending_a_legacy_validator_does_not_rewarn(self):
+ Validator = validators.create(meta_schema={}, default_types={})
+ self.assertTrue(self.flushWarnings())
+
+ validators.extend(Validator)
+ self.assertFalse(self.flushWarnings())
+
+ def test_accessing_default_types_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ DeprecationWarning,
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ # https://tm.tl/9363 :'(
+ sys.modules[self.assertWarns.__module__].__file__,
+
+ getattr,
+ Validator,
+ "DEFAULT_TYPES",
+ )
+
+ def test_accessing_default_types_on_the_instance_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ DeprecationWarning,
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ # https://tm.tl/9363 :'(
+ sys.modules[self.assertWarns.__module__].__file__,
+
+ getattr,
+ Validator({}),
+ "DEFAULT_TYPES",
+ )
+
+ def test_providing_types_to_init_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The types argument is deprecated. "
+ "Provide a type_checker to jsonschema.validators.extend "
+ "instead."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=Validator,
+ schema={},
+ types={"bar": object},
+ )
+
+
+class TestIterErrors(TestCase):
+ def setUp(self):
+ self.validator = validators.Draft3Validator({})
+
+ def test_iter_errors(self):
+ instance = [1, 2]
+ schema = {
+ u"disallow": u"array",
+ u"enum": [["a", "b", "c"], ["d", "e", "f"]],
+ u"minItems": 3,
+ }
+
+ got = (e.message for e in self.validator.iter_errors(instance, schema))
+ expected = [
+ "%r is disallowed for [1, 2]" % (schema["disallow"],),
+ "[1, 2] is too short",
+ "[1, 2] is not one of %r" % (schema["enum"],),
+ ]
+ self.assertEqual(sorted(got), sorted(expected))
+
+ def test_iter_errors_multiple_failures_one_validator(self):
+ instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
+ schema = {
+ u"properties": {
+ "foo": {u"type": "string"},
+ "bar": {u"minItems": 2},
+ "baz": {u"maximum": 10, u"enum": [2, 4, 6, 8]},
+ },
+ }
+
+ errors = list(self.validator.iter_errors(instance, schema))
+ self.assertEqual(len(errors), 4)
+
+
+class TestValidationErrorMessages(TestCase):
+ def message_for(self, instance, schema, *args, **kwargs):
+ kwargs.setdefault("cls", validators.Draft3Validator)
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(instance, schema, *args, **kwargs)
+ return e.exception.message
+
+ def test_single_type_failure(self):
+ message = self.message_for(instance=1, schema={u"type": u"string"})
+ self.assertEqual(message, "1 is not of type %r" % u"string")
+
+ def test_single_type_list_failure(self):
+ message = self.message_for(instance=1, schema={u"type": [u"string"]})
+ self.assertEqual(message, "1 is not of type %r" % u"string")
+
+ def test_multiple_type_failure(self):
+ types = u"string", u"object"
+ message = self.message_for(instance=1, schema={u"type": list(types)})
+ self.assertEqual(message, "1 is not of type %r, %r" % types)
+
+ def test_object_without_title_type_failure(self):
+ type = {u"type": [{u"minimum": 3}]}
+ message = self.message_for(instance=1, schema={u"type": [type]})
+ self.assertEqual(message, "1 is less than the minimum of 3")
+
+ def test_object_with_named_type_failure(self):
+ schema = {u"type": [{u"name": "Foo", u"minimum": 3}]}
+ message = self.message_for(instance=1, schema=schema)
+ self.assertEqual(message, "1 is less than the minimum of 3")
+
+ def test_minimum(self):
+ message = self.message_for(instance=1, schema={"minimum": 2})
+ self.assertEqual(message, "1 is less than the minimum of 2")
+
+ def test_maximum(self):
+ message = self.message_for(instance=1, schema={"maximum": 0})
+ self.assertEqual(message, "1 is greater than the maximum of 0")
+
+ def test_dependencies_single_element(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: on}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft3Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_dependencies_list_draft3(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: [on]}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft3Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_dependencies_list_draft7(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: [on]}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft7Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_additionalItems_single_failure(self):
+ message = self.message_for(
+ instance=[2],
+ schema={u"items": [], u"additionalItems": False},
+ )
+ self.assertIn("(2 was unexpected)", message)
+
+ def test_additionalItems_multiple_failures(self):
+ message = self.message_for(
+ instance=[1, 2, 3],
+ schema={u"items": [], u"additionalItems": False}
+ )
+ self.assertIn("(1, 2, 3 were unexpected)", message)
+
+ def test_additionalProperties_single_failure(self):
+ additional = "foo"
+ schema = {u"additionalProperties": False}
+ message = self.message_for(instance={additional: 2}, schema=schema)
+ self.assertIn("(%r was unexpected)" % (additional,), message)
+
+ def test_additionalProperties_multiple_failures(self):
+ schema = {u"additionalProperties": False}
+ message = self.message_for(
+ instance=dict.fromkeys(["foo", "bar"]),
+ schema=schema,
+ )
+
+ self.assertIn(repr("foo"), message)
+ self.assertIn(repr("bar"), message)
+ self.assertIn("were unexpected)", message)
+
+ def test_const(self):
+ schema = {u"const": 12}
+ message = self.message_for(
+ instance={"foo": "bar"},
+ schema=schema,
+ cls=validators.Draft6Validator,
+ )
+ self.assertIn("12 was expected", message)
+
+ def test_contains(self):
+ schema = {u"contains": {u"const": 12}}
+ message = self.message_for(
+ instance=[2, {}, []],
+ schema=schema,
+ cls=validators.Draft6Validator,
+ )
+ self.assertIn(
+ "None of [2, {}, []] are valid under the given schema",
+ message,
+ )
+
+ def test_invalid_format_default_message(self):
+ checker = FormatChecker(formats=())
+ checker.checks(u"thing")(lambda value: False)
+
+ schema = {u"format": u"thing"}
+ message = self.message_for(
+ instance="bla",
+ schema=schema,
+ format_checker=checker,
+ )
+
+ self.assertIn(repr("bla"), message)
+ self.assertIn(repr("thing"), message)
+ self.assertIn("is not a", message)
+
+ def test_additionalProperties_false_patternProperties(self):
+ schema = {u"type": u"object",
+ u"additionalProperties": False,
+ u"patternProperties": {
+ u"^abc$": {u"type": u"string"},
+ u"^def$": {u"type": u"string"},
+ }}
+ message = self.message_for(
+ instance={u"zebra": 123},
+ schema=schema,
+ cls=validators.Draft4Validator,
+ )
+ self.assertEqual(
+ message,
+ "{} does not match any of the regexes: {}, {}".format(
+ repr(u"zebra"), repr(u"^abc$"), repr(u"^def$"),
+ ),
+ )
+ message = self.message_for(
+ instance={u"zebra": 123, u"fish": 456},
+ schema=schema,
+ cls=validators.Draft4Validator,
+ )
+ self.assertEqual(
+ message,
+ "{}, {} do not match any of the regexes: {}, {}".format(
+ repr(u"fish"), repr(u"zebra"), repr(u"^abc$"), repr(u"^def$")
+ ),
+ )
+
+ def test_False_schema(self):
+ message = self.message_for(
+ instance="something",
+ schema=False,
+ cls=validators.Draft7Validator,
+ )
+ self.assertIn("False schema does not allow 'something'", message)
+
+
+class TestValidationErrorDetails(TestCase):
+ # TODO: These really need unit tests for each individual validator, rather
+ # than just these higher level tests.
+ def test_anyOf(self):
+ instance = 5
+ schema = {
+ "anyOf": [
+ {"minimum": 20},
+ {"type": "string"},
+ ],
+ }
+
+ validator = validators.Draft4Validator(schema)
+ errors = list(validator.iter_errors(instance))
+ self.assertEqual(len(errors), 1)
+ e = errors[0]
+
+ self.assertEqual(e.validator, "anyOf")
+ self.assertEqual(e.validator_value, schema["anyOf"])
+ self.assertEqual(e.instance, instance)
+ self.assertEqual(e.schema, schema)
+ self.assertIsNone(e.parent)
+
+ self.assertEqual(e.path, deque([]))
+ self.assertEqual(e.relative_path, deque([]))
+ self.assertEqual(e.absolute_path, deque([]))
+
+ self.assertEqual(e.schema_path, deque(["anyOf"]))
+ self.assertEqual(e.relative_schema_path, deque(["anyOf"]))
+ self.assertEqual(e.absolute_schema_path, deque(["anyOf"]))
+
+ self.assertEqual(len(e.context), 2)
+
+ e1, e2 = sorted_errors(e.context)
+
+ self.assertEqual(e1.validator, "minimum")
+ self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"])
+ self.assertEqual(e1.instance, instance)
+ self.assertEqual(e1.schema, schema["anyOf"][0])
+ self.assertIs(e1.parent, e)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e1.absolute_path, deque([]))
+ self.assertEqual(e1.relative_path, deque([]))
+
+ self.assertEqual(e1.schema_path, deque([0, "minimum"]))
+ self.assertEqual(e1.relative_schema_path, deque([0, "minimum"]))
+ self.assertEqual(
+ e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]),
+ )
+
+ self.assertFalse(e1.context)
+
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"])
+ self.assertEqual(e2.instance, instance)
+ self.assertEqual(e2.schema, schema["anyOf"][1])
+ self.assertIs(e2.parent, e)
+
+ self.assertEqual(e2.path, deque([]))
+ self.assertEqual(e2.relative_path, deque([]))
+ self.assertEqual(e2.absolute_path, deque([]))
+
+ self.assertEqual(e2.schema_path, deque([1, "type"]))
+ self.assertEqual(e2.relative_schema_path, deque([1, "type"]))
+ self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"]))
+
+ self.assertEqual(len(e2.context), 0)
+
+ def test_type(self):
+ instance = {"foo": 1}
+ schema = {
+ "type": [
+ {"type": "integer"},
+ {
+ "type": "object",
+ "properties": {"foo": {"enum": [2]}},
+ },
+ ],
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = list(validator.iter_errors(instance))
+ self.assertEqual(len(errors), 1)
+ e = errors[0]
+
+ self.assertEqual(e.validator, "type")
+ self.assertEqual(e.validator_value, schema["type"])
+ self.assertEqual(e.instance, instance)
+ self.assertEqual(e.schema, schema)
+ self.assertIsNone(e.parent)
+
+ self.assertEqual(e.path, deque([]))
+ self.assertEqual(e.relative_path, deque([]))
+ self.assertEqual(e.absolute_path, deque([]))
+
+ self.assertEqual(e.schema_path, deque(["type"]))
+ self.assertEqual(e.relative_schema_path, deque(["type"]))
+ self.assertEqual(e.absolute_schema_path, deque(["type"]))
+
+ self.assertEqual(len(e.context), 2)
+
+ e1, e2 = sorted_errors(e.context)
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e1.validator_value, schema["type"][0]["type"])
+ self.assertEqual(e1.instance, instance)
+ self.assertEqual(e1.schema, schema["type"][0])
+ self.assertIs(e1.parent, e)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e1.relative_path, deque([]))
+ self.assertEqual(e1.absolute_path, deque([]))
+
+ self.assertEqual(e1.schema_path, deque([0, "type"]))
+ self.assertEqual(e1.relative_schema_path, deque([0, "type"]))
+ self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"]))
+
+ self.assertFalse(e1.context)
+
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e2.validator_value, [2])
+ self.assertEqual(e2.instance, 1)
+ self.assertEqual(e2.schema, {u"enum": [2]})
+ self.assertIs(e2.parent, e)
+
+ self.assertEqual(e2.path, deque(["foo"]))
+ self.assertEqual(e2.relative_path, deque(["foo"]))
+ self.assertEqual(e2.absolute_path, deque(["foo"]))
+
+ self.assertEqual(
+ e2.schema_path, deque([1, "properties", "foo", "enum"]),
+ )
+ self.assertEqual(
+ e2.relative_schema_path, deque([1, "properties", "foo", "enum"]),
+ )
+ self.assertEqual(
+ e2.absolute_schema_path,
+ deque(["type", 1, "properties", "foo", "enum"]),
+ )
+
+ self.assertFalse(e2.context)
+
+ def test_single_nesting(self):
+ instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
+ schema = {
+ "properties": {
+ "foo": {"type": "string"},
+ "bar": {"minItems": 2},
+ "baz": {"maximum": 10, "enum": [2, 4, 6, 8]},
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2, e3, e4 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["baz"]))
+ self.assertEqual(e3.path, deque(["baz"]))
+ self.assertEqual(e4.path, deque(["foo"]))
+
+ self.assertEqual(e1.relative_path, deque(["bar"]))
+ self.assertEqual(e2.relative_path, deque(["baz"]))
+ self.assertEqual(e3.relative_path, deque(["baz"]))
+ self.assertEqual(e4.relative_path, deque(["foo"]))
+
+ self.assertEqual(e1.absolute_path, deque(["bar"]))
+ self.assertEqual(e2.absolute_path, deque(["baz"]))
+ self.assertEqual(e3.absolute_path, deque(["baz"]))
+ self.assertEqual(e4.absolute_path, deque(["foo"]))
+
+ self.assertEqual(e1.validator, "minItems")
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e3.validator, "maximum")
+ self.assertEqual(e4.validator, "type")
+
+ def test_multiple_nesting(self):
+ instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"]
+ schema = {
+ "type": "string",
+ "items": {
+ "type": ["string", "object"],
+ "properties": {
+ "foo": {"enum": [1, 3]},
+ "bar": {
+ "type": "array",
+ "properties": {
+ "bar": {"required": True},
+ "baz": {"minItems": 2},
+ },
+ },
+ },
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e2.path, deque([0]))
+ self.assertEqual(e3.path, deque([1, "bar"]))
+ self.assertEqual(e4.path, deque([1, "bar", "bar"]))
+ self.assertEqual(e5.path, deque([1, "bar", "baz"]))
+ self.assertEqual(e6.path, deque([1, "foo"]))
+
+ self.assertEqual(e1.schema_path, deque(["type"]))
+ self.assertEqual(e2.schema_path, deque(["items", "type"]))
+ self.assertEqual(
+ list(e3.schema_path), ["items", "properties", "bar", "type"],
+ )
+ self.assertEqual(
+ list(e4.schema_path),
+ ["items", "properties", "bar", "properties", "bar", "required"],
+ )
+ self.assertEqual(
+ list(e5.schema_path),
+ ["items", "properties", "bar", "properties", "baz", "minItems"]
+ )
+ self.assertEqual(
+ list(e6.schema_path), ["items", "properties", "foo", "enum"],
+ )
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e3.validator, "type")
+ self.assertEqual(e4.validator, "required")
+ self.assertEqual(e5.validator, "minItems")
+ self.assertEqual(e6.validator, "enum")
+
+ def test_recursive(self):
+ schema = {
+ "definitions": {
+ "node": {
+ "anyOf": [{
+ "type": "object",
+ "required": ["name", "children"],
+ "properties": {
+ "name": {
+ "type": "string",
+ },
+ "children": {
+ "type": "object",
+ "patternProperties": {
+ "^.*$": {
+ "$ref": "#/definitions/node",
+ },
+ },
+ },
+ },
+ }],
+ },
+ },
+ "type": "object",
+ "required": ["root"],
+ "properties": {"root": {"$ref": "#/definitions/node"}},
+ }
+
+ instance = {
+ "root": {
+ "name": "root",
+ "children": {
+ "a": {
+ "name": "a",
+ "children": {
+ "ab": {
+ "name": "ab",
+ # missing "children"
+ },
+ },
+ },
+ },
+ },
+ }
+ validator = validators.Draft4Validator(schema)
+
+ e, = validator.iter_errors(instance)
+ self.assertEqual(e.absolute_path, deque(["root"]))
+ self.assertEqual(
+ e.absolute_schema_path, deque(["properties", "root", "anyOf"]),
+ )
+
+ e1, = e.context
+ self.assertEqual(e1.absolute_path, deque(["root", "children", "a"]))
+ self.assertEqual(
+ e1.absolute_schema_path, deque(
+ [
+ "properties",
+ "root",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ ],
+ ),
+ )
+
+ e2, = e1.context
+ self.assertEqual(
+ e2.absolute_path, deque(
+ ["root", "children", "a", "children", "ab"],
+ ),
+ )
+ self.assertEqual(
+ e2.absolute_schema_path, deque(
+ [
+ "properties",
+ "root",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ ],
+ ),
+ )
+
+ def test_additionalProperties(self):
+ instance = {"bar": "bar", "foo": 2}
+ schema = {"additionalProperties": {"type": "integer", "minimum": 5}}
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["foo"]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_patternProperties(self):
+ instance = {"bar": 1, "foo": 2}
+ schema = {
+ "patternProperties": {
+ "bar": {"type": "string"},
+ "foo": {"minimum": 5},
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["foo"]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_additionalItems(self):
+ instance = ["foo", 1]
+ schema = {
+ "items": [],
+ "additionalItems": {"type": "integer", "minimum": 5},
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([0]))
+ self.assertEqual(e2.path, deque([1]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_additionalItems_with_items(self):
+ instance = ["foo", "bar", 1]
+ schema = {
+ "items": [{}],
+ "additionalItems": {"type": "integer", "minimum": 5},
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([1]))
+ self.assertEqual(e2.path, deque([2]))
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_propertyNames(self):
+ instance = {"foo": 12}
+ schema = {"propertyNames": {"not": {"const": "foo"}}}
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(instance)
+
+ self.assertEqual(error.validator, "not")
+ self.assertEqual(
+ error.message,
+ "%r is not allowed for %r" % ({"const": "foo"}, "foo"),
+ )
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.schema_path, deque(["propertyNames", "not"]))
+
+ def test_if_then(self):
+ schema = {
+ "if": {"const": 12},
+ "then": {"const": 13},
+ }
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(12)
+
+ self.assertEqual(error.validator, "const")
+ self.assertEqual(error.message, "13 was expected")
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.schema_path, deque(["if", "then", "const"]))
+
+ def test_if_else(self):
+ schema = {
+ "if": {"const": 12},
+ "else": {"const": 13},
+ }
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(15)
+
+ self.assertEqual(error.validator, "const")
+ self.assertEqual(error.message, "13 was expected")
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.schema_path, deque(["if", "else", "const"]))
+
+ def test_boolean_schema_False(self):
+ validator = validators.Draft7Validator(False)
+ error, = validator.iter_errors(12)
+
+ self.assertEqual(
+ (
+ error.message,
+ error.validator,
+ error.validator_value,
+ error.instance,
+ error.schema,
+ error.schema_path,
+ ),
+ (
+ "False schema does not allow 12",
+ None,
+ None,
+ 12,
+ False,
+ deque([]),
+ ),
+ )
+
+ def test_ref(self):
+ ref, schema = "someRef", {"additionalProperties": {"type": "integer"}}
+ validator = validators.Draft7Validator(
+ {"$ref": ref},
+ resolver=validators.RefResolver("", {}, store={ref: schema}),
+ )
+ error, = validator.iter_errors({"foo": "notAnInteger"})
+
+ self.assertEqual(
+ (
+ error.message,
+ error.validator,
+ error.validator_value,
+ error.instance,
+ error.absolute_path,
+ error.schema,
+ error.schema_path,
+ ),
+ (
+ "'notAnInteger' is not of type 'integer'",
+ "type",
+ "integer",
+ "notAnInteger",
+ deque(["foo"]),
+ {"type": "integer"},
+ deque(["additionalProperties", "type"]),
+ ),
+ )
+
+
+class MetaSchemaTestsMixin(object):
+ # TODO: These all belong upstream
+ def test_invalid_properties(self):
+ with self.assertRaises(exceptions.SchemaError):
+ self.Validator.check_schema({"properties": {"test": object()}})
+
+ def test_minItems_invalid_string(self):
+ with self.assertRaises(exceptions.SchemaError):
+ # needs to be an integer
+ self.Validator.check_schema({"minItems": "1"})
+
+ def test_enum_allows_empty_arrays(self):
+ """
+ Technically, all the spec says is they SHOULD have elements, not MUST.
+
+ See https://github.com/Julian/jsonschema/issues/529.
+ """
+ self.Validator.check_schema({"enum": []})
+
+ def test_enum_allows_non_unique_items(self):
+ """
+ Technically, all the spec says is they SHOULD be unique, not MUST.
+
+ See https://github.com/Julian/jsonschema/issues/529.
+ """
+ self.Validator.check_schema({"enum": [12, 12]})
+
+
+class ValidatorTestMixin(MetaSchemaTestsMixin, object):
+ def test_valid_instances_are_valid(self):
+ schema, instance = self.valid
+ self.assertTrue(self.Validator(schema).is_valid(instance))
+
+ def test_invalid_instances_are_not_valid(self):
+ schema, instance = self.invalid
+ self.assertFalse(self.Validator(schema).is_valid(instance))
+
+ def test_non_existent_properties_are_ignored(self):
+ self.Validator({object(): object()}).validate(instance=object())
+
+ def test_it_creates_a_ref_resolver_if_not_provided(self):
+ self.assertIsInstance(
+ self.Validator({}).resolver,
+ validators.RefResolver,
+ )
+
+ def test_it_delegates_to_a_ref_resolver(self):
+ ref, schema = "someCoolRef", {"type": "integer"}
+ resolver = validators.RefResolver("", {}, store={ref: schema})
+ validator = self.Validator({"$ref": ref}, resolver=resolver)
+
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate(None)
+
+ def test_it_delegates_to_a_legacy_ref_resolver(self):
+ """
+ Legacy RefResolvers support only the context manager form of
+ resolution.
+ """
+
+ class LegacyRefResolver(object):
+ @contextmanager
+ def resolving(this, ref):
+ self.assertEqual(ref, "the ref")
+ yield {"type": "integer"}
+
+ resolver = LegacyRefResolver()
+ schema = {"$ref": "the ref"}
+
+ with self.assertRaises(exceptions.ValidationError):
+ self.Validator(schema, resolver=resolver).validate(None)
+
+ def test_is_type_is_true_for_valid_type(self):
+ self.assertTrue(self.Validator({}).is_type("foo", "string"))
+
+ def test_is_type_is_false_for_invalid_type(self):
+ self.assertFalse(self.Validator({}).is_type("foo", "array"))
+
+ def test_is_type_evades_bool_inheriting_from_int(self):
+ self.assertFalse(self.Validator({}).is_type(True, "integer"))
+ self.assertFalse(self.Validator({}).is_type(True, "number"))
+
+ @unittest.skipIf(PY3, "In Python 3 json.load always produces unicode")
+ def test_string_a_bytestring_is_a_string(self):
+ self.Validator({"type": "string"}).validate(b"foo")
+
+ def test_patterns_can_be_native_strings(self):
+ """
+ See https://github.com/Julian/jsonschema/issues/611.
+ """
+ self.Validator({"pattern": "foo"}).validate("foo")
+
+ def test_it_can_validate_with_decimals(self):
+ schema = {"items": {"type": "number"}}
+ Validator = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "number",
+ lambda checker, thing: isinstance(
+ thing, (int, float, Decimal),
+ ) and not isinstance(thing, bool),
+ )
+ )
+
+ validator = Validator(schema)
+ validator.validate([1, 1.1, Decimal(1) / Decimal(8)])
+
+ invalid = ["foo", {}, [], True, None]
+ self.assertEqual(
+ [error.instance for error in validator.iter_errors(invalid)],
+ invalid,
+ )
+
+ def test_it_returns_true_for_formats_it_does_not_know_about(self):
+ validator = self.Validator(
+ {"format": "carrot"}, format_checker=FormatChecker(),
+ )
+ validator.validate("bugs")
+
+ def test_it_does_not_validate_formats_by_default(self):
+ validator = self.Validator({})
+ self.assertIsNone(validator.format_checker)
+
+ def test_it_validates_formats_if_a_checker_is_provided(self):
+ checker = FormatChecker()
+ bad = ValueError("Bad!")
+
+ @checker.checks("foo", raises=ValueError)
+ def check(value):
+ if value == "good":
+ return True
+ elif value == "bad":
+ raise bad
+ else: # pragma: no cover
+ self.fail("What is {}? [Baby Don't Hurt Me]".format(value))
+
+ validator = self.Validator(
+ {"format": "foo"}, format_checker=checker,
+ )
+
+ validator.validate("good")
+ with self.assertRaises(exceptions.ValidationError) as cm:
+ validator.validate("bad")
+
+ # Make sure original cause is attached
+ self.assertIs(cm.exception.cause, bad)
+
+ def test_non_string_custom_type(self):
+ non_string_type = object()
+ schema = {"type": [non_string_type]}
+ Crazy = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ non_string_type,
+ lambda checker, thing: isinstance(thing, int),
+ )
+ )
+ Crazy(schema).validate(15)
+
+ def test_it_properly_formats_tuples_in_errors(self):
+ """
+ A tuple instance properly formats validation errors for uniqueItems.
+
+ See https://github.com/Julian/jsonschema/pull/224
+ """
+ TupleValidator = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "array",
+ lambda checker, thing: isinstance(thing, tuple),
+ )
+ )
+ with self.assertRaises(exceptions.ValidationError) as e:
+ TupleValidator({"uniqueItems": True}).validate((1, 1))
+ self.assertIn("(1, 1) has non-unique elements", str(e.exception))
+
+
+class AntiDraft6LeakMixin(object):
+ """
+ Make sure functionality from draft 6 doesn't leak backwards in time.
+ """
+
+ def test_True_is_not_a_schema(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ self.Validator.check_schema(True)
+ self.assertIn("True is not of type", str(e.exception))
+
+ def test_False_is_not_a_schema(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ self.Validator.check_schema(False)
+ self.assertIn("False is not of type", str(e.exception))
+
+ @unittest.skip(bug(523))
+ def test_True_is_not_a_schema_even_if_you_forget_to_check(self):
+ resolver = validators.RefResolver("", {})
+ with self.assertRaises(Exception) as e:
+ self.Validator(True, resolver=resolver).validate(12)
+ self.assertNotIsInstance(e.exception, exceptions.ValidationError)
+
+ @unittest.skip(bug(523))
+ def test_False_is_not_a_schema_even_if_you_forget_to_check(self):
+ resolver = validators.RefResolver("", {})
+ with self.assertRaises(Exception) as e:
+ self.Validator(False, resolver=resolver).validate(12)
+ self.assertNotIsInstance(e.exception, exceptions.ValidationError)
+
+
+class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
+ Validator = validators.Draft3Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+ def test_any_type_is_valid_for_type_any(self):
+ validator = self.Validator({"type": "any"})
+ validator.validate(object())
+
+ def test_any_type_is_redefinable(self):
+ """
+ Sigh, because why not.
+ """
+ Crazy = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "any", lambda checker, thing: isinstance(thing, int),
+ )
+ )
+ validator = Crazy({"type": "any"})
+ validator.validate(12)
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate("foo")
+
+ def test_is_type_is_true_for_any_type(self):
+ self.assertTrue(self.Validator({}).is_valid(object(), {"type": "any"}))
+
+ def test_is_type_does_not_evade_bool_if_it_is_being_tested(self):
+ self.assertTrue(self.Validator({}).is_type(True, "boolean"))
+ self.assertTrue(self.Validator({}).is_valid(True, {"type": "any"}))
+
+
+class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
+ Validator = validators.Draft4Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestDraft6Validator(ValidatorTestMixin, TestCase):
+ Validator = validators.Draft6Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestDraft7Validator(ValidatorTestMixin, TestCase):
+ Validator = validators.Draft7Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestValidatorFor(SynchronousTestCase):
+ def test_draft_3(self):
+ schema = {"$schema": "http://json-schema.org/draft-03/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft3Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-03/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft3Validator,
+ )
+
+ def test_draft_4(self):
+ schema = {"$schema": "http://json-schema.org/draft-04/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft4Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-04/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft4Validator,
+ )
+
+ def test_draft_6(self):
+ schema = {"$schema": "http://json-schema.org/draft-06/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft6Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-06/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft6Validator,
+ )
+
+ def test_draft_7(self):
+ schema = {"$schema": "http://json-schema.org/draft-07/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-07/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ def test_True(self):
+ self.assertIs(
+ validators.validator_for(True),
+ validators._LATEST_VERSION,
+ )
+
+ def test_False(self):
+ self.assertIs(
+ validators.validator_for(False),
+ validators._LATEST_VERSION,
+ )
+
+ def test_custom_validator(self):
+ Validator = validators.create(
+ meta_schema={"id": "meta schema id"},
+ version="12",
+ id_of=lambda s: s.get("id", ""),
+ )
+ schema = {"$schema": "meta schema id"}
+ self.assertIs(
+ validators.validator_for(schema),
+ Validator,
+ )
+
+ def test_custom_validator_draft6(self):
+ Validator = validators.create(
+ meta_schema={"$id": "meta schema $id"},
+ version="13",
+ )
+ schema = {"$schema": "meta schema $id"}
+ self.assertIs(
+ validators.validator_for(schema),
+ Validator,
+ )
+
+ def test_validator_for_jsonschema_default(self):
+ self.assertIs(validators.validator_for({}), validators._LATEST_VERSION)
+
+ def test_validator_for_custom_default(self):
+ self.assertIs(validators.validator_for({}, default=None), None)
+
+ def test_warns_if_meta_schema_specified_was_not_found(self):
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The metaschema specified by $schema was not found. "
+ "Using the latest draft to validate, but this will raise "
+ "an error in the future."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=validators.validator_for,
+ schema={u"$schema": "unknownSchema"},
+ default={},
+ )
+
+ def test_does_not_warn_if_meta_schema_is_unspecified(self):
+ validators.validator_for(schema={}, default={}),
+ self.assertFalse(self.flushWarnings())
+
+
+class TestValidate(SynchronousTestCase):
+ def assertUses(self, schema, Validator):
+ result = []
+ self.patch(Validator, "check_schema", result.append)
+ validators.validate({}, schema)
+ self.assertEqual(result, [schema])
+
+ def test_draft3_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-03/schema#"},
+ Validator=validators.Draft3Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-03/schema"},
+ Validator=validators.Draft3Validator,
+ )
+
+ def test_draft4_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-04/schema#"},
+ Validator=validators.Draft4Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-04/schema"},
+ Validator=validators.Draft4Validator,
+ )
+
+ def test_draft6_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-06/schema#"},
+ Validator=validators.Draft6Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-06/schema"},
+ Validator=validators.Draft6Validator,
+ )
+
+ def test_draft7_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema#"},
+ Validator=validators.Draft7Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema"},
+ Validator=validators.Draft7Validator,
+ )
+
+ def test_draft7_validator_is_the_default(self):
+ self.assertUses(schema={}, Validator=validators.Draft7Validator)
+
+ def test_validation_error_message(self):
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(12, {"type": "string"})
+ self.assertRegexpMatches(
+ str(e.exception),
+ "(?s)Failed validating u?'.*' in schema.*On instance",
+ )
+
+ def test_schema_error_message(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ validators.validate(12, {"type": 12})
+ self.assertRegexpMatches(
+ str(e.exception),
+ "(?s)Failed validating u?'.*' in metaschema.*On schema",
+ )
+
+ def test_it_uses_best_match(self):
+ # This is a schema that best_match will recurse into
+ schema = {"oneOf": [{"type": "string"}, {"type": "array"}]}
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(12, schema)
+ self.assertIn("12 is not of type", str(e.exception))
+
+
+class TestRefResolver(SynchronousTestCase):
+
+ base_uri = ""
+ stored_uri = "foo://stored"
+ stored_schema = {"stored": "schema"}
+
+ def setUp(self):
+ self.referrer = {}
+ self.store = {self.stored_uri: self.stored_schema}
+ self.resolver = validators.RefResolver(
+ self.base_uri, self.referrer, self.store,
+ )
+
+ def test_it_does_not_retrieve_schema_urls_from_the_network(self):
+ ref = validators.Draft3Validator.META_SCHEMA["id"]
+ self.patch(
+ self.resolver,
+ "resolve_remote",
+ lambda *args, **kwargs: self.fail("Should not have been called!"),
+ )
+ with self.resolver.resolving(ref) as resolved:
+ pass
+ self.assertEqual(resolved, validators.Draft3Validator.META_SCHEMA)
+
+ def test_it_resolves_local_refs(self):
+ ref = "#/properties/foo"
+ self.referrer["properties"] = {"foo": object()}
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, self.referrer["properties"]["foo"])
+
+ def test_it_resolves_local_refs_with_id(self):
+ schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}}
+ resolver = validators.RefResolver.from_schema(
+ schema,
+ id_of=lambda schema: schema.get(u"id", u""),
+ )
+ with resolver.resolving("#/a") as resolved:
+ self.assertEqual(resolved, schema["a"])
+ with resolver.resolving("http://bar/schema#/a") as resolved:
+ self.assertEqual(resolved, schema["a"])
+
+ def test_it_retrieves_stored_refs(self):
+ with self.resolver.resolving(self.stored_uri) as resolved:
+ self.assertIs(resolved, self.stored_schema)
+
+ self.resolver.store["cached_ref"] = {"foo": 12}
+ with self.resolver.resolving("cached_ref#/foo") as resolved:
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_unstored_refs_via_requests(self):
+ ref = "http://bar#baz"
+ schema = {"baz": 12}
+
+ if "requests" in sys.modules:
+ self.addCleanup(
+ sys.modules.__setitem__, "requests", sys.modules["requests"],
+ )
+ sys.modules["requests"] = ReallyFakeRequests({"http://bar": schema})
+
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_unstored_refs_via_urlopen(self):
+ ref = "http://bar#baz"
+ schema = {"baz": 12}
+
+ if "requests" in sys.modules:
+ self.addCleanup(
+ sys.modules.__setitem__, "requests", sys.modules["requests"],
+ )
+ sys.modules["requests"] = None
+
+ @contextmanager
+ def fake_urlopen(url):
+ self.assertEqual(url, "http://bar")
+ yield BytesIO(json.dumps(schema).encode("utf8"))
+
+ self.addCleanup(setattr, validators, "urlopen", validators.urlopen)
+ validators.urlopen = fake_urlopen
+
+ with self.resolver.resolving(ref) as resolved:
+ pass
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_local_refs_via_urlopen(self):
+ with tempfile.NamedTemporaryFile(delete=False, mode="wt") as tempf:
+ self.addCleanup(os.remove, tempf.name)
+ json.dump({"foo": "bar"}, tempf)
+
+ ref = "file://{}#foo".format(pathname2url(tempf.name))
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, "bar")
+
+ def test_it_can_construct_a_base_uri_from_a_schema(self):
+ schema = {"id": "foo"}
+ resolver = validators.RefResolver.from_schema(
+ schema,
+ id_of=lambda schema: schema.get(u"id", u""),
+ )
+ self.assertEqual(resolver.base_uri, "foo")
+ self.assertEqual(resolver.resolution_scope, "foo")
+ with resolver.resolving("") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("#") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("foo") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("foo#") as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
+ schema = {}
+ resolver = validators.RefResolver.from_schema(schema)
+ self.assertEqual(resolver.base_uri, "")
+ self.assertEqual(resolver.resolution_scope, "")
+ with resolver.resolving("") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("#") as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_custom_uri_scheme_handlers(self):
+ def handler(url):
+ self.assertEqual(url, ref)
+ return schema
+
+ schema = {"foo": "bar"}
+ ref = "foo://bar"
+ resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ with resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_cache_remote_on(self):
+ response = [object()]
+
+ def handler(url):
+ try:
+ return response.pop()
+ except IndexError: # pragma: no cover
+ self.fail("Response must not have been cached!")
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver(
+ "", {}, cache_remote=True, handlers={"foo": handler},
+ )
+ with resolver.resolving(ref):
+ pass
+ with resolver.resolving(ref):
+ pass
+
+ def test_cache_remote_off(self):
+ response = [object()]
+
+ def handler(url):
+ try:
+ return response.pop()
+ except IndexError: # pragma: no cover
+ self.fail("Handler called twice!")
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver(
+ "", {}, cache_remote=False, handlers={"foo": handler},
+ )
+ with resolver.resolving(ref):
+ pass
+
+ def test_if_you_give_it_junk_you_get_a_resolution_error(self):
+ error = ValueError("Oh no! What's this?")
+
+ def handler(url):
+ raise error
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ with self.assertRaises(exceptions.RefResolutionError) as err:
+ with resolver.resolving(ref):
+ self.fail("Shouldn't get this far!") # pragma: no cover
+ self.assertEqual(err.exception, exceptions.RefResolutionError(error))
+
+ def test_helpful_error_message_on_failed_pop_scope(self):
+ resolver = validators.RefResolver("", {})
+ resolver.pop_scope()
+ with self.assertRaises(exceptions.RefResolutionError) as exc:
+ resolver.pop_scope()
+ self.assertIn("Failed to pop the scope", str(exc.exception))
+
+
+def sorted_errors(errors):
+ def key(error):
+ return (
+ [str(e) for e in error.path],
+ [str(e) for e in error.schema_path],
+ )
+ return sorted(errors, key=key)
+
+
+class ReallyFakeRequests(object):
+
+ _responses = attr.ib()
+
+ def get(self, url):
+ response = self._responses.get(url)
+ if url is None: # pragma: no cover
+ raise ValueError("Unknown URL: " + repr(url))
+ return _ReallyFakeJSONResponse(json.dumps(response))
+
+
+class _ReallyFakeJSONResponse(object):
+
+ _response = attr.ib()
+
+ def json(self):
+ return json.loads(self._response)
diff --git a/contrib/python/jsonschema/py3/jsonschema/validators.py b/contrib/python/jsonschema/py3/jsonschema/validators.py
new file mode 100644
index 00000000000..1dc420c70d2
--- /dev/null
+++ b/contrib/python/jsonschema/py3/jsonschema/validators.py
@@ -0,0 +1,970 @@
+"""
+Creation and extension of validators, with implementations for existing drafts.
+"""
+from __future__ import division
+
+from warnings import warn
+import contextlib
+import json
+import numbers
+
+from six import add_metaclass
+
+from jsonschema import (
+ _legacy_validators,
+ _types,
+ _utils,
+ _validators,
+ exceptions,
+)
+from jsonschema.compat import (
+ Sequence,
+ int_types,
+ iteritems,
+ lru_cache,
+ str_types,
+ unquote,
+ urldefrag,
+ urljoin,
+ urlopen,
+ urlsplit,
+)
+
+# Sigh. https://gitlab.com/pycqa/flake8/issues/280
+# https://github.com/pyga/ebb-lint/issues/7
+# Imported for backwards compatibility.
+from jsonschema.exceptions import ErrorTree
+ErrorTree
+
+
+class _DontDoThat(Exception):
+ """
+ Raised when a Validators with non-default type checker is misused.
+
+ Asking one for DEFAULT_TYPES doesn't make sense, since type checkers
+ exist for the unrepresentable cases where DEFAULT_TYPES can't
+ represent the type relationship.
+ """
+
+ def __str__(self):
+ return "DEFAULT_TYPES cannot be used on Validators using TypeCheckers"
+
+
+validators = {}
+meta_schemas = _utils.URIDict()
+
+
+def _generate_legacy_type_checks(types=()):
+ """
+ Generate newer-style type checks out of JSON-type-name-to-type mappings.
+
+ Arguments:
+
+ types (dict):
+
+ A mapping of type names to their Python types
+
+ Returns:
+
+ A dictionary of definitions to pass to `TypeChecker`
+ """
+ types = dict(types)
+
+ def gen_type_check(pytypes):
+ pytypes = _utils.flatten(pytypes)
+
+ def type_check(checker, instance):
+ if isinstance(instance, bool):
+ if bool not in pytypes:
+ return False
+ return isinstance(instance, pytypes)
+
+ return type_check
+
+ definitions = {}
+ for typename, pytypes in iteritems(types):
+ definitions[typename] = gen_type_check(pytypes)
+
+ return definitions
+
+
+_DEPRECATED_DEFAULT_TYPES = {
+ u"array": list,
+ u"boolean": bool,
+ u"integer": int_types,
+ u"null": type(None),
+ u"number": numbers.Number,
+ u"object": dict,
+ u"string": str_types,
+}
+_TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES = _types.TypeChecker(
+ type_checkers=_generate_legacy_type_checks(_DEPRECATED_DEFAULT_TYPES),
+)
+
+
+def validates(version):
+ """
+ Register the decorated validator for a ``version`` of the specification.
+
+ Registered validators and their meta schemas will be considered when
+ parsing ``$schema`` properties' URIs.
+
+ Arguments:
+
+ version (str):
+
+ An identifier to use as the version's name
+
+ Returns:
+
+ collections.Callable:
+
+ a class decorator to decorate the validator with the version
+ """
+
+ def _validates(cls):
+ validators[version] = cls
+ meta_schema_id = cls.ID_OF(cls.META_SCHEMA)
+ if meta_schema_id:
+ meta_schemas[meta_schema_id] = cls
+ return cls
+ return _validates
+
+
+def _DEFAULT_TYPES(self):
+ if self._CREATED_WITH_DEFAULT_TYPES is None:
+ raise _DontDoThat()
+
+ warn(
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self._DEFAULT_TYPES
+
+
+class _DefaultTypesDeprecatingMetaClass(type):
+ DEFAULT_TYPES = property(_DEFAULT_TYPES)
+
+
+def _id_of(schema):
+ if schema is True or schema is False:
+ return u""
+ return schema.get(u"$id", u"")
+
+
+def create(
+ meta_schema,
+ validators=(),
+ version=None,
+ default_types=None,
+ type_checker=None,
+ id_of=_id_of,
+):
+ """
+ Create a new validator class.
+
+ Arguments:
+
+ meta_schema (collections.Mapping):
+
+ the meta schema for the new validator class
+
+ validators (collections.Mapping):
+
+ a mapping from names to callables, where each callable will
+ validate the schema property with the given name.
+
+ Each callable should take 4 arguments:
+
+ 1. a validator instance,
+ 2. the value of the property being validated within the
+ instance
+ 3. the instance
+ 4. the schema
+
+ version (str):
+
+ an identifier for the version that this validator class will
+ validate. If provided, the returned validator class will
+ have its ``__name__`` set to include the version, and also
+ will have `jsonschema.validators.validates` automatically
+ called for the given version.
+
+ type_checker (jsonschema.TypeChecker):
+
+ a type checker, used when applying the :validator:`type` validator.
+
+ If unprovided, a `jsonschema.TypeChecker` will be created
+ with a set of default types typical of JSON Schema drafts.
+
+ default_types (collections.Mapping):
+
+ .. deprecated:: 3.0.0
+
+ Please use the type_checker argument instead.
+
+ If set, it provides mappings of JSON types to Python types
+ that will be converted to functions and redefined in this
+ object's `jsonschema.TypeChecker`.
+
+ id_of (collections.Callable):
+
+ A function that given a schema, returns its ID.
+
+ Returns:
+
+ a new `jsonschema.IValidator` class
+ """
+
+ if default_types is not None:
+ if type_checker is not None:
+ raise TypeError(
+ "Do not specify default_types when providing a type checker.",
+ )
+ _created_with_default_types = True
+ warn(
+ (
+ "The default_types argument is deprecated. "
+ "Use the type_checker argument instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ type_checker = _types.TypeChecker(
+ type_checkers=_generate_legacy_type_checks(default_types),
+ )
+ else:
+ default_types = _DEPRECATED_DEFAULT_TYPES
+ if type_checker is None:
+ _created_with_default_types = False
+ type_checker = _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES
+ elif type_checker is _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES:
+ _created_with_default_types = False
+ else:
+ _created_with_default_types = None
+
+ @add_metaclass(_DefaultTypesDeprecatingMetaClass)
+ class Validator(object):
+
+ VALIDATORS = dict(validators)
+ META_SCHEMA = dict(meta_schema)
+ TYPE_CHECKER = type_checker
+ ID_OF = staticmethod(id_of)
+
+ DEFAULT_TYPES = property(_DEFAULT_TYPES)
+ _DEFAULT_TYPES = dict(default_types)
+ _CREATED_WITH_DEFAULT_TYPES = _created_with_default_types
+
+ def __init__(
+ self,
+ schema,
+ types=(),
+ resolver=None,
+ format_checker=None,
+ ):
+ if types:
+ warn(
+ (
+ "The types argument is deprecated. Provide "
+ "a type_checker to jsonschema.validators.extend "
+ "instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ self.TYPE_CHECKER = self.TYPE_CHECKER.redefine_many(
+ _generate_legacy_type_checks(types),
+ )
+
+ if resolver is None:
+ resolver = RefResolver.from_schema(schema, id_of=id_of)
+
+ self.resolver = resolver
+ self.format_checker = format_checker
+ self.schema = schema
+
+ @classmethod
+ def check_schema(cls, schema):
+ for error in cls(cls.META_SCHEMA).iter_errors(schema):
+ raise exceptions.SchemaError.create_from(error)
+
+ def iter_errors(self, instance, _schema=None):
+ if _schema is None:
+ _schema = self.schema
+
+ if _schema is True:
+ return
+ elif _schema is False:
+ yield exceptions.ValidationError(
+ "False schema does not allow %r" % (instance,),
+ validator=None,
+ validator_value=None,
+ instance=instance,
+ schema=_schema,
+ )
+ return
+
+ scope = id_of(_schema)
+ if scope:
+ self.resolver.push_scope(scope)
+ try:
+ ref = _schema.get(u"$ref")
+ if ref is not None:
+ validators = [(u"$ref", ref)]
+ else:
+ validators = iteritems(_schema)
+
+ for k, v in validators:
+ validator = self.VALIDATORS.get(k)
+ if validator is None:
+ continue
+
+ errors = validator(self, v, instance, _schema) or ()
+ for error in errors:
+ # set details if not already set by the called fn
+ error._set(
+ validator=k,
+ validator_value=v,
+ instance=instance,
+ schema=_schema,
+ )
+ if k != u"$ref":
+ error.schema_path.appendleft(k)
+ yield error
+ finally:
+ if scope:
+ self.resolver.pop_scope()
+
+ def descend(self, instance, schema, path=None, schema_path=None):
+ for error in self.iter_errors(instance, schema):
+ if path is not None:
+ error.path.appendleft(path)
+ if schema_path is not None:
+ error.schema_path.appendleft(schema_path)
+ yield error
+
+ def validate(self, *args, **kwargs):
+ for error in self.iter_errors(*args, **kwargs):
+ raise error
+
+ def is_type(self, instance, type):
+ try:
+ return self.TYPE_CHECKER.is_type(instance, type)
+ except exceptions.UndefinedTypeCheck:
+ raise exceptions.UnknownType(type, instance, self.schema)
+
+ def is_valid(self, instance, _schema=None):
+ error = next(self.iter_errors(instance, _schema), None)
+ return error is None
+
+ if version is not None:
+ Validator = validates(version)(Validator)
+ Validator.__name__ = version.title().replace(" ", "") + "Validator"
+
+ return Validator
+
+
+def extend(validator, validators=(), version=None, type_checker=None):
+ """
+ Create a new validator class by extending an existing one.
+
+ Arguments:
+
+ validator (jsonschema.IValidator):
+
+ an existing validator class
+
+ validators (collections.Mapping):
+
+ a mapping of new validator callables to extend with, whose
+ structure is as in `create`.
+
+ .. note::
+
+ Any validator callables with the same name as an
+ existing one will (silently) replace the old validator
+ callable entirely, effectively overriding any validation
+ done in the "parent" validator class.
+
+ If you wish to instead extend the behavior of a parent's
+ validator callable, delegate and call it directly in
+ the new validator function by retrieving it using
+ ``OldValidator.VALIDATORS["validator_name"]``.
+
+ version (str):
+
+ a version for the new validator class
+
+ type_checker (jsonschema.TypeChecker):
+
+ a type checker, used when applying the :validator:`type` validator.
+
+ If unprovided, the type checker of the extended
+ `jsonschema.IValidator` will be carried along.`
+
+ Returns:
+
+ a new `jsonschema.IValidator` class extending the one provided
+
+ .. note:: Meta Schemas
+
+ The new validator class will have its parent's meta schema.
+
+ If you wish to change or extend the meta schema in the new
+ validator class, modify ``META_SCHEMA`` directly on the returned
+ class. Note that no implicit copying is done, so a copy should
+ likely be made before modifying it, in order to not affect the
+ old validator.
+ """
+
+ all_validators = dict(validator.VALIDATORS)
+ all_validators.update(validators)
+
+ if type_checker is None:
+ type_checker = validator.TYPE_CHECKER
+ elif validator._CREATED_WITH_DEFAULT_TYPES:
+ raise TypeError(
+ "Cannot extend a validator created with default_types "
+ "with a type_checker. Update the validator to use a "
+ "type_checker when created."
+ )
+ return create(
+ meta_schema=validator.META_SCHEMA,
+ validators=all_validators,
+ version=version,
+ type_checker=type_checker,
+ id_of=validator.ID_OF,
+ )
+
+
+Draft3Validator = create(
+ meta_schema=_utils.load_schema("draft3"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"dependencies": _legacy_validators.dependencies_draft3,
+ u"disallow": _legacy_validators.disallow_draft3,
+ u"divisibleBy": _validators.multipleOf,
+ u"enum": _validators.enum,
+ u"extends": _legacy_validators.extends_draft3,
+ u"format": _validators.format,
+ u"items": _legacy_validators.items_draft3_draft4,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maximum": _legacy_validators.maximum_draft3_draft4,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minimum": _legacy_validators.minimum_draft3_draft4,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _legacy_validators.properties_draft3,
+ u"type": _legacy_validators.type_draft3,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft3_type_checker,
+ version="draft3",
+ id_of=lambda schema: schema.get(u"id", ""),
+)
+
+Draft4Validator = create(
+ meta_schema=_utils.load_schema("draft4"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"format": _validators.format,
+ u"items": _legacy_validators.items_draft3_draft4,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _legacy_validators.maximum_draft3_draft4,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _legacy_validators.minimum_draft3_draft4,
+ u"multipleOf": _validators.multipleOf,
+ u"not": _validators.not_,
+ u"oneOf": _validators.oneOf,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft4_type_checker,
+ version="draft4",
+ id_of=lambda schema: schema.get(u"id", ""),
+)
+
+Draft6Validator = create(
+ meta_schema=_utils.load_schema("draft6"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"not": _validators.not_,
+ u"oneOf": _validators.oneOf,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft6_type_checker,
+ version="draft6",
+)
+
+Draft7Validator = create(
+ meta_schema=_utils.load_schema("draft7"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"if": _validators.if_,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"oneOf": _validators.oneOf,
+ u"not": _validators.not_,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft7_type_checker,
+ version="draft7",
+)
+
+_LATEST_VERSION = Draft7Validator
+
+
+class RefResolver(object):
+ """
+ Resolve JSON References.
+
+ Arguments:
+
+ base_uri (str):
+
+ The URI of the referring document
+
+ referrer:
+
+ The actual referring document
+
+ store (dict):
+
+ A mapping from URIs to documents to cache
+
+ cache_remote (bool):
+
+ Whether remote refs should be cached after first resolution
+
+ handlers (dict):
+
+ A mapping from URI schemes to functions that should be used
+ to retrieve them
+
+ urljoin_cache (:func:`functools.lru_cache`):
+
+ A cache that will be used for caching the results of joining
+ the resolution scope to subscopes.
+
+ remote_cache (:func:`functools.lru_cache`):
+
+ A cache that will be used for caching the results of
+ resolved remote URLs.
+
+ Attributes:
+
+ cache_remote (bool):
+
+ Whether remote refs should be cached after first resolution
+ """
+
+ def __init__(
+ self,
+ base_uri,
+ referrer,
+ store=(),
+ cache_remote=True,
+ handlers=(),
+ urljoin_cache=None,
+ remote_cache=None,
+ ):
+ if urljoin_cache is None:
+ urljoin_cache = lru_cache(1024)(urljoin)
+ if remote_cache is None:
+ remote_cache = lru_cache(1024)(self.resolve_from_url)
+
+ self.referrer = referrer
+ self.cache_remote = cache_remote
+ self.handlers = dict(handlers)
+
+ self._scopes_stack = [base_uri]
+ self.store = _utils.URIDict(
+ (id, validator.META_SCHEMA)
+ for id, validator in iteritems(meta_schemas)
+ )
+ self.store.update(store)
+ self.store[base_uri] = referrer
+
+ self._urljoin_cache = urljoin_cache
+ self._remote_cache = remote_cache
+
+ @classmethod
+ def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
+ """
+ Construct a resolver from a JSON schema object.
+
+ Arguments:
+
+ schema:
+
+ the referring schema
+
+ Returns:
+
+ `RefResolver`
+ """
+
+ return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs)
+
+ def push_scope(self, scope):
+ """
+ Enter a given sub-scope.
+
+ Treats further dereferences as being performed underneath the
+ given scope.
+ """
+ self._scopes_stack.append(
+ self._urljoin_cache(self.resolution_scope, scope),
+ )
+
+ def pop_scope(self):
+ """
+ Exit the most recent entered scope.
+
+ Treats further dereferences as being performed underneath the
+ original scope.
+
+ Don't call this method more times than `push_scope` has been
+ called.
+ """
+ try:
+ self._scopes_stack.pop()
+ except IndexError:
+ raise exceptions.RefResolutionError(
+ "Failed to pop the scope from an empty stack. "
+ "`pop_scope()` should only be called once for every "
+ "`push_scope()`"
+ )
+
+ @property
+ def resolution_scope(self):
+ """
+ Retrieve the current resolution scope.
+ """
+ return self._scopes_stack[-1]
+
+ @property
+ def base_uri(self):
+ """
+ Retrieve the current base URI, not including any fragment.
+ """
+ uri, _ = urldefrag(self.resolution_scope)
+ return uri
+
+ @contextlib.contextmanager
+ def in_scope(self, scope):
+ """
+ Temporarily enter the given scope for the duration of the context.
+ """
+ self.push_scope(scope)
+ try:
+ yield
+ finally:
+ self.pop_scope()
+
+ @contextlib.contextmanager
+ def resolving(self, ref):
+ """
+ Resolve the given ``ref`` and enter its resolution scope.
+
+ Exits the scope on exit of this context manager.
+
+ Arguments:
+
+ ref (str):
+
+ The reference to resolve
+ """
+
+ url, resolved = self.resolve(ref)
+ self.push_scope(url)
+ try:
+ yield resolved
+ finally:
+ self.pop_scope()
+
+ def resolve(self, ref):
+ """
+ Resolve the given reference.
+ """
+ url = self._urljoin_cache(self.resolution_scope, ref)
+ return url, self._remote_cache(url)
+
+ def resolve_from_url(self, url):
+ """
+ Resolve the given remote URL.
+ """
+ url, fragment = urldefrag(url)
+ try:
+ document = self.store[url]
+ except KeyError:
+ try:
+ document = self.resolve_remote(url)
+ except Exception as exc:
+ raise exceptions.RefResolutionError(exc)
+
+ return self.resolve_fragment(document, fragment)
+
+ def resolve_fragment(self, document, fragment):
+ """
+ Resolve a ``fragment`` within the referenced ``document``.
+
+ Arguments:
+
+ document:
+
+ The referent document
+
+ fragment (str):
+
+ a URI fragment to resolve within it
+ """
+
+ fragment = fragment.lstrip(u"/")
+ parts = unquote(fragment).split(u"/") if fragment else []
+
+ for part in parts:
+ part = part.replace(u"~1", u"/").replace(u"~0", u"~")
+
+ if isinstance(document, Sequence):
+ # Array indexes should be turned into integers
+ try:
+ part = int(part)
+ except ValueError:
+ pass
+ try:
+ document = document[part]
+ except (TypeError, LookupError):
+ raise exceptions.RefResolutionError(
+ "Unresolvable JSON pointer: %r" % fragment
+ )
+
+ return document
+
+ def resolve_remote(self, uri):
+ """
+ Resolve a remote ``uri``.
+
+ If called directly, does not check the store first, but after
+ retrieving the document at the specified URI it will be saved in
+ the store if :attr:`cache_remote` is True.
+
+ .. note::
+
+ If the requests_ library is present, ``jsonschema`` will use it to
+ request the remote ``uri``, so that the correct encoding is
+ detected and used.
+
+ If it isn't, or if the scheme of the ``uri`` is not ``http`` or
+ ``https``, UTF-8 is assumed.
+
+ Arguments:
+
+ uri (str):
+
+ The URI to resolve
+
+ Returns:
+
+ The retrieved document
+
+ .. _requests: https://pypi.org/project/requests/
+ """
+ try:
+ import requests
+ except ImportError:
+ requests = None
+
+ scheme = urlsplit(uri).scheme
+
+ if scheme in self.handlers:
+ result = self.handlers[scheme](uri)
+ elif scheme in [u"http", u"https"] and requests:
+ # Requests has support for detecting the correct encoding of
+ # json over http
+ result = requests.get(uri).json()
+ else:
+ # Otherwise, pass off to urllib and assume utf-8
+ with urlopen(uri) as url:
+ result = json.loads(url.read().decode("utf-8"))
+
+ if self.cache_remote:
+ self.store[uri] = result
+ return result
+
+
+def validate(instance, schema, cls=None, *args, **kwargs):
+ """
+ Validate an instance under the given schema.
+
+ >>> validate([2, 3, 4], {"maxItems": 2})
+ Traceback (most recent call last):
+ ...
+ ValidationError: [2, 3, 4] is too long
+
+ :func:`validate` will first verify that the provided schema is
+ itself valid, since not doing so can lead to less obvious error
+ messages and fail in less obvious or consistent ways.
+
+ If you know you have a valid schema already, especially if you
+ intend to validate multiple instances with the same schema, you
+ likely would prefer using the `IValidator.validate` method directly
+ on a specific validator (e.g. ``Draft7Validator.validate``).
+
+
+ Arguments:
+
+ instance:
+
+ The instance to validate
+
+ schema:
+
+ The schema to validate with
+
+ cls (IValidator):
+
+ The class that will be used to validate the instance.
+
+ If the ``cls`` argument is not provided, two things will happen
+ in accordance with the specification. First, if the schema has a
+ :validator:`$schema` property containing a known meta-schema [#]_
+ then the proper validator will be used. The specification recommends
+ that all schemas contain :validator:`$schema` properties for this
+ reason. If no :validator:`$schema` property is found, the default
+ validator class is the latest released draft.
+
+ Any other provided positional and keyword arguments will be passed
+ on when instantiating the ``cls``.
+
+ Raises:
+
+ `jsonschema.exceptions.ValidationError` if the instance
+ is invalid
+
+ `jsonschema.exceptions.SchemaError` if the schema itself
+ is invalid
+
+ .. rubric:: Footnotes
+ .. [#] known by a validator registered with
+ `jsonschema.validators.validates`
+ """
+ if cls is None:
+ cls = validator_for(schema)
+
+ cls.check_schema(schema)
+ validator = cls(schema, *args, **kwargs)
+ error = exceptions.best_match(validator.iter_errors(instance))
+ if error is not None:
+ raise error
+
+
+def validator_for(schema, default=_LATEST_VERSION):
+ """
+ Retrieve the validator class appropriate for validating the given schema.
+
+ Uses the :validator:`$schema` property that should be present in the
+ given schema to look up the appropriate validator class.
+
+ Arguments:
+
+ schema (collections.Mapping or bool):
+
+ the schema to look at
+
+ default:
+
+ the default to return if the appropriate validator class
+ cannot be determined.
+
+ If unprovided, the default is to return the latest supported
+ draft.
+ """
+ if schema is True or schema is False or u"$schema" not in schema:
+ return default
+ if schema[u"$schema"] not in meta_schemas:
+ warn(
+ (
+ "The metaschema specified by $schema was not found. "
+ "Using the latest draft to validate, but this will raise "
+ "an error in the future."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return meta_schemas.get(schema[u"$schema"], _LATEST_VERSION)
diff --git a/contrib/python/jsonschema/py3/tests/ya.make b/contrib/python/jsonschema/py3/tests/ya.make
new file mode 100644
index 00000000000..73af0ff813b
--- /dev/null
+++ b/contrib/python/jsonschema/py3/tests/ya.make
@@ -0,0 +1,32 @@
+PY3TEST()
+
+PEERDIR(
+ contrib/python/jsonschema
+ contrib/python/Twisted
+)
+
+IF (PYTHON2)
+ PEERDIR(
+ contrib/python/mock
+ )
+ENDIF()
+
+SRCDIR(contrib/python/jsonschema/py3/jsonschema/tests)
+
+PY_SRCS(
+ NAMESPACE jsonschema.tests
+ _helpers.py
+)
+
+TEST_SRCS(
+ __init__.py
+ test_cli.py
+ test_exceptions.py
+ test_format.py
+ test_types.py
+ test_validators.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/jsonschema/py3/ya.make b/contrib/python/jsonschema/py3/ya.make
new file mode 100644
index 00000000000..b40b94052b3
--- /dev/null
+++ b/contrib/python/jsonschema/py3/ya.make
@@ -0,0 +1,49 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(3.2.0)
+
+LICENSE(MIT)
+
+PEERDIR(
+ contrib/python/attrs
+ contrib/python/pyrsistent
+ contrib/python/setuptools
+ contrib/python/six
+)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ jsonschema/__init__.py
+ jsonschema/__main__.py
+ jsonschema/_format.py
+ jsonschema/_legacy_validators.py
+ jsonschema/_reflect.py
+ jsonschema/_types.py
+ jsonschema/_utils.py
+ jsonschema/_validators.py
+ jsonschema/cli.py
+ jsonschema/compat.py
+ jsonschema/exceptions.py
+ jsonschema/validators.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/jsonschema/py3/
+ .dist-info/METADATA
+ .dist-info/entry_points.txt
+ .dist-info/top_level.txt
+ jsonschema/schemas/draft3.json
+ jsonschema/schemas/draft4.json
+ jsonschema/schemas/draft6.json
+ jsonschema/schemas/draft7.json
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)
diff --git a/contrib/python/jsonschema/ya.make b/contrib/python/jsonschema/ya.make
new file mode 100644
index 00000000000..7b62c0d5ca1
--- /dev/null
+++ b/contrib/python/jsonschema/ya.make
@@ -0,0 +1,18 @@
+PY23_LIBRARY()
+
+LICENSE(Service-Py23-Proxy)
+
+IF (PYTHON2)
+ PEERDIR(contrib/python/jsonschema/py2)
+ELSE()
+ PEERDIR(contrib/python/jsonschema/py3)
+ENDIF()
+
+NO_LINT()
+
+END()
+
+RECURSE(
+ py2
+ py3
+)