Skip to content

Commit 7514426

Browse files
committed
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.
1 parent 0cb3a4c commit 7514426

File tree

7 files changed

+173
-145
lines changed

7 files changed

+173
-145
lines changed

docs/creating.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,11 @@ Any validating function that validates against a subschema should call
2323
instance, or schema, it should pass one or both of the ``path`` or
2424
``schema_path`` arguments to ``descend`` in order to properly maintain
2525
where in the instance or schema respectively the error occurred.
26+
27+
The Validator Protocol
28+
----------------------
29+
30+
``jsonschema`` defines a protocol, ``jsonschema.protocols.IValidator`` which
31+
can be used in type annotations to describe the type of a validator object.
32+
33+
For full details, see `validator-protocol`.

docs/errors.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ error objects.
275275
As you can see, `jsonschema.exceptions.ErrorTree` takes an
276276
iterable of `ValidationError`\s when constructing a tree so
277277
you can directly pass it the return value of a validator object's
278-
`jsonschema.IValidator.iter_errors` method.
278+
`jsonschema.protocols.IValidator.iter_errors` method.
279279

280280
`ErrorTree`\s support a number of useful operations. The first one we
281281
might want to perform is to check whether a given element in our instance

docs/faq.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ but fail the second!
9090

9191
Still, filling in defaults is a thing that is useful. `jsonschema`
9292
allows you to `define your own validator classes and callables
93-
<creating>`, so you can easily create an `jsonschema.IValidator` that
94-
does do default setting. Here's some code to get you started. (In
93+
<creating>`, so you can easily create an `jsonschema.protocols.IValidator`
94+
that does do default setting. Here's some code to get you started. (In
9595
this code, we add the default properties to each object *before* the
9696
properties are validated, so the default values themselves will need to
9797
be valid under the schema.)

docs/validate.rst

Lines changed: 4 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -19,150 +19,16 @@ The simplest way to validate an instance under a given schema is to use the
1919
fundamentals underway at `Understanding JSON Schema
2020
<https://json-schema.org/understanding-json-schema/>`_
2121
22+
.. _validator-protocol:
2223

2324
The Validator Interface
2425
-----------------------
2526

26-
`jsonschema` defines an (informal) interface that all validator
27+
`jsonschema` defines an interface that all validator
2728
classes should adhere to.
2829

29-
.. class:: IValidator(schema, types=(), resolver=None, format_checker=None)
30-
31-
:argument dict schema: the schema that the validator object
32-
will validate with. It is assumed to be valid, and providing
33-
an invalid schema can lead to undefined behavior. See
34-
`IValidator.check_schema` to validate a schema first.
35-
:argument resolver: an instance of `RefResolver` that will be
36-
used to resolve :validator:`$ref` properties (JSON references). If
37-
unprovided, one will be created.
38-
:argument format_checker: an instance of `FormatChecker`
39-
whose `FormatChecker.conforms` method will be called to
40-
check and see if instances conform to each :validator:`format`
41-
property present in the schema. If unprovided, no validation
42-
will be done for :validator:`format`. Certain formats require
43-
additional packages to be installed (ipv5, uri, color, date-time).
44-
The required packages can be found at the bottom of this page.
45-
:argument types:
46-
.. deprecated:: 3.0.0
47-
48-
Use `TypeChecker.redefine` and
49-
`jsonschema.validators.extend` instead of this argument.
50-
51-
See `validating-types` for details.
52-
53-
If used, this overrides or extends the list of known types when
54-
validating the :validator:`type` property.
55-
56-
What is provided should map strings (type names) to class objects
57-
that will be checked via `isinstance`.
58-
59-
60-
.. attribute:: META_SCHEMA
61-
62-
An object representing the validator's meta schema (the schema that
63-
describes valid schemas in the given version).
64-
65-
.. attribute:: VALIDATORS
66-
67-
A mapping of validator names (`str`\s) to functions
68-
that validate the validator property with that name. For more
69-
information see `creating-validators`.
70-
71-
.. attribute:: TYPE_CHECKER
72-
73-
A `TypeChecker` that will be used when validating :validator:`type`
74-
properties in JSON schemas.
75-
76-
.. attribute:: schema
77-
78-
The schema that was passed in when initializing the object.
79-
80-
.. attribute:: DEFAULT_TYPES
81-
82-
.. deprecated:: 3.0.0
83-
84-
Use of this attribute is deprecated in favor of the new `type
85-
checkers <TypeChecker>`.
86-
87-
See `validating-types` for details.
88-
89-
For backwards compatibility on existing validator classes, a mapping of
90-
JSON types to Python class objects which define the Python types for
91-
each JSON type.
92-
93-
Any existing code using this attribute should likely transition to
94-
using `TypeChecker.is_type`.
95-
96-
97-
.. classmethod:: check_schema(schema)
98-
99-
Validate the given schema against the validator's `META_SCHEMA`.
100-
101-
:raises: `jsonschema.exceptions.SchemaError` if the schema
102-
is invalid
103-
104-
.. method:: is_type(instance, type)
105-
106-
Check if the instance is of the given (JSON Schema) type.
107-
108-
:type type: str
109-
:rtype: bool
110-
:raises: `jsonschema.exceptions.UnknownType` if ``type``
111-
is not a known type.
112-
113-
.. method:: is_valid(instance)
114-
115-
Check if the instance is valid under the current `schema`.
116-
117-
:rtype: bool
118-
119-
>>> schema = {"maxItems" : 2}
120-
>>> Draft3Validator(schema).is_valid([2, 3, 4])
121-
False
122-
123-
.. method:: iter_errors(instance)
124-
125-
Lazily yield each of the validation errors in the given instance.
126-
127-
:rtype: an `collections.abc.Iterable` of
128-
`jsonschema.exceptions.ValidationError`\s
129-
130-
>>> schema = {
131-
... "type" : "array",
132-
... "items" : {"enum" : [1, 2, 3]},
133-
... "maxItems" : 2,
134-
... }
135-
>>> v = Draft3Validator(schema)
136-
>>> for error in sorted(v.iter_errors([2, 3, 4]), key=str):
137-
... print(error.message)
138-
4 is not one of [1, 2, 3]
139-
[2, 3, 4] is too long
140-
141-
.. method:: validate(instance)
142-
143-
Check if the instance is valid under the current `schema`.
144-
145-
:raises: `jsonschema.exceptions.ValidationError` if the
146-
instance is invalid
147-
148-
>>> schema = {"maxItems" : 2}
149-
>>> Draft3Validator(schema).validate([2, 3, 4])
150-
Traceback (most recent call last):
151-
...
152-
ValidationError: [2, 3, 4] is too long
153-
154-
.. method:: evolve(**kwargs)
155-
156-
Create a new validator like this one, but with given changes.
157-
158-
Preserves all other attributes, so can be used to e.g. create a
159-
validator with a different schema but with the same :validator:`$ref`
160-
resolution behavior.
161-
162-
>>> validator = Draft202012Validator({})
163-
>>> validator.evolve(schema={"type": "number"})
164-
Draft202012Validator(schema={'type': 'number'}, format_checker=None)
165-
30+
.. autoclass:: jsonschema.protocols.IValidator
31+
:members:
16632

