diff options
author | Julian Berman <Julian@GrayVines.com> | 2021-08-25 18:34:29 +0100 |
---|---|---|
committer | Julian Berman <Julian@GrayVines.com> | 2021-08-25 18:34:29 +0100 |
commit | 996437f0693adf66ac6d24df7770057fc0fd9b15 (patch) | |
tree | d07f9d80d67799ffa85b62f7f0e07398ef94417e | |
parent | 452169710f20a38372bdd686f79680df1215d188 (diff) | |
download | jsonschema-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.rst | 12 | ||||
-rw-r--r-- | jsonschema/_legacy_validators.py | 7 | ||||
-rw-r--r-- | jsonschema/_utils.py | 22 | ||||
-rw-r--r-- | jsonschema/_validators.py | 11 | ||||
-rw-r--r-- | jsonschema/tests/test_deprecations.py | 36 | ||||
-rw-r--r-- | jsonschema/validators.py | 31 |
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: |