summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rosen <sirosen@globus.org>2021-12-09 23:37:12 +0000
committerStephen Rosen <sirosen@globus.org>2021-12-13 19:09:54 +0000
commit75144264a569aee8d6aab62ba606ba4f71e057cb (patch)
treeee334858e7f3f974d1cc1504d591970b642c0a0b
parent0cb3a4c87c994725d75a7aac54ef94cc71407097 (diff)
downloadjsonschema-75144264a569aee8d6aab62ba606ba4f71e057cb.tar.gz
Add `jsonschema.protocols.IValidator`
This is a Protocol implementation for type checking under mypy and other static analyzers. It uses the protocol class defined in py3.8+ and uses typing_extensions as a backport for py3.7 The documentation-only validator class has been replaced with the protocol, and docs are now driven via autoclass on the protocol. Importantly, several documented methods of the class have been removed, as they were marked deprecated under jsonschema v3.0 and are no longer provided by the builtin validators. Minor adjustments to the docs are made to repoint references at the new class definition.
-rw-r--r--docs/creating.rst8
-rw-r--r--docs/errors.rst2
-rw-r--r--docs/faq.rst4
-rw-r--r--docs/validate.rst142
-rw-r--r--jsonschema/protocols.py152
-rw-r--r--jsonschema/validators.py9
-rw-r--r--setup.cfg1
7 files changed, 173 insertions, 145 deletions
diff --git a/docs/creating.rst b/docs/creating.rst
index a9f18d1..468e81d 100644
--- a/docs/creating.rst
+++ b/docs/creating.rst
@@ -23,3 +23,11 @@ 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, ``jsonschema.protocols.IValidator`` 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..ceb5607 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.IValidator.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..dbe0ef3 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.IValidator`
+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/validate.rst b/docs/validate.rst
index 3d8134d..d6b901f 100644
--- a/docs/validate.rst
+++ b/docs/validate.rst
@@ -19,150 +19,16 @@ 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
-----------------------
-`jsonschema` defines an (informal) interface that all validator
+`jsonschema` defines an interface 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.IValidator
+ :members:
All of the `versioned validators <versioned-validators>` that are included with
`jsonschema` adhere to the interface, and implementers of validator classes
diff --git a/jsonschema/protocols.py b/jsonschema/protocols.py
new file mode 100644
index 0000000..c36e844
--- /dev/null
+++ b/jsonschema/protocols.py
@@ -0,0 +1,152 @@
+"""
+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 ._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 IValidator(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
+ `IValidator.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) -> "IValidator":
+ """
+ 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/validators.py b/jsonschema/validators.py
index 4ab101c..fe64764 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.IValidator` class
"""
@attr.s
@@ -284,7 +284,7 @@ def extend(validator, validators=(), version=None, type_checker=None):
Arguments:
- validator (jsonschema.IValidator):
+ validator (jsonschema.protocols.IValidator):
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.IValidator` will be carried along.
Returns:
- a new `jsonschema.IValidator` class extending the one provided
+ a new `jsonschema.protocols.IValidator` class extending the one
+ provided
.. note:: Meta Schemas
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 =