16733
All of the `versioned validators <versioned-validators>` that are included with
16834
`jsonschema` adhere to the interface, and implementers of validator classes

jsonschema/protocols.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""
2+
typing.Protocol classes for jsonschema interfaces.
3+
"""
4+
5+
# for reference material on Protocols, see
6+
# https://www.python.org/dev/peps/pep-0544/
7+
8+
from typing import Any, ClassVar, Iterator, Optional
9+
10+
try:
11+
from typing import Protocol, runtime_checkable
12+
except ImportError:
13+
from typing_extensions import Protocol, runtime_checkable
14+
15+
from ._format import FormatChecker
16+
from ._types import TypeChecker
17+
from .exceptions import ValidationError
18+
from .validators import RefResolver
19+
20+
# For code authors working on the validator protocol, these are the three
21+
# use-cases which should be kept in mind:
22+
#
23+
# 1. As a protocol class, it can be used in type annotations to describe the
24+
# available methods and attributes of a validator
25+
# 2. It is the source of autodoc for the validator documentation
26+
# 3. It is runtime_checkable, meaning that it can be used in isinstance()
27+
# checks.
28+
#
29+
# Since protocols are not base classes, isinstance() checking is limited in
30+
# its capabilities. See docs on runtime_checkable for detail
31+
32+
33+
@runtime_checkable
34+
class IValidator(Protocol):
35+
"""
36+
The protocol to which all validator classes should adhere.
37+
38+
:argument dict schema: the schema that the validator object
39+
will validate with. It is assumed to be valid, and providing
40+
an invalid schema can lead to undefined behavior. See
41+
`IValidator.check_schema` to validate a schema first.
42+
:argument resolver: an instance of `jsonschema.RefResolver` that will be
43+
used to resolve :validator:`$ref` properties (JSON references). If
44+
unprovided, one will be created.
45+
:argument format_checker: an instance of `jsonschema.FormatChecker`
46+
whose `jsonschema.FormatChecker.conforms` method will be called to
47+
check and see if instances conform to each :validator:`format`
48+
property present in the schema. If unprovided, no validation
49+
will be done for :validator:`format`. Certain formats require
50+
additional packages to be installed (ipv5, uri, color, date-time).
51+
The required packages can be found at the bottom of this page.
52+
"""
53+
54+
#: An object representing the validator's meta schema (the schema that
55+
#: describes valid schemas in the given version).
56+
META_SCHEMA: ClassVar[dict]
57+
58+
#: A mapping of validator names (`str`\s) to functions
59+
#: that validate the validator property with that name. For more
60+
#: information see `creating-validators`.
61+
VALIDATORS: ClassVar[dict]
62+
63+
#: A `jsonschema.TypeChecker` that will be used when validating
64+
#: :validator:`type` properties in JSON schemas.
65+
TYPE_CHECKER: ClassVar[TypeChecker]
66+
67+
#: The schema that was passed in when initializing the object.
68+
schema: dict
69+
70+
def __init__(
71+
self,
72+
schema: dict,
73+
resolver: Optional[RefResolver] = None,
74+
format_checker: Optional[FormatChecker] = None,
75+
) -> None:
76+
...
77+
78+
@classmethod
79+
def check_schema(cls, schema: dict) -> None:
80+
"""
81+
Validate the given schema against the validator's `META_SCHEMA`.
82+
83+
:raises: `jsonschema.exceptions.SchemaError` if the schema
84+
is invalid
85+
"""
86+
87+
def is_type(self, instance: Any, type: str) -> bool:
88+
"""
89+
Check if the instance is of the given (JSON Schema) type.
90+
91+
:type type: str
92+
:rtype: bool
93+
:raises: `jsonschema.exceptions.UnknownType` if ``type``
94+
is not a known type.
95+
"""
96+
97+
def is_valid(self, instance: dict) -> bool:
98+
"""
99+
Check if the instance is valid under the current `schema`.
100+
101+
:rtype: bool
102+
103+
>>> schema = {"maxItems" : 2}
104+
>>> Draft3Validator(schema).is_valid([2, 3, 4])
105+
False
106+
"""
107+
108+
def iter_errors(self, instance: dict) -> Iterator[ValidationError]:
109+
r"""
110+
Lazily yield each of the validation errors in the given instance.
111+
112+
:rtype: an `collections.abc.Iterable` of
113+
`jsonschema.exceptions.ValidationError`\s
114+
115+
>>> schema = {
116+
... "type" : "array",
117+
... "items" : {"enum" : [1, 2, 3]},
118+
... "maxItems" : 2,
119+
... }
120+
>>> v = Draft3Validator(schema)
121+
>>> for error in sorted(v.iter_errors([2, 3, 4]), key=str):
122+
... print(error.message)
123+
4 is not one of [1, 2, 3]
124+
[2, 3, 4] is too long
125+
"""
126+
127+
def validate(self, instance: dict) -> None:
128+
"""
129+
Check if the instance is valid under the current `schema`.
130+
131+
:raises: `jsonschema.exceptions.ValidationError` if the
132+
instance is invalid
133+
134+
>>> schema = {"maxItems" : 2}
135+
>>> Draft3Validator(schema).validate([2, 3, 4])
136+
Traceback (most recent call last):
137+
...
138+
ValidationError: [2, 3, 4] is too long
139+
"""
140+
141+
def evolve(self, **kwargs) -> "IValidator":
142+
"""
143+
Create a new validator like this one, but with given changes.
144+
145+
Preserves all other attributes, so can be used to e.g. create a
146+
validator with a different schema but with the same :validator:`$ref`
147+
resolution behavior.
148+
149+
>>> validator = Draft202012Validator({})
150+
>>> validator.evolve(schema={"type": "number"})
151+
Draft202012Validator(schema={'type': 'number'}, format_checker=None)
152+
"""

jsonschema/validators.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def create(
155155
156156
Returns:
157157
158-
a new `jsonschema.IValidator` class
158+
a new `jsonschema.protocols.IValidator` class
159159
"""
160160

161161
@attr.s
@@ -284,7 +284,7 @@ def extend(validator, validators=(), version=None, type_checker=None):
284284
285285
Arguments:
286286
287-
validator (jsonschema.IValidator):
287+
validator (jsonschema.protocols.IValidator):
288288
289289
an existing validator class
290290
@@ -314,11 +314,12 @@ def extend(validator, validators=(), version=None, type_checker=None):
314314
a type checker, used when applying the :validator:`type` validator.
315315
316316
If unprovided, the type checker of the extended
317-
`jsonschema.IValidator` will be carried along.
317+
`jsonschema.protocols.IValidator` will be carried along.
318318
319319
Returns:
320320
321-
a new `jsonschema.IValidator` class extending the one provided
321+
a new `jsonschema.protocols.IValidator` class extending the one
322+
provided
322323
323324
.. note:: Meta Schemas
324325

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ install_requires =
3232
importlib_metadata;python_version<'3.8'
3333
importlib_resources>=1.4.0;python_version<'3.9'
3434
pyrsistent>=0.14.0,!=0.17.0,!=0.17.1,!=0.17.2
35+
typing_extensions;python_version<'3.8'
3536

3637
[options.extras_require]
3738
format =

0 commit comments

Comments
 (0)