summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2021-12-15 04:38:29 -0800
committerGitHub <noreply@github.com>2021-12-15 04:38:29 -0800
commit48280329f11021e8772bacbaf0b16efcd0f78af4 (patch)
tree813bdcc753037c7bff8b4c8b71779e7ba3a35d65
parentac7b8389f14e845a966345171eadf3515cc3052f (diff)
parent3a6292591334f85f3a79a3c3612dabc79f3e6e31 (diff)
downloadjsonschema-48280329f11021e8772bacbaf0b16efcd0f78af4.tar.gz
Merge pull request #890 from sirosen/add-validator-protocol
Add `jsonschema.protocols.IValidator`
-rw-r--r--README.rst2
-rw-r--r--docs/creating.rst9
-rw-r--r--docs/errors.rst2
-rw-r--r--docs/faq.rst4
-rw-r--r--docs/spelling-wordlist.txt2
-rw-r--r--docs/validate.rst154
-rw-r--r--jsonschema/__init__.py1
-rw-r--r--jsonschema/_types.py2
-rw-r--r--jsonschema/protocols.py153
-rw-r--r--jsonschema/tests/test_validators.py23
-rw-r--r--jsonschema/validators.py13
-rw-r--r--setup.cfg1
12 files changed, 209 insertions, 157 deletions
diff --git a/README.rst b/README.rst
index fcb11f1..ccdbca8 100644
--- a/README.rst
+++ b/README.rst
@@ -74,7 +74,7 @@ Features
and
`Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft3Validator>`_
-* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
+* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.protocols.Validator.iter_errors>`_
that can iteratively report *all* validation errors.
* `Programmatic querying <https://python-jsonschema.readthedocs.io/en/latest/errors/>`_
diff --git a/docs/creating.rst b/docs/creating.rst
index a9f18d1..810293f 100644
--- a/docs/creating.rst
+++ b/docs/creating.rst
@@ -23,3 +23,12 @@ Any validating function that validates against a subschema should call
instance, or schema, it should pass one or both of the ``path`` or
``schema_path`` arguments to ``descend`` in order to properly maintain
where in the instance or schema respectively the error occurred.
+
+The Validator Protocol
+----------------------
+
+``jsonschema`` defines a `protocol <typing.Protocol>`,
+`jsonschema.protocols.Validator` which can be used in type annotations to
+describe the type of a validator object.
+
+For full details, see `validator-protocol`.
diff --git a/docs/errors.rst b/docs/errors.rst
index 2b2f485..7cef27e 100644
--- a/docs/errors.rst
+++ b/docs/errors.rst
@@ -275,7 +275,7 @@ error objects.
As you can see, `jsonschema.exceptions.ErrorTree` takes an
iterable of `ValidationError`\s when constructing a tree so
you can directly pass it the return value of a validator object's
-`jsonschema.IValidator.iter_errors` method.
+`jsonschema.protocols.Validator.iter_errors` method.
`ErrorTree`\s support a number of useful operations. The first one we
might want to perform is to check whether a given element in our instance
diff --git a/docs/faq.rst b/docs/faq.rst
index d8eea5b..4fa2ac3 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -90,8 +90,8 @@ but fail the second!
Still, filling in defaults is a thing that is useful. `jsonschema`
allows you to `define your own validator classes and callables
-<creating>`, so you can easily create an `jsonschema.IValidator` that
-does do default setting. Here's some code to get you started. (In
+<creating>`, so you can easily create an `jsonschema.protocols.Validator`
+that does do default setting. Here's some code to get you started. (In
this code, we add the default properties to each object *before* the
properties are validated, so the default values themselves will need to
be valid under the schema.)
diff --git a/docs/spelling-wordlist.txt b/docs/spelling-wordlist.txt
index 7a6627b..36805a1 100644
--- a/docs/spelling-wordlist.txt
+++ b/docs/spelling-wordlist.txt
@@ -1,5 +1,5 @@
# this appears to be misinterpreting Napoleon types as prose, sigh...
-IValidator
+Validator
TypeChecker
UnknownType
ValidationError
diff --git a/docs/validate.rst b/docs/validate.rst
index 3d8134d..6be8f9c 100644
--- a/docs/validate.rst
+++ b/docs/validate.rst
@@ -19,160 +19,26 @@ The simplest way to validate an instance under a given schema is to use the
fundamentals underway at `Understanding JSON Schema
<https://json-schema.org/understanding-json-schema/>`_
+.. _validator-protocol:
-The Validator Interface
+The Validator Protocol
-----------------------
-`jsonschema` defines an (informal) interface that all validator
+`jsonschema` defines a protocol that all validator
classes should adhere to.
-.. class:: IValidator(schema, types=(), resolver=None, format_checker=None)
-
- :argument dict schema: the schema that the validator object
- will validate with. It is assumed to be valid, and providing
- an invalid schema can lead to undefined behavior. See
- `IValidator.check_schema` to validate a schema first.
- :argument resolver: an instance of `RefResolver` that will be
- used to resolve :validator:`$ref` properties (JSON references). If
- unprovided, one will be created.
- :argument format_checker: an instance of `FormatChecker`
- whose `FormatChecker.conforms` method will be called to
- check and see if instances conform to each :validator:`format`
- property present in the schema. If unprovided, no validation
- will be done for :validator:`format`. Certain formats require
- additional packages to be installed (ipv5, uri, color, date-time).
- The required packages can be found at the bottom of this page.
- :argument types:
- .. deprecated:: 3.0.0
-
- Use `TypeChecker.redefine` and
- `jsonschema.validators.extend` instead of this argument.
-
- See `validating-types` for details.
-
- If used, this overrides or extends the list of known types when
- validating the :validator:`type` property.
-
- What is provided should map strings (type names) to class objects
- that will be checked via `isinstance`.
-
-
- .. attribute:: META_SCHEMA
-
- An object representing the validator's meta schema (the schema that
- describes valid schemas in the given version).
-
- .. attribute:: VALIDATORS
-
- A mapping of validator names (`str`\s) to functions
- that validate the validator property with that name. For more
- information see `creating-validators`.
-
- .. attribute:: TYPE_CHECKER
-
- A `TypeChecker` that will be used when validating :validator:`type`
- properties in JSON schemas.
-
- .. attribute:: schema
-
- The schema that was passed in when initializing the object.
-
- .. attribute:: DEFAULT_TYPES
-
- .. deprecated:: 3.0.0
-
- Use of this attribute is deprecated in favor of the new `type
- checkers <TypeChecker>`.
-
- See `validating-types` for details.
-
- For backwards compatibility on existing validator classes, a mapping of
- JSON types to Python class objects which define the Python types for
- each JSON type.
-
- Any existing code using this attribute should likely transition to
- using `TypeChecker.is_type`.
-
-
- .. classmethod:: check_schema(schema)
-
- Validate the given schema against the validator's `META_SCHEMA`.
-
- :raises: `jsonschema.exceptions.SchemaError` if the schema
- is invalid
-
- .. method:: is_type(instance, type)
-
- Check if the instance is of the given (JSON Schema) type.
-
- :type type: str
- :rtype: bool
- :raises: `jsonschema.exceptions.UnknownType` if ``type``
- is not a known type.
-
- .. method:: is_valid(instance)
-
- Check if the instance is valid under the current `schema`.
-
- :rtype: bool
-
- >>> schema = {"maxItems" : 2}
- >>> Draft3Validator(schema).is_valid([2, 3, 4])
- False
-
- .. method:: iter_errors(instance)
-
- Lazily yield each of the validation errors in the given instance.
-
- :rtype: an `collections.abc.Iterable` of
- `jsonschema.exceptions.ValidationError`\s
-
- >>> schema = {
- ... "type" : "array",
- ... "items" : {"enum" : [1, 2, 3]},
- ... "maxItems" : 2,
- ... }
- >>> v = Draft3Validator(schema)
- >>> for error in sorted(v.iter_errors([2, 3, 4]), key=str):
- ... print(error.message)
- 4 is not one of [1, 2, 3]
- [2, 3, 4] is too long
-
- .. method:: validate(instance)
-
- Check if the instance is valid under the current `schema`.
-
- :raises: `jsonschema.exceptions.ValidationError` if the
- instance is invalid
-
- >>> schema = {"maxItems" : 2}
- >>> Draft3Validator(schema).validate([2, 3, 4])
- Traceback (most recent call last):
- ...
- ValidationError: [2, 3, 4] is too long
-
- .. method:: evolve(**kwargs)
-
- Create a new validator like this one, but with given changes.
-
- Preserves all other attributes, so can be used to e.g. create a
- validator with a different schema but with the same :validator:`$ref`
- resolution behavior.
-
- >>> validator = Draft202012Validator({})
- >>> validator.evolve(schema={"type": "number"})
- Draft202012Validator(schema={'type': 'number'}, format_checker=None)
-
+.. autoclass:: jsonschema.protocols.Validator
+ :members:
All of the `versioned validators <versioned-validators>` that are included with
-`jsonschema` adhere to the interface, and implementers of validator classes
+`jsonschema` adhere to the protocol, and implementers of validator classes
that extend or complement the ones included should adhere to it as well. For
more information see `creating-validators`.
Type Checking
-------------
-To handle JSON Schema's :validator:`type` property, a `IValidator` uses
+To handle JSON Schema's :validator:`type` property, a `Validator` uses
an associated `TypeChecker`. The type checker provides an immutable
mapping between names of types and functions that can test if an instance is
of that type. The defaults are suitable for most users - each of the
@@ -216,7 +82,7 @@ given how common validating these types are.
If you *do* want the generality, or just want to add a few specific additional
types as being acceptable for a validator object, then you should update an
existing `TypeChecker` or create a new one. You may then create a new
-`IValidator` via `jsonschema.validators.extend`.
+`Validator` via `jsonschema.validators.extend`.
.. code-block:: python
@@ -244,7 +110,7 @@ Versioned Validators
`jsonschema` ships with validator classes for various versions of
the JSON Schema specification. For details on the methods and attributes
-that each validator class provides see the `IValidator` interface,
+that each validator class provides see the `Validator` interface,
which each included validator class implements.
.. autoclass:: Draft202012Validator
@@ -289,7 +155,7 @@ JSON Schema defines the :validator:`format` property which can be used to check
if primitive types (``string``\s, ``number``\s, ``boolean``\s) conform to
well-defined formats. By default, no validation is enforced, but optionally,
validation can be enabled by hooking in a format-checking object into an
-`IValidator`.
+`Validator`.
.. doctest::
diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py
index c7fa76b..cd2102a 100644
--- a/jsonschema/__init__.py
+++ b/jsonschema/__init__.py
@@ -26,6 +26,7 @@ from jsonschema.exceptions import (
SchemaError,
ValidationError,
)
+from jsonschema.protocols import Validator
from jsonschema.validators import (
Draft3Validator,
Draft4Validator,
diff --git a/jsonschema/_types.py b/jsonschema/_types.py
index 6ba2bad..f24e433 100644
--- a/jsonschema/_types.py
+++ b/jsonschema/_types.py
@@ -49,7 +49,7 @@ class TypeChecker(object):
"""
A ``type`` property checker.
- A `TypeChecker` performs type checking for an `IValidator`. Type
+ A `TypeChecker` performs type checking for a `Validator`. Type
checks to perform are updated using `TypeChecker.redefine` or
`TypeChecker.redefine_many` and removed via `TypeChecker.remove`.
Each of these return a new `TypeChecker` object.
diff --git a/jsonschema/protocols.py b/jsonschema/protocols.py
new file mode 100644
index 0000000..1e2689b
--- /dev/null
+++ b/jsonschema/protocols.py
@@ -0,0 +1,153 @@
+"""
+typing.Protocol classes for jsonschema interfaces.
+"""
+
+# for reference material on Protocols, see
+# https://www.python.org/dev/peps/pep-0544/
+
+from typing import Any, ClassVar, Iterator, Optional
+
+try:
+ from typing import Protocol, runtime_checkable
+except ImportError:
+ from typing_extensions import Protocol, runtime_checkable
+
+from jsonschema._format import FormatChecker
+
+from ._types import TypeChecker
+from .exceptions import ValidationError
+from .validators import RefResolver
+
+# For code authors working on the validator protocol, these are the three
+# use-cases which should be kept in mind:
+#
+# 1. As a protocol class, it can be used in type annotations to describe the
+# available methods and attributes of a validator
+# 2. It is the source of autodoc for the validator documentation
+# 3. It is runtime_checkable, meaning that it can be used in isinstance()
+# checks.
+#
+# Since protocols are not base classes, isinstance() checking is limited in
+# its capabilities. See docs on runtime_checkable for detail
+
+
+@runtime_checkable
+class Validator(Protocol):
+ """
+ The protocol to which all validator classes should adhere.
+
+ :argument dict schema: the schema that the validator object
+ will validate with. It is assumed to be valid, and providing
+ an invalid schema can lead to undefined behavior. See
+ `Validator.check_schema` to validate a schema first.
+ :argument resolver: an instance of `jsonschema.RefResolver` that will be
+ used to resolve :validator:`$ref` properties (JSON references). If
+ unprovided, one will be created.
+ :argument format_checker: an instance of `jsonschema.FormatChecker`
+ whose `jsonschema.FormatChecker.conforms` method will be called to
+ check and see if instances conform to each :validator:`format`
+ property present in the schema. If unprovided, no validation
+ will be done for :validator:`format`. Certain formats require
+ additional packages to be installed (ipv5, uri, color, date-time).
+ The required packages can be found at the bottom of this page.
+ """
+
+ #: An object representing the validator's meta schema (the schema that
+ #: describes valid schemas in the given version).
+ META_SCHEMA: ClassVar[dict]
+
+ #: A mapping of validator names (`str`\s) to functions
+ #: that validate the validator property with that name. For more
+ #: information see `creating-validators`.
+ VALIDATORS: ClassVar[dict]
+
+ #: A `jsonschema.TypeChecker` that will be used when validating
+ #: :validator:`type` properties in JSON schemas.
+ TYPE_CHECKER: ClassVar[TypeChecker]
+
+ #: The schema that was passed in when initializing the object.
+ schema: dict
+
+ def __init__(
+ self,
+ schema: dict,
+ resolver: Optional[RefResolver] = None,
+ format_checker: Optional[FormatChecker] = None,
+ ) -> None:
+ ...
+
+ @classmethod
+ def check_schema(cls, schema: dict) -> None:
+ """
+ Validate the given schema against the validator's `META_SCHEMA`.
+
+ :raises: `jsonschema.exceptions.SchemaError` if the schema
+ is invalid
+ """
+
+ def is_type(self, instance: Any, type: str) -> bool:
+ """
+ Check if the instance is of the given (JSON Schema) type.
+
+ :type type: str
+ :rtype: bool
+ :raises: `jsonschema.exceptions.UnknownType` if ``type``
+ is not a known type.
+ """
+
+ def is_valid(self, instance: dict) -> bool:
+ """
+ Check if the instance is valid under the current `schema`.
+
+ :rtype: bool
+
+ >>> schema = {"maxItems" : 2}
+ >>> Draft3Validator(schema).is_valid([2, 3, 4])
+ False
+ """
+
+ def iter_errors(self, instance: dict) -> Iterator[ValidationError]:
+ r"""
+ Lazily yield each of the validation errors in the given instance.
+
+ :rtype: an `collections.abc.Iterable` of
+ `jsonschema.exceptions.ValidationError`\s
+
+ >>> schema = {
+ ... "type" : "array",
+ ... "items" : {"enum" : [1, 2, 3]},
+ ... "maxItems" : 2,
+ ... }
+ >>> v = Draft3Validator(schema)
+ >>> for error in sorted(v.iter_errors([2, 3, 4]), key=str):
+ ... print(error.message)
+ 4 is not one of [1, 2, 3]
+ [2, 3, 4] is too long
+ """
+
+ def validate(self, instance: dict) -> None:
+ """
+ Check if the instance is valid under the current `schema`.
+
+ :raises: `jsonschema.exceptions.ValidationError` if the
+ instance is invalid
+
+ >>> schema = {"maxItems" : 2}
+ >>> Draft3Validator(schema).validate([2, 3, 4])
+ Traceback (most recent call last):
+ ...
+ ValidationError: [2, 3, 4] is too long
+ """
+
+ def evolve(self, **kwargs) -> "Validator":
+ """
+ Create a new validator like this one, but with given changes.
+
+ Preserves all other attributes, so can be used to e.g. create a
+ validator with a different schema but with the same :validator:`$ref`
+ resolution behavior.
+
+ >>> validator = Draft202012Validator({})
+ >>> validator.evolve(schema={"type": "number"})
+ Draft202012Validator(schema={'type': 'number'}, format_checker=None)
+ """
diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py
index 429f868..9e52ce9 100644
--- a/jsonschema/tests/test_validators.py
+++ b/jsonschema/tests/test_validators.py
@@ -13,7 +13,13 @@ import warnings
import attr
-from jsonschema import FormatChecker, TypeChecker, exceptions, validators
+from jsonschema import (
+ FormatChecker,
+ TypeChecker,
+ exceptions,
+ protocols,
+ validators,
+)
from jsonschema.tests._helpers import bug
@@ -2128,6 +2134,21 @@ class TestRefResolver(TestCase):
self.assertIn("Failed to pop the scope", str(exc.exception))
+class TestValidatorProtocol(TestCase):
+ def test_each_validator_is_instance_of_protocol(self):
+ schema = {}
+ for validator_cls in [
+ validators.Draft3Validator,
+ validators.Draft4Validator,
+ validators.Draft6Validator,
+ validators.Draft7Validator,
+ validators.Draft201909Validator,
+ validators.Draft202012Validator,
+ ]:
+ validator = validator_cls(schema)
+ assert isinstance(validator, protocols.Validator)
+
+
def sorted_errors(errors):
def key(error):
return (
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
index 4ab101c..e037c4b 100644
--- a/jsonschema/validators.py
+++ b/jsonschema/validators.py
@@ -155,7 +155,7 @@ def create(
Returns:
- a new `jsonschema.IValidator` class
+ a new `jsonschema.protocols.Validator` class
"""
@attr.s
@@ -284,7 +284,7 @@ def extend(validator, validators=(), version=None, type_checker=None):
Arguments:
- validator (jsonschema.IValidator):
+ validator (jsonschema.protocols.Validator):
an existing validator class
@@ -314,11 +314,12 @@ def extend(validator, validators=(), version=None, type_checker=None):
a type checker, used when applying the :validator:`type` validator.
If unprovided, the type checker of the extended
- `jsonschema.IValidator` will be carried along.
+ `jsonschema.protocols.Validator` will be carried along.
Returns:
- a new `jsonschema.IValidator` class extending the one provided
+ a new `jsonschema.protocols.Validator` class extending the one
+ provided
.. note:: Meta Schemas
@@ -916,7 +917,7 @@ def validate(instance, schema, cls=None, *args, **kwargs):
If you know you have a valid schema already, especially if you
intend to validate multiple instances with the same schema, you
- likely would prefer using the `IValidator.validate` method directly
+ likely would prefer using the `Validator.validate` method directly
on a specific validator (e.g. ``Draft7Validator.validate``).
@@ -930,7 +931,7 @@ def validate(instance, schema, cls=None, *args, **kwargs):
The schema to validate with
- cls (IValidator):
+ cls (Validator):
The class that will be used to validate the instance.
diff --git a/setup.cfg b/setup.cfg
index bcfb4a6..c081c64 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -32,6 +32,7 @@ install_requires =
importlib_metadata;python_version<'3.8'
importlib_resources>=1.4.0;python_version<'3.9'
pyrsistent>=0.14.0,!=0.17.0,!=0.17.1,!=0.17.2
+ typing_extensions;python_version<'3.8'
[options.extras_require]
format =