summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2023-03-06 13:18:27 +0200
committerJulian Berman <Julian@GrayVines.com>2023-03-06 14:40:26 +0200
commit94c60e4170b68811aeac0bbc6bc9442d29f073eb (patch)
tree8844b49a967643a2707b0547dc7b0d835d2301ef
parent2b547c7085ed5171a9319837c275b87e9aa3bde8 (diff)
downloadjsonschema-94c60e4170b68811aeac0bbc6bc9442d29f073eb.tar.gz
Speed up Validator.evolve by pre-computing fields.
We're not a general class, so we know what fields we need ahead of time. This seems to give ~15% speedup on Validator evolution, which happens often as part of walking up and down schemas.
-rw-r--r--jsonschema/validators.py78
1 files changed, 61 insertions, 17 deletions
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
index 3182d13..62e86df 100644
--- a/jsonschema/validators.py
+++ b/jsonschema/validators.py
@@ -217,6 +217,23 @@ 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 = self._registry.resolver_with_root(
@@ -257,18 +274,10 @@ def create(
return self._ref_resolver
def evolve(self, **changes):
- # Essentially reproduces attr.evolve, but may involve instantiating
- # a different class than this one.
- cls = self.__class__
-
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 = field.alias
+ for (attr_name, init_name) in evolve_fields:
if init_name not in changes:
changes[init_name] = getattr(self, attr_name)
@@ -328,17 +337,46 @@ def create(
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
+
if resolver is None:
resolver = self._resolver.in_subresource(
specification.create_resource(schema),
)
- validator = self.evolve(schema=schema, _resolver=resolver)
- for error in validator.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
+ 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):
@@ -389,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"