Skip to content

Commit

Permalink
Newer attrs API in validators.py and some type hints for create.
Browse files Browse the repository at this point in the history
  • Loading branch information
Julian committed Mar 15, 2023
1 parent 8bdec06 commit 69fc7b4
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 29 deletions.
28 changes: 28 additions & 0 deletions jsonschema/_typing.py
@@ -0,0 +1,28 @@
"""
Some (initially private) typing helpers for jsonschema's types.
"""
from typing import Any, Callable, Iterable, Protocol, Tuple, Union

import referencing.jsonschema

from jsonschema.protocols import Validator


class SchemaKeywordValidator(Protocol):
def __call__(
self,
validator: Validator,
value: Any,
instance: Any,
schema: referencing.jsonschema.Schema,
) -> None:
...


id_of = Callable[[referencing.jsonschema.Schema], Union[str, None]]


ApplicableValidators = Callable[
[referencing.jsonschema.Schema],
Iterable[Tuple[str, Any]],
]
5 changes: 3 additions & 2 deletions jsonschema/protocols.py
Expand Up @@ -7,7 +7,7 @@

from __future__ import annotations

from collections.abc import Callable, Mapping
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, ClassVar, Iterable
import sys

Expand All @@ -28,6 +28,7 @@
# therefore, only import at type-checking time (to avoid circular references),
# but use `jsonschema` for any types which will otherwise not be resolvable
if TYPE_CHECKING:
from jsonschema import _typing
import jsonschema
import jsonschema.validators
import referencing.jsonschema
Expand Down Expand Up @@ -110,7 +111,7 @@ class Validator(Protocol):
FORMAT_CHECKER: ClassVar[jsonschema.FormatChecker]

#: A function which given a schema returns its ID.
ID_OF: Callable[[Any], str | None]
ID_OF: _typing.id_of

#: The schema that will be used to validate instances
schema: Mapping | bool
Expand Down
65 changes: 38 additions & 27 deletions jsonschema/validators.py
Expand Up @@ -4,7 +4,7 @@
from __future__ import annotations

from collections import deque
from collections.abc import Mapping, Sequence
from collections.abc import Iterable, Mapping, Sequence
from functools import lru_cache
from operator import methodcaller
from urllib.parse import unquote, urldefrag, urljoin, urlsplit
Expand All @@ -15,16 +15,17 @@
import reprlib
import warnings

from attrs import define, field, fields
from jsonschema_specifications import REGISTRY as SPECIFICATIONS
from referencing import Specification
from rpds import HashTrieMap
import attr
import referencing.jsonschema

from jsonschema import (
_format,
_legacy_validators,
_types,
_typing,
_utils,
_validators,
exceptions,
Expand Down Expand Up @@ -102,24 +103,29 @@ def _validates(cls):


def create(
meta_schema,
validators=(),
version=None,
type_checker=_types.draft202012_type_checker,
format_checker=_format.draft202012_format_checker,
id_of=referencing.jsonschema.DRAFT202012.id_of,
applicable_validators=methodcaller("items"),
meta_schema: referencing.jsonschema.ObjectSchema,
validators: (
Mapping[str, _typing.SchemaKeywordValidator]
| Iterable[tuple[str, _typing.SchemaKeywordValidator]]
) = (),
version: str | None = None,
type_checker: _types.TypeChecker = _types.draft202012_type_checker,
format_checker: _format.FormatChecker = _format.draft202012_format_checker,
id_of: _typing.id_of = referencing.jsonschema.DRAFT202012.id_of,
applicable_validators: _typing.ApplicableValidators = methodcaller(
"items",
),
):
"""
Create a new validator class.
Arguments:
meta_schema (collections.abc.Mapping):
meta_schema:
the meta schema for the new validator class
validators (collections.abc.Mapping):
validators:
a mapping from names to callables, where each callable will
validate the schema property with the given name.
Expand All @@ -132,37 +138,42 @@ def create(
3. the instance
4. the schema
version (str):
version:
an identifier for the version that this validator class will
validate. If provided, the returned validator class will
have its ``__name__`` set to include the version, and also
will have `jsonschema.validators.validates` automatically
called for the given version.
type_checker (jsonschema.TypeChecker):
type_checker:
a type checker, used when applying the :kw:`type` keyword.
If unprovided, a `jsonschema.TypeChecker` will be created
with a set of default types typical of JSON Schema drafts.
format_checker (jsonschema.FormatChecker):
format_checker:
a format checker, used when applying the :kw:`format` keyword.
If unprovided, a `jsonschema.FormatChecker` will be created
with a set of default formats typical of JSON Schema drafts.
id_of (collections.abc.Callable):
id_of:
A function that given a schema, returns its ID.
applicable_validators (collections.abc.Callable):
applicable_validators:
A function that given a schema, returns the list of
applicable validators (validation keywords and callables)
A function that, given a schema, returns the list of
applicable schema keywords and associated values
which will be used to validate the instance.
This is mostly used to support pre-draft 7 versions of JSON Schema
which specified behavior around ignoring keywords if they were
siblings of a ``$ref`` keyword. If you're not attempting to
implement similar behavior, you can typically ignore this argument
and leave it at its default.
Returns:
Expand All @@ -176,7 +187,7 @@ def create(
default=Specification.OPAQUE,
)

@attr.s
@define
class Validator:

VALIDATORS = dict(validators)
Expand All @@ -185,17 +196,17 @@ class Validator:
FORMAT_CHECKER = format_checker_arg
ID_OF = staticmethod(id_of)

schema = attr.ib(repr=reprlib.repr)
_ref_resolver = attr.ib(default=None, repr=False, alias="resolver")
format_checker = attr.ib(default=None)
schema: referencing.jsonschema.Schema = field(repr=reprlib.repr)
_ref_resolver = field(default=None, repr=False, alias="resolver")
format_checker: _format.FormatChecker | None = field(default=None)
# TODO: include new meta-schemas added at runtime
_registry = attr.ib(
_registry: referencing.jsonschema.SchemaRegistry = field(
default=SPECIFICATIONS,
converter=SPECIFICATIONS.combine, # type: ignore[misc]
kw_only=True,
repr=False,
)
_resolver = attr.ib(
_resolver = field(
alias="_resolver",
default=None,
kw_only=True,
Expand All @@ -222,7 +233,7 @@ def evolve(self, **changes):
schema = changes.setdefault("schema", self.schema)
NewValidator = validator_for(schema, default=cls)

for field in attr.fields(cls):
for field in fields(cls): # noqa: F402
if not field.init:
continue
attr_name = field.name
Expand Down Expand Up @@ -429,14 +440,14 @@ def is_valid(self, instance, _schema=None):

evolve_fields = [
(field.name, field.alias)
for field in attr.fields(Validator)
for field in fields(Validator)
if field.init
]

if version is not None:
safe = version.title().replace(" ", "").replace("-", "")
Validator.__name__ = Validator.__qualname__ = f"{safe}Validator"
Validator = validates(version)(Validator)
Validator = validates(version)(Validator) # type: ignore[misc]

return Validator

Expand Down

0 comments on commit 69fc7b4

Please sign in to comment.