summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2023-03-14 14:49:01 -0400
committerGitHub <noreply@github.com>2023-03-14 14:49:01 -0400
commitdf1501cae303eed37660a7e5b4cd4c69b06d1f9b (patch)
tree0b7373a7e0dd03145d3b67cb2afd31479c8b4002
parent6b749cfa2f47d4dd9ee74f8e01fa4fc0f8f10df6 (diff)
parentfcbeced3a52ed12ad07c3670a84ba608d9c1339c (diff)
downloadjsonschema-df1501cae303eed37660a7e5b4cd4c69b06d1f9b.tar.gz
Merge pull request #1049 from python-jsonschema/referencing
Replace `$ref` & `$dynamicRef` support / `RefResolver` with the new referencing library
-rw-r--r--.github/workflows/ci.yml12
-rw-r--r--CHANGELOG.rst7
-rw-r--r--README.rst3
-rw-r--r--docs/api/jsonschema/validators/index.rst1
-rw-r--r--docs/conf.py28
-rw-r--r--docs/errors.rst1
-rw-r--r--docs/faq.rst57
-rw-r--r--docs/index.rst1
-rw-r--r--docs/referencing.rst375
-rw-r--r--docs/requirements.txt92
-rw-r--r--docs/spelling-wordlist.txt4
-rw-r--r--jsonschema/__init__.py45
-rw-r--r--jsonschema/_legacy_validators.py57
-rw-r--r--jsonschema/_types.py26
-rw-r--r--jsonschema/_utils.py58
-rw-r--r--jsonschema/_validators.py27
-rw-r--r--jsonschema/benchmarks/issue232.py4
-rw-r--r--jsonschema/cli.py4
-rw-r--r--jsonschema/exceptions.py20
-rw-r--r--jsonschema/protocols.py12
-rw-r--r--jsonschema/schemas/draft2019-09.json42
-rw-r--r--jsonschema/schemas/draft2020-12.json58
-rw-r--r--jsonschema/schemas/draft3.json172
-rw-r--r--jsonschema/schemas/draft4.json149
-rw-r--r--jsonschema/schemas/draft6.json153
-rw-r--r--jsonschema/schemas/draft7.json166
-rw-r--r--jsonschema/schemas/vocabularies/draft2019-09/applicator56
-rw-r--r--jsonschema/schemas/vocabularies/draft2019-09/content17
-rw-r--r--jsonschema/schemas/vocabularies/draft2019-09/core57
-rw-r--r--jsonschema/schemas/vocabularies/draft2019-09/meta-data37
-rw-r--r--jsonschema/schemas/vocabularies/draft2019-09/validation98
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/applicator48
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/content17
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/core51
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/format14
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/format-annotation14
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/format-assertion14
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/meta-data37
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/unevaluated15
-rw-r--r--jsonschema/schemas/vocabularies/draft2020-12/validation98
-rw-r--r--jsonschema/tests/_suite.py50
-rw-r--r--jsonschema/tests/test_cli.py12
-rw-r--r--jsonschema/tests/test_deprecations.py212
-rw-r--r--jsonschema/tests/test_format.py3
-rw-r--r--jsonschema/tests/test_jsonschema_test_suite.py288
-rw-r--r--jsonschema/tests/test_validators.py123
-rw-r--r--jsonschema/validators.py314
-rw-r--r--pyproject.toml9
-rw-r--r--tox.ini7
49 files changed, 1052 insertions, 2113 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index efb163b..06208ab 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,18 +38,6 @@ jobs:
toxenv: pypy3-formatnongpl-build
- name: pypy-3.9
toxenv: pypy3-formatnongpl-tests
- - name: 3.7
- toxenv: py37-noextra-build
- - name: 3.7
- toxenv: py37-noextra-tests
- - name: 3.7
- toxenv: py37-format-build
- - name: 3.7
- toxenv: py37-format-tests
- - name: 3.7
- toxenv: py37-formatnongpl-build
- - name: 3.7
- toxenv: py37-formatnongpl-tests
- name: 3.8
toxenv: py38-noextra-build
- name: 3.8
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 69edbd4..66d689d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,10 @@
+v4.18.0
+=======
+
+* ``jsonschema.RefResolver`` is now deprecated in favor of the new `referencing library <https://github.com/python-jsonschema/referencing/>`_.
+ ``referencing`` will begin in beta, but already is more compliant than the existing ``$ref`` support.
+ Please file issues on the ``referencing`` tracker if there is functionality missing from it.
+
v4.17.3
=======
diff --git a/README.rst b/README.rst
index 29cdb67..ea97daa 100644
--- a/README.rst
+++ b/README.rst
@@ -59,8 +59,7 @@ It can also be used from the command line by installing `check-jsonschema <https
Features
--------
-* Partial support for `Draft 2020-12 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft202012Validator>`_ and `Draft 2019-09 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft201909Validator>`_, except for ``dynamicRef`` / ``recursiveRef`` and ``$vocabulary`` (in-progress).
- Full support for `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft7Validator>`_, `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft6Validator>`_, `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft4Validator>`_ and `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft3Validator>`_
+* Full support for `Draft 2020-12 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft202012Validator>`_, `Draft 2019-09 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft201909Validator>`_, `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft7Validator>`_, `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft6Validator>`_, `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft4Validator>`_ and `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft3Validator>`_
* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/protocols/#jsonschema.protocols.Validator.iter_errors>`_ that can iteratively report *all* validation errors.
diff --git a/docs/api/jsonschema/validators/index.rst b/docs/api/jsonschema/validators/index.rst
index f5cf82c..13a9991 100644
--- a/docs/api/jsonschema/validators/index.rst
+++ b/docs/api/jsonschema/validators/index.rst
@@ -4,3 +4,4 @@
.. automodule:: jsonschema.validators
:members:
:undoc-members:
+ :private-members: _RefResolver
diff --git a/docs/conf.py b/docs/conf.py
index 06d4a64..6b1fd0a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -38,29 +38,36 @@ html_theme = "furo"
# See sphinx-doc/sphinx#10785
_TYPE_ALIASES = {
- "jsonschema._format._F", # format checkers
+ "jsonschema._format._F": ("data", "_F"),
}
-def _resolve_type_aliases(app, env, node, contnode):
- if (
- node["refdomain"] == "py"
- and node["reftype"] == "class"
- and node["reftarget"] in _TYPE_ALIASES
- ):
+def _resolve_broken_refs(app, env, node, contnode):
+ if node["refdomain"] != "py":
+ return
+
+ if node["reftarget"].startswith("referencing."): # :( :( :( :( :(
+ node["reftype"] = "data"
+ from sphinx.ext import intersphinx
+ return intersphinx.resolve_reference_in_inventory(
+ env, "referencing", node, contnode,
+ )
+
+ kind, target = _TYPE_ALIASES.get(node["reftarget"], (None, None))
+ if kind is not None:
return app.env.get_domain("py").resolve_xref(
env,
node["refdoc"],
app.builder,
- "data",
- node["reftarget"],
+ kind,
+ target,
node,
contnode,
)
def setup(app):
- app.connect("missing-reference", _resolve_type_aliases)
+ app.connect("missing-reference", _resolve_broken_refs)
# = Builders =
@@ -116,6 +123,7 @@ autosectionlabel_prefix_document = True
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
+ "referencing": ("https://referencing.readthedocs.io/en/stable/", None),
"ujs": ("https://json-schema.org/understanding-json-schema/", None),
}
diff --git a/docs/errors.rst b/docs/errors.rst
index 7cfb502..5b0230f 100644
--- a/docs/errors.rst
+++ b/docs/errors.rst
@@ -273,6 +273,7 @@ error objects.
.. testcode::
+ from jsonschema.exceptions import ErrorTree
tree = ErrorTree(v.iter_errors(instance))
As you can see, `jsonschema.exceptions.ErrorTree` takes an
diff --git a/docs/faq.rst b/docs/faq.rst
index 4dc1c5b..5ae3e62 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -85,9 +85,10 @@ The JSON object ``{}`` is simply the Python `dict` ``{}``, and a JSON Schema lik
The :kw:`$ref` keyword is a single notable exception.
- Specifically, in the case where `jsonschema` is asked to `resolve a remote reference <jsonschema.validators.RefResolver>`, it has no choice but to assume that the remote reference is serialized as JSON, and to deserialize it using the `json` module.
+ Specifically, in the case where `jsonschema` is asked to resolve a remote reference, it has no choice but to assume that the remote reference is serialized as JSON, and to deserialize it using the `json` module.
One cannot today therefore reference some remote piece of YAML and have it deserialized into Python objects by this library without doing some additional work.
+ See `Resolving References to Schemas Written in YAML <referencing:Resolving References to Schemas Written in YAML>` for details.
In practice what this means for JSON-like formats like YAML and TOML is that indeed one can generally schematize and then validate them exactly as if they were JSON by simply first deserializing them using libraries like ``PyYAML`` or the like, and passing the resulting Python objects into functions within this library.
@@ -99,60 +100,6 @@ In such cases one is recommended to first pre-process the data such that the res
In the previous example, if the desired behavior is to transparently coerce numeric properties to strings, as Javascript might, then do the conversion explicitly before passing data to this library.
-How do I configure a base URI for $ref resolution using local files?
---------------------------------------------------------------------
-
-`jsonschema` supports loading schemas from the filesystem.
-
-The most common mistake when configuring a `jsonschema.validators.RefResolver`
-to retrieve schemas from the local filesystem is to give it a base URI
-which points to a directory, but forget to add a trailing slash.
-
-For example, given a directory ``/tmp/foo/`` with ``bar/schema.json``
-within it, you should use something like:
-
-.. code-block:: python
-
- from pathlib import Path
-
- import jsonschema.validators
-
- path = Path("/tmp/foo")
- resolver = jsonschema.validators.RefResolver(
- base_uri=f"{path.as_uri()}/",
- referrer=True,
- )
- jsonschema.validate(
- instance={},
- schema={"$ref": "bar/schema.json"},
- resolver=resolver,
- )
-
-where note:
-
- * the base URI has a trailing slash, even though
- `pathlib.PurePath.as_uri` does not add it!
- * any relative refs are now given relative to the provided directory
-
-If you forget the trailing slash, you'll find references are resolved a
-directory too high.
-
-You're likely familiar with this behavior from your browser. If you
-visit a page at ``https://example.com/foo``, then links on it like
-``<a href="./bar">`` take you to ``https://example.com/bar``, not
-``https://example.com/foo/bar``. For this reason many sites will
-redirect ``https://example.com/foo`` to ``https://example.com/foo/``,
-i.e. add the trailing slash, so that relative links on the page will keep the
-last path component.
-
-There are, in summary, 2 ways to do this properly:
-
-* Remember to include a trailing slash, so your base URI is
- ``file:///foo/bar/`` rather than ``file:///foo/bar``, as shown above
-* Use a file within the directory as your base URI rather than the
- directory itself, i.e. ``file://foo/bar/baz.json``, which will of course
- cause ``baz.json`` to be removed while resolving relative URIs
-
Why doesn't my schema's default property set the default on my instance?
------------------------------------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index d66aa8b..949ab44 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,6 +12,7 @@ Contents
validate
errors
+ referencing
creating
faq
api/index
diff --git a/docs/referencing.rst b/docs/referencing.rst
new file mode 100644
index 0000000..e3f0e7f
--- /dev/null
+++ b/docs/referencing.rst
@@ -0,0 +1,375 @@
+=========================
+JSON (Schema) Referencing
+=========================
+
+The JSON Schema :kw:`$ref` and :kw:`$dynamicRef` keywords allow schema authors to combine multiple schemas (or subschemas) together for reuse or deduplication.
+
+The `referencing <referencing:index>` library was written in order to provide a simple, well-behaved and well-tested implementation of this kind of reference resolution [1]_.
+It has its `own documentation which is worth reviewing <referencing:intro>`, but this page serves as an introduction which is tailored specifically to JSON Schema, and even more specifically to how to configure `referencing <referencing:index>` for use with `Validator` objects in order to customize the behavior of the :kw:`$ref` keyword and friends in your schemas.
+
+Configuring `jsonschema` for custom referencing behavior is essentially a two step process:
+
+ * Create a `referencing.Registry` object that behaves the way you wish
+
+ * Pass the `referencing.Registry` to your `Validator` when instantiating it
+
+The examples below essentially follow these two steps.
+
+.. [1] One that in fact is independent of this `jsonschema` library itself, and may some day be used by other tools or implementations.
+
+
+Introduction to the `referencing <referencing:index>` API
+---------------------------------------------------------
+
+There are 3 main objects to be aware of in the `referencing` API:
+
+ * `referencing.Registry`, which represents a specific immutable set of JSON Schemas (either in-memory or retrievable)
+ * `referencing.Specification`, which represents a specific *version* of the JSON Schema specification, which can have differing referencing behavior.
+ JSON Schema-specific specifications live in the `referencing.jsonschema` module and are named like `referencing.jsonschema.DRAFT202012`.
+ * `referencing.Resource`, which represents a specific JSON Schema (often a Python `dict`) *along* with a specific `referencing.Specification` it is to be interpreted under.
+
+As a concrete example, the simple schema ``{"type": "integer"}`` may be interpreted as a schema under either Draft 2020-12 or Draft 4 of the JSON Schema specification (amongst others); in draft 2020-12, the float ``2.0`` must be considered an integer, whereas in draft 4, it potentially is not.
+If you mean the former (i.e. to associate this schema with draft 2020-12), you'd use ``referencing.Resource(contents={"type": "integer"}, specification=referencing.jsonschema.DRAFT202012)``, whereas for the latter you'd use `referencing.jsonschema.DRAFT4`.
+
+.. seealso:: the JSON Schema :kw:`$schema` keyword
+
+ Which should generally be used to remove all ambiguity and identify *internally* to the schema what version it is written for.
+
+A schema may be identified via one or more URIs, either because they contain an :kw:`$id` keyword (in suitable versions of the JSON Schema specification) which indicates their canonical URI, or simply because you wish to externally associate a URI with the schema, regardless of whether it contains an ``$id`` keyword.
+You could add the aforementioned simple schema to a `referencing.Registry` by creating an empty registry and then identifying it via some URI:
+
+.. testcode::
+
+ from referencing import Registry, Resource
+ from referencing.jsonschema import DRAFT202012
+ schema = Resource(contents={"type": "integer"}, specification=DRAFT202012)
+ registry = Registry().with_resource(uri="http://example.com/my/schema", resource=schema)
+ print(registry)
+
+.. testoutput::
+
+ <Registry (1 uncrawled resource)>
+
+.. note::
+
+ `referencing.Registry` is an entirely immutable object.
+ All of its methods which add schemas (resources) to itself return *new* registry objects containing the added schemas.
+
+You could also confirm your schema is in the registry if you'd like, via `referencing.Registry.contents`, which will show you the contents of a resource at a given URI:
+
+.. testcode::
+
+ print(registry.contents("http://example.com/my/schema"))
+
+.. testoutput::
+
+ {'type': 'integer'}
+
+For further details, see the `referencing documentation <referencing:intro>`.
+
+Common Scenarios
+----------------
+
+.. _in-memory-schemas:
+
+Making Additional In-Memory Schemas Available
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The most common scenario one is likely to encounter is the desire to include a small number of additional in-memory schemas, making them available for use during validation.
+
+For instance, imagine the below schema for non-negative integers:
+
+.. code:: json
+
+ {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "integer",
+ "minimum": 0
+ }
+
+We may wish to have other schemas we write be able to make use of this schema, and refer to it as ``http://example.com/nonneg-int-schema`` and/or as ``urn:nonneg-integer-schema``.
+
+To do so we make use of APIs from the referencing library to create a `referencing.Registry` which maps the URIs above to this schema:
+
+.. code:: python
+
+ from referencing import Registry, Resource
+ schema = Resource.from_contents(
+ {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "integer",
+ "minimum": 0,
+ },
+ )
+ registry = Registry().with_resources(
+ [
+ ("http://example.com/nonneg-int-schema", schema),
+ ("urn:nonneg-integer-schema", schema),
+ ],
+ )
+
+What's above is likely mostly self-explanatory, other than the presence of the `referencing.Resource.from_contents` function.
+Its purpose is to convert a piece of "opaque" JSON (or really a Python `dict` containing deserialized JSON) into an object which indicates what *version* of JSON Schema the schema is meant to be interpreted under.
+Calling it will inspect a :kw:`$schema` keyword present in the given schema and use that to associate the JSON with an appropriate `specification <referencing.Specification>`.
+If your schemas do not contain ``$schema`` dialect identifiers, and you intend for them to be interpreted always under a specific dialect -- say Draft 2020-12 of JSON Schema -- you may instead use e.g.:
+
+.. code:: python
+
+ from referencing import Registry, Resource
+ from referencing.jsonschema import DRAFT2020212
+ schema = DRAFT202012.create_resource({"type": "integer", "minimum": 0})
+ registry = Registry().with_resources(
+ [
+ ("http://example.com/nonneg-int-schema", schema),
+ ("urn:nonneg-integer-schema", schema),
+ ],
+ )
+
+which has the same functional effect.
+
+You can now pass this registry to your `Validator`, which allows a schema passed to it to make use of the aforementioned URIs to refer to our non-negative integer schema.
+Here for instance is an example which validates that instances are JSON objects with non-negative integral values:
+
+.. code:: python
+
+ from jsonschema import Draft202012Validator
+ validator = Draft202012Validator(
+ {
+ "type": "object",
+ "additionalProperties": {"$ref": "urn:nonneg-integer-schema"},
+ },
+ registry=registry, # the critical argument, our registry from above
+ )
+ validator.validate({"foo": 37})
+ validator.validate({"foo": -37}) # Uh oh!
+
+.. _ref-filesystem:
+
+Resolving References from the File System
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Another common request from schema authors is to be able to map URIs to the file system, perhaps while developing a set of schemas in different local files.
+The referencing library supports doing so dynamically by configuring a callable which can be used to retrieve any schema which is *not* already pre-loaded in the manner described `above <in-memory-schemas>`.
+
+Here we resolve any schema beginning with ``http://localhost`` to a directory ``/tmp/schemas`` on the local filesystem (note of course that this will not work if run directly unless you have populated that directory with some schemas):
+
+.. code:: python
+
+ from pathlib import Path
+ import json
+
+ from referencing import Registry, Resource
+ from referencing.exceptions import NoSuchResource
+
+ SCHEMAS = Path("/tmp/schemas")
+
+ def retrieve_from_filesystem(uri: str):
+ if not uri.startswith("http://localhost/"):
+ raise NoSuchResource(ref=uri)
+ path = SCHEMAS / Path(uri.removeprefix("http://localhost/"))
+ contents = json.loads(path.read_text())
+ return Resource.from_contents(contents)
+
+ registry = Registry(retrieve=retrieve_from_filesystem)
+
+Such a registry can then be used with `Validator` objects in the same way shown above, and any such references to URIs which are not already in-memory will be retrieved from the configured directory.
+
+We can mix the two examples above if we wish for some in-memory schemas to be available in addition to the filesystem schemas, e.g.:
+
+.. code:: python
+
+ from referencing.jsonschema import DRAFT7
+ registry = Registry(retrieve=retrieve_from_filesystem).with_resource(
+ "urn:non-empty-array", DRAFT7.create_resource({"type": "array", "minItems": 1}),
+ )
+
+where we've made use of the similar `referencing.Registry.with_resource` function to add a single additional resource.
+
+Resolving References to Schemas Written in YAML
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Generalizing slightly, the retrieval function provided need not even assume that it is retrieving JSON.
+As long as you deserialize what you have retrieved into Python objects, you may equally be retrieving references to YAML documents or any other format.
+
+Here for instance we retrieve YAML documents in a way similar to the `above <ref-filesystem>` using PyYAML:
+
+.. code:: python
+
+ from pathlib import Path
+ import yaml
+
+ from referencing import Registry, Resource
+ from referencing.exceptions import NoSuchResource
+
+ SCHEMAS = Path("/tmp/yaml-schemas")
+
+ def retrieve_yaml(uri: str):
+ if not uri.startswith("http://localhost/"):
+ raise NoSuchResource(ref=uri)
+ path = SCHEMAS / Path(uri.removeprefix("http://localhost/"))
+ contents = yaml.safe_load(path.read_text())
+ return Resource.from_contents(contents)
+
+ registry = Registry(retrieve=retrieve_yaml)
+
+.. note::
+
+ Not all YAML fits within the JSON data model.
+
+ JSON Schema is defined specifically for JSON, and has well-defined behavior strictly for Python objects which could have possibly existed as JSON.
+
+ If you stick to the subset of YAML for which this is the case then you shouldn't have issue, but if you pass schemas (or instances) around whose structure could never have possibly existed as JSON (e.g. a mapping whose keys are not strings), all bets are off.
+
+One could similarly imagine a retrieval function which switches on whether to call ``yaml.safe_load`` or ``json.loads`` by file extension (or some more reliable mechanism) and thereby support retrieving references of various different file formats.
+
+.. _http:
+
+Automatically Retrieving Resources Over HTTP
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the general case, the JSON Schema specifications tend to `discourage <https://json-schema.org/draft/2020-12/json-schema-core.html#name-loading-a-referenced-schema>`_ implementations (like this one) from automatically retrieving references over the network, or even assuming such a thing is feasible (as schemas may be identified by URIs which are strictly identifiers, and not necessarily downloadable from the URI even when such a thing is sensical).
+
+However, if you as a schema author are in a situation where you indeed do wish to do so for convenience (and understand the implications of doing so), you may do so by making use of the ``retrieve`` argument to `referencing.Registry`.
+
+Here is how one would configure a registry to automatically retrieve schemas from the `JSON Schema Store <https://www.schemastore.org>`_ on the fly using the `httpx <https://www.python-httpx.org/>`_:
+
+.. code:: python
+
+ from referencing import Registry, Resource
+ import httpx
+
+ def retrieve_via_httpx(uri: str):
+ response = httpx.get(uri)
+ return Resource.from_contents(response.json())
+
+ registry = Registry(retrieve=retrieve_via_httpx)
+
+Given such a registry, we can now, for instance, validate instances against schemas from the schema store by passing the ``registry`` we configured to our `Validator` as in previous examples:
+
+.. code:: python
+
+ from jsonschema import Draft202012Validator
+ Draft202012Validator(
+ {"$ref": "https://json.schemastore.org/pyproject.json"},
+ registry=registry,
+ ).validate({"project": {"name": 12}})
+
+which should in this case indicate the example data is invalid:
+
+.. code:: python
+
+ Traceback (most recent call last):
+ File "example.py", line 14, in <module>
+ ).validate({"project": {"name": 12}})
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "jsonschema/validators.py", line 345, in validate
+ raise error
+ jsonschema.exceptions.ValidationError: 12 is not of type 'string'
+
+ Failed validating 'type' in schema['properties']['project']['properties']['name']:
+ {'pattern': '^([a-zA-Z\\d]|[a-zA-Z\\d][\\w.-]*[a-zA-Z\\d])$',
+ 'title': 'Project name',
+ 'type': 'string'}
+
+ On instance['project']['name']:
+ 12
+
+Retrieving resources from a SQLite database or some other network-accessible resource should be more or less similar, replacing the HTTP client with one for your database of course.
+
+.. warning::
+
+ Be sure you understand the security implications of the reference resolution you configure.
+ And if you accept untrusted schemas, doubly sure!
+
+ You wouldn't want a user causing your machine to go off and retrieve giant files off the network by passing it a ``$ref`` to some huge blob, or exploiting similar vulnerabilities in your setup.
+
+
+Migrating From ``RefResolver``
+------------------------------
+
+Older versions of `jsonschema` used a different object -- `_RefResolver` -- for reference resolution, which you a schema author may already be configuring for your own use.
+
+`_RefResolver` is now fully deprecated and replaced by the use of `referencing.Registry` as shown in examples above.
+
+If you are not already constructing your own `_RefResolver`, this change should be transparent to you (or even recognizably improved, as the point of the migration was to improve the quality of the referencing implementation and enable some new functionality).
+
+.. table:: Rough equivalence between `_RefResolver` and `referencing.Registry` APIs
+ :widths: auto
+
+ =========================================================== =====================================================================================================================
+ Old API New API
+ =========================================================== =====================================================================================================================
+ ``RefResolver.from_schema({"$id": "urn:example:foo", ...}`` ``Registry().with_resource(uri="urn:example:foo", resource=Resource.from_contents({"$id": "urn:example:foo", ...}))``
+ Overriding ``RefResolver.resolve_from_url`` Passing a callable to `referencing.Registry`\ 's ``retrieve`` argument
+ ``DraftNValidator(..., resolver=_RefResolver(...))`` `` DraftNValidator(..., registry=Registry().with_resources(...))``
+ =========================================================== =====================================================================================================================
+
+
+Here are some more specifics on how to migrate to the newer APIs:
+
+The ``store`` argument
+~~~~~~~~~~~~~~~~~~~~~~
+
+`_RefResolver`\ 's ``store`` argument was essentially the equivalent of `referencing.Registry`\ 's in-memory schema storage.
+
+If you currently pass a set of schemas via e.g.:
+
+.. code:: python
+
+ from jsonschema import Draft202012Validator, RefResolver
+ resolver = RefResolver.from_schema(
+ schema={"title": "my schema"},
+ store={"http://example.com": {"type": "integer"}},
+ )
+ validator = Draft202012Validator(
+ {"$ref": "http://example.com"},
+ resolver=resolver,
+ )
+ validator.validate("foo")
+
+you should be able to simply move to something like:
+
+.. code:: python
+
+ from referencing import Registry
+ from referencing.jsonschema import DRAFT202012
+
+ from jsonschema import Draft202012Validator
+
+ registry = Registry().with_resource(
+ "http://example.com",
+ DRAFT202012.create_resource({"type": "integer"}),
+ )
+ validator = Draft202012Validator(
+ {"$ref": "http://example.com"},
+ registry=registry,
+ )
+ validator.validate("foo")
+
+Handlers
+~~~~~~~~
+
+The ``handlers`` functionality from `_RefResolver` was a way to support additional HTTP schemes for schema retrieval.
+
+Here you should move to a custom ``retrieve`` function which does whatever you'd like.
+E.g. in pseudocode:
+
+.. code:: python
+
+ from urllib.parse import urlsplit
+
+ def retrieve(uri: str):
+ parsed = urlsplit(uri)
+ if parsed.scheme == "file":
+ ...
+ elif parsed.scheme == "custom":
+ ...
+
+ registry = Registry(retrieve=retrieve)
+
+
+Other Key Functional Differences
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Whilst `_RefResolver` *did* automatically retrieve remote references (against the recommendation of the spec, and in a way which therefore could lead to questionable security concerns when combined with untrusted schemas), `referencing.Registry` does *not* do so.
+If you rely on this behavior, you should follow the `above example of retrieving resources over HTTP <http>`.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 48ee19b..0db48a3 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,25 +1,33 @@
#
-# This file is autogenerated by pip-compile with python 3.11
-# To update, run:
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
#
-# pip-compile docs/requirements.in
+# pip-compile --resolver=backtracking docs/requirements.in
#
-alabaster==0.7.12
+alabaster==0.7.13
# via sphinx
-astroid==2.12.13
+astroid==2.15.0
# via sphinx-autoapi
attrs==22.2.0
- # via jsonschema
-babel==2.11.0
+ # via
+ # jsonschema
+ # referencing
+babel==2.12.1
# via sphinx
-beautifulsoup4==4.11.1
+beautifulsoup4==4.11.2
# via furo
certifi==2022.12.7
# via requests
-charset-normalizer==2.1.1
+charset-normalizer==3.1.0
# via requests
+contourpy==1.0.7
+ # via matplotlib
+cycler==0.11.0
+ # via matplotlib
docutils==0.19
# via sphinx
+fonttools==4.39.0
+ # via matplotlib
furo==2022.12.7
# via -r docs/requirements.in
idna==3.4
@@ -32,35 +40,59 @@ jinja2==3.1.2
# sphinx-autoapi
file:.#egg=jsonschema
# via -r docs/requirements.in
-lazy-object-proxy==1.8.0
+jsonschema-specifications==2023.3.4
+ # via jsonschema
+kiwisolver==1.4.4
+ # via matplotlib
+lazy-object-proxy==1.9.0
# via astroid
lxml==4.9.2
# via
# -r docs/requirements.in
# sphinx-json-schema-spec
-markupsafe==2.1.1
+markupsafe==2.1.2
# via jinja2
-packaging==22.0
- # via sphinx
+matplotlib==3.7.1
+ # via sphinxext-opengraph
+numpy==1.24.2
+ # via
+ # contourpy
+ # matplotlib
+packaging==23.0
+ # via
+ # matplotlib
+ # sphinx
+pillow==9.4.0
+ # via matplotlib
pyenchant==3.2.2
# via sphinxcontrib-spelling
-pygments==2.13.0
+pygments==2.14.0
# via
# furo
# sphinx
-pyrsistent==0.19.2
- # via jsonschema
-pytz==2022.7
- # via babel
+pyparsing==3.0.9
+ # via matplotlib
+python-dateutil==2.8.2
+ # via matplotlib
pyyaml==6.0
# via sphinx-autoapi
-requests==2.28.1
+referencing==0.24.4
+ # via
+ # jsonschema
+ # jsonschema-specifications
+requests==2.28.2
# via sphinx
+rpds-py==0.6.1
+ # via
+ # jsonschema
+ # referencing
+six==1.16.0
+ # via python-dateutil
snowballstemmer==2.2.0
# via sphinx
-soupsieve==2.3.2.post1
+soupsieve==2.4
# via beautifulsoup4
-sphinx==5.3.0
+sphinx==6.1.3
# via
# -r docs/requirements.in
# furo
@@ -71,21 +103,21 @@ sphinx==5.3.0
# sphinx-json-schema-spec
# sphinxcontrib-spelling
# sphinxext-opengraph
-sphinx-autoapi==2.0.0
+sphinx-autoapi==2.0.1
# via -r docs/requirements.in
-sphinx-autodoc-typehints==1.19.5
+sphinx-autodoc-typehints==1.22
# via -r docs/requirements.in
sphinx-basic-ng==1.0.0b1
# via furo
sphinx-copybutton==0.5.1
# via -r docs/requirements.in
-sphinx-json-schema-spec==2.3.3
+sphinx-json-schema-spec==2023.2.4
# via -r docs/requirements.in
-sphinxcontrib-applehelp==1.0.2
+sphinxcontrib-applehelp==1.0.4
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
-sphinxcontrib-htmlhelp==2.0.0
+sphinxcontrib-htmlhelp==2.0.1
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
@@ -93,13 +125,13 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
-sphinxcontrib-spelling==7.7.0
+sphinxcontrib-spelling==8.0.0
# via -r docs/requirements.in
-sphinxext-opengraph==0.7.4
+sphinxext-opengraph==0.8.1
# via -r docs/requirements.in
unidecode==1.3.6
# via sphinx-autoapi
-urllib3==1.26.13
+urllib3==1.26.15
# via requests
-wrapt==1.14.1
+wrapt==1.15.0
# via astroid
diff --git a/docs/spelling-wordlist.txt b/docs/spelling-wordlist.txt
index a2c7d56..640d56f 100644
--- a/docs/spelling-wordlist.txt
+++ b/docs/spelling-wordlist.txt
@@ -6,10 +6,12 @@ ValidationError
# 0th, sigh...
th
+amongst
callables
# non-codeblocked cls from autoapi
cls
deque
+deduplication
dereferences
deserialize
deserialized
@@ -30,6 +32,7 @@ online
outputter
pre
programmatically
+pseudocode
recurses
regex
repr
@@ -41,6 +44,7 @@ submodules
subschema
subschemas
subscopes
+untrusted
uri
validator
validators
diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py
index 6628fc7..ad7affc 100644
--- a/jsonschema/__init__.py
+++ b/jsonschema/__init__.py
@@ -12,13 +12,7 @@ import warnings
from jsonschema._format import FormatChecker
from jsonschema._types import TypeChecker
-from jsonschema.exceptions import (
- ErrorTree,
- FormatError,
- RefResolutionError,
- SchemaError,
- ValidationError,
-)
+from jsonschema.exceptions import SchemaError, ValidationError
from jsonschema.protocols import Validator
from jsonschema.validators import (
Draft3Validator,
@@ -27,7 +21,6 @@ from jsonschema.validators import (
Draft7Validator,
Draft201909Validator,
Draft202012Validator,
- RefResolver,
validate,
)
@@ -48,6 +41,42 @@ def __getattr__(name):
import importlib_metadata as metadata
return metadata.version("jsonschema")
+ elif name == "RefResolver":
+ from jsonschema.validators import _RefResolver
+ warnings.warn(
+ _RefResolver._DEPRECATION_MESSAGE,
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _RefResolver
+ elif name == "ErrorTree":
+ warnings.warn(
+ "Importing ErrorTree directly from the jsonschema package "
+ "is deprecated and will become an ImportError. Import it from "
+ "jsonschema.exceptions instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ from jsonschema.exceptions import ErrorTree
+ return ErrorTree
+ elif name == "FormatError":
+ warnings.warn(
+ "Importing FormatError directly from the jsonschema package "
+ "is deprecated and will become an ImportError. Import it from "
+ "jsonschema.exceptions instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ from jsonschema.exceptions import FormatError
+ return FormatError
+ elif name == "RefResolutionError":
+ from jsonschema.exceptions import _RefResolutionError
+ warnings.warn(
+ _RefResolutionError._DEPRECATION_MESSAGE,
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _RefResolutionError
format_checkers = {
"draft3_format_checker": Draft3Validator,
diff --git a/jsonschema/_legacy_validators.py b/jsonschema/_legacy_validators.py
index cc5e3f4..aebf1b9 100644
--- a/jsonschema/_legacy_validators.py
+++ b/jsonschema/_legacy_validators.py
@@ -1,20 +1,9 @@
+from referencing.jsonschema import lookup_recursive_ref
+
from jsonschema import _utils
from jsonschema.exceptions import ValidationError
-def id_of_ignore_ref(property="$id"):
- def id_of(schema):
- """
- Ignore an ``$id`` sibling of ``$ref`` if it is present.
-
- Otherwise, return the ID of the given schema.
- """
- if schema is True or schema is False or "$ref" in schema:
- return ""
- return schema.get(property, "")
- return id_of
-
-
def ignore_ref_siblings(schema):
"""
Ignore siblings of ``$ref`` if it is present.
@@ -223,22 +212,12 @@ def contains_draft6_draft7(validator, contains, instance, schema):
def recursiveRef(validator, recursiveRef, instance, schema):
- lookup_url, target = validator.resolver.resolution_scope, validator.schema
-
- for each in reversed(validator.resolver._scopes_stack[1:]):
- lookup_url, next_target = validator.resolver.resolve(each)
- if next_target.get("$recursiveAnchor"):
- target = next_target
- else:
- break
-
- fragment = recursiveRef.lstrip("#")
- subschema = validator.resolver.resolve_fragment(target, fragment)
- # FIXME: This is gutted (and not calling .descend) because it can trigger
- # recursion errors, so there's a bug here. Re-enable the tests to
- # see it.
- subschema
- return []
+ resolved = lookup_recursive_ref(validator._resolver)
+ yield from validator.descend(
+ instance,
+ resolved.contents,
+ resolver=resolved.resolver,
+ )
def find_evaluated_item_indexes_by_schema(validator, instance, schema):
@@ -256,15 +235,17 @@ def find_evaluated_item_indexes_by_schema(validator, instance, schema):
return list(range(0, len(instance)))
if "$ref" in schema:
- scope, resolved = validator.resolver.resolve(schema["$ref"])
- validator.resolver.push_scope(scope)
-
- try:
- evaluated_indexes += find_evaluated_item_indexes_by_schema(
- validator, instance, resolved,
- )
- finally:
- validator.resolver.pop_scope()
+ resolved = validator._resolver.lookup(schema["$ref"])
+ evaluated_indexes.extend(
+ find_evaluated_item_indexes_by_schema(
+ validator.evolve(
+ schema=resolved.contents,
+ _resolver=resolved.resolver,
+ ),
+ instance,
+ resolved.contents,
+ ),
+ )
if "items" in schema:
if validator.is_type(schema["items"], "object"):
diff --git a/jsonschema/_types.py b/jsonschema/_types.py
index 5b543f7..9f8dfa0 100644
--- a/jsonschema/_types.py
+++ b/jsonschema/_types.py
@@ -1,26 +1,22 @@
from __future__ import annotations
+from typing import Any, Callable, Mapping
import numbers
-import typing
-from pyrsistent import pmap
-from pyrsistent.typing import PMap
+from rpds import HashTrieMap
import attr
from jsonschema.exceptions import UndefinedTypeCheck
-# unfortunately, the type of pmap is generic, and if used as the attr.ib
+# unfortunately, the type of HashTrieMap is generic, and if used as the attr.ib
# converter, the generic type is presented to mypy, which then fails to match
# the concrete type of a type checker mapping
# this "do nothing" wrapper presents the correct information to mypy
-def _typed_pmap_converter(
- init_val: typing.Mapping[
- str,
- typing.Callable[["TypeChecker", typing.Any], bool],
- ],
-) -> PMap[str, typing.Callable[["TypeChecker", typing.Any], bool]]:
- return pmap(init_val)
+def _typed_map_converter(
+ init_val: Mapping[str, Callable[["TypeChecker", Any], bool]],
+) -> HashTrieMap[str, Callable[["TypeChecker", Any], bool]]:
+ return HashTrieMap.convert(init_val)
def is_array(checker, instance):
@@ -82,11 +78,11 @@ class TypeChecker:
The initial mapping of types to their checking functions.
"""
- _type_checkers: PMap[
- str, typing.Callable[["TypeChecker", typing.Any], bool],
+ _type_checkers: HashTrieMap[
+ str, Callable[["TypeChecker", Any], bool],
] = attr.ib(
- default=pmap(),
- converter=_typed_pmap_converter,
+ default=HashTrieMap(),
+ converter=_typed_map_converter,
)
def __repr__(self):
diff --git a/jsonschema/_utils.py b/jsonschema/_utils.py
index 418348c..b14b70e 100644
--- a/jsonschema/_utils.py
+++ b/jsonschema/_utils.py
@@ -1,15 +1,7 @@
from collections.abc import Mapping, MutableMapping, Sequence
from urllib.parse import urlsplit
import itertools
-import json
import re
-import sys
-
-# The files() API was added in Python 3.9.
-if sys.version_info >= (3, 9): # pragma: no cover
- from importlib import resources
-else: # pragma: no cover
- import importlib_resources as resources # type: ignore
class URIDict(MutableMapping):
@@ -52,16 +44,6 @@ class Unset:
return "<unset>"
-def load_schema(name):
- """
- Load a schema from ./schemas/``name``.json and return it.
- """
-
- path = resources.files(__package__).joinpath(f"schemas/{name}.json")
- data = path.read_text(encoding="utf-8")
- return json.loads(data)
-
-
def format_as_index(container, indices):
"""
Construct a single string containing indexing operations for the indices.
@@ -219,15 +201,17 @@ def find_evaluated_item_indexes_by_schema(validator, instance, schema):
return list(range(0, len(instance)))
if "$ref" in schema:
- scope, resolved = validator.resolver.resolve(schema["$ref"])
- validator.resolver.push_scope(scope)
-
- try:
- evaluated_indexes += find_evaluated_item_indexes_by_schema(
- validator, instance, resolved,
- )
- finally:
- validator.resolver.pop_scope()
+ resolved = validator._resolver.lookup(schema["$ref"])
+ evaluated_indexes.extend(
+ find_evaluated_item_indexes_by_schema(
+ validator.evolve(
+ schema=resolved.contents,
+ _resolver=resolved.resolver,
+ ),
+ instance,
+ resolved.contents,
+ ),
+ )
if "prefixItems" in schema:
evaluated_indexes += list(range(0, len(schema["prefixItems"])))
@@ -278,15 +262,17 @@ def find_evaluated_property_keys_by_schema(validator, instance, schema):
evaluated_keys = []
if "$ref" in schema:
- scope, resolved = validator.resolver.resolve(schema["$ref"])
- validator.resolver.push_scope(scope)
-
- try:
- evaluated_keys += find_evaluated_property_keys_by_schema(
- validator, instance, resolved,
- )
- finally:
- validator.resolver.pop_scope()
+ resolved = validator._resolver.lookup(schema["$ref"])
+ evaluated_keys.extend(
+ find_evaluated_property_keys_by_schema(
+ validator.evolve(
+ schema=resolved.contents,
+ _resolver=resolved.resolver,
+ ),
+ instance,
+ resolved.contents,
+ ),
+ )
for keyword in [
"properties", "additionalProperties", "unevaluatedProperties",
diff --git a/jsonschema/_validators.py b/jsonschema/_validators.py
index 8542a87..7351c48 100644
--- a/jsonschema/_validators.py
+++ b/jsonschema/_validators.py
@@ -1,5 +1,4 @@
from fractions import Fraction
-from urllib.parse import urldefrag, urljoin
import re
from jsonschema._utils import (
@@ -286,33 +285,11 @@ def enum(validator, enums, instance, schema):
def ref(validator, ref, instance, schema):
- resolve = getattr(validator.resolver, "resolve", None)
- if resolve is None:
- with validator.resolver.resolving(ref) as resolved:
- yield from validator.descend(instance, resolved)
- else:
- scope, resolved = validator.resolver.resolve(ref)
- validator.resolver.push_scope(scope)
-
- try:
- yield from validator.descend(instance, resolved)
- finally:
- validator.resolver.pop_scope()
+ yield from validator._validate_reference(ref=ref, instance=instance)
def dynamicRef(validator, dynamicRef, instance, schema):
- _, fragment = urldefrag(dynamicRef)
-
- for url in validator.resolver._scopes_stack:
- lookup_url = urljoin(url, dynamicRef)
- with validator.resolver.resolving(lookup_url) as subschema:
- if ("$dynamicAnchor" in subschema
- and fragment == subschema["$dynamicAnchor"]):
- yield from validator.descend(instance, subschema)
- break
- else:
- with validator.resolver.resolving(dynamicRef) as subschema:
- yield from validator.descend(instance, subschema)
+ yield from validator._validate_reference(ref=dynamicRef, instance=instance)
def type(validator, types, instance, schema):
diff --git a/jsonschema/benchmarks/issue232.py b/jsonschema/benchmarks/issue232.py
index bf357e9..efd0715 100644
--- a/jsonschema/benchmarks/issue232.py
+++ b/jsonschema/benchmarks/issue232.py
@@ -6,14 +6,14 @@ See https://github.com/python-jsonschema/jsonschema/pull/232.
from pathlib import Path
from pyperf import Runner
-from pyrsistent import m
+from referencing import Registry
from jsonschema.tests._suite import Version
import jsonschema
issue232 = Version(
path=Path(__file__).parent / "issue232",
- remotes=m(),
+ remotes=Registry(),
name="issue232",
)
diff --git a/jsonschema/cli.py b/jsonschema/cli.py
index 1292b1a..d6b9ad9 100644
--- a/jsonschema/cli.py
+++ b/jsonschema/cli.py
@@ -23,7 +23,7 @@ except ImportError:
import attr
from jsonschema.exceptions import SchemaError
-from jsonschema.validators import RefResolver, validator_for
+from jsonschema.validators import _RefResolver, validator_for
warnings.warn(
(
@@ -277,7 +277,7 @@ def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin):
raise _CannotLoadFile()
instances = ["<stdin>"]
- resolver = RefResolver(
+ resolver = _RefResolver(
base_uri=arguments["base_uri"],
referrer=schema,
) if arguments["base_uri"] is not None else None
diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py
index 149d838..5e88d74 100644
--- a/jsonschema/exceptions.py
+++ b/jsonschema/exceptions.py
@@ -9,6 +9,7 @@ from textwrap import dedent, indent
from typing import ClassVar
import heapq
import itertools
+import warnings
import attr
@@ -20,6 +21,17 @@ STRONG_MATCHES: frozenset[str] = frozenset()
_unset = _utils.Unset()
+def __getattr__(name):
+ if name == "RefResolutionError":
+ warnings.warn(
+ _RefResolutionError._DEPRECATION_MESSAGE,
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _RefResolutionError
+ raise AttributeError(f"module {__name__} has no attribute {name}")
+
+
class _Error(Exception):
_word_for_schema_in_error_message: ClassVar[str]
@@ -181,11 +193,17 @@ class SchemaError(_Error):
@attr.s(hash=True)
-class RefResolutionError(Exception):
+class _RefResolutionError(Exception):
"""
A ref could not be resolved.
"""
+ _DEPRECATION_MESSAGE = (
+ "jsonschema.exceptions.RefResolutionError is deprecated as of version "
+ "4.18.0. If you wish to catch potential reference resolution errors, "
+ "directly catch referencing.exceptions.Unresolvable."
+ )
+
_cause = attr.ib()
def __str__(self):
diff --git a/jsonschema/protocols.py b/jsonschema/protocols.py
index 5f52166..65f5898 100644
--- a/jsonschema/protocols.py
+++ b/jsonschema/protocols.py
@@ -30,6 +30,7 @@ else:
if TYPE_CHECKING:
import jsonschema
import jsonschema.validators
+ import referencing.jsonschema
from jsonschema.exceptions import ValidationError
@@ -60,11 +61,20 @@ class Validator(Protocol):
an invalid schema can lead to undefined behavior. See
`Validator.check_schema` to validate a schema first.
+ registry:
+
+ a schema registry that will be used for looking up JSON references
+
resolver:
a resolver that will be used to resolve :kw:`$ref`
properties (JSON references). If unprovided, one will be created.
+ .. deprecated:: v4.18.0
+
+ `RefResolver <_RefResolver>` has been deprecated in favor of
+ `referencing`, and with it, this argument.
+
format_checker:
if provided, a checker which will be used to assert about
@@ -108,7 +118,7 @@ class Validator(Protocol):
def __init__(
self,
schema: Mapping | bool,
- resolver: jsonschema.validators.RefResolver | None = None,
+ registry: referencing.jsonschema.SchemaRegistry,
format_checker: jsonschema.FormatChecker | None = None,
) -> None:
...
diff --git a/jsonschema/schemas/draft2019-09.json b/jsonschema/schemas/draft2019-09.json
deleted file mode 100644
index 2248a0c..0000000
--- a/jsonschema/schemas/draft2019-09.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/schema",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/core": true,
- "https://json-schema.org/draft/2019-09/vocab/applicator": true,
- "https://json-schema.org/draft/2019-09/vocab/validation": true,
- "https://json-schema.org/draft/2019-09/vocab/meta-data": true,
- "https://json-schema.org/draft/2019-09/vocab/format": false,
- "https://json-schema.org/draft/2019-09/vocab/content": true
- },
- "$recursiveAnchor": true,
-
- "title": "Core and Validation specifications meta-schema",
- "allOf": [
- {"$ref": "meta/core"},
- {"$ref": "meta/applicator"},
- {"$ref": "meta/validation"},
- {"$ref": "meta/meta-data"},
- {"$ref": "meta/format"},
- {"$ref": "meta/content"}
- ],
- "type": ["object", "boolean"],
- "properties": {
- "definitions": {
- "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.",
- "type": "object",
- "additionalProperties": { "$recursiveRef": "#" },
- "default": {}
- },
- "dependencies": {
- "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"",
- "type": "object",
- "additionalProperties": {
- "anyOf": [
- { "$recursiveRef": "#" },
- { "$ref": "meta/validation#/$defs/stringArray" }
- ]
- }
- }
- }
-}
diff --git a/jsonschema/schemas/draft2020-12.json b/jsonschema/schemas/draft2020-12.json
deleted file mode 100644
index d5e2d31..0000000
--- a/jsonschema/schemas/draft2020-12.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/schema",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/core": true,
- "https://json-schema.org/draft/2020-12/vocab/applicator": true,
- "https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
- "https://json-schema.org/draft/2020-12/vocab/validation": true,
- "https://json-schema.org/draft/2020-12/vocab/meta-data": true,
- "https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
- "https://json-schema.org/draft/2020-12/vocab/content": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Core and Validation specifications meta-schema",
- "allOf": [
- {"$ref": "meta/core"},
- {"$ref": "meta/applicator"},
- {"$ref": "meta/unevaluated"},
- {"$ref": "meta/validation"},
- {"$ref": "meta/meta-data"},
- {"$ref": "meta/format-annotation"},
- {"$ref": "meta/content"}
- ],
- "type": ["object", "boolean"],
- "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.",
- "properties": {
- "definitions": {
- "$comment": "\"definitions\" has been replaced by \"$defs\".",
- "type": "object",
- "additionalProperties": { "$dynamicRef": "#meta" },
- "deprecated": true,
- "default": {}
- },
- "dependencies": {
- "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.",
- "type": "object",
- "additionalProperties": {
- "anyOf": [
- { "$dynamicRef": "#meta" },
- { "$ref": "meta/validation#/$defs/stringArray" }
- ]
- },
- "deprecated": true,
- "default": {}
- },
- "$recursiveAnchor": {
- "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".",
- "$ref": "meta/core#/$defs/anchorString",
- "deprecated": true
- },
- "$recursiveRef": {
- "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".",
- "$ref": "meta/core#/$defs/uriReferenceString",
- "deprecated": true
- }
- }
-}
diff --git a/jsonschema/schemas/draft3.json b/jsonschema/schemas/draft3.json
deleted file mode 100644
index 8b26b1f..0000000
--- a/jsonschema/schemas/draft3.json
+++ /dev/null
@@ -1,172 +0,0 @@
-{
- "$schema" : "http://json-schema.org/draft-03/schema#",
- "id" : "http://json-schema.org/draft-03/schema#",
- "type" : "object",
-
- "properties" : {
- "type" : {
- "type" : ["string", "array"],
- "items" : {
- "type" : ["string", {"$ref" : "#"}]
- },
- "uniqueItems" : true,
- "default" : "any"
- },
-
- "properties" : {
- "type" : "object",
- "additionalProperties" : {"$ref" : "#"},
- "default" : {}
- },
-
- "patternProperties" : {
- "type" : "object",
- "additionalProperties" : {"$ref" : "#"},
- "default" : {}
- },
-
- "additionalProperties" : {
- "type" : [{"$ref" : "#"}, "boolean"],
- "default" : {}
- },
-
- "items" : {
- "type" : [{"$ref" : "#"}, "array"],
- "items" : {"$ref" : "#"},
- "default" : {}
- },
-
- "additionalItems" : {
- "type" : [{"$ref" : "#"}, "boolean"],
- "default" : {}
- },
-
- "required" : {
- "type" : "boolean",
- "default" : false
- },
-
- "dependencies" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : ["string", "array", {"$ref" : "#"}],
- "items" : {
- "type" : "string"
- }
- },
- "default" : {}
- },
-
- "minimum" : {
- "type" : "number"
- },
-
- "maximum" : {
- "type" : "number"
- },
-
- "exclusiveMinimum" : {
- "type" : "boolean",
- "default" : false
- },
-
- "exclusiveMaximum" : {
- "type" : "boolean",
- "default" : false
- },
-
- "minItems" : {
- "type" : "integer",
- "minimum" : 0,
- "default" : 0
- },
-
- "maxItems" : {
- "type" : "integer",
- "minimum" : 0
- },
-
- "uniqueItems" : {
- "type" : "boolean",
- "default" : false
- },
-
- "pattern" : {
- "type" : "string",
- "format" : "regex"
- },
-
- "minLength" : {
- "type" : "integer",
- "minimum" : 0,
- "default" : 0
- },
-
- "maxLength" : {
- "type" : "integer"
- },
-
- "enum" : {
- "type" : "array",
- "minItems" : 1,
- "uniqueItems" : true
- },
-
- "default" : {
- "type" : "any"
- },
-
- "title" : {
- "type" : "string"
- },
-
- "description" : {
- "type" : "string"
- },
-
- "format" : {
- "type" : "string"
- },
-
- "divisibleBy" : {
- "type" : "number",
- "minimum" : 0,
- "exclusiveMinimum" : true,
- "default" : 1
- },
-
- "disallow" : {
- "type" : ["string", "array"],
- "items" : {
- "type" : ["string", {"$ref" : "#"}]
- },
- "uniqueItems" : true
- },
-
- "extends" : {
- "type" : [{"$ref" : "#"}, "array"],
- "items" : {"$ref" : "#"},
- "default" : {}
- },
-
- "id" : {
- "type" : "string"
- },
-
- "$ref" : {
- "type" : "string"
- },
-
- "$schema" : {
- "type" : "string",
- "format" : "uri"
- }
- },
-
- "dependencies" : {
- "exclusiveMinimum" : "minimum",
- "exclusiveMaximum" : "maximum"
- },
-
- "default" : {}
-}
diff --git a/jsonschema/schemas/draft4.json b/jsonschema/schemas/draft4.json
deleted file mode 100644
index bcbb847..0000000
--- a/jsonschema/schemas/draft4.json
+++ /dev/null
@@ -1,149 +0,0 @@
-{
- "id": "http://json-schema.org/draft-04/schema#",
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Core schema meta-schema",
- "definitions": {
- "schemaArray": {
- "type": "array",
- "minItems": 1,
- "items": { "$ref": "#" }
- },
- "positiveInteger": {
- "type": "integer",
- "minimum": 0
- },
- "positiveIntegerDefault0": {
- "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
- },
- "simpleTypes": {
- "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
- },
- "stringArray": {
- "type": "array",
- "items": { "type": "string" },
- "minItems": 1,
- "uniqueItems": true
- }
- },
- "type": "object",
- "properties": {
- "id": {
- "type": "string"
- },
- "$schema": {
- "type": "string"
- },
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- },
- "default": {},
- "multipleOf": {
- "type": "number",
- "minimum": 0,
- "exclusiveMinimum": true
- },
- "maximum": {
- "type": "number"
- },
- "exclusiveMaximum": {
- "type": "boolean",
- "default": false
- },
- "minimum": {
- "type": "number"
- },
- "exclusiveMinimum": {
- "type": "boolean",
- "default": false
- },
- "maxLength": { "$ref": "#/definitions/positiveInteger" },
- "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
- "pattern": {
- "type": "string",
- "format": "regex"
- },
- "additionalItems": {
- "anyOf": [
- { "type": "boolean" },
- { "$ref": "#" }
- ],
- "default": {}
- },
- "items": {
- "anyOf": [
- { "$ref": "#" },
- { "$ref": "#/definitions/schemaArray" }
- ],
- "default": {}
- },
- "maxItems": { "$ref": "#/definitions/positiveInteger" },
- "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
- "uniqueItems": {
- "type": "boolean",
- "default": false
- },
- "maxProperties": { "$ref": "#/definitions/positiveInteger" },
- "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
- "required": { "$ref": "#/definitions/stringArray" },
- "additionalProperties": {
- "anyOf": [
- { "type": "boolean" },
- { "$ref": "#" }
- ],
- "default": {}
- },
- "definitions": {
- "type": "object",
- "additionalProperties": { "$ref": "#" },
- "default": {}
- },
- "properties": {
- "type": "object",
- "additionalProperties": { "$ref": "#" },
- "default": {}
- },
- "patternProperties": {
- "type": "object",
- "additionalProperties": { "$ref": "#" },
- "default": {}
- },
- "dependencies": {
- "type": "object",
- "additionalProperties": {
- "anyOf": [
- { "$ref": "#" },
- { "$ref": "#/definitions/stringArray" }
- ]
- }
- },
- "enum": {
- "type": "array",
- "minItems": 1,
- "uniqueItems": true
- },
- "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": "#" }
- },
- "dependencies": {
- "exclusiveMaximum": [ "maximum" ],
- "exclusiveMinimum": [ "minimum" ]
- },
- "default": {}
-}
diff --git a/jsonschema/schemas/draft6.json b/jsonschema/schemas/draft6.json
deleted file mode 100644
index a0d2bf7..0000000
--- a/jsonschema/schemas/draft6.json
+++ /dev/null
@@ -1,153 +0,0 @@
-{
- "$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/jsonschema/schemas/draft7.json b/jsonschema/schemas/draft7.json
deleted file mode 100644
index 746cde9..0000000
--- a/jsonschema/schemas/draft7.json
+++ /dev/null
@@ -1,166 +0,0 @@
-{
- "$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/jsonschema/schemas/vocabularies/draft2019-09/applicator b/jsonschema/schemas/vocabularies/draft2019-09/applicator
deleted file mode 100644
index 24a1cc4..0000000
--- a/jsonschema/schemas/vocabularies/draft2019-09/applicator
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/meta/applicator",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/applicator": true
- },
- "$recursiveAnchor": true,
-
- "title": "Applicator vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "additionalItems": { "$recursiveRef": "#" },
- "unevaluatedItems": { "$recursiveRef": "#" },
- "items": {
- "anyOf": [
- { "$recursiveRef": "#" },
- { "$ref": "#/$defs/schemaArray" }
- ]
- },
- "contains": { "$recursiveRef": "#" },
- "additionalProperties": { "$recursiveRef": "#" },
- "unevaluatedProperties": { "$recursiveRef": "#" },
- "properties": {
- "type": "object",
- "additionalProperties": { "$recursiveRef": "#" },
- "default": {}
- },
- "patternProperties": {
- "type": "object",
- "additionalProperties": { "$recursiveRef": "#" },
- "propertyNames": { "format": "regex" },
- "default": {}
- },
- "dependentSchemas": {
- "type": "object",
- "additionalProperties": {
- "$recursiveRef": "#"
- }
- },
- "propertyNames": { "$recursiveRef": "#" },
- "if": { "$recursiveRef": "#" },
- "then": { "$recursiveRef": "#" },
- "else": { "$recursiveRef": "#" },
- "allOf": { "$ref": "#/$defs/schemaArray" },
- "anyOf": { "$ref": "#/$defs/schemaArray" },
- "oneOf": { "$ref": "#/$defs/schemaArray" },
- "not": { "$recursiveRef": "#" }
- },
- "$defs": {
- "schemaArray": {
- "type": "array",
- "minItems": 1,
- "items": { "$recursiveRef": "#" }
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2019-09/content b/jsonschema/schemas/vocabularies/draft2019-09/content
deleted file mode 100644
index f6752a8..0000000
--- a/jsonschema/schemas/vocabularies/draft2019-09/content
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/meta/content",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/content": true
- },
- "$recursiveAnchor": true,
-
- "title": "Content vocabulary meta-schema",
-
- "type": ["object", "boolean"],
- "properties": {
- "contentMediaType": { "type": "string" },
- "contentEncoding": { "type": "string" },
- "contentSchema": { "$recursiveRef": "#" }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2019-09/core b/jsonschema/schemas/vocabularies/draft2019-09/core
deleted file mode 100644
index eb708a5..0000000
--- a/jsonschema/schemas/vocabularies/draft2019-09/core
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/meta/core",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/core": true
- },
- "$recursiveAnchor": true,
-
- "title": "Core vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "$id": {
- "type": "string",
- "format": "uri-reference",
- "$comment": "Non-empty fragments not allowed.",
- "pattern": "^[^#]*#?$"
- },
- "$schema": {
- "type": "string",
- "format": "uri"
- },
- "$anchor": {
- "type": "string",
- "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$"
- },
- "$ref": {
- "type": "string",
- "format": "uri-reference"
- },
- "$recursiveRef": {
- "type": "string",
- "format": "uri-reference"
- },
- "$recursiveAnchor": {
- "type": "boolean",
- "default": false
- },
- "$vocabulary": {
- "type": "object",
- "propertyNames": {
- "type": "string",
- "format": "uri"
- },
- "additionalProperties": {
- "type": "boolean"
- }
- },
- "$comment": {
- "type": "string"
- },
- "$defs": {
- "type": "object",
- "additionalProperties": { "$recursiveRef": "#" },
- "default": {}
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2019-09/meta-data b/jsonschema/schemas/vocabularies/draft2019-09/meta-data
deleted file mode 100644
index da04cff..0000000
--- a/jsonschema/schemas/vocabularies/draft2019-09/meta-data
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/meta/meta-data",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/meta-data": true
- },
- "$recursiveAnchor": true,
-
- "title": "Meta-data vocabulary meta-schema",
-
- "type": ["object", "boolean"],
- "properties": {
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- },
- "default": true,
- "deprecated": {
- "type": "boolean",
- "default": false
- },
- "readOnly": {
- "type": "boolean",
- "default": false
- },
- "writeOnly": {
- "type": "boolean",
- "default": false
- },
- "examples": {
- "type": "array",
- "items": true
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2019-09/validation b/jsonschema/schemas/vocabularies/draft2019-09/validation
deleted file mode 100644
index 9f59677..0000000
--- a/jsonschema/schemas/vocabularies/draft2019-09/validation
+++ /dev/null
@@ -1,98 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/meta/validation",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/validation": true
- },
- "$recursiveAnchor": true,
-
- "title": "Validation vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "multipleOf": {
- "type": "number",
- "exclusiveMinimum": 0
- },
- "maximum": {
- "type": "number"
- },
- "exclusiveMaximum": {
- "type": "number"
- },
- "minimum": {
- "type": "number"
- },
- "exclusiveMinimum": {
- "type": "number"
- },
- "maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
- "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
- "pattern": {
- "type": "string",
- "format": "regex"
- },
- "maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
- "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
- "uniqueItems": {
- "type": "boolean",
- "default": false
- },
- "maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
- "minContains": {
- "$ref": "#/$defs/nonNegativeInteger",
- "default": 1
- },
- "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
- "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
- "required": { "$ref": "#/$defs/stringArray" },
- "dependentRequired": {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/$defs/stringArray"
- }
- },
- "const": true,
- "enum": {
- "type": "array",
- "items": true
- },
- "type": {
- "anyOf": [
- { "$ref": "#/$defs/simpleTypes" },
- {
- "type": "array",
- "items": { "$ref": "#/$defs/simpleTypes" },
- "minItems": 1,
- "uniqueItems": true
- }
- ]
- }
- },
- "$defs": {
- "nonNegativeInteger": {
- "type": "integer",
- "minimum": 0
- },
- "nonNegativeIntegerDefault0": {
- "$ref": "#/$defs/nonNegativeInteger",
- "default": 0
- },
- "simpleTypes": {
- "enum": [
- "array",
- "boolean",
- "integer",
- "null",
- "number",
- "object",
- "string"
- ]
- },
- "stringArray": {
- "type": "array",
- "items": { "type": "string" },
- "uniqueItems": true,
- "default": []
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/applicator b/jsonschema/schemas/vocabularies/draft2020-12/applicator
deleted file mode 100644
index ca69923..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/applicator
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/applicator",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/applicator": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Applicator vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "prefixItems": { "$ref": "#/$defs/schemaArray" },
- "items": { "$dynamicRef": "#meta" },
- "contains": { "$dynamicRef": "#meta" },
- "additionalProperties": { "$dynamicRef": "#meta" },
- "properties": {
- "type": "object",
- "additionalProperties": { "$dynamicRef": "#meta" },
- "default": {}
- },
- "patternProperties": {
- "type": "object",
- "additionalProperties": { "$dynamicRef": "#meta" },
- "propertyNames": { "format": "regex" },
- "default": {}
- },
- "dependentSchemas": {
- "type": "object",
- "additionalProperties": { "$dynamicRef": "#meta" },
- "default": {}
- },
- "propertyNames": { "$dynamicRef": "#meta" },
- "if": { "$dynamicRef": "#meta" },
- "then": { "$dynamicRef": "#meta" },
- "else": { "$dynamicRef": "#meta" },
- "allOf": { "$ref": "#/$defs/schemaArray" },
- "anyOf": { "$ref": "#/$defs/schemaArray" },
- "oneOf": { "$ref": "#/$defs/schemaArray" },
- "not": { "$dynamicRef": "#meta" }
- },
- "$defs": {
- "schemaArray": {
- "type": "array",
- "minItems": 1,
- "items": { "$dynamicRef": "#meta" }
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/content b/jsonschema/schemas/vocabularies/draft2020-12/content
deleted file mode 100644
index 2f6e056..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/content
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/content",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/content": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Content vocabulary meta-schema",
-
- "type": ["object", "boolean"],
- "properties": {
- "contentEncoding": { "type": "string" },
- "contentMediaType": { "type": "string" },
- "contentSchema": { "$dynamicRef": "#meta" }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/core b/jsonschema/schemas/vocabularies/draft2020-12/core
deleted file mode 100644
index dfc092d..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/core
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/core",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/core": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Core vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "$id": {
- "$ref": "#/$defs/uriReferenceString",
- "$comment": "Non-empty fragments not allowed.",
- "pattern": "^[^#]*#?$"
- },
- "$schema": { "$ref": "#/$defs/uriString" },
- "$ref": { "$ref": "#/$defs/uriReferenceString" },
- "$anchor": { "$ref": "#/$defs/anchorString" },
- "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" },
- "$dynamicAnchor": { "$ref": "#/$defs/anchorString" },
- "$vocabulary": {
- "type": "object",
- "propertyNames": { "$ref": "#/$defs/uriString" },
- "additionalProperties": {
- "type": "boolean"
- }
- },
- "$comment": {
- "type": "string"
- },
- "$defs": {
- "type": "object",
- "additionalProperties": { "$dynamicRef": "#meta" }
- }
- },
- "$defs": {
- "anchorString": {
- "type": "string",
- "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$"
- },
- "uriString": {
- "type": "string",
- "format": "uri"
- },
- "uriReferenceString": {
- "type": "string",
- "format": "uri-reference"
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/format b/jsonschema/schemas/vocabularies/draft2020-12/format
deleted file mode 100644
index 09bbfdd..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/format
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "https://json-schema.org/draft/2019-09/meta/format",
- "$vocabulary": {
- "https://json-schema.org/draft/2019-09/vocab/format": true
- },
- "$recursiveAnchor": true,
-
- "title": "Format vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "format": { "type": "string" }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/format-annotation b/jsonschema/schemas/vocabularies/draft2020-12/format-annotation
deleted file mode 100644
index 51ef7ea..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/format-annotation
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/format-annotation": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Format vocabulary meta-schema for annotation results",
- "type": ["object", "boolean"],
- "properties": {
- "format": { "type": "string" }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/format-assertion b/jsonschema/schemas/vocabularies/draft2020-12/format-assertion
deleted file mode 100644
index 5e73fd7..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/format-assertion
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/format-assertion": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Format vocabulary meta-schema for assertion results",
- "type": ["object", "boolean"],
- "properties": {
- "format": { "type": "string" }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/meta-data b/jsonschema/schemas/vocabularies/draft2020-12/meta-data
deleted file mode 100644
index 05cbc22..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/meta-data
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/meta-data",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/meta-data": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Meta-data vocabulary meta-schema",
-
- "type": ["object", "boolean"],
- "properties": {
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- },
- "default": true,
- "deprecated": {
- "type": "boolean",
- "default": false
- },
- "readOnly": {
- "type": "boolean",
- "default": false
- },
- "writeOnly": {
- "type": "boolean",
- "default": false
- },
- "examples": {
- "type": "array",
- "items": true
- }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/unevaluated b/jsonschema/schemas/vocabularies/draft2020-12/unevaluated
deleted file mode 100644
index 5f62a3f..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/unevaluated
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/unevaluated": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Unevaluated applicator vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "unevaluatedItems": { "$dynamicRef": "#meta" },
- "unevaluatedProperties": { "$dynamicRef": "#meta" }
- }
-}
diff --git a/jsonschema/schemas/vocabularies/draft2020-12/validation b/jsonschema/schemas/vocabularies/draft2020-12/validation
deleted file mode 100644
index 606b87b..0000000
--- a/jsonschema/schemas/vocabularies/draft2020-12/validation
+++ /dev/null
@@ -1,98 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://json-schema.org/draft/2020-12/meta/validation",
- "$vocabulary": {
- "https://json-schema.org/draft/2020-12/vocab/validation": true
- },
- "$dynamicAnchor": "meta",
-
- "title": "Validation vocabulary meta-schema",
- "type": ["object", "boolean"],
- "properties": {
- "type": {
- "anyOf": [
- { "$ref": "#/$defs/simpleTypes" },
- {
- "type": "array",
- "items": { "$ref": "#/$defs/simpleTypes" },
- "minItems": 1,
- "uniqueItems": true
- }
- ]
- },
- "const": true,
- "enum": {
- "type": "array",
- "items": true
- },
- "multipleOf": {
- "type": "number",
- "exclusiveMinimum": 0
- },
- "maximum": {
- "type": "number"
- },
- "exclusiveMaximum": {
- "type": "number"
- },
- "minimum": {
- "type": "number"
- },
- "exclusiveMinimum": {
- "type": "number"
- },
- "maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
- "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
- "pattern": {
- "type": "string",
- "format": "regex"
- },
- "maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
- "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
- "uniqueItems": {
- "type": "boolean",
- "default": false
- },
- "maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
- "minContains": {
- "$ref": "#/$defs/nonNegativeInteger",
- "default": 1
- },
- "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
- "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
- "required": { "$ref": "#/$defs/stringArray" },
- "dependentRequired": {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/$defs/stringArray"
- }
- }
- },
- "$defs": {
- "nonNegativeInteger": {
- "type": "integer",
- "minimum": 0
- },
- "nonNegativeIntegerDefault0": {
- "$ref": "#/$defs/nonNegativeInteger",
- "default": 0
- },
- "simpleTypes": {
- "enum": [
- "array",
- "boolean",
- "integer",
- "null",
- "number",
- "object",
- "string"
- ]
- },
- "stringArray": {
- "type": "array",
- "items": { "type": "string" },
- "uniqueItems": true,
- "default": []
- }
- }
-}
diff --git a/jsonschema/tests/_suite.py b/jsonschema/tests/_suite.py
index c598e22..6be05be 100644
--- a/jsonschema/tests/_suite.py
+++ b/jsonschema/tests/_suite.py
@@ -15,6 +15,8 @@ import sys
import unittest
from attrs import field, frozen
+from referencing import Registry
+import referencing.jsonschema
if TYPE_CHECKING:
import pyperf
@@ -47,13 +49,38 @@ def _find_suite():
class Suite:
_root: Path = field(factory=_find_suite)
- _remotes: Mapping[str, Mapping[str, Any] | bool] = field(init=False)
+ _remotes: referencing.jsonschema.SchemaRegistry = field(init=False)
def __attrs_post_init__(self):
jsonschema_suite = self._root.joinpath("bin", "jsonschema_suite")
argv = [sys.executable, str(jsonschema_suite), "remotes"]
remotes = subprocess.check_output(argv).decode("utf-8")
- object.__setattr__(self, "_remotes", json.loads(remotes))
+
+ resources = json.loads(remotes)
+
+ li = "http://localhost:1234/locationIndependentIdentifierPre2019.json"
+ li4 = "http://localhost:1234/locationIndependentIdentifierDraft4.json"
+
+ registry = Registry().with_resources(
+ [
+ (
+ li,
+ referencing.jsonschema.DRAFT7.create_resource(
+ contents=resources.pop(li),
+ ),
+ ),
+ (
+ li4,
+ referencing.jsonschema.DRAFT4.create_resource(
+ contents=resources.pop(li4),
+ ),
+ ),
+ ],
+ ).with_contents(
+ resources.items(),
+ default_specification=referencing.jsonschema.DRAFT202012,
+ )
+ object.__setattr__(self, "_remotes", registry)
def benchmark(self, runner: pyperf.Runner): # pragma: no cover
for name, Validator in _VALIDATORS.items():
@@ -74,7 +101,7 @@ class Suite:
class Version:
_path: Path
- _remotes: Mapping[str, Mapping[str, Any] | bool]
+ _remotes: referencing.jsonschema.SchemaRegistry
name: str
@@ -173,7 +200,7 @@ class _Test:
valid: bool
- _remotes: Mapping[str, Mapping[str, Any] | bool]
+ _remotes: referencing.jsonschema.SchemaRegistry
comment: str | None = None
@@ -218,20 +245,11 @@ class _Test:
def validate(self, Validator, **kwargs):
Validator.check_schema(self.schema)
- resolver = jsonschema.RefResolver.from_schema(
+ validator = Validator(
schema=self.schema,
- store=self._remotes,
- id_of=Validator.ID_OF,
+ registry=self._remotes,
+ **kwargs,
)
-
- # XXX: #693 asks to improve the public API for this, since yeah, it's
- # bad. Figures that since it's hard for end-users, we experience
- # the pain internally here too.
- def prevent_network_access(uri):
- raise RuntimeError(f"Tried to access the network: {uri}")
- resolver.resolve_remote = prevent_network_access
-
- validator = Validator(schema=self.schema, resolver=resolver, **kwargs)
if os.environ.get("JSON_SCHEMA_DEBUG", "0") != "0":
breakpoint()
validator.validate(instance=self.data)
diff --git a/jsonschema/tests/test_cli.py b/jsonschema/tests/test_cli.py
index 84f8812..c5b0b2e 100644
--- a/jsonschema/tests/test_cli.py
+++ b/jsonschema/tests/test_cli.py
@@ -16,13 +16,11 @@ try: # pragma: no cover
except ImportError: # pragma: no cover
import importlib_metadata as metadata # type: ignore
-from pyrsistent import m
-
from jsonschema import Draft4Validator, Draft202012Validator
from jsonschema.exceptions import (
- RefResolutionError,
SchemaError,
ValidationError,
+ _RefResolutionError,
)
from jsonschema.validators import _LATEST_VERSION, validate
@@ -70,13 +68,13 @@ def _message_for(non_json):
class TestCLI(TestCase):
def run_cli(
- self, argv, files=m(), stdin=StringIO(), exit_code=0, **override,
+ self, argv, files=None, stdin=StringIO(), exit_code=0, **override,
):
arguments = cli.parse_args(argv)
arguments.update(override)
self.assertFalse(hasattr(cli, "open"))
- cli.open = fake_open(files)
+ cli.open = fake_open(files or {})
try:
stdout, stderr = StringIO(), StringIO()
actual_exit_code = cli.run(
@@ -747,7 +745,7 @@ class TestCLI(TestCase):
schema = '{"$ref": "someNonexistentFile.json#definitions/num"}'
instance = "1"
- with self.assertRaises(RefResolutionError) as e:
+ with self.assertRaises(_RefResolutionError) as e:
self.assertOutputs(
files=dict(
some_schema=schema,
@@ -766,7 +764,7 @@ class TestCLI(TestCase):
schema = '{"$ref": "foo.json#definitions/num"}'
instance = "1"
- with self.assertRaises(RefResolutionError) as e:
+ with self.assertRaises(_RefResolutionError) as e:
self.assertOutputs(
files=dict(
some_schema=schema,
diff --git a/jsonschema/tests/test_deprecations.py b/jsonschema/tests/test_deprecations.py
index 3e8a9cc..2beb02f 100644
--- a/jsonschema/tests/test_deprecations.py
+++ b/jsonschema/tests/test_deprecations.py
@@ -3,7 +3,7 @@ import importlib
import subprocess
import sys
-from jsonschema import FormatChecker, validators
+from jsonschema import FormatChecker, exceptions, validators
class TestDeprecations(TestCase):
@@ -12,15 +12,11 @@ class TestDeprecations(TestCase):
As of v4.0.0, __version__ is deprecated in favor of importlib.metadata.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.__version__ is deprecated"
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import __version__ # noqa
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.__version__ is deprecated",
- ),
- )
def test_validators_ErrorTree(self):
"""
@@ -28,15 +24,38 @@ class TestDeprecations(TestCase):
deprecated in favor of doing so from jsonschema.exceptions.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Importing ErrorTree from jsonschema.validators is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema.validators import ErrorTree # noqa
+ self.assertEqual(ErrorTree, exceptions.ErrorTree)
+ self.assertEqual(w.filename, __file__)
+
+ def test_import_ErrorTree(self):
+ """
+ As of v4.18.0, importing ErrorTree from the package root is
+ deprecated in favor of doing so from jsonschema.exceptions.
+ """
+
+ message = "Importing ErrorTree directly from the jsonschema package "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ from jsonschema import ErrorTree # noqa
+
+ self.assertEqual(ErrorTree, exceptions.ErrorTree)
+ self.assertEqual(w.filename, __file__)
+
+ def test_import_FormatError(self):
+ """
+ As of v4.18.0, importing FormatError from the package root is
+ deprecated in favor of doing so from jsonschema.exceptions.
+ """
+
+ message = "Importing FormatError directly from the jsonschema package "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ from jsonschema import FormatError # noqa
+
+ self.assertEqual(FormatError, exceptions.FormatError)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Importing ErrorTree from jsonschema.validators is deprecated",
- ),
- )
def test_validators_validators(self):
"""
@@ -44,16 +63,12 @@ class TestDeprecations(TestCase):
deprecated.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.validators.validators is deprecated"
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
value = validators.validators
- self.assertEqual(value, validators._VALIDATORS)
+ self.assertEqual(value, validators._VALIDATORS)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.validators.validators is deprecated",
- ),
- )
def test_validators_meta_schemas(self):
"""
@@ -61,33 +76,25 @@ class TestDeprecations(TestCase):
deprecated.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.validators.meta_schemas is deprecated"
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
value = validators.meta_schemas
- self.assertEqual(value, validators._META_SCHEMAS)
+ self.assertEqual(value, validators._META_SCHEMAS)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.validators.meta_schemas is deprecated",
- ),
- )
def test_RefResolver_in_scope(self):
"""
As of v4.0.0, RefResolver.in_scope is deprecated.
"""
- resolver = validators.RefResolver.from_schema({})
- with self.assertWarns(DeprecationWarning) as w:
+ resolver = validators._RefResolver.from_schema({})
+ message = "jsonschema.RefResolver.in_scope is deprecated "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
with resolver.in_scope("foo"):
pass
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "jsonschema.RefResolver.in_scope is deprecated ",
- ),
- )
def test_Validator_is_valid_two_arguments(self):
"""
@@ -96,16 +103,12 @@ class TestDeprecations(TestCase):
"""
validator = validators.Draft7Validator({})
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Passing a schema to Validator.is_valid is deprecated "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
result = validator.is_valid("foo", {"type": "number"})
self.assertFalse(result)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Passing a schema to Validator.is_valid is deprecated ",
- ),
- )
def test_Validator_iter_errors_two_arguments(self):
"""
@@ -114,16 +117,57 @@ class TestDeprecations(TestCase):
"""
validator = validators.Draft7Validator({})
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Passing a schema to Validator.iter_errors is deprecated "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
error, = validator.iter_errors("foo", {"type": "number"})
self.assertEqual(error.validator, "type")
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Passing a schema to Validator.iter_errors is deprecated ",
- ),
- )
+
+ def test_Validator_resolver(self):
+ """
+ As of v4.18.0, accessing Validator.resolver is deprecated.
+ """
+
+ validator = validators.Draft7Validator({})
+ message = "Accessing Draft7Validator.resolver is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ self.assertIsInstance(validator.resolver, validators._RefResolver)
+
+ self.assertEqual(w.filename, __file__)
+
+ def test_RefResolver(self):
+ """
+ As of v4.18.0, RefResolver is fully deprecated.
+ """
+
+ message = "jsonschema.RefResolver is deprecated"
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ from jsonschema import RefResolver # noqa: F401
+ self.assertEqual(w.filename, __file__)
+
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ from jsonschema.validators import RefResolver # noqa: F401, F811
+ self.assertEqual(w.filename, __file__)
+
+ def test_RefResolutionError(self):
+ """
+ As of v4.18.0, RefResolutionError is deprecated in favor of directly
+ catching errors from the referencing library.
+ """
+
+ message = "jsonschema.exceptions.RefResolutionError is deprecated"
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ from jsonschema import RefResolutionError # noqa: F401
+
+ self.assertEqual(RefResolutionError, exceptions._RefResolutionError)
+ self.assertEqual(w.filename, __file__)
+
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
+ from jsonschema.exceptions import RefResolutionError # noqa
+
+ self.assertEqual(RefResolutionError, exceptions._RefResolutionError)
+ self.assertEqual(w.filename, __file__)
def test_Validator_subclassing(self):
"""
@@ -137,16 +181,14 @@ class TestDeprecations(TestCase):
A future version will explicitly raise an error.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Subclassing validator classes is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
class Subclass(validators.Draft202012Validator):
pass
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith("Subclassing validator classes is "),
- )
- with self.assertWarns(DeprecationWarning) as w:
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
class AnotherSubclass(validators.create(meta_schema={})):
pass
@@ -158,13 +200,11 @@ class TestDeprecations(TestCase):
self.addCleanup(FormatChecker.checkers.pop, "boom", None)
- with self.assertWarns(DeprecationWarning) as w:
+ message = "FormatChecker.cls_checks "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
FormatChecker.cls_checks("boom")
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith("FormatChecker.cls_checks "),
- )
def test_draftN_format_checker(self):
"""
@@ -172,7 +212,8 @@ class TestDeprecations(TestCase):
in favor of Validator.FORMAT_CHECKER.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.draft202012_format_checker is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import draft202012_format_checker # noqa
self.assertIs(
@@ -180,14 +221,9 @@ class TestDeprecations(TestCase):
validators.Draft202012Validator.FORMAT_CHECKER,
)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.draft202012_format_checker is ",
- ),
- msg=w.warning,
- )
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.draft201909_format_checker is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import draft201909_format_checker # noqa
self.assertIs(
@@ -195,14 +231,9 @@ class TestDeprecations(TestCase):
validators.Draft201909Validator.FORMAT_CHECKER,
)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.draft201909_format_checker is ",
- ),
- msg=w.warning,
- )
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.draft7_format_checker is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import draft7_format_checker # noqa
self.assertIs(
@@ -210,14 +241,9 @@ class TestDeprecations(TestCase):
validators.Draft7Validator.FORMAT_CHECKER,
)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.draft7_format_checker is ",
- ),
- msg=w.warning,
- )
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.draft6_format_checker is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import draft6_format_checker # noqa
self.assertIs(
@@ -225,14 +251,9 @@ class TestDeprecations(TestCase):
validators.Draft6Validator.FORMAT_CHECKER,
)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.draft6_format_checker is ",
- ),
- msg=w.warning,
- )
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.draft4_format_checker is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import draft4_format_checker # noqa
self.assertIs(
@@ -240,14 +261,9 @@ class TestDeprecations(TestCase):
validators.Draft4Validator.FORMAT_CHECKER,
)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.draft4_format_checker is ",
- ),
- msg=w.warning,
- )
- with self.assertWarns(DeprecationWarning) as w:
+ message = "Accessing jsonschema.draft3_format_checker is "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
from jsonschema import draft3_format_checker # noqa
self.assertIs(
@@ -255,12 +271,6 @@ class TestDeprecations(TestCase):
validators.Draft3Validator.FORMAT_CHECKER,
)
self.assertEqual(w.filename, __file__)
- self.assertTrue(
- str(w.warning).startswith(
- "Accessing jsonschema.draft3_format_checker is ",
- ),
- msg=w.warning,
- )
with self.assertRaises(ImportError):
from jsonschema import draft1234_format_checker # noqa
@@ -270,16 +280,12 @@ class TestDeprecations(TestCase):
As of v4.17.0, importing jsonschema.cli is deprecated.
"""
- with self.assertWarns(DeprecationWarning) as w:
+ message = "The jsonschema CLI is deprecated and will be removed "
+ with self.assertWarnsRegex(DeprecationWarning, message) as w:
import jsonschema.cli
importlib.reload(jsonschema.cli)
self.assertEqual(w.filename, importlib.__file__)
- self.assertTrue(
- str(w.warning).startswith(
- "The jsonschema CLI is deprecated and will be removed ",
- ),
- )
def test_cli(self):
"""
diff --git a/jsonschema/tests/test_format.py b/jsonschema/tests/test_format.py
index 5dd06cf..a5a1d0c 100644
--- a/jsonschema/tests/test_format.py
+++ b/jsonschema/tests/test_format.py
@@ -4,7 +4,8 @@ Tests for the parts of jsonschema related to the :kw:`format` keyword.
from unittest import TestCase
-from jsonschema import FormatChecker, FormatError, ValidationError
+from jsonschema import FormatChecker, ValidationError
+from jsonschema.exceptions import FormatError
from jsonschema.validators import Draft4Validator
BOOM = ValueError("Boom!")
diff --git a/jsonschema/tests/test_jsonschema_test_suite.py b/jsonschema/tests/test_jsonschema_test_suite.py
index 5ebd7ed..fd2c499 100644
--- a/jsonschema/tests/test_jsonschema_test_suite.py
+++ b/jsonschema/tests/test_jsonschema_test_suite.py
@@ -8,7 +8,6 @@ See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details.
import sys
-from jsonschema.tests._helpers import bug
from jsonschema.tests._suite import Suite
import jsonschema
@@ -135,13 +134,6 @@ TestDraft3 = DRAFT3.to_unittest_testcase(
skip=lambda test: (
missing_format(jsonschema.Draft3Validator)(test)
or complex_email_validation(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "$ref prevents a sibling id from changing the base uri"
- ),
- )(test)
),
)
@@ -160,49 +152,6 @@ TestDraft4 = DRAFT4.to_unittest_testcase(
or leap_second(test)
or missing_format(jsonschema.Draft4Validator)(test)
or complex_email_validation(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description="Recursive references between schemas",
- )(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "Location-independent identifier with "
- "base URI change in subschema"
- ),
- )(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "$ref prevents a sibling id from changing the base uri"
- ),
- )(test)
- or skip(
- message=bug(),
- subject="id",
- description="match $ref to id",
- )(test)
- or skip(
- message=bug(),
- subject="id",
- description="no match on enum or $ref to id",
- )(test)
- or skip(
- message=bug(),
- subject="refRemote",
- case_description="base URI change - change folder in subschema",
- )(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "id must be resolved against nearest parent, "
- "not just immediate parent"
- ),
- )(test)
),
)
@@ -220,11 +169,6 @@ TestDraft6 = DRAFT6.to_unittest_testcase(
or leap_second(test)
or missing_format(jsonschema.Draft6Validator)(test)
or complex_email_validation(test)
- or skip(
- message=bug(),
- subject="refRemote",
- case_description="base URI change - change folder in subschema",
- )(test)
),
)
@@ -243,19 +187,6 @@ TestDraft7 = DRAFT7.to_unittest_testcase(
or leap_second(test)
or missing_format(jsonschema.Draft7Validator)(test)
or complex_email_validation(test)
- or skip(
- message=bug(),
- subject="refRemote",
- case_description="base URI change - change folder in subschema",
- )(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "$id must be resolved against nearest parent, "
- "not just immediate parent"
- ),
- )(test)
),
)
@@ -268,115 +199,12 @@ TestDraft201909 = DRAFT201909.to_unittest_testcase(
DRAFT201909.optional_cases_of(name="non-bmp-regex"),
DRAFT201909.optional_cases_of(name="refOfUnknownKeyword"),
Validator=jsonschema.Draft201909Validator,
- skip=lambda test: (
- skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- case_description=(
- "$recursiveRef with no $recursiveAnchor in "
- "the initial target schema resource"
- ),
- description=(
- "leaf node does not match: recursion uses the inner schema"
- ),
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description="leaf node matches: recursion uses the inner schema",
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- case_description=(
- "dynamic $recursiveRef destination (not predictable "
- "at schema compile time)"
- ),
- description="integer node",
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- case_description=(
- "multiple dynamic paths to the $recursiveRef keyword"
- ),
- description="recurse to integerNode - floats are not allowed",
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description="integer does not match as a property value",
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description=(
- "leaf node does not match: "
- "recursion only uses inner schema"
- ),
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description=(
- "leaf node matches: "
- "recursion only uses inner schema"
- ),
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description=(
- "two levels, integer does not match as a property value"
- ),
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description="recursive mismatch",
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="recursiveRef",
- description="two levels, no match",
- )(test)
- or skip(
- message="recursiveRef support isn't working yet.",
- subject="id",
- case_description=(
- "Invalid use of fragments in location-independent $id"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="defs",
- description="invalid definition schema",
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="anchor",
- case_description="same $anchor with different base uri",
- )(test)
- or skip(
- message="Vocabulary support is still in-progress.",
- subject="vocabulary",
- description=(
- "no validation: invalid number, but it still validates"
- ),
- )(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "$id must be resolved against nearest parent, "
- "not just immediate parent"
- ),
- )(test)
- or skip(
- message=bug(),
- subject="refRemote",
- case_description="remote HTTP ref with nested absolute ref",
- )(test)
+ skip=skip(
+ message="Vocabulary support is still in-progress.",
+ subject="vocabulary",
+ description=(
+ "no validation: invalid number, but it still validates"
+ ),
),
)
@@ -404,104 +232,12 @@ TestDraft202012 = DRAFT202012.to_unittest_testcase(
DRAFT202012.optional_cases_of(name="non-bmp-regex"),
DRAFT202012.optional_cases_of(name="refOfUnknownKeyword"),
Validator=jsonschema.Draft202012Validator,
- skip=lambda test: (
- skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description="The recursive part is not valid against the root",
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description="incorrect extended schema",
- case_description=(
- "$ref and $dynamicAnchor are independent of order - "
- "$defs first"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description="correct extended schema",
- case_description=(
- "$ref and $dynamicAnchor are independent of order - "
- "$defs first"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description="correct extended schema",
- case_description=(
- "$ref and $dynamicAnchor are independent of order - $ref first"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description="incorrect extended schema",
- case_description=(
- "$ref and $dynamicAnchor are independent of order - $ref first"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description=(
- "/then/$defs/thingy is the final stop for the $dynamicRef"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description=(
- "string matches /$defs/thingy, but the $dynamicRef "
- "does not stop here"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description=(
- "string matches /$defs/thingy, but the $dynamicRef "
- "does not stop here"
- ),
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="dynamicRef",
- description="recurse to integerNode - floats are not allowed",
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="defs",
- description="invalid definition schema",
- )(test)
- or skip(
- message="dynamicRef support isn't fully working yet.",
- subject="anchor",
- case_description="same $anchor with different base uri",
- )(test)
- or skip(
- message="Vocabulary support is still in-progress.",
- subject="vocabulary",
- description=(
- "no validation: invalid number, but it still validates"
- ),
- )(test)
- or skip(
- message=bug(),
- subject="ref",
- case_description=(
- "$id must be resolved against nearest parent, "
- "not just immediate parent"
- ),
- )(test)
- or skip(
- message=bug(),
- subject="refRemote",
- case_description="remote HTTP ref with nested absolute ref",
- )(test)
+ skip=skip(
+ message="Vocabulary support is still in-progress.",
+ subject="vocabulary",
+ description=(
+ "no validation: invalid number, but it still validates"
+ ),
),
)
diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py
index 0bc60de..6a5756b 100644
--- a/jsonschema/tests/test_validators.py
+++ b/jsonschema/tests/test_validators.py
@@ -10,7 +10,6 @@ import json
import os
import sys
import tempfile
-import unittest
import warnings
import attr
@@ -22,7 +21,6 @@ from jsonschema import (
protocols,
validators,
)
-from jsonschema.tests._helpers import bug
def fail(validator, errors, instance, schema):
@@ -1189,7 +1187,7 @@ class TestValidationErrorDetails(TestCase):
ref, schema = "someRef", {"additionalProperties": {"type": "integer"}}
validator = validators.Draft7Validator(
{"$ref": ref},
- resolver=validators.RefResolver("", {}, store={ref: schema}),
+ resolver=validators._RefResolver("", {}, store={ref: schema}),
)
error, = validator.iter_errors({"foo": "notAnInteger"})
@@ -1539,31 +1537,25 @@ class ValidatorTestMixin(MetaSchemaTestsMixin, object):
def test_non_existent_properties_are_ignored(self):
self.Validator({object(): object()}).validate(instance=object())
- def test_it_creates_a_ref_resolver_if_not_provided(self):
- self.assertIsInstance(
- self.Validator({}).resolver,
- validators.RefResolver,
- )
-
- def test_it_delegates_to_a_ref_resolver(self):
- ref, schema = "someCoolRef", {"type": "integer"}
- resolver = validators.RefResolver("", {}, store={ref: schema})
- validator = self.Validator({"$ref": ref}, resolver=resolver)
-
- with self.assertRaises(exceptions.ValidationError):
- validator.validate(None)
-
def test_evolve(self):
- ref, schema = "someCoolRef", {"type": "integer"}
- resolver = validators.RefResolver("", {}, store={ref: schema})
-
- validator = self.Validator(schema, resolver=resolver)
- new = validator.evolve(schema={"type": "string"})
+ schema, format_checker = {"type": "integer"}, FormatChecker()
+ original = self.Validator(
+ schema,
+ format_checker=format_checker,
+ )
+ new = original.evolve(
+ schema={"type": "string"},
+ format_checker=self.Validator.FORMAT_CHECKER,
+ )
- expected = self.Validator({"type": "string"}, resolver=resolver)
+ expected = self.Validator(
+ {"type": "string"},
+ format_checker=self.Validator.FORMAT_CHECKER,
+ _resolver=new._resolver,
+ )
self.assertEqual(new, expected)
- self.assertNotEqual(new, validator)
+ self.assertNotEqual(new, original)
def test_evolve_with_subclass(self):
"""
@@ -1588,24 +1580,6 @@ class ValidatorTestMixin(MetaSchemaTestsMixin, object):
self.assertEqual(new.foo, [1, 2, 3])
self.assertEqual(new._bar, 12)
- def test_it_delegates_to_a_legacy_ref_resolver(self):
- """
- Legacy RefResolvers support only the context manager form of
- resolution.
- """
-
- class LegacyRefResolver:
- @contextmanager
- def resolving(this, ref):
- self.assertEqual(ref, "the ref")
- yield {"type": "integer"}
-
- resolver = LegacyRefResolver()
- schema = {"$ref": "the ref"}
-
- with self.assertRaises(exceptions.ValidationError):
- self.Validator(schema, resolver=resolver).validate(None)
-
def test_is_type_is_true_for_valid_type(self):
self.assertTrue(self.Validator({}).is_type("foo", "string"))
@@ -1766,6 +1740,37 @@ class ValidatorTestMixin(MetaSchemaTestsMixin, object):
with self.assertRaises(exceptions.ValidationError):
validator.validate(instance)
+ def test_it_creates_a_ref_resolver_if_not_provided(self):
+ with self.assertWarns(DeprecationWarning):
+ resolver = self.Validator({}).resolver
+ self.assertIsInstance(resolver, validators._RefResolver)
+
+ def test_it_upconverts_from_deprecated_RefResolvers(self):
+ ref, schema = "someCoolRef", {"type": "integer"}
+ resolver = validators._RefResolver("", {}, store={ref: schema})
+ validator = self.Validator({"$ref": ref}, resolver=resolver)
+
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate(None)
+
+ def test_it_upconverts_from_yet_older_deprecated_legacy_RefResolvers(self):
+ """
+ Legacy RefResolvers support only the context manager form of
+ resolution.
+ """
+
+ class LegacyRefResolver:
+ @contextmanager
+ def resolving(this, ref):
+ self.assertEqual(ref, "the ref")
+ yield {"type": "integer"}
+
+ resolver = LegacyRefResolver()
+ schema = {"$ref": "the ref"}
+
+ with self.assertRaises(exceptions.ValidationError):
+ self.Validator(schema, resolver=resolver).validate(None)
+
class AntiDraft6LeakMixin:
"""
@@ -1782,18 +1787,14 @@ class AntiDraft6LeakMixin:
self.Validator.check_schema(False)
self.assertIn("False is not of type", str(e.exception))
- @unittest.skip(bug(523))
def test_True_is_not_a_schema_even_if_you_forget_to_check(self):
- resolver = validators.RefResolver("", {})
with self.assertRaises(Exception) as e:
- self.Validator(True, resolver=resolver).validate(12)
+ self.Validator(True).validate(12)
self.assertNotIsInstance(e.exception, exceptions.ValidationError)
- @unittest.skip(bug(523))
def test_False_is_not_a_schema_even_if_you_forget_to_check(self):
- resolver = validators.RefResolver("", {})
with self.assertRaises(Exception) as e:
- self.Validator(False, resolver=resolver).validate(12)
+ self.Validator(False).validate(12)
self.assertNotIsInstance(e.exception, exceptions.ValidationError)
@@ -1867,7 +1868,7 @@ class TestLatestValidator(TestCase):
def test_ref_resolvers_may_have_boolean_schemas_stored(self):
ref = "someCoolRef"
schema = {"$ref": ref}
- resolver = validators.RefResolver("", {}, store={ref: False})
+ resolver = validators._RefResolver("", {}, store={ref: False})
validator = validators._LATEST_VERSION(schema, resolver=resolver)
with self.assertRaises(exceptions.ValidationError):
@@ -2123,7 +2124,7 @@ class TestRefResolver(TestCase):
def setUp(self):
self.referrer = {}
self.store = {self.stored_uri: self.stored_schema}
- self.resolver = validators.RefResolver(
+ self.resolver = validators._RefResolver(
self.base_uri, self.referrer, self.store,
)
@@ -2143,7 +2144,7 @@ class TestRefResolver(TestCase):
def test_it_resolves_local_refs_with_id(self):
schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}}
- resolver = validators.RefResolver.from_schema(
+ resolver = validators._RefResolver.from_schema(
schema,
id_of=lambda schema: schema.get("id", ""),
)
@@ -2206,7 +2207,7 @@ class TestRefResolver(TestCase):
def test_it_can_construct_a_base_uri_from_a_schema(self):
schema = {"id": "foo"}
- resolver = validators.RefResolver.from_schema(
+ resolver = validators._RefResolver.from_schema(
schema,
id_of=lambda schema: schema.get("id", ""),
)
@@ -2223,7 +2224,7 @@ class TestRefResolver(TestCase):
def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
schema = {}
- resolver = validators.RefResolver.from_schema(schema)
+ resolver = validators._RefResolver.from_schema(schema)
self.assertEqual(resolver.base_uri, "")
self.assertEqual(resolver.resolution_scope, "")
with resolver.resolving("") as resolved:
@@ -2238,7 +2239,7 @@ class TestRefResolver(TestCase):
schema = {"foo": "bar"}
ref = "foo://bar"
- resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ resolver = validators._RefResolver("", {}, handlers={"foo": handler})
with resolver.resolving(ref) as resolved:
self.assertEqual(resolved, schema)
@@ -2252,7 +2253,7 @@ class TestRefResolver(TestCase):
self.fail("Response must not have been cached!")
ref = "foo://bar"
- resolver = validators.RefResolver(
+ resolver = validators._RefResolver(
"", {}, cache_remote=True, handlers={"foo": handler},
)
with resolver.resolving(ref):
@@ -2270,7 +2271,7 @@ class TestRefResolver(TestCase):
self.fail("Handler called twice!")
ref = "foo://bar"
- resolver = validators.RefResolver(
+ resolver = validators._RefResolver(
"", {}, cache_remote=False, handlers={"foo": handler},
)
with resolver.resolving(ref):
@@ -2283,16 +2284,16 @@ class TestRefResolver(TestCase):
raise error
ref = "foo://bar"
- resolver = validators.RefResolver("", {}, handlers={"foo": handler})
- with self.assertRaises(exceptions.RefResolutionError) as err:
+ resolver = validators._RefResolver("", {}, handlers={"foo": handler})
+ with self.assertRaises(exceptions._RefResolutionError) as err:
with resolver.resolving(ref):
self.fail("Shouldn't get this far!") # pragma: no cover
- self.assertEqual(err.exception, exceptions.RefResolutionError(error))
+ self.assertEqual(err.exception, exceptions._RefResolutionError(error))
def test_helpful_error_message_on_failed_pop_scope(self):
- resolver = validators.RefResolver("", {})
+ resolver = validators._RefResolver("", {})
resolver.pop_scope()
- with self.assertRaises(exceptions.RefResolutionError) as exc:
+ with self.assertRaises(exceptions._RefResolutionError) as exc:
resolver.pop_scope()
self.assertIn("Failed to pop the scope", str(exc.exception))
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
index d370dc5..62e86df 100644
--- a/jsonschema/validators.py
+++ b/jsonschema/validators.py
@@ -13,11 +13,13 @@ from warnings import warn
import contextlib
import json
import reprlib
-import typing
import warnings
-from pyrsistent import m
+from jsonschema_specifications import REGISTRY as SPECIFICATIONS
+from referencing import Specification
+from rpds import HashTrieMap
import attr
+import referencing.jsonschema
from jsonschema import (
_format,
@@ -33,7 +35,6 @@ _UNSET = _utils.Unset()
_VALIDATORS: dict[str, Validator] = {}
_META_SCHEMAS = _utils.URIDict()
-_VOCABULARIES: list[tuple[str, typing.Any]] = []
def __getattr__(name):
@@ -62,6 +63,13 @@ def __getattr__(name):
stacklevel=2,
)
return _META_SCHEMAS
+ elif name == "RefResolver":
+ warnings.warn(
+ _RefResolver._DEPRECATION_MESSAGE,
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _RefResolver
raise AttributeError(f"module {__name__} has no attribute {name}")
@@ -93,34 +101,13 @@ def validates(version):
return _validates
-def _id_of(schema):
- """
- Return the ID of a schema for recent JSON Schema drafts.
- """
- if schema is True or schema is False:
- return ""
- return schema.get("$id", "")
-
-
-def _store_schema_list():
- if not _VOCABULARIES:
- package = _utils.resources.files(__package__)
- for version in package.joinpath("schemas", "vocabularies").iterdir():
- for path in version.iterdir():
- vocabulary = json.loads(path.read_text())
- _VOCABULARIES.append((vocabulary["$id"], vocabulary))
- return [
- (id, validator.META_SCHEMA) for id, validator in _META_SCHEMAS.items()
- ] + _VOCABULARIES
-
-
def create(
meta_schema,
validators=(),
version=None,
type_checker=_types.draft202012_type_checker,
format_checker=_format.draft202012_format_checker,
- id_of=_id_of,
+ id_of=referencing.jsonschema.DRAFT202012.id_of,
applicable_validators=methodcaller("items"),
):
"""
@@ -184,6 +171,11 @@ def create(
# preemptively don't shadow the `Validator.format_checker` local
format_checker_arg = format_checker
+ specification = referencing.jsonschema.specification_with(
+ dialect_id=id_of(meta_schema),
+ default=Specification.OPAQUE,
+ )
+
@attr.s
class Validator:
@@ -194,8 +186,21 @@ def create(
ID_OF = staticmethod(id_of)
schema = attr.ib(repr=reprlib.repr)
- resolver = attr.ib(default=None, repr=False)
+ _ref_resolver = attr.ib(default=None, repr=False, alias="resolver")
format_checker = attr.ib(default=None)
+ # TODO: include new meta-schemas added at runtime
+ _registry = attr.ib(
+ default=SPECIFICATIONS,
+ converter=SPECIFICATIONS.combine, # type: ignore[misc]
+ kw_only=True,
+ repr=False,
+ )
+ _resolver = attr.ib(
+ alias="_resolver",
+ default=None,
+ kw_only=True,
+ repr=False,
+ )
def __init_subclass__(cls):
warnings.warn(
@@ -212,11 +217,27 @@ def create(
stacklevel=2,
)
+ def evolve(self, **changes):
+ cls = self.__class__
+ schema = changes.setdefault("schema", self.schema)
+ NewValidator = validator_for(schema, default=cls)
+
+ for field in attr.fields(cls):
+ if not field.init:
+ continue
+ attr_name = field.name
+ init_name = field.alias
+ if init_name not in changes:
+ changes[init_name] = getattr(self, attr_name)
+
+ return NewValidator(**changes)
+
+ cls.evolve = evolve
+
def __attrs_post_init__(self):
- if self.resolver is None:
- self.resolver = RefResolver.from_schema(
- self.schema,
- id_of=id_of,
+ if self._resolver is None:
+ self._resolver = self._registry.resolver_with_root(
+ resource=specification.create_resource(self.schema),
)
@classmethod
@@ -231,19 +252,32 @@ def create(
for error in validator.iter_errors(schema):
raise exceptions.SchemaError.create_from(error)
- def evolve(self, **changes):
- # Essentially reproduces attr.evolve, but may involve instantiating
- # a different class than this one.
- cls = self.__class__
+ @property
+ def resolver(self):
+ warnings.warn(
+ (
+ f"Accessing {self.__class__.__name__}.resolver is "
+ "deprecated as of v4.18.0, in favor of the "
+ "https://github.com/python-jsonschema/referencing "
+ "library, which provides more compliant referencing "
+ "behavior as well as more flexible APIs for "
+ "customization."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if self._ref_resolver is None:
+ self._ref_resolver = _RefResolver.from_schema(
+ self.schema,
+ id_of=id_of,
+ )
+ return self._ref_resolver
+ def evolve(self, **changes):
schema = changes.setdefault("schema", self.schema)
- NewValidator = validator_for(schema, default=cls)
+ NewValidator = validator_for(schema, default=self.__class__)
- for field in attr.fields(cls):
- if not field.init:
- continue
- attr_name = field.name # To deal with private attributes.
- init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+ for (attr_name, init_name) in evolve_fields:
if init_name not in changes:
changes[init_name] = getattr(self, attr_name)
@@ -276,39 +310,73 @@ def create(
)
return
- scope = id_of(_schema)
- if scope:
- self.resolver.push_scope(scope)
- try:
- for k, v in applicable_validators(_schema):
- validator = self.VALIDATORS.get(k)
- if validator is None:
- continue
+ for k, v in applicable_validators(_schema):
+ validator = self.VALIDATORS.get(k)
+ if validator is None:
+ continue
+
+ errors = validator(self, v, instance, _schema) or ()
+ for error in errors:
+ # set details if not already set by the called fn
+ error._set(
+ validator=k,
+ validator_value=v,
+ instance=instance,
+ schema=_schema,
+ type_checker=self.TYPE_CHECKER,
+ )
+ if k not in {"if", "$ref"}:
+ error.schema_path.appendleft(k)
+ yield error
+
+ def descend(
+ self,
+ instance,
+ schema,
+ path=None,
+ schema_path=None,
+ resolver=None,
+ ):
+ if schema is True:
+ return
+ elif schema is False:
+ yield exceptions.ValidationError(
+ f"False schema does not allow {instance!r}",
+ validator=None,
+ validator_value=None,
+ instance=instance,
+ schema=schema,
+ )
+ return
- errors = validator(self, v, instance, _schema) or ()
- for error in errors:
- # set details if not already set by the called fn
- error._set(
- validator=k,
- validator_value=v,
- instance=instance,
- schema=_schema,
- type_checker=self.TYPE_CHECKER,
- )
- if k not in {"if", "$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.evolve(schema=schema).iter_errors(instance):
- if path is not None:
- error.path.appendleft(path)
- if schema_path is not None:
- error.schema_path.appendleft(schema_path)
- yield error
+ if resolver is None:
+ resolver = self._resolver.in_subresource(
+ specification.create_resource(schema),
+ )
+ evolved = self.evolve(schema=schema, _resolver=resolver)
+
+ for k, v in applicable_validators(schema):
+ validator = evolved.VALIDATORS.get(k)
+ if validator is None:
+ continue
+
+ errors = validator(evolved, v, instance, schema) or ()
+ for error in errors:
+ # set details if not already set by the called fn
+ error._set(
+ validator=k,
+ validator_value=v,
+ instance=instance,
+ schema=schema,
+ type_checker=evolved.TYPE_CHECKER,
+ )
+ if k not in {"if", "$ref"}:
+ error.schema_path.appendleft(k)
+ if path is not None:
+ error.path.appendleft(path)
+ if schema_path is not None:
+ error.schema_path.appendleft(schema_path)
+ yield error
def validate(self, *args, **kwargs):
for error in self.iter_errors(*args, **kwargs):
@@ -320,6 +388,28 @@ def create(
except exceptions.UndefinedTypeCheck:
raise exceptions.UnknownType(type, instance, self.schema)
+ def _validate_reference(self, ref, instance):
+ if self._ref_resolver is None:
+ resolved = self._resolver.lookup(ref)
+ return self.descend(
+ instance,
+ resolved.contents,
+ resolver=resolved.resolver,
+ )
+ else:
+ resolve = getattr(self._ref_resolver, "resolve", None)
+ if resolve is None:
+ with self._ref_resolver.resolving(ref) as resolved:
+ return self.descend(instance, resolved)
+ else:
+ scope, resolved = resolve(ref)
+ self._ref_resolver.push_scope(scope)
+
+ try:
+ return self.descend(instance, resolved)
+ finally:
+ self._ref_resolver.pop_scope()
+
def is_valid(self, instance, _schema=None):
if _schema is not None:
warnings.warn(
@@ -337,6 +427,12 @@ def create(
error = next(self.iter_errors(instance), None)
return error is None
+ evolve_fields = [
+ (field.name, field.alias)
+ for field in attr.fields(Validator)
+ if field.init
+ ]
+
if version is not None:
safe = version.title().replace(" ", "").replace("-", "")
Validator.__name__ = Validator.__qualname__ = f"{safe}Validator"
@@ -430,7 +526,9 @@ def extend(
Draft3Validator = create(
- meta_schema=_utils.load_schema("draft3"),
+ meta_schema=SPECIFICATIONS.contents(
+ "http://json-schema.org/draft-03/schema#",
+ ),
validators={
"$ref": _validators.ref,
"additionalItems": _validators.additionalItems,
@@ -457,12 +555,14 @@ Draft3Validator = create(
type_checker=_types.draft3_type_checker,
format_checker=_format.draft3_format_checker,
version="draft3",
- id_of=_legacy_validators.id_of_ignore_ref(property="id"),
+ id_of=referencing.jsonschema.DRAFT3.id_of,
applicable_validators=_legacy_validators.ignore_ref_siblings,
)
Draft4Validator = create(
- meta_schema=_utils.load_schema("draft4"),
+ meta_schema=SPECIFICATIONS.contents(
+ "http://json-schema.org/draft-04/schema#",
+ ),
validators={
"$ref": _validators.ref,
"additionalItems": _validators.additionalItems,
@@ -494,12 +594,14 @@ Draft4Validator = create(
type_checker=_types.draft4_type_checker,
format_checker=_format.draft4_format_checker,
version="draft4",
- id_of=_legacy_validators.id_of_ignore_ref(property="id"),
+ id_of=referencing.jsonschema.DRAFT4.id_of,
applicable_validators=_legacy_validators.ignore_ref_siblings,
)
Draft6Validator = create(
- meta_schema=_utils.load_schema("draft6"),
+ meta_schema=SPECIFICATIONS.contents(
+ "http://json-schema.org/draft-06/schema#",
+ ),
validators={
"$ref": _validators.ref,
"additionalItems": _validators.additionalItems,
@@ -536,12 +638,14 @@ Draft6Validator = create(
type_checker=_types.draft6_type_checker,
format_checker=_format.draft6_format_checker,
version="draft6",
- id_of=_legacy_validators.id_of_ignore_ref(),
+ id_of=referencing.jsonschema.DRAFT6.id_of,
applicable_validators=_legacy_validators.ignore_ref_siblings,
)
Draft7Validator = create(
- meta_schema=_utils.load_schema("draft7"),
+ meta_schema=SPECIFICATIONS.contents(
+ "http://json-schema.org/draft-07/schema#",
+ ),
validators={
"$ref": _validators.ref,
"additionalItems": _validators.additionalItems,
@@ -579,12 +683,14 @@ Draft7Validator = create(
type_checker=_types.draft7_type_checker,
format_checker=_format.draft7_format_checker,
version="draft7",
- id_of=_legacy_validators.id_of_ignore_ref(),
+ id_of=referencing.jsonschema.DRAFT7.id_of,
applicable_validators=_legacy_validators.ignore_ref_siblings,
)
Draft201909Validator = create(
- meta_schema=_utils.load_schema("draft2019-09"),
+ meta_schema=SPECIFICATIONS.contents(
+ "https://json-schema.org/draft/2019-09/schema",
+ ),
validators={
"$recursiveRef": _legacy_validators.recursiveRef,
"$ref": _validators.ref,
@@ -629,7 +735,9 @@ Draft201909Validator = create(
)
Draft202012Validator = create(
- meta_schema=_utils.load_schema("draft2020-12"),
+ meta_schema=SPECIFICATIONS.contents(
+ "https://json-schema.org/draft/2020-12/schema",
+ ),
validators={
"$dynamicRef": _validators.dynamicRef,
"$ref": _validators.ref,
@@ -677,7 +785,7 @@ Draft202012Validator = create(
_LATEST_VERSION = Draft202012Validator
-class RefResolver:
+class _RefResolver:
"""
Resolve JSON References.
@@ -719,13 +827,26 @@ class RefResolver:
cache_remote (bool):
Whether remote refs should be cached after first resolution
+
+ .. deprecated:: v4.18.0
+
+ ``RefResolver`` has been deprecated in favor of `referencing`.
"""
+ _DEPRECATION_MESSAGE = (
+ "jsonschema.RefResolver is deprecated as of v4.18.0, in favor of the "
+ "https://github.com/python-jsonschema/referencing library, which "
+ "provides more compliant referencing behavior as well as more "
+ "flexible APIs for customization. A future release will remove "
+ "RefResolver. Please file a feature request (on referencing) if you "
+ "are missing an API for the kind of customization you need."
+ )
+
def __init__(
self,
base_uri,
referrer,
- store=m(),
+ store=HashTrieMap(),
cache_remote=True,
handlers=(),
urljoin_cache=None,
@@ -742,7 +863,12 @@ class RefResolver:
self._scopes_stack = [base_uri]
- self.store = _utils.URIDict(_store_schema_list())
+ self.store = _utils.URIDict(
+ (uri, each.contents) for uri, each in SPECIFICATIONS.items()
+ )
+ self.store.update(
+ (id, each.META_SCHEMA) for id, each in _META_SCHEMAS.items()
+ )
self.store.update(store)
self.store.update(
(schema["$id"], schema)
@@ -755,7 +881,13 @@ class RefResolver:
self._remote_cache = remote_cache
@classmethod
- def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
+ def from_schema(
+ cls,
+ schema,
+ id_of=referencing.jsonschema.DRAFT202012.id_of,
+ *args,
+ **kwargs,
+ ):
"""
Construct a resolver from a JSON schema object.
@@ -767,10 +899,10 @@ class RefResolver:
Returns:
- `RefResolver`
+ `_RefResolver`
"""
- return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) # noqa: B026, E501
+ return cls(base_uri=id_of(schema) or "", referrer=schema, *args, **kwargs) # noqa: B026, E501
def push_scope(self, scope):
"""
@@ -796,7 +928,7 @@ class RefResolver:
try:
self._scopes_stack.pop()
except IndexError:
- raise exceptions.RefResolutionError(
+ raise exceptions._RefResolutionError(
"Failed to pop the scope from an empty stack. "
"`pop_scope()` should only be called once for every "
"`push_scope()`",
@@ -912,7 +1044,7 @@ class RefResolver:
try:
document = self.resolve_remote(url)
except Exception as exc:
- raise exceptions.RefResolutionError(exc)
+ raise exceptions._RefResolutionError(exc)
return self.resolve_fragment(document, fragment)
@@ -966,7 +1098,7 @@ class RefResolver:
try:
document = document[part]
except (TypeError, LookupError):
- raise exceptions.RefResolutionError(
+ raise exceptions._RefResolutionError(
f"Unresolvable JSON pointer: {fragment!r}",
)
diff --git a/pyproject.toml b/pyproject.toml
index 71f6d34..f709c9f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,7 +8,7 @@ source = "vcs"
[project]
name = "jsonschema"
description = "An implementation of JSON Schema validation for Python"
-requires-python = ">=3.7"
+requires-python = ">=3.8"
license = {text = "MIT"}
keywords = ["validation", "data validation", "jsonschema", "json"]
authors = [
@@ -21,7 +21,6 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
- "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
@@ -32,8 +31,10 @@ classifiers = [
dynamic = ["version", "readme"]
dependencies = [
- "attrs>=17.4.0",
- "pyrsistent>=0.14.0,!=0.17.0,!=0.17.1,!=0.17.2",
+ "attrs>=22.2.0",
+ "jsonschema-specifications>=2023.03.4",
+ "referencing>=0.24.4",
+ "rpds-py>=0.6.1",
"importlib_metadata;python_version<'3.8'",
"typing_extensions;python_version<'3.8'",
diff --git a/tox.ini b/tox.ini
index 61e6af8..83bf9c2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
[tox]
min_version = 4.0.9
envlist =
- py{37,38,39,310,311,py3}-{noextra,format,formatnongpl}-{build,tests}
+ py{38,39,310,311,py3}-{noextra,format,formatnongpl}-{build,tests}
{noextra,format,formatnongpl}-audit
readme
secrets
@@ -35,7 +35,7 @@ commands =
build: {envpython} -m build {toxinidir} --outdir {envtmpdir}/dist
- tests,coverage,ghcoverage: {envpython} -Werror -m {env:MAYBE_COVERAGE:} twisted.trial {posargs:jsonschema}
+ tests,coverage,ghcoverage: {envpython} -Werror -m {env:MAYBE_COVERAGE:} virtue {posargs:jsonschema}
tests: {envpython} -m doctest {toxinidir}/README.rst
coverage: {envpython} -m coverage report --show-missing
@@ -53,7 +53,7 @@ deps =
perf: pyperf
- tests,coverage,ghcoverage: twisted
+ tests,coverage,ghcoverage: virtue
coverage,ghcoverage: coverage>=7.0.0b1
@@ -92,7 +92,6 @@ deps =
# FIXME: Why are we repeating dependencies here?
attrs
mypy
- pyrsistent
types-requests
commands = {envpython} -m mypy --config {toxinidir}/pyproject.toml {posargs} {toxinidir}/jsonschema