summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2021-08-25 18:34:29 +0100
committerJulian Berman <Julian@GrayVines.com>2021-08-25 18:34:29 +0100
commit996437f0693adf66ac6d24df7770057fc0fd9b15 (patch)
treed07f9d80d67799ffa85b62f7f0e07398ef94417e
parent452169710f20a38372bdd686f79680df1215d188 (diff)
downloadjsonschema-996437f0693adf66ac6d24df7770057fc0fd9b15.tar.gz
Add Validator.evolve, deprecating passing _schema to methods.
A Validator should be thought of as encapsulating validation with a single fixed schema. Previously, iter_errors and is_valid allowed passing a second argument, which was a different schema to use for one method call. This was mostly for convenience, since the second argument is often used during sub-validation whilst say, recursing. The correct way to do so now is to say: validator.evolve(schema=new_schema).iter_errors(...) validator.evolve(schema=new_schema).is_valid(...) instead, which is essentially equally convenient. Closes: #522
-rw-r--r--docs/validate.rst12
-rw-r--r--jsonschema/_legacy_validators.py7
-rw-r--r--jsonschema/_utils.py22
-rw-r--r--jsonschema/_validators.py11
-rw-r--r--jsonschema/tests/test_deprecations.py36
-rw-r--r--jsonschema/validators.py31
6 files changed, 99 insertions, 20 deletions
diff --git a/docs/validate.rst b/docs/validate.rst
index 1733b2f..c8bbaf4 100644
--- a/docs/validate.rst
+++ b/docs/validate.rst
@@ -151,6 +151,18 @@ classes should adhere to.
...
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)
+
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/_legacy_validators.py b/jsonschema/_legacy_validators.py
index c0bab32..c8eff2c 100644
--- a/jsonschema/_legacy_validators.py
+++ b/jsonschema/_legacy_validators.py
@@ -72,7 +72,7 @@ def dependencies_draft4_draft6_draft7(
def disallow_draft3(validator, disallow, instance, schema):
for disallowed in _utils.ensure_list(disallow):
- if validator.is_valid(instance, {"type": [disallowed]}):
+ if validator.evolve(schema={"type": [disallowed]}).is_valid(instance):
message = f"{disallowed!r} is disallowed for {instance!r}"
yield ValidationError(message)
@@ -200,7 +200,10 @@ def contains_draft6_draft7(validator, contains, instance, schema):
if not validator.is_type(instance, "array"):
return
- if not any(validator.is_valid(element, contains) for element in instance):
+ if not any(
+ validator.evolve(schema=contains).is_valid(element)
+ for element in instance
+ ):
yield ValidationError(
f"None of {instance!r} are valid under the given schema",
)
diff --git a/jsonschema/_utils.py b/jsonschema/_utils.py
index f3603c5..3125f1e 100644
--- a/jsonschema/_utils.py
+++ b/jsonschema/_utils.py
@@ -240,7 +240,7 @@ def find_evaluated_item_indexes_by_schema(validator, instance, schema):
evaluated_indexes += list(range(0, len(schema["prefixItems"])))
if "if" in schema:
- if validator.is_valid(instance, schema["if"]):
+ if validator.evolve(schema=schema["if"]).is_valid(instance):
evaluated_indexes += find_evaluated_item_indexes_by_schema(
validator, instance, schema["if"],
)
@@ -257,7 +257,7 @@ def find_evaluated_item_indexes_by_schema(validator, instance, schema):
for keyword in ["contains", "unevaluatedItems"]:
if keyword in schema:
for k, v in enumerate(instance):
- if validator.is_valid(v, schema[keyword]):
+ if validator.evolve(schema=schema[keyword]).is_valid(v):
evaluated_indexes.append(k)
for keyword in ["allOf", "oneOf", "anyOf"]:
@@ -301,22 +301,24 @@ def find_evaluated_property_keys_by_schema(validator, instance, schema):
if keyword in schema:
if validator.is_type(schema[keyword], "boolean"):
for property, value in instance.items():
- if validator.is_valid({property: value}, schema[keyword]):
+ if validator.evolve(schema=schema[keyword]).is_valid(
+ {property: value},
+ ):
evaluated_keys.append(property)
if validator.is_type(schema[keyword], "object"):
for property, subschema in schema[keyword].items():
- if property in instance and validator.is_valid(
- instance[property], subschema,
- ):
+ if property in instance and validator.evolve(
+ schema=subschema,
+ ).is_valid(instance[property]):
evaluated_keys.append(property)
if "patternProperties" in schema:
for property, value in instance.items():
for pattern, _ in schema["patternProperties"].items():
- if re.search(pattern, property) and validator.is_valid(
- {property: value}, schema["patternProperties"],
- ):
+ if re.search(pattern, property) and validator.evolve(
+ schema=schema["patternProperties"],
+ ).is_valid({property: value}):
evaluated_keys.append(property)
if "dependentSchemas" in schema:
@@ -337,7 +339,7 @@ def find_evaluated_property_keys_by_schema(validator, instance, schema):
)
if "if" in schema:
- if validator.is_valid(instance, schema["if"]):
+ if validator.evolve(schema=schema["if"]).is_valid(instance):
evaluated_keys += find_evaluated_property_keys_by_schema(
validator, instance, schema["if"],
)
diff --git a/jsonschema/_validators.py b/jsonschema/_validators.py
index dec6b21..e0845ea 100644
--- a/jsonschema/_validators.py
+++ b/jsonschema/_validators.py
@@ -112,7 +112,7 @@ def contains(validator, contains, instance, schema):
max_contains = schema.get("maxContains", len(instance))
for each in instance:
- if validator.is_valid(each, contains):
+ if validator.evolve(schema=contains).is_valid(each):
matches += 1
if matches > max_contains:
yield ValidationError(
@@ -388,7 +388,10 @@ def oneOf(validator, oneOf, instance, schema):
context=all_errors,
)
- more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)]
+ more_valid = [
+ each for _, each in subschemas
+ if validator.evolve(schema=each).is_valid(instance)
+ ]
if more_valid:
more_valid.append(first_valid)
reprs = ", ".join(repr(schema) for schema in more_valid)
@@ -396,13 +399,13 @@ def oneOf(validator, oneOf, instance, schema):
def not_(validator, not_schema, instance, schema):
- if validator.is_valid(instance, not_schema):
+ if validator.evolve(schema=not_schema).is_valid(instance):
message = f"{instance!r} should not be valid under {not_schema!r}"
yield ValidationError(message)
def if_(validator, if_schema, instance, schema):
- if validator.is_valid(instance, if_schema):
+ if validator.evolve(schema=if_schema).is_valid(instance):
if "then" in schema:
then = schema["then"]
yield from validator.descend(instance, then, schema_path="then")
diff --git a/jsonschema/tests/test_deprecations.py b/jsonschema/tests/test_deprecations.py
index 0ddc375..c1f4c21 100644
--- a/jsonschema/tests/test_deprecations.py
+++ b/jsonschema/tests/test_deprecations.py
@@ -1,6 +1,6 @@
from unittest import TestCase
-from jsonschema.validators import RefResolver
+from jsonschema.validators import Draft7Validator, RefResolver
class TestDeprecations(TestCase):
@@ -48,3 +48,37 @@ class TestDeprecations(TestCase):
"jsonschema.RefResolver.in_scope is deprecated ",
),
)
+
+ def test_Validator_is_valid_two_arguments(self):
+ """
+ As of v4.0.0, calling is_valid with two arguments (to provide a
+ different schema) is deprecated.
+ """
+
+ validator = Draft7Validator({})
+ with self.assertWarns(DeprecationWarning) as w:
+ result = validator.is_valid("foo", {"type": "number"})
+
+ self.assertFalse(result)
+ self.assertTrue(
+ str(w.warning).startswith(
+ "Passing a schema to Validator.is_valid is deprecated ",
+ ),
+ )
+
+ def test_Validator_iter_errors_two_arguments(self):
+ """
+ As of v4.0.0, calling iter_errors with two arguments (to provide a
+ different schema) is deprecated.
+ """
+
+ validator = Draft7Validator({})
+ with self.assertWarns(DeprecationWarning) as w:
+ error, = validator.iter_errors("foo", {"type": "number"})
+
+ self.assertEqual(error.validator, "type")
+ self.assertTrue(
+ str(w.warning).startswith(
+ "Passing a schema to Validator.iter_errors is deprecated ",
+ ),
+ )
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
index 9338fb7..7a5424e 100644
--- a/jsonschema/validators.py
+++ b/jsonschema/validators.py
@@ -172,8 +172,21 @@ def create(
for error in cls(cls.META_SCHEMA).iter_errors(schema):
raise exceptions.SchemaError.create_from(error)
+ def evolve(self, **kwargs):
+ return attr.evolve(self, **kwargs)
+
def iter_errors(self, instance, _schema=None):
- if _schema is None:
+ if _schema is not None:
+ warnings.warn(
+ (
+ "Passing a schema to Validator.iter_errors "
+ "is deprecated and will be removed in a future "
+ "release. Call validator.evolve(schema=new_schema)."
+ "iter_errors(...) instead."
+ ),
+ DeprecationWarning,
+ )
+ else:
_schema = self.schema
if _schema is True:
@@ -214,7 +227,7 @@ def create(
self.resolver.pop_scope()
def descend(self, instance, schema, path=None, schema_path=None):
- for error in self.iter_errors(instance, schema):
+ for error in self.evolve(schema=schema).iter_errors(instance):
if path is not None:
error.path.appendleft(path)
if schema_path is not None:
@@ -232,7 +245,19 @@ def create(
raise exceptions.UnknownType(type, instance, self.schema)
def is_valid(self, instance, _schema=None):
- error = next(self.iter_errors(instance, _schema), None)
+ if _schema is not None:
+ warnings.warn(
+ (
+ "Passing a schema to Validator.is_valid is deprecated "
+ "and will be removed in a future release. Call "
+ "validator.evolve(schema=new_schema).is_valid(...) "
+ "instead."
+ ),
+ DeprecationWarning,
+ )
+ self = self.evolve(schema=_schema)
+
+ error = next(self.iter_errors(instance), None)
return error is None
if version is not None: