summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/external/__init__.py7
-rw-r--r--lib/spack/external/jsonschema/README.rst104
-rw-r--r--lib/spack/external/jsonschema/__init__.py31
-rw-r--r--lib/spack/external/jsonschema/_format.py287
-rw-r--r--lib/spack/external/jsonschema/_legacy_validators.py141
-rw-r--r--lib/spack/external/jsonschema/_types.py188
-rw-r--r--lib/spack/external/jsonschema/_utils.py29
-rw-r--r--lib/spack/external/jsonschema/_validators.py279
-rw-r--r--lib/spack/external/jsonschema/cli.py36
-rw-r--r--lib/spack/external/jsonschema/compat.py46
-rw-r--r--lib/spack/external/jsonschema/exceptions.py164
-rw-r--r--lib/spack/external/jsonschema/schemas/draft3.json4
-rw-r--r--lib/spack/external/jsonschema/schemas/draft4.json7
-rw-r--r--lib/spack/external/jsonschema/schemas/draft6.json153
-rw-r--r--lib/spack/external/jsonschema/schemas/draft7.json166
-rw-r--r--lib/spack/external/jsonschema/tests/__init__.py0
-rw-r--r--lib/spack/external/jsonschema/tests/compat.py15
-rw-r--r--lib/spack/external/jsonschema/tests/test_cli.py110
-rw-r--r--lib/spack/external/jsonschema/tests/test_exceptions.py382
-rw-r--r--lib/spack/external/jsonschema/tests/test_format.py63
-rw-r--r--lib/spack/external/jsonschema/tests/test_jsonschema_test_suite.py290
-rw-r--r--lib/spack/external/jsonschema/tests/test_validators.py786
-rw-r--r--lib/spack/external/jsonschema/validators.py891
23 files changed, 1979 insertions, 2200 deletions
diff --git a/lib/spack/external/__init__.py b/lib/spack/external/__init__.py
index 833aa47b8d..c4c4855fe0 100644
--- a/lib/spack/external/__init__.py
+++ b/lib/spack/external/__init__.py
@@ -65,11 +65,8 @@ jsonschema
* Homepage: https://pypi.python.org/pypi/jsonschema
* Usage: An implementation of JSON Schema for Python.
-* Version: 2.4.0 (last version before functools32 dependency was added)
-* Note: functools32 doesn't support Python 2.6 or 3.0, so jsonschema
- cannot be upgraded any further until we drop 2.6.
- Also, jsonschema/validators.py has been modified NOT to try to import
- requests (see 7a1dd517b8).
+* Version: 3.2.0 (last version before 2.7 and 3.6 support was dropped)
+* Note: We don't include tests or benchmarks; just what Spack needs.
markupsafe
----------
diff --git a/lib/spack/external/jsonschema/README.rst b/lib/spack/external/jsonschema/README.rst
deleted file mode 100644
index 20c2fe6266..0000000000
--- a/lib/spack/external/jsonschema/README.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-==========
-jsonschema
-==========
-
-``jsonschema`` is an implementation of `JSON Schema <http://json-schema.org>`_
-for Python (supporting 2.6+ 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({"name" : "Eggs", "price" : 34.99}, schema)
-
- >>> validate(
- ... {"name" : "Eggs", "price" : "Invalid"}, schema
- ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- ValidationError: 'Invalid' is not of type 'number'
-
-
-Features
---------
-
-* Full support for
- `Draft 3 <https://python-jsonschema.readthedocs.org/en/latest/validate/#jsonschema.Draft3Validator>`_
- **and** `Draft 4 <https://python-jsonschema.readthedocs.org/en/latest/validate/#jsonschema.Draft4Validator>`_
- of the schema.
-
-* `Lazy validation <https://python-jsonschema.readthedocs.org/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
- that can iteratively report *all* validation errors.
-
-* Small and extensible
-
-* `Programmatic querying <https://python-jsonschema.readthedocs.org/en/latest/errors/#module-jsonschema>`_
- of which properties or items failed validation.
-
-
-Release Notes
--------------
-
-* A simple CLI was added for validation
-* Validation errors now keep full absolute paths and absolute schema paths in
- their ``absolute_path`` and ``absolute_schema_path`` attributes. The ``path``
- and ``schema_path`` attributes are deprecated in favor of ``relative_path``
- and ``relative_schema_path``\ .
-
-*Note:* Support for Python 3.2 was dropped in this release, and installation
-now uses setuptools.
-
-
-Running the Test Suite
-----------------------
-
-``jsonschema`` uses the wonderful `Tox <http://tox.readthedocs.org>`_ for its
-test suite. (It really is wonderful, if for some reason you haven't heard of
-it, you really should use it for your projects).
-
-Assuming you have ``tox`` installed (perhaps via ``pip install tox`` or your
-package manager), just run ``tox`` in the directory of your source checkout to
-run ``jsonschema``'s test suite on all of the versions of Python ``jsonschema``
-supports. Note that you'll need to have all of those versions installed in
-order to run the tests on each of them, otherwise ``tox`` will skip (and fail)
-the tests on that version.
-
-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.
-
-
-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 <http://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 woo me with beer money on
-`Gittip <https://www.gittip.com/Julian/>`_ or via Google Wallet with the email
-in my GitHub profile.
diff --git a/lib/spack/external/jsonschema/__init__.py b/lib/spack/external/jsonschema/__init__.py
index 6c099f1d8b..6dfdb9419a 100644
--- a/lib/spack/external/jsonschema/__init__.py
+++ b/lib/spack/external/jsonschema/__init__.py
@@ -4,23 +4,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, :func:`validate` is the quickest way to simply validate a given
+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,
+ FormatChecker,
+ draft3_format_checker,
+ draft4_format_checker,
+ draft6_format_checker,
+ draft7_format_checker,
)
+from jsonschema._types import TypeChecker
from jsonschema.validators import (
- Draft3Validator, Draft4Validator, RefResolver, validate
+ Draft3Validator,
+ Draft4Validator,
+ Draft6Validator,
+ Draft7Validator,
+ RefResolver,
+ validate,
)
-
-
-__version__ = "2.4.0"
-
-
-# flake8: noqa
+# try:
+# from importlib import metadata
+# except ImportError: # for Python<3.8
+# import importlib_metadata as metadata
+# __version__ = metadata.version("jsonschema")
+# set the version manually here, as we don't install dist-info or egg-info
+# files for vendored spack externals.
+__version__ = '3.2.0'
diff --git a/lib/spack/external/jsonschema/_format.py b/lib/spack/external/jsonschema/_format.py
index bb52d183ad..281a7cfcff 100644
--- a/lib/spack/external/jsonschema/_format.py
+++ b/lib/spack/external/jsonschema/_format.py
@@ -1,6 +1,7 @@
import datetime
import re
import socket
+import struct
from jsonschema.compat import str_types
from jsonschema.exceptions import FormatError
@@ -14,17 +15,19 @@ class FormatChecker(object):
validation. If validation is desired however, instances of this class can
be hooked into validators to enable format validation.
- :class:`FormatChecker` objects always return ``True`` when asked about
+ `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 :meth:`FormatChecker.checks` or
- :meth:`FormatChecker.cls_checks` decorators.
+ returns a ``bool``, use the `FormatChecker.checks` or
+ `FormatChecker.cls_checks` decorators.
- :argument iterable formats: the known formats to validate. This argument
- can be used to limit which formats will be used
- during validation.
+ Arguments:
+ formats (~collections.Iterable):
+
+ The known formats to validate. This argument can be used to
+ limit which formats will be used during validation.
"""
checkers = {}
@@ -35,16 +38,27 @@ class FormatChecker(object):
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.
- :argument str format: the format that the decorated function will check
- :argument Exception raises: the exception(s) raised by the decorated
- function when an invalid instance is found. The exception object
- will be accessible as the :attr:`ValidationError.cause` attribute
- of the resulting validation error.
+ 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):
@@ -58,11 +72,20 @@ class FormatChecker(object):
"""
Check whether the instance conforms to the given format.
- :argument instance: the instance to check
- :type: any primitive type (str, number, bool)
- :argument str format: the format that instance should conform to
- :raises: :exc:`FormatError` if instance does not conform to 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:
@@ -83,11 +106,19 @@ class FormatChecker(object):
"""
Check whether the instance conforms to the given format.
- :argument instance: the instance to check
- :type: any primitive type (str, number, bool)
- :argument str format: the format that instance should conform to
- :rtype: bool
+ 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:
@@ -98,25 +129,55 @@ class FormatChecker(object):
return True
-_draft_checkers = {"draft3": [], "draft4": []}
+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(both=None, draft3=None, draft4=None, raises=()):
- draft3 = draft3 or both
- draft4 = draft4 or both
+
+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:
- _draft_checkers["draft3"].append(draft3)
- func = FormatChecker.cls_checks(draft3, raises)(func)
+ func = _draft_checkers["draft3"].checks(draft3, raises)(func)
if draft4:
- _draft_checkers["draft4"].append(draft4)
- func = FormatChecker.cls_checks(draft4, raises)(func)
+ 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("email")
+@_checks_drafts(name="idn-email")
+@_checks_drafts(name="email")
def is_email(instance):
if not isinstance(instance, str_types):
return True
@@ -125,7 +186,10 @@ def is_email(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")
+
+@_checks_drafts(
+ draft3="ip-address", draft4="ipv4", draft6="ipv4", draft7="ipv4",
+)
def is_ipv4(instance):
if not isinstance(instance, str_types):
return True
@@ -135,7 +199,11 @@ def is_ipv4(instance):
if hasattr(socket, "inet_pton"):
- @_checks_drafts("ipv6", raises=socket.error)
+ # 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
@@ -144,7 +212,13 @@ if hasattr(socket, "inet_pton"):
_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$")
-@_checks_drafts(draft3="host-name", draft4="hostname")
+
+@_checks_drafts(
+ draft3="host-name",
+ draft4="hostname",
+ draft6="hostname",
+ draft7="hostname",
+)
def is_host_name(instance):
if not isinstance(instance, str_types):
return True
@@ -158,46 +232,103 @@ def is_host_name(instance):
try:
- import rfc3987
+ # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
+ import idna
except ImportError:
pass
else:
- @_checks_drafts("uri", raises=ValueError)
- def is_uri(instance):
+ @_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError)
+ def is_idn_host_name(instance):
if not isinstance(instance, str_types):
return True
- return rfc3987.parse(instance, rule="URI")
+ idna.encode(instance)
+ return True
try:
- import strict_rfc3339
+ import rfc3987
except ImportError:
try:
- import isodate
+ from rfc3986_validator import validate_rfc3986
except ImportError:
pass
else:
- @_checks_drafts("date-time", raises=(ValueError, isodate.ISO8601Error))
- def is_date(instance):
+ @_checks_drafts(name="uri")
+ def is_uri(instance):
if not isinstance(instance, str_types):
return True
- return isodate.parse_datetime(instance)
-else:
- @_checks_drafts("date-time")
- def is_date(instance):
+ 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 strict_rfc3339.validate_rfc3339(instance)
+ 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("regex", raises=re.error)
+@_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", raises=ValueError)
+@_checks_drafts(draft3="date", draft7="date", raises=ValueError)
def is_date(instance):
if not isinstance(instance, str_types):
return True
@@ -205,7 +336,7 @@ def is_date(instance):
@_checks_drafts(draft3="time", raises=ValueError)
-def is_time(instance):
+def is_draft3_time(instance):
if not isinstance(instance, str_types):
return True
return datetime.datetime.strptime(instance, "%H:%M:%S")
@@ -219,7 +350,6 @@ 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 (
@@ -229,12 +359,67 @@ else:
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)
-draft3_format_checker = FormatChecker(_draft_checkers["draft3"])
-draft4_format_checker = FormatChecker(_draft_checkers["draft4"])
+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/lib/spack/external/jsonschema/_legacy_validators.py b/lib/spack/external/jsonschema/_legacy_validators.py
new file mode 100644
index 0000000000..264ff7d713
--- /dev/null
+++ b/lib/spack/external/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/lib/spack/external/jsonschema/_types.py b/lib/spack/external/jsonschema/_types.py
new file mode 100644
index 0000000000..a71a4e34bd
--- /dev/null
+++ b/lib/spack/external/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
+
+
+@attr.s(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/lib/spack/external/jsonschema/_utils.py b/lib/spack/external/jsonschema/_utils.py
index 2262f3305d..ceb880198d 100644
--- a/lib/spack/external/jsonschema/_utils.py
+++ b/lib/spack/external/jsonschema/_utils.py
@@ -3,13 +3,12 @@ import json
import pkgutil
import re
-from jsonschema.compat import str_types, MutableMapping, urlsplit
+from jsonschema.compat import MutableMapping, str_types, urlsplit
class URIDict(MutableMapping):
"""
Dictionary which uses normalized URIs as keys.
-
"""
def normalize(self, uri):
@@ -41,7 +40,6 @@ class URIDict(MutableMapping):
class Unset(object):
"""
An as-of-yet unset attribute or unprovided default parameter.
-
"""
def __repr__(self):
@@ -51,17 +49,15 @@ class Unset(object):
def load_schema(name):
"""
Load a schema from ./schemas/``name``.json and return it.
-
"""
- data = pkgutil.get_data(__package__, "schemas/{0}.json".format(name))
+ 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 :func:`textwrap.indent` from Python 3.3.
-
+ A dumb version of `textwrap.indent` from Python 3.3.
"""
return "\n".join(" " * (4 * times) + line for line in string.splitlines())
@@ -73,8 +69,11 @@ def format_as_index(indices):
For example, [1, 2, "foo"] -> [1][2]["foo"]
- :type indices: sequence
+ Arguments:
+ indices (sequence):
+
+ The indices to format.
"""
if not indices:
@@ -90,7 +89,6 @@ def find_additional_properties(instance, schema):
/ or ``patternProperties``.
Assumes ``instance`` is dict-like already.
-
"""
properties = schema.get("properties", {})
@@ -105,7 +103,6 @@ def find_additional_properties(instance, schema):
def extras_msg(extras):
"""
Create an error message for extra items or properties.
-
"""
if len(extras) == 1:
@@ -123,7 +120,6 @@ def types_msg(instance, types):
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 = []
@@ -143,7 +139,6 @@ def flatten(suitable_for_isinstance):
* an arbitrary nested tree of tuples
Return a flattened tuple of the given argument.
-
"""
types = set()
@@ -163,7 +158,6 @@ def ensure_list(thing):
Wrap ``thing`` in a list if it's a single str.
Otherwise, return it unchanged.
-
"""
if isinstance(thing, str_types):
@@ -171,10 +165,16 @@ def ensure_list(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:
@@ -191,7 +191,6 @@ def uniq(container):
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:
diff --git a/lib/spack/external/jsonschema/_validators.py b/lib/spack/external/jsonschema/_validators.py
index c6e801ccb2..179fec09a9 100644
--- a/lib/spack/external/jsonschema/_validators.py
+++ b/lib/spack/external/jsonschema/_validators.py
@@ -1,13 +1,18 @@
import re
-from jsonschema import _utils
+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
-FLOAT_TOLERANCE = 10 ** -15
-
-
def patternProperties(validator, patternProperties, instance, schema):
if not validator.is_type(instance, "object"):
return
@@ -21,35 +26,60 @@ def patternProperties(validator, patternProperties, instance, schema):
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(_utils.find_additional_properties(instance, schema))
+ 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:
- error = "Additional properties are not allowed (%s %s unexpected)"
- yield ValidationError(error % _utils.extras_msg(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, "object"):
- for index, item in enumerate(instance):
- for error in validator.descend(item, items, path=index):
- yield error
- else:
+ 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):
@@ -68,41 +98,66 @@ def additionalItems(validator, aI, instance, schema):
error = "Additional items are not allowed (%s %s unexpected)"
yield ValidationError(
error %
- _utils.extras_msg(instance[len(schema.get("items", [])):])
+ extras_msg(instance[len(schema.get("items", [])):])
)
-def minimum(validator, minimum, instance, schema):
+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 schema.get("exclusiveMinimum", False):
- failed = float(instance) <= minimum
- cmp = "less than or equal to"
- else:
- failed = float(instance) < minimum
- cmp = "less than"
+ if instance <= minimum:
+ yield ValidationError(
+ "%r is less than or equal to the minimum of %r" % (
+ instance, minimum,
+ ),
+ )
- if failed:
+
+def exclusiveMaximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance >= maximum:
yield ValidationError(
- "%r is %s the minimum of %r" % (instance, cmp, minimum)
+ "%r is greater than or equal to the maximum of %r" % (
+ instance, maximum,
+ ),
)
-def maximum(validator, maximum, instance, schema):
+def minimum(validator, minimum, 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 instance < minimum:
+ yield ValidationError(
+ "%r is less than the minimum of %r" % (instance, minimum)
+ )
- if failed:
+
+def maximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance > maximum:
yield ValidationError(
- "%r is %s the maximum of %r" % (instance, cmp, maximum)
+ "%r is greater than the maximum of %r" % (instance, maximum)
)
@@ -111,8 +166,8 @@ def multipleOf(validator, dB, instance, schema):
return
if isinstance(dB, float):
- mod = instance % dB
- failed = (mod > FLOAT_TOLERANCE) and (dB - mod) > FLOAT_TOLERANCE
+ quotient = instance / dB
+ failed = int(quotient) != quotient
else:
failed = instance % dB
@@ -134,9 +189,9 @@ def uniqueItems(validator, uI, instance, schema):
if (
uI and
validator.is_type(instance, "array") and
- not _utils.uniq(instance)
+ not uniq(instance)
):
- yield ValidationError("%r has non-unique elements" % instance)
+ yield ValidationError("%r has non-unique elements" % (instance,))
def pattern(validator, patrn, instance, schema):
@@ -173,104 +228,52 @@ def dependencies(validator, dependencies, instance, schema):
if property not in instance:
continue
- if validator.is_type(dependency, "object"):
+ 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
- else:
- dependencies = _utils.ensure_list(dependency)
- for dependency in dependencies:
- if dependency not in instance:
- yield ValidationError(
- "%r is a dependency of %r" % (dependency, property)
- )
def enum(validator, enums, instance, schema):
- if instance not in enums:
+ 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):
- with validator.resolver.resolving(ref) as resolved:
- for error in validator.descend(instance, resolved):
- yield error
-
-
-def type_draft3(validator, types, instance, schema):
- types = _utils.ensure_list(types)
-
- all_errors = []
- for index, type in enumerate(types):
- if type == "any":
- return
- 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
+ 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:
- yield ValidationError(
- _utils.types_msg(instance, types), context=all_errors,
- )
-
-
-def properties_draft3(validator, properties, instance, schema):
- if not validator.is_type(instance, "object"):
- return
+ scope, resolved = validator.resolver.resolve(ref)
+ validator.resolver.push_scope(scope)
- for property, subschema in iteritems(properties):
- if property in instance:
- for error in validator.descend(
- instance[property],
- subschema,
- path=property,
- schema_path=property,
- ):
+ try:
+ for error in validator.descend(instance, resolved):
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 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)
- )
+ finally:
+ validator.resolver.pop_scope()
-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 type_draft4(validator, types, instance, schema):
- types = _utils.ensure_list(types)
+def type(validator, types, instance, schema):
+ types = ensure_list(types)
if not any(validator.is_type(instance, type) for type in types):
- yield ValidationError(_utils.types_msg(instance, types))
+ yield ValidationError(types_msg(instance, types))
-def properties_draft4(validator, properties, instance, schema):
+def properties(validator, properties, instance, schema):
if not validator.is_type(instance, "object"):
return
@@ -285,7 +288,7 @@ def properties_draft4(validator, properties, instance, schema):
yield error
-def required_draft4(validator, required, instance, schema):
+def required(validator, required, instance, schema):
if not validator.is_type(instance, "object"):
return
for property in required:
@@ -293,33 +296,31 @@ def required_draft4(validator, required, instance, schema):
yield ValidationError("%r is a required property" % property)
-def minProperties_draft4(validator, mP, instance, schema):
+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_draft4(validator, mP, instance, schema):
+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_draft4(validator, allOf, instance, schema):
+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 oneOf_draft4(validator, oneOf, instance, schema):
- subschemas = enumerate(oneOf)
+def anyOf(validator, anyOf, instance, schema):
all_errors = []
- for index, subschema in subschemas:
+ for index, subschema in enumerate(anyOf):
errs = list(validator.descend(instance, subschema, schema_path=index))
if not errs:
- first_valid = subschema
break
all_errors.extend(errs)
else:
@@ -328,20 +329,14 @@ def oneOf_draft4(validator, oneOf, instance, schema):
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 anyOf_draft4(validator, anyOf, instance, schema):
+def oneOf(validator, oneOf, instance, schema):
+ subschemas = enumerate(oneOf)
all_errors = []
- for index, subschema in enumerate(anyOf):
+ 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:
@@ -350,9 +345,29 @@ def anyOf_draft4(validator, anyOf, instance, schema):
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_draft4(validator, not_schema, instance, schema):
+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/lib/spack/external/jsonschema/cli.py b/lib/spack/external/jsonschema/cli.py
index 0126564f46..ab3335b27c 100644
--- a/lib/spack/external/jsonschema/cli.py
+++ b/lib/spack/external/jsonschema/cli.py
@@ -1,8 +1,12 @@
+"""
+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
@@ -26,26 +30,37 @@ parser.add_argument(
action="append",
dest="instances",
type=_json_file,
- help="a path to a JSON instance to validate "
- "(may be specified multiple times)",
+ 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",
+ 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.",
+ 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",
+ help="the JSON Schema to validate with (i.e. schema.json)",
type=_json_file,
)
@@ -64,6 +79,9 @@ def main(args=sys.argv[1:]):
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):
diff --git a/lib/spack/external/jsonschema/compat.py b/lib/spack/external/jsonschema/compat.py
index 6ca49ab6be..47e0980455 100644
--- a/lib/spack/external/jsonschema/compat.py
+++ b/lib/spack/external/jsonschema/compat.py
@@ -1,52 +1,54 @@
-from __future__ import unicode_literals
-import sys
+"""
+Python 2/3 compatibility helpers.
+
+Note: This module is *not* public API.
+"""
+import contextlib
import operator
+import sys
+
try:
- from collections import MutableMapping, Sequence # noqa
-except ImportError:
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 io import StringIO
+ from functools import lru_cache
+ from io import StringIO as NativeIO
from urllib.parse import (
- unquote, urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit
+ unquote, urljoin, urlunsplit, SplitResult, urlsplit
)
- from urllib.request import urlopen
+ 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 StringIO import StringIO
- from urlparse import (
- urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit # noqa
- )
- from urllib import unquote # noqa
- from urllib2 import urlopen # 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")
-
-# On python < 3.3 fragments are not handled properly with unknown schemes
-def urlsplit(url):
- scheme, netloc, path, query, fragment = _urlsplit(url)
- if "#" in path:
- path, fragment = path.split("#", 1)
- return SplitResult(scheme, netloc, path, query, fragment)
+ from functools32 import lru_cache
def urldefrag(url):
if "#" in url:
s, n, p, q, frag = urlsplit(url)
- defrag = urlunsplit((s, n, p, q, ''))
+ defrag = urlunsplit((s, n, p, q, ""))
else:
defrag = url
- frag = ''
+ frag = ""
return defrag, frag
diff --git a/lib/spack/external/jsonschema/exceptions.py b/lib/spack/external/jsonschema/exceptions.py
index 478e59c531..691dcffe6c 100644
--- a/lib/spack/external/jsonschema/exceptions.py
+++ b/lib/spack/external/jsonschema/exceptions.py
@@ -1,8 +1,13 @@
+"""
+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
@@ -27,6 +32,18 @@ class _Error(Exception):
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)
@@ -44,9 +61,6 @@ class _Error(Exception):
def __repr__(self):
return "<%s: %r>" % (self.__class__.__name__, self.message)
- def __str__(self):
- return unicode(self).encode("utf-8")
-
def __unicode__(self):
essential_for_verbose = (
self.validator, self.validator_value, self.instance, self.schema,
@@ -58,22 +72,27 @@ class _Error(Exception):
pinstance = pprint.pformat(self.instance, width=72)
return self.message + textwrap.dedent("""
- Failed validating %r in schema%s:
+ Failed validating %r in %s%s:
%s
- On instance%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):
@@ -86,7 +105,7 @@ class _Error(Exception):
return self.relative_path
path = deque(self.relative_path)
- path.extendleft(parent.absolute_path)
+ path.extendleft(reversed(parent.absolute_path))
return path
@property
@@ -96,7 +115,7 @@ class _Error(Exception):
return self.relative_schema_path
path = deque(self.relative_schema_path)
- path.extendleft(parent.absolute_schema_path)
+ path.extendleft(reversed(parent.absolute_schema_path))
return path
def _set(self, **kwargs):
@@ -113,26 +132,63 @@ class _Error(Exception):
class ValidationError(_Error):
- pass
+ """
+ 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):
- pass
+ """
+ A schema was invalid under its corresponding metaschema.
+ """
+
+ _word_for_schema_in_error_message = "metaschema"
+ _word_for_instance_in_error_message = "schema"
+@attr.s(hash=True)
class RefResolutionError(Exception):
- pass
+ """
+ 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 __str__(self):
- return unicode(self).encode("utf-8")
-
def __unicode__(self):
pschema = pprint.pformat(self.schema, width=72)
pinstance = pprint.pformat(self.instance, width=72)
@@ -147,29 +203,34 @@ class UnknownType(Exception):
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 __str__(self):
- return self.message.encode("utf-8")
-
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
@@ -184,12 +245,11 @@ class ErrorTree(object):
container = container[element]
container.errors[error.validator] = error
- self._instance = error.instance
+ container._instance = error.instance
def __contains__(self, index):
"""
Check whether ``instance[index]`` has any errors.
-
"""
return index in self._contents
@@ -201,8 +261,7 @@ class ErrorTree(object):
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 :class:`LookupError`.
-
+ subclass of `exceptions.LookupError`.
"""
if self._instance is not _unset and index not in self:
@@ -210,22 +269,22 @@ class ErrorTree(object):
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):
"""
- Same as :attr:`total_errors`.
-
+ Return the `total_errors`.
"""
-
return self.total_errors
def __repr__(self):
@@ -235,7 +294,6 @@ class ErrorTree(object):
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))
@@ -243,6 +301,21 @@ class ErrorTree(object):
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
@@ -253,6 +326,43 @@ 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:
diff --git a/lib/spack/external/jsonschema/schemas/draft3.json b/lib/spack/external/jsonschema/schemas/draft3.json
index 5bcefe30d5..f8a09c563b 100644
--- a/lib/spack/external/jsonschema/schemas/draft3.json
+++ b/lib/spack/external/jsonschema/schemas/draft3.json
@@ -80,9 +80,7 @@
"type": "number"
},
"enum": {
- "minItems": 1,
- "type": "array",
- "uniqueItems": true
+ "type": "array"
},
"exclusiveMaximum": {
"default": false,
diff --git a/lib/spack/external/jsonschema/schemas/draft4.json b/lib/spack/external/jsonschema/schemas/draft4.json
index fead5cefab..9b666cff88 100644
--- a/lib/spack/external/jsonschema/schemas/draft4.json
+++ b/lib/spack/external/jsonschema/schemas/draft4.json
@@ -111,9 +111,7 @@
"type": "string"
},
"enum": {
- "minItems": 1,
- "type": "array",
- "uniqueItems": true
+ "type": "array"
},
"exclusiveMaximum": {
"default": false,
@@ -123,6 +121,9 @@
"default": false,
"type": "boolean"
},
+ "format": {
+ "type": "string"
+ },
"id": {
"format": "uri",
"type": "string"
diff --git a/lib/spack/external/jsonschema/schemas/draft6.json b/lib/spack/external/jsonschema/schemas/draft6.json
new file mode 100644
index 0000000000..a0d2bf7896
--- /dev/null
+++ b/lib/spack/external/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/lib/spack/external/jsonschema/schemas/draft7.json b/lib/spack/external/jsonschema/schemas/draft7.json
new file mode 100644
index 0000000000..746cde9690
--- /dev/null
+++ b/lib/spack/external/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/lib/spack/external/jsonschema/tests/__init__.py b/lib/spack/external/jsonschema/tests/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/spack/external/jsonschema/tests/__init__.py
+++ /dev/null
diff --git a/lib/spack/external/jsonschema/tests/compat.py b/lib/spack/external/jsonschema/tests/compat.py
deleted file mode 100644
index b37483f5dd..0000000000
--- a/lib/spack/external/jsonschema/tests/compat.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import sys
-
-
-if sys.version_info[:2] < (2, 7): # pragma: no cover
- import unittest2 as unittest
-else:
- import unittest
-
-try:
- from unittest import mock
-except ImportError:
- import mock
-
-
-# flake8: noqa
diff --git a/lib/spack/external/jsonschema/tests/test_cli.py b/lib/spack/external/jsonschema/tests/test_cli.py
deleted file mode 100644
index f625ca989d..0000000000
--- a/lib/spack/external/jsonschema/tests/test_cli.py
+++ /dev/null
@@ -1,110 +0,0 @@
-from jsonschema import Draft4Validator, ValidationError, cli
-from jsonschema.compat import StringIO
-from jsonschema.tests.compat import mock, unittest
-
-
-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 []
- return FakeValidator
-
-
-class TestParser(unittest.TestCase):
- FakeValidator = fake_validator()
-
- def setUp(self):
- mock_open = mock.mock_open()
- patch_open = mock.patch.object(cli, "open", mock_open, create=True)
- patch_open.start()
- self.addCleanup(patch_open.stop)
-
- mock_json_load = mock.Mock()
- mock_json_load.return_value = {}
- patch_json_load = mock.patch("json.load")
- patch_json_load.start()
- self.addCleanup(patch_json_load.stop)
-
- def test_find_validator_by_fully_qualified_object_name(self):
- arguments = cli.parse_args(
- [
- "--validator",
- "jsonschema.tests.test_cli.TestParser.FakeValidator",
- "--instance", "foo.json",
- "schema.json",
- ]
- )
- self.assertIs(arguments["validator"], self.FakeValidator)
-
- def test_find_validator_in_jsonschema(self):
- arguments = cli.parse_args(
- [
- "--validator", "Draft4Validator",
- "--instance", "foo.json",
- "schema.json",
- ]
- )
- self.assertIs(arguments["validator"], Draft4Validator)
-
-
-class TestCLI(unittest.TestCase):
- def test_successful_validation(self):
- stdout, stderr = StringIO(), StringIO()
- 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 = StringIO(), StringIO()
- 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 = StringIO(), StringIO()
- 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/lib/spack/external/jsonschema/tests/test_exceptions.py b/lib/spack/external/jsonschema/tests/test_exceptions.py
deleted file mode 100644
index 9e5793c628..0000000000
--- a/lib/spack/external/jsonschema/tests/test_exceptions.py
+++ /dev/null
@@ -1,382 +0,0 @@
-import textwrap
-
-from jsonschema import Draft4Validator, exceptions
-from jsonschema.compat import PY3
-from jsonschema.tests.compat import mock, unittest
-
-
-class TestBestMatch(unittest.TestCase):
- def best_match(self, errors):
- errors = list(errors)
- best = exceptions.best_match(errors)
- reversed_best = exceptions.best_match(reversed(errors))
- self.assertEqual(
- best,
- reversed_best,
- msg="Didn't return a consistent best match!\n"
- "Got: {0}\n\nThen: {1}".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(unittest.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(unittest.TestCase):
- def test_it_knows_how_many_total_errors_it_contains(self):
- errors = [mock.MagicMock() for _ 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_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 TestErrorReprStr(unittest.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:
- 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_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):
- with mock.patch("pprint.pformat") as pformat:
- str(self.make_error())
- self.assertEqual(pformat.call_count, 2) # schema + instance
-
- 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.
-
- """
-
- instance = mock.MagicMock()
- error = exceptions.ValidationError(
- "a message",
- validator="foo",
- instance=instance,
- validator_value="some",
- schema="schema",
- )
- str(error)
- self.assertFalse(instance.__eq__.called)
diff --git a/lib/spack/external/jsonschema/tests/test_format.py b/lib/spack/external/jsonschema/tests/test_format.py
deleted file mode 100644
index 8392ca1de3..0000000000
--- a/lib/spack/external/jsonschema/tests/test_format.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-Tests for the parts of jsonschema related to the :validator:`format` property.
-
-"""
-
-from jsonschema.tests.compat import mock, unittest
-
-from jsonschema import FormatError, ValidationError, FormatChecker
-from jsonschema.validators import Draft4Validator
-
-
-class TestFormatChecker(unittest.TestCase):
- def setUp(self):
- self.fn = mock.Mock()
-
- 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):
- with mock.patch.dict(FormatChecker.checkers, clear=True):
- FormatChecker.cls_checks("new")(self.fn)
- self.assertEqual(FormatChecker.checkers, {"new" : (self.fn, ())})
-
- def test_it_can_register_checkers(self):
- checker = FormatChecker()
- checker.checks("new")(self.fn)
- self.assertEqual(
- checker.checkers,
- dict(FormatChecker.checkers, new=(self.fn, ()))
- )
-
- def test_it_catches_registered_errors(self):
- checker = FormatChecker()
- cause = self.fn.side_effect = ValueError()
-
- checker.checks("foo", raises=ValueError)(self.fn)
-
- with self.assertRaises(FormatError) as cm:
- checker.check("bar", "foo")
-
- self.assertIs(cm.exception.cause, cause)
- self.assertIs(cm.exception.__cause__, cause)
-
- # Unregistered errors should not be caught
- self.fn.side_effect = AttributeError
- with self.assertRaises(AttributeError):
- checker.check("bar", "foo")
-
- def test_format_error_causes_become_validation_error_causes(self):
- checker = FormatChecker()
- checker.checks("foo", raises=ValueError)(self.fn)
- cause = self.fn.side_effect = ValueError()
- validator = Draft4Validator({"format" : "foo"}, format_checker=checker)
-
- with self.assertRaises(ValidationError) as cm:
- validator.validate("bar")
-
- self.assertIs(cm.exception.__cause__, cause)
diff --git a/lib/spack/external/jsonschema/tests/test_jsonschema_test_suite.py b/lib/spack/external/jsonschema/tests/test_jsonschema_test_suite.py
deleted file mode 100644
index 75c6857bc0..0000000000
--- a/lib/spack/external/jsonschema/tests/test_jsonschema_test_suite.py
+++ /dev/null
@@ -1,290 +0,0 @@
-"""
-Test runner for the JSON Schema official test suite
-
-Tests comprehensive correctness of each draft's validator.
-
-See https://github.com/json-schema/JSON-Schema-Test-Suite for details.
-
-"""
-
-from contextlib import closing
-from decimal import Decimal
-import glob
-import json
-import io
-import itertools
-import os
-import re
-import subprocess
-import sys
-
-try:
- from sys import pypy_version_info
-except ImportError:
- pypy_version_info = None
-
-from jsonschema import (
- FormatError, SchemaError, ValidationError, Draft3Validator,
- Draft4Validator, FormatChecker, draft3_format_checker,
- draft4_format_checker, validate,
-)
-from jsonschema.compat import PY3
-from jsonschema.tests.compat import mock, unittest
-import jsonschema
-
-
-REPO_ROOT = os.path.join(os.path.dirname(jsonschema.__file__), os.path.pardir)
-SUITE = os.getenv("JSON_SCHEMA_TEST_SUITE", os.path.join(REPO_ROOT, "json"))
-
-if not os.path.isdir(SUITE):
- raise ValueError(
- "Can't find the JSON-Schema-Test-Suite directory. Set the "
- "'JSON_SCHEMA_TEST_SUITE' environment variable or run the tests from "
- "alongside a checkout of the suite."
- )
-
-TESTS_DIR = os.path.join(SUITE, "tests")
-JSONSCHEMA_SUITE = os.path.join(SUITE, "bin", "jsonschema_suite")
-
-remotes_stdout = subprocess.Popen(
- ["python", JSONSCHEMA_SUITE, "remotes"], stdout=subprocess.PIPE,
-).stdout
-
-with closing(remotes_stdout):
- if PY3:
- remotes_stdout = io.TextIOWrapper(remotes_stdout)
- REMOTES = json.load(remotes_stdout)
-
-
-def make_case(schema, data, valid, name):
- if valid:
- def test_case(self):
- kwargs = getattr(self, "validator_kwargs", {})
- validate(data, schema, cls=self.validator_class, **kwargs)
- else:
- def test_case(self):
- kwargs = getattr(self, "validator_kwargs", {})
- with self.assertRaises(ValidationError):
- validate(data, schema, cls=self.validator_class, **kwargs)
-
- if not PY3:
- name = name.encode("utf-8")
- test_case.__name__ = name
-
- return test_case
-
-
-def maybe_skip(skip, test_case, case, test):
- if skip is not None:
- reason = skip(case, test)
- if reason is not None:
- test_case = unittest.skip(reason)(test_case)
- return test_case
-
-
-def load_json_cases(tests_glob, ignore_glob="", basedir=TESTS_DIR, skip=None):
- if ignore_glob:
- ignore_glob = os.path.join(basedir, ignore_glob)
-
- def add_test_methods(test_class):
- ignored = set(glob.iglob(ignore_glob))
-
- for filename in glob.iglob(os.path.join(basedir, tests_glob)):
- if filename in ignored:
- continue
-
- validating, _ = os.path.splitext(os.path.basename(filename))
- id = itertools.count(1)
-
- with open(filename) as test_file:
- for case in json.load(test_file):
- for test in case["tests"]:
- name = "test_%s_%s_%s" % (
- validating,
- next(id),
- re.sub(r"[\W ]+", "_", test["description"]),
- )
- assert not hasattr(test_class, name), name
-
- test_case = make_case(
- data=test["data"],
- schema=case["schema"],
- valid=test["valid"],
- name=name,
- )
- test_case = maybe_skip(skip, test_case, case, test)
- setattr(test_class, name, test_case)
-
- return test_class
- return add_test_methods
-
-
-class TypesMixin(object):
- @unittest.skipIf(PY3, "In Python 3 json.load always produces unicode")
- def test_string_a_bytestring_is_a_string(self):
- self.validator_class({"type" : "string"}).validate(b"foo")
-
-
-class DecimalMixin(object):
- def test_it_can_validate_with_decimals(self):
- schema = {"type" : "number"}
- validator = self.validator_class(
- schema, types={"number" : (int, float, Decimal)}
- )
-
- for valid in [1, 1.1, Decimal(1) / Decimal(8)]:
- validator.validate(valid)
-
- for invalid in ["foo", {}, [], True, None]:
- with self.assertRaises(ValidationError):
- validator.validate(invalid)
-
-
-def missing_format(checker):
- def missing_format(case, test):
- format = case["schema"].get("format")
- if format not in checker.checkers:
- return "Format checker {0!r} not found.".format(format)
- elif (
- format == "date-time" and
- pypy_version_info is not None and
- pypy_version_info[:2] <= (1, 9)
- ):
- # datetime.datetime is overzealous about typechecking in <=1.9
- return "datetime.datetime is broken on this version of PyPy."
- return missing_format
-
-
-class FormatMixin(object):
- def test_it_returns_true_for_formats_it_does_not_know_about(self):
- validator = self.validator_class(
- {"format" : "carrot"}, format_checker=FormatChecker(),
- )
- validator.validate("bugs")
-
- def test_it_does_not_validate_formats_by_default(self):
- validator = self.validator_class({})
- self.assertIsNone(validator.format_checker)
-
- def test_it_validates_formats_if_a_checker_is_provided(self):
- checker = mock.Mock(spec=FormatChecker)
- validator = self.validator_class(
- {"format" : "foo"}, format_checker=checker,
- )
-
- validator.validate("bar")
-
- checker.check.assert_called_once_with("bar", "foo")
-
- cause = ValueError()
- checker.check.side_effect = FormatError('aoeu', cause=cause)
-
- with self.assertRaises(ValidationError) as cm:
- validator.validate("bar")
- # Make sure original cause is attached
- self.assertIs(cm.exception.cause, cause)
-
- def test_it_validates_formats_of_any_type(self):
- checker = mock.Mock(spec=FormatChecker)
- validator = self.validator_class(
- {"format" : "foo"}, format_checker=checker,
- )
-
- validator.validate([1, 2, 3])
-
- checker.check.assert_called_once_with([1, 2, 3], "foo")
-
- cause = ValueError()
- checker.check.side_effect = FormatError('aoeu', cause=cause)
-
- with self.assertRaises(ValidationError) as cm:
- validator.validate([1, 2, 3])
- # Make sure original cause is attached
- self.assertIs(cm.exception.cause, cause)
-
-
-if sys.maxunicode == 2 ** 16 - 1: # This is a narrow build.
- def narrow_unicode_build(case, test):
- if "supplementary Unicode" in test["description"]:
- return "Not running surrogate Unicode case, this Python is narrow."
-else:
- def narrow_unicode_build(case, test): # This isn't, skip nothing.
- return
-
-
-@load_json_cases(
- "draft3/*.json",
- skip=narrow_unicode_build,
- ignore_glob="draft3/refRemote.json",
-)
-@load_json_cases(
- "draft3/optional/format.json", skip=missing_format(draft3_format_checker)
-)
-@load_json_cases("draft3/optional/bignum.json")
-@load_json_cases("draft3/optional/zeroTerminatedFloats.json")
-class TestDraft3(unittest.TestCase, TypesMixin, DecimalMixin, FormatMixin):
- validator_class = Draft3Validator
- validator_kwargs = {"format_checker" : draft3_format_checker}
-
- def test_any_type_is_valid_for_type_any(self):
- validator = self.validator_class({"type" : "any"})
- validator.validate(mock.Mock())
-
- # TODO: we're in need of more meta schema tests
- def test_invalid_properties(self):
- with self.assertRaises(SchemaError):
- validate({}, {"properties": {"test": True}},
- cls=self.validator_class)
-
- def test_minItems_invalid_string(self):
- with self.assertRaises(SchemaError):
- # needs to be an integer
- validate([1], {"minItems" : "1"}, cls=self.validator_class)
-
-
-@load_json_cases(
- "draft4/*.json",
- skip=narrow_unicode_build,
- ignore_glob="draft4/refRemote.json",
-)
-@load_json_cases(
- "draft4/optional/format.json", skip=missing_format(draft4_format_checker)
-)
-@load_json_cases("draft4/optional/bignum.json")
-@load_json_cases("draft4/optional/zeroTerminatedFloats.json")
-class TestDraft4(unittest.TestCase, TypesMixin, DecimalMixin, FormatMixin):
- validator_class = Draft4Validator
- validator_kwargs = {"format_checker" : draft4_format_checker}
-
- # TODO: we're in need of more meta schema tests
- def test_invalid_properties(self):
- with self.assertRaises(SchemaError):
- validate({}, {"properties": {"test": True}},
- cls=self.validator_class)
-
- def test_minItems_invalid_string(self):
- with self.assertRaises(SchemaError):
- # needs to be an integer
- validate([1], {"minItems" : "1"}, cls=self.validator_class)
-
-
-class RemoteRefResolutionMixin(object):
- def setUp(self):
- patch = mock.patch("jsonschema.validators.requests")
- requests = patch.start()
- requests.get.side_effect = self.resolve
- self.addCleanup(patch.stop)
-
- def resolve(self, reference):
- _, _, reference = reference.partition("http://localhost:1234/")
- return mock.Mock(**{"json.return_value" : REMOTES.get(reference)})
-
-
-@load_json_cases("draft3/refRemote.json")
-class Draft3RemoteResolution(RemoteRefResolutionMixin, unittest.TestCase):
- validator_class = Draft3Validator
-
-
-@load_json_cases("draft4/refRemote.json")
-class Draft4RemoteResolution(RemoteRefResolutionMixin, unittest.TestCase):
- validator_class = Draft4Validator
diff --git a/lib/spack/external/jsonschema/tests/test_validators.py b/lib/spack/external/jsonschema/tests/test_validators.py
deleted file mode 100644
index f8692388ea..0000000000
--- a/lib/spack/external/jsonschema/tests/test_validators.py
+++ /dev/null
@@ -1,786 +0,0 @@
-from collections import deque
-from contextlib import contextmanager
-import json
-
-from jsonschema import FormatChecker, ValidationError
-from jsonschema.tests.compat import mock, unittest
-from jsonschema.validators import (
- RefResolutionError, UnknownType, Draft3Validator,
- Draft4Validator, RefResolver, create, extend, validator_for, validate,
-)
-
-
-class TestCreateAndExtend(unittest.TestCase):
- def setUp(self):
- self.meta_schema = {u"properties" : {u"smelly" : {}}}
- self.smelly = mock.MagicMock()
- self.validators = {u"smelly" : self.smelly}
- self.types = {u"dict" : dict}
- self.Validator = create(
- meta_schema=self.meta_schema,
- validators=self.validators,
- default_types=self.types,
- )
-
- self.validator_value = 12
- self.schema = {u"smelly" : self.validator_value}
- self.validator = self.Validator(self.schema)
-
- def test_attrs(self):
- self.assertEqual(self.Validator.VALIDATORS, self.validators)
- self.assertEqual(self.Validator.META_SCHEMA, self.meta_schema)
- self.assertEqual(self.Validator.DEFAULT_TYPES, self.types)
-
- def test_init(self):
- self.assertEqual(self.validator.schema, self.schema)
-
- def test_iter_errors(self):
- instance = "hello"
-
- self.smelly.return_value = []
- self.assertEqual(list(self.validator.iter_errors(instance)), [])
-
- error = mock.Mock()
- self.smelly.return_value = [error]
- self.assertEqual(list(self.validator.iter_errors(instance)), [error])
-
- self.smelly.assert_called_with(
- self.validator, self.validator_value, instance, self.schema,
- )
-
- def test_if_a_version_is_provided_it_is_registered(self):
- with mock.patch("jsonschema.validators.validates") as validates:
- validates.side_effect = lambda version : lambda cls : cls
- Validator = create(meta_schema={u"id" : ""}, version="my version")
- validates.assert_called_once_with("my version")
- self.assertEqual(Validator.__name__, "MyVersionValidator")
-
- def test_if_a_version_is_not_provided_it_is_not_registered(self):
- with mock.patch("jsonschema.validators.validates") as validates:
- create(meta_schema={u"id" : "id"})
- self.assertFalse(validates.called)
-
- def test_extend(self):
- validators = dict(self.Validator.VALIDATORS)
- new = mock.Mock()
-
- Extended = extend(self.Validator, validators={u"a new one" : new})
-
- validators.update([(u"a new one", new)])
- self.assertEqual(Extended.VALIDATORS, validators)
- self.assertNotIn(u"a new one", self.Validator.VALIDATORS)
-
- self.assertEqual(Extended.META_SCHEMA, self.Validator.META_SCHEMA)
- self.assertEqual(Extended.DEFAULT_TYPES, self.Validator.DEFAULT_TYPES)
-
-
-class TestIterErrors(unittest.TestCase):
- def setUp(self):
- self.validator = 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(unittest.TestCase):
- def message_for(self, instance, schema, *args, **kwargs):
- kwargs.setdefault("cls", Draft3Validator)
- with self.assertRaises(ValidationError) as e:
- 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 not of type %r" % (type,))
-
- def test_object_with_name_type_failure(self):
- name = "Foo"
- schema = {u"type" : [{u"name" : name, u"minimum" : 3}]}
- message = self.message_for(instance=1, schema=schema)
- self.assertEqual(message, "1 is not of type %r" % (name,))
-
- 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_failure_has_single_element_not_list(self):
- depend, on = "bar", "foo"
- schema = {u"dependencies" : {depend : on}}
- message = self.message_for({"bar" : 2}, schema)
- self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
-
- def test_additionalItems_single_failure(self):
- message = self.message_for(
- [2], {u"items" : [], u"additionalItems" : False},
- )
- self.assertIn("(2 was unexpected)", message)
-
- def test_additionalItems_multiple_failures(self):
- message = self.message_for(
- [1, 2, 3], {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({additional : 2}, schema)
- self.assertIn("(%r was unexpected)" % (additional,), message)
-
- def test_additionalProperties_multiple_failures(self):
- schema = {u"additionalProperties" : False}
- message = self.message_for(dict.fromkeys(["foo", "bar"]), schema)
-
- self.assertIn(repr("foo"), message)
- self.assertIn(repr("bar"), message)
- self.assertIn("were unexpected)", message)
-
- def test_invalid_format_default_message(self):
- checker = FormatChecker(formats=())
- check_fn = mock.Mock(return_value=False)
- checker.checks(u"thing")(check_fn)
-
- schema = {u"format" : u"thing"}
- message = self.message_for("bla", schema, format_checker=checker)
-
- self.assertIn(repr("bla"), message)
- self.assertIn(repr("thing"), message)
- self.assertIn("is not a", message)
-
-
-class TestValidationErrorDetails(unittest.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 = 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 = 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 = 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 = 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_additionalProperties(self):
- instance = {"bar": "bar", "foo": 2}
- schema = {
- "additionalProperties" : {"type": "integer", "minimum": 5}
- }
-
- validator = 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 = 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 = 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 = 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")
-
-
-class ValidatorTestMixin(object):
- def setUp(self):
- self.instance = mock.Mock()
- self.schema = {}
- self.resolver = mock.Mock()
- self.validator = self.validator_class(self.schema)
-
- def test_valid_instances_are_valid(self):
- errors = iter([])
-
- with mock.patch.object(
- self.validator, "iter_errors", return_value=errors,
- ):
- self.assertTrue(
- self.validator.is_valid(self.instance, self.schema)
- )
-
- def test_invalid_instances_are_not_valid(self):
- errors = iter([mock.Mock()])
-
- with mock.patch.object(
- self.validator, "iter_errors", return_value=errors,
- ):
- self.assertFalse(
- self.validator.is_valid(self.instance, self.schema)
- )
-
- def test_non_existent_properties_are_ignored(self):
- instance, my_property, my_value = mock.Mock(), mock.Mock(), mock.Mock()
- validate(instance=instance, schema={my_property : my_value})
-
- def test_it_creates_a_ref_resolver_if_not_provided(self):
- self.assertIsInstance(self.validator.resolver, RefResolver)
-
- def test_it_delegates_to_a_ref_resolver(self):
- resolver = RefResolver("", {})
- schema = {"$ref" : mock.Mock()}
-
- @contextmanager
- def resolving():
- yield {"type": "integer"}
-
- with mock.patch.object(resolver, "resolving") as resolve:
- resolve.return_value = resolving()
- with self.assertRaises(ValidationError):
- self.validator_class(schema, resolver=resolver).validate(None)
-
- resolve.assert_called_once_with(schema["$ref"])
-
- 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"))
-
- def test_is_type_raises_exception_for_unknown_type(self):
- with self.assertRaises(UnknownType):
- self.validator.is_type("foo", object())
-
-
-class TestDraft3Validator(ValidatorTestMixin, unittest.TestCase):
- validator_class = Draft3Validator
-
- def test_is_type_is_true_for_any_type(self):
- self.assertTrue(self.validator.is_valid(mock.Mock(), {"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"}))
-
- def test_non_string_custom_types(self):
- schema = {'type': [None]}
- cls = self.validator_class(schema, types={None: type(None)})
- cls.validate(None, schema)
-
-
-class TestDraft4Validator(ValidatorTestMixin, unittest.TestCase):
- validator_class = Draft4Validator
-
-
-class TestBuiltinFormats(unittest.TestCase):
- """
- The built-in (specification-defined) formats do not raise type errors.
-
- If an instance or value is not a string, it should be ignored.
-
- """
-
-
-for format in FormatChecker.checkers:
- def test(self, format=format):
- v = Draft4Validator({"format": format}, format_checker=FormatChecker())
- v.validate(123)
-
- name = "test_{0}_ignores_non_strings".format(format)
- test.__name__ = name
- setattr(TestBuiltinFormats, name, test)
- del test # Ugh py.test. Stop discovering top level tests.
-
-
-class TestValidatorFor(unittest.TestCase):
- def test_draft_3(self):
- schema = {"$schema" : "http://json-schema.org/draft-03/schema"}
- self.assertIs(validator_for(schema), Draft3Validator)
-
- schema = {"$schema" : "http://json-schema.org/draft-03/schema#"}
- self.assertIs(validator_for(schema), Draft3Validator)
-
- def test_draft_4(self):
- schema = {"$schema" : "http://json-schema.org/draft-04/schema"}
- self.assertIs(validator_for(schema), Draft4Validator)
-
- schema = {"$schema" : "http://json-schema.org/draft-04/schema#"}
- self.assertIs(validator_for(schema), Draft4Validator)
-
- def test_custom_validator(self):
- Validator = create(meta_schema={"id" : "meta schema id"}, version="12")
- schema = {"$schema" : "meta schema id"}
- self.assertIs(validator_for(schema), Validator)
-
- def test_validator_for_jsonschema_default(self):
- self.assertIs(validator_for({}), Draft4Validator)
-
- def test_validator_for_custom_default(self):
- self.assertIs(validator_for({}, default=None), None)
-
-
-class TestValidate(unittest.TestCase):
- def test_draft3_validator_is_chosen(self):
- schema = {"$schema" : "http://json-schema.org/draft-03/schema#"}
- with mock.patch.object(Draft3Validator, "check_schema") as chk_schema:
- validate({}, schema)
- chk_schema.assert_called_once_with(schema)
- # Make sure it works without the empty fragment
- schema = {"$schema" : "http://json-schema.org/draft-03/schema"}
- with mock.patch.object(Draft3Validator, "check_schema") as chk_schema:
- validate({}, schema)
- chk_schema.assert_called_once_with(schema)
-
- def test_draft4_validator_is_chosen(self):
- schema = {"$schema" : "http://json-schema.org/draft-04/schema#"}
- with mock.patch.object(Draft4Validator, "check_schema") as chk_schema:
- validate({}, schema)
- chk_schema.assert_called_once_with(schema)
-
- def test_draft4_validator_is_the_default(self):
- with mock.patch.object(Draft4Validator, "check_schema") as chk_schema:
- validate({}, {})
- chk_schema.assert_called_once_with({})
-
-
-class TestRefResolver(unittest.TestCase):
-
- 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 = RefResolver(self.base_uri, self.referrer, self.store)
-
- def test_it_does_not_retrieve_schema_urls_from_the_network(self):
- ref = Draft3Validator.META_SCHEMA["id"]
- with mock.patch.object(self.resolver, "resolve_remote") as remote:
- with self.resolver.resolving(ref) as resolved:
- self.assertEqual(resolved, Draft3Validator.META_SCHEMA)
- self.assertFalse(remote.called)
-
- 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": "foo://bar/schema#", "a": {"foo": "bar"}}
- resolver = RefResolver.from_schema(schema)
- with resolver.resolving("#/a") as resolved:
- self.assertEqual(resolved, schema["a"])
- with resolver.resolving("foo://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}
-
- with mock.patch("jsonschema.validators.requests") as requests:
- requests.get.return_value.json.return_value = schema
- with self.resolver.resolving(ref) as resolved:
- self.assertEqual(resolved, 12)
- requests.get.assert_called_once_with("http://bar")
-
- def test_it_retrieves_unstored_refs_via_urlopen(self):
- ref = "http://bar#baz"
- schema = {"baz" : 12}
-
- with mock.patch("jsonschema.validators.requests", None):
- with mock.patch("jsonschema.validators.urlopen") as urlopen:
- urlopen.return_value.read.return_value = (
- json.dumps(schema).encode("utf8"))
- with self.resolver.resolving(ref) as resolved:
- self.assertEqual(resolved, 12)
- urlopen.assert_called_once_with("http://bar")
-
- def test_it_can_construct_a_base_uri_from_a_schema(self):
- schema = {"id" : "foo"}
- resolver = RefResolver.from_schema(schema)
- self.assertEqual(resolver.base_uri, "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 = RefResolver.from_schema(schema)
- self.assertEqual(resolver.base_uri, "")
- 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):
- schema = {"foo": "bar"}
- ref = "foo://bar"
- foo_handler = mock.Mock(return_value=schema)
- resolver = RefResolver("", {}, handlers={"foo": foo_handler})
- with resolver.resolving(ref) as resolved:
- self.assertEqual(resolved, schema)
- foo_handler.assert_called_once_with(ref)
-
- def test_cache_remote_on(self):
- ref = "foo://bar"
- foo_handler = mock.Mock()
- resolver = RefResolver(
- "", {}, cache_remote=True, handlers={"foo" : foo_handler},
- )
- with resolver.resolving(ref):
- pass
- with resolver.resolving(ref):
- pass
- foo_handler.assert_called_once_with(ref)
-
- def test_cache_remote_off(self):
- ref = "foo://bar"
- foo_handler = mock.Mock()
- resolver = RefResolver(
- "", {}, cache_remote=False, handlers={"foo" : foo_handler},
- )
- with resolver.resolving(ref):
- pass
- with resolver.resolving(ref):
- pass
- self.assertEqual(foo_handler.call_count, 2)
-
- def test_if_you_give_it_junk_you_get_a_resolution_error(self):
- ref = "foo://bar"
- foo_handler = mock.Mock(side_effect=ValueError("Oh no! What's this?"))
- resolver = RefResolver("", {}, handlers={"foo" : foo_handler})
- with self.assertRaises(RefResolutionError) as err:
- with resolver.resolving(ref):
- pass
- self.assertEqual(str(err.exception), "Oh no! What's this?")
-
-
-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)
diff --git a/lib/spack/external/jsonschema/validators.py b/lib/spack/external/jsonschema/validators.py
index 30c3515398..1dc420c70d 100644
--- a/lib/spack/external/jsonschema/validators.py
+++ b/lib/spack/external/jsonschema/validators.py
@@ -1,26 +1,107 @@
+"""
+Creation and extension of validators, with implementations for existing drafts.
+"""
from __future__ import division
+from warnings import warn
import contextlib
import json
import numbers
-requests = None
+from six import add_metaclass
-from jsonschema import _utils, _validators
+from jsonschema import (
+ _legacy_validators,
+ _types,
+ _utils,
+ _validators,
+ exceptions,
+)
from jsonschema.compat import (
- Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen,
- str_types, int_types, iteritems,
+ Sequence,
+ int_types,
+ iteritems,
+ lru_cache,
+ str_types,
+ unquote,
+ urldefrag,
+ urljoin,
+ urlopen,
+ urlsplit,
)
-from jsonschema.exceptions import ErrorTree # Backwards compatibility # noqa
-from jsonschema.exceptions import RefResolutionError, SchemaError, UnknownType
+# 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"
-_unset = _utils.Unset()
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.
@@ -28,40 +109,180 @@ def validates(version):
Registered validators and their meta schemas will be considered when
parsing ``$schema`` properties' URIs.
- :argument str version: an identifier to use as the version's name
- :returns: a class decorator to decorate the validator with the version
+ 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
- if u"id" in cls.META_SCHEMA:
- meta_schemas[cls.META_SCHEMA[u"id"]] = 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 create(meta_schema, validators=(), version=None, default_types=None): # noqa
- if default_types is None:
- 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,
- }
+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)
- DEFAULT_TYPES = dict(default_types)
+ 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,
+ self,
+ schema,
+ types=(),
+ resolver=None,
+ format_checker=None,
):
- self._types = dict(self.DEFAULT_TYPES)
- self._types.update(types)
+ 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)
+ resolver = RefResolver.from_schema(schema, id_of=id_of)
self.resolver = resolver
self.format_checker = format_checker
@@ -70,13 +291,28 @@ def create(meta_schema, validators=(), version=None, default_types=None): # noq
@classmethod
def check_schema(cls, schema):
for error in cls(cls.META_SCHEMA).iter_errors(schema):
- raise SchemaError.create_from(error)
+ raise exceptions.SchemaError.create_from(error)
def iter_errors(self, instance, _schema=None):
if _schema is None:
_schema = self.schema
- with self.resolver.in_scope(_schema.get(u"id", u"")):
+ 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)]
@@ -100,6 +336,9 @@ def create(meta_schema, validators=(), version=None, default_types=None): # noq
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):
@@ -114,19 +353,10 @@ def create(meta_schema, validators=(), version=None, default_types=None): # noq
raise error
def is_type(self, instance, type):
- if type not in self._types:
- raise UnknownType(type, instance, self.schema)
- pytypes = self._types[type]
-
- # bool inherits from int, so ensure bools aren't reported as ints
- if isinstance(instance, bool):
- pytypes = _utils.flatten(pytypes)
- is_number = any(
- issubclass(pytype, numbers.Number) for pytype in pytypes
- )
- if is_number and bool not in pytypes:
- return False
- return isinstance(instance, pytypes)
+ 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)
@@ -139,104 +369,290 @@ def create(meta_schema, validators=(), version=None, default_types=None): # noq
return Validator
-def extend(validator, validators, version=None):
+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,
- default_types=validator.DEFAULT_TYPES,
+ 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" : _validators.dependencies,
- u"disallow" : _validators.disallow_draft3,
- u"divisibleBy" : _validators.multipleOf,
- u"enum" : _validators.enum,
- u"extends" : _validators.extends_draft3,
- u"format" : _validators.format,
- u"items" : _validators.items,
- u"maxItems" : _validators.maxItems,
- u"maxLength" : _validators.maxLength,
- u"maximum" : _validators.maximum,
- u"minItems" : _validators.minItems,
- u"minLength" : _validators.minLength,
- u"minimum" : _validators.minimum,
- u"multipleOf" : _validators.multipleOf,
- u"pattern" : _validators.pattern,
- u"patternProperties" : _validators.patternProperties,
- u"properties" : _validators.properties_draft3,
- u"type" : _validators.type_draft3,
- u"uniqueItems" : _validators.uniqueItems,
+ 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_draft4,
- u"anyOf" : _validators.anyOf_draft4,
- u"dependencies" : _validators.dependencies,
- u"enum" : _validators.enum,
- u"format" : _validators.format,
- u"items" : _validators.items,
- u"maxItems" : _validators.maxItems,
- u"maxLength" : _validators.maxLength,
- u"maxProperties" : _validators.maxProperties_draft4,
- u"maximum" : _validators.maximum,
- u"minItems" : _validators.minItems,
- u"minLength" : _validators.minLength,
- u"minProperties" : _validators.minProperties_draft4,
- u"minimum" : _validators.minimum,
- u"multipleOf" : _validators.multipleOf,
- u"not" : _validators.not_draft4,
- u"oneOf" : _validators.oneOf_draft4,
- u"pattern" : _validators.pattern,
- u"patternProperties" : _validators.patternProperties,
- u"properties" : _validators.properties_draft4,
- u"required" : _validators.required_draft4,
- u"type" : _validators.type_draft4,
- u"uniqueItems" : _validators.uniqueItems,
+ 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.
- :argument str base_uri: URI of the referring document
- :argument referrer: the actual referring document
- :argument dict store: a mapping from URIs to documents to cache
- :argument bool cache_remote: whether remote refs should be cached after
- first resolution
- :argument dict handlers: a mapping from URI schemes to functions that
- should be used to retrieve them
+ 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=(),
+ self,
+ base_uri,
+ referrer,
+ store=(),
+ cache_remote=True,
+ handlers=(),
+ urljoin_cache=None,
+ remote_cache=None,
):
- self.base_uri = base_uri
- self.resolution_scope = base_uri
- # This attribute is not used, it is for backwards compatibility
+ 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)
@@ -244,64 +660,139 @@ class RefResolver(object):
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, *args, **kwargs):
+ def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
"""
Construct a resolver from a JSON schema object.
- :argument schema schema: the referring schema
- :rtype: :class:`RefResolver`
+ 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.
- return cls(schema.get(u"id", u""), schema, *args, **kwargs)
+ 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):
- old_scope = self.resolution_scope
- self.resolution_scope = urljoin(old_scope, scope)
+ """
+ Temporarily enter the given scope for the duration of the context.
+ """
+ self.push_scope(scope)
try:
yield
finally:
- self.resolution_scope = old_scope
+ self.pop_scope()
@contextlib.contextmanager
def resolving(self, ref):
"""
- Context manager which resolves a JSON ``ref`` and enters the
- resolution scope of this ref.
+ Resolve the given ``ref`` and enter its resolution scope.
+
+ Exits the scope on exit of this context manager.
+
+ Arguments:
- :argument str ref: reference to resolve
+ ref (str):
+ The reference to resolve
"""
- full_uri = urljoin(self.resolution_scope, ref)
- uri, fragment = urldefrag(full_uri)
- if not uri:
- uri = self.base_uri
+ url, resolved = self.resolve(ref)
+ self.push_scope(url)
+ try:
+ yield resolved
+ finally:
+ self.pop_scope()
- if uri in self.store:
- document = self.store[uri]
- else:
+ 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(uri)
+ document = self.resolve_remote(url)
except Exception as exc:
- raise RefResolutionError(exc)
+ raise exceptions.RefResolutionError(exc)
- old_base_uri, self.base_uri = self.base_uri, uri
- try:
- with self.in_scope(uri):
- yield self.resolve_fragment(document, fragment)
- finally:
- self.base_uri = old_base_uri
+ return self.resolve_fragment(document, fragment)
def resolve_fragment(self, document, fragment):
"""
Resolve a ``fragment`` within the referenced ``document``.
- :argument document: the referrant document
- :argument str fragment: a URI fragment to resolve within it
+ Arguments:
+
+ document:
+ The referent document
+
+ fragment (str):
+
+ a URI fragment to resolve within it
"""
fragment = fragment.lstrip(u"/")
@@ -319,7 +810,7 @@ class RefResolver(object):
try:
document = document[part]
except (TypeError, LookupError):
- raise RefResolutionError(
+ raise exceptions.RefResolutionError(
"Unresolvable JSON pointer: %r" % fragment
)
@@ -329,8 +820,9 @@ class RefResolver(object):
"""
Resolve a remote ``uri``.
- Does not check the store first, but stores the retrieved document in
- the store if :attr:`RefResolver.cache_remote` is True.
+ 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::
@@ -341,85 +833,138 @@ class RefResolver(object):
If it isn't, or if the scheme of the ``uri`` is not ``http`` or
``https``, UTF-8 is assumed.
- :argument str uri: the URI to resolve
- :returns: the retrieved document
+ Arguments:
+
+ uri (str):
- .. _requests: http://pypi.python.org/pypi/requests/
+ 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 and
- getattr(requests.Response, "json", None) is not None
- ):
+ elif scheme in [u"http", u"https"] and requests:
# Requests has support for detecting the correct encoding of
# json over http
- if callable(requests.Response.json):
- result = requests.get(uri).json()
- else:
- result = requests.get(uri).json
+ result = requests.get(uri).json()
else:
# Otherwise, pass off to urllib and assume utf-8
- result = json.loads(urlopen(uri).read().decode("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 validator_for(schema, default=_unset):
- if default is _unset:
- default = Draft4Validator
- return meta_schemas.get(schema.get(u"$schema", u""), default)
-
-
def validate(instance, schema, cls=None, *args, **kwargs):
"""
Validate an instance under the given schema.
- >>> validate([2, 3, 4], {"maxItems" : 2})
+ >>> 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 or don't care, you might prefer using the
- :meth:`~IValidator.validate` method directly on a specific validator
- (e.g. :meth:`Draft4Validator.validate`).
+ :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:
- :argument instance: the instance to validate
- :argument schema: the schema to validate with
- :argument cls: an :class:`IValidator` class that will be used to validate
- the instance.
+ The instance to validate
- 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
- :class:`Draft4Validator`.
+ schema:
- Any other provided positional and keyword arguments will be passed on when
- instantiating the ``cls``.
+ The schema to validate with
- :raises:
- :exc:`ValidationError` if the instance is invalid
+ cls (IValidator):
- :exc:`SchemaError` if the schema itself is invalid
+ 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 :func:`validates`
+ .. [#] known by a validator registered with
+ `jsonschema.validators.validates`
"""
if cls is None:
cls = validator_for(schema)
+
cls.check_schema(schema)
- cls(schema, *args, **kwargs).validate(instance)
+ 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)