From eb633b216bad1acb1a7739279c5ab83bdd63d7a1 Mon Sep 17 00:00:00 2001 From: Julian Berman Date: Wed, 18 Aug 2021 16:53:02 +0100 Subject: Tidy up validation error messages, particularly using f-strings. --- jsonschema/_format.py | 4 +- jsonschema/_legacy_validators.py | 31 +++-- jsonschema/_utils.py | 4 +- jsonschema/_validators.py | 112 ++++++++--------- jsonschema/exceptions.py | 57 +++++---- jsonschema/tests/_suite.py | 2 +- jsonschema/tests/test_exceptions.py | 18 ++- jsonschema/tests/test_types.py | 5 +- jsonschema/tests/test_validators.py | 235 ++++++++++++++++++++++++++++++------ jsonschema/validators.py | 4 +- 10 files changed, 320 insertions(+), 152 deletions(-) diff --git a/jsonschema/_format.py b/jsonschema/_format.py index 1395a59..d99c717 100644 --- a/jsonschema/_format.py +++ b/jsonschema/_format.py @@ -97,9 +97,7 @@ class FormatChecker(object): except raises as e: cause = e if not result: - raise FormatError( - "%r is not a %r" % (instance, format), cause=cause, - ) + raise FormatError(f"{instance!r} is not a {format!r}", cause=cause) def conforms(self, instance, format): """ diff --git a/jsonschema/_legacy_validators.py b/jsonschema/_legacy_validators.py index a3b3094..3a8b499 100644 --- a/jsonschema/_legacy_validators.py +++ b/jsonschema/_legacy_validators.py @@ -32,13 +32,13 @@ def dependencies_draft3(validator, dependencies, instance, schema): yield error elif validator.is_type(dependency, "string"): if dependency not in instance: - message = "%r is a dependency of %r" - yield ValidationError(message % (dependency, property)) + message = f"{dependency!r} is a dependency of {property!r}" + yield ValidationError(message) else: for each in dependency: if each not in instance: - message = "%r is a dependency of %r" - yield ValidationError(message % (each, property)) + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) def dependencies_draft4_draft6_draft7( @@ -63,8 +63,8 @@ def dependencies_draft4_draft6_draft7( if validator.is_type(dependency, "array"): for each in dependency: if each not in instance: - message = "%r is a dependency of %r" - yield ValidationError(message % (each, property)) + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) else: for error in validator.descend( instance, dependency, schema_path=property, @@ -75,9 +75,8 @@ 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]}): - yield ValidationError( - "%r is disallowed for %r" % (disallowed, instance), - ) + message = f"{disallowed!r} is disallowed for {instance!r}" + yield ValidationError(message) def extends_draft3(validator, extends, instance, schema): @@ -134,9 +133,8 @@ def minimum_draft3_draft4(validator, minimum, instance, schema): cmp = "less than" if failed: - yield ValidationError( - "%r is %s the minimum of %r" % (instance, cmp, minimum), - ) + message = f"{instance!r} is {cmp} the minimum of {minimum!r}" + yield ValidationError(message) def maximum_draft3_draft4(validator, maximum, instance, schema): @@ -151,9 +149,8 @@ def maximum_draft3_draft4(validator, maximum, instance, schema): cmp = "greater than" if failed: - yield ValidationError( - "%r is %s the maximum of %r" % (instance, cmp, maximum), - ) + message = f"{instance!r} is {cmp} the maximum of {maximum!r}" + yield ValidationError(message) def properties_draft3(validator, properties, instance, schema): @@ -170,7 +167,7 @@ def properties_draft3(validator, properties, instance, schema): ): yield error elif subschema.get("required", False): - error = ValidationError("%r is a required property" % property) + error = ValidationError(f"{property!r} is a required property") error._set( validator="required", validator_value=subschema["required"], @@ -207,7 +204,7 @@ def contains_draft6_draft7(validator, contains, instance, schema): if not any(validator.is_valid(element, contains) for element in instance): yield ValidationError( - "None of %r are valid under the given schema" % (instance,), + f"None of {instance!r} are valid under the given schema", ) diff --git a/jsonschema/_utils.py b/jsonschema/_utils.py index 9142cc5..0af7355 100644 --- a/jsonschema/_utils.py +++ b/jsonschema/_utils.py @@ -86,7 +86,7 @@ def format_as_index(indices): if not indices: return "" - return "[%s]" % "][".join(repr(index) for index in indices) + return f"[{']['.join(repr(index) for index in indices)}]" def find_additional_properties(instance, schema): @@ -136,7 +136,7 @@ def types_msg(instance, types): reprs.append(repr(type["name"])) except Exception: reprs.append(repr(type)) - return "%r is not of type %s" % (instance, ", ".join(reprs)) + return f"{instance!r} is not of type {', '.join(reprs)}" def flatten(suitable_for_isinstance): diff --git a/jsonschema/_validators.py b/jsonschema/_validators.py index ebbcaee..100fa03 100644 --- a/jsonschema/_validators.py +++ b/jsonschema/_validators.py @@ -53,16 +53,16 @@ def additionalProperties(validator, aP, instance, schema): yield error elif not aP and extras: if "patternProperties" in schema: - patterns = sorted(schema["patternProperties"]) if len(extras) == 1: verb = "does" else: verb = "do" - error = "%s %s not match any of the regexes: %s" % ( - ", ".join(map(repr, sorted(extras))), - verb, - ", ".join(map(repr, patterns)), + + joined = ", ".join(repr(each) for each in sorted(extras)) + patterns = ", ".join( + repr(each) for each in sorted(schema["patternProperties"]) ) + error = f"{joined} {verb} not match any of the regexes: {patterns}" yield ValidationError(error) else: error = "Additional properties are not allowed (%s %s unexpected)" @@ -75,10 +75,11 @@ def items(validator, items, instance, schema): if validator.is_type(items, "boolean") and "prefixItems" in schema: if not items: - if len(instance) > len(schema["prefixItems"]): - yield ValidationError( - "%r has more items than defined in prefixItems" % instance, - ) + got = len(instance) + maximum = len(schema["prefixItems"]) + if got > maximum: + message = f"Expected at most {maximum} items, but found {got}" + yield ValidationError(message) else: non_prefixed_items = ( instance[len(schema["prefixItems"]):] @@ -112,7 +113,7 @@ def additionalItems(validator, aI, instance, schema): def const(validator, const, instance, schema): if not equal(instance, const): - yield ValidationError("%r was expected" % (const,)) + yield ValidationError(f"{const!r} was expected") def contains(validator, contains, instance, schema): @@ -133,40 +134,39 @@ def contains(validator, contains, instance, schema): matches = sum(1 for each in instance if validator.is_valid(each, contains)) - # default contains behavior if not matches: yield ValidationError( - "None of %r are valid under the given schema" % (instance,), + f"{instance!r} does not contain items matching the given schema", ) return if min_contains and max_contains is None: if matches < min_contains: yield ValidationError( - "Too few matches under the given schema. " - "Expected %d but there were only %d." % ( - min_contains, matches, - ), + "Too few items match the given schema " + f"(expected {min_contains} but only {matches} matched)", ) return if min_contains is None and max_contains: if matches > max_contains: yield ValidationError( - "Too many matches under the given schema. " - "Expected %d but there were only %d." % ( - max_contains, matches, - ), + "Too many items match the given schema " + f"(expected at most {max_contains} but {matches} matched)", ) return if min_contains and max_contains: - if matches < min_contains or matches > max_contains: + if matches < min_contains: + only = "only " + elif matches > max_contains: + only = "" + else: + only = None + if only is not None: yield ValidationError( - "Invalid number or matches under the given schema, " - "expected between %d and %d, got %d" % ( - min_contains, max_contains, matches, - ), + f"Expected between {min_contains} and {max_contains} items " + f"to match the given schema but {only}{matches} matched", ) return @@ -177,9 +177,8 @@ def exclusiveMinimum(validator, minimum, instance, schema): if instance <= minimum: yield ValidationError( - "%r is less than or equal to the minimum of %r" % ( - instance, minimum, - ), + f"{instance!r} is less than or equal to " + f"the minimum of {minimum!r}", ) @@ -189,9 +188,8 @@ def exclusiveMaximum(validator, maximum, instance, schema): if instance >= maximum: yield ValidationError( - "%r is greater than or equal to the maximum of %r" % ( - instance, maximum, - ), + f"{instance!r} is greater than or equal " + f"to the maximum of {maximum!r}", ) @@ -200,9 +198,8 @@ def minimum(validator, minimum, instance, schema): return if instance < minimum: - yield ValidationError( - "%r is less than the minimum of %r" % (instance, minimum), - ) + message = f"{instance!r} is less than the minimum of {minimum!r}" + yield ValidationError(message) def maximum(validator, maximum, instance, schema): @@ -210,9 +207,8 @@ def maximum(validator, maximum, instance, schema): return if instance > maximum: - yield ValidationError( - "%r is greater than the maximum of %r" % (instance, maximum), - ) + message = f"{instance!r} is greater than the maximum of {maximum!r}" + yield ValidationError(message) def multipleOf(validator, dB, instance, schema): @@ -239,17 +235,17 @@ def multipleOf(validator, dB, instance, schema): failed = instance % dB if failed: - yield ValidationError("%r is not a multiple of %r" % (instance, dB)) + yield ValidationError(f"{instance!r} is not a multiple of {dB}") def minItems(validator, mI, instance, schema): if validator.is_type(instance, "array") and len(instance) < mI: - yield ValidationError("%r is too short" % (instance,)) + yield ValidationError(f"{instance!r} is too short") def maxItems(validator, mI, instance, schema): if validator.is_type(instance, "array") and len(instance) > mI: - yield ValidationError("%r is too long" % (instance,)) + yield ValidationError(f"{instance!r} is too long") def uniqueItems(validator, uI, instance, schema): @@ -258,7 +254,7 @@ def uniqueItems(validator, uI, instance, schema): validator.is_type(instance, "array") and not uniq(instance) ): - yield ValidationError("%r has non-unique elements" % (instance,)) + yield ValidationError(f"{instance!r} has non-unique elements") def pattern(validator, patrn, instance, schema): @@ -266,7 +262,7 @@ def pattern(validator, patrn, instance, schema): validator.is_type(instance, "string") and not re.search(patrn, instance) ): - yield ValidationError("%r does not match %r" % (instance, patrn)) + yield ValidationError(f"{instance!r} does not match {patrn!r}") def format(validator, format, instance, schema): @@ -279,12 +275,12 @@ def format(validator, format, instance, schema): def minLength(validator, mL, instance, schema): if validator.is_type(instance, "string") and len(instance) < mL: - yield ValidationError("%r is too short" % (instance,)) + yield ValidationError(f"{instance!r} is too short") def maxLength(validator, mL, instance, schema): if validator.is_type(instance, "string") and len(instance) > mL: - yield ValidationError("%r is too long" % (instance,)) + yield ValidationError(f"{instance!r} is too long") def dependentRequired(validator, dependentRequired, instance, schema): @@ -297,8 +293,8 @@ def dependentRequired(validator, dependentRequired, instance, schema): for each in dependency: if each not in instance: - message = "%r is a dependency of %r" - yield ValidationError(message % (each, property)) + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) def dependentSchemas(validator, dependentSchemas, instance, schema): @@ -307,7 +303,7 @@ def dependentSchemas(validator, dependentSchemas, instance, schema): continue for error in validator.descend( - instance, dependency, schema_path=property, + instance, dependency, schema_path=property, ): yield error @@ -316,9 +312,9 @@ def enum(validator, enums, instance, schema): if instance == 0 or instance == 1: unbooled = unbool(instance) if all(unbooled != unbool(each) for each in enums): - yield ValidationError("%r is not one of %r" % (instance, enums)) + yield ValidationError(f"{instance!r} is not one of {enums!r}") elif instance not in enums: - yield ValidationError("%r is not one of %r" % (instance, enums)) + yield ValidationError(f"{instance!r} is not one of {enums!r}") def ref(validator, ref, instance, schema): @@ -383,21 +379,19 @@ def required(validator, required, instance, schema): return for property in required: if property not in instance: - yield ValidationError("%r is a required property" % property) + yield ValidationError(f"{property!r} is a required property") def minProperties(validator, mP, instance, schema): if validator.is_type(instance, "object") and len(instance) < mP: - yield ValidationError( - "%r does not have enough properties" % (instance,), - ) + yield ValidationError(f"{instance!r} does not have enough properties") def maxProperties(validator, mP, instance, schema): if not validator.is_type(instance, "object"): return if validator.is_type(instance, "object") and len(instance) > mP: - yield ValidationError("%r has too many properties" % (instance,)) + yield ValidationError(f"{instance!r} has too many properties") def allOf(validator, allOf, instance, schema): @@ -415,7 +409,7 @@ def anyOf(validator, anyOf, instance, schema): all_errors.extend(errs) else: yield ValidationError( - "%r is not valid under any of the given schemas" % (instance,), + f"{instance!r} is not valid under any of the given schemas", context=all_errors, ) @@ -431,7 +425,7 @@ def oneOf(validator, oneOf, instance, schema): all_errors.extend(errs) else: yield ValidationError( - "%r is not valid under any of the given schemas" % (instance,), + f"{instance!r} is not valid under any of the given schemas", context=all_errors, ) @@ -439,15 +433,13 @@ def oneOf(validator, oneOf, instance, schema): if more_valid: more_valid.append(first_valid) reprs = ", ".join(repr(schema) for schema in more_valid) - yield ValidationError( - "%r is valid under each of %s" % (instance, reprs), - ) + yield ValidationError(f"{instance!r} is valid under each of {reprs}") def not_(validator, not_schema, instance, schema): if validator.is_valid(instance, not_schema): yield ValidationError( - "%r is not allowed for %r" % (not_schema, instance), + f"{not_schema!r} is not allowed for {instance!r}", ) diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index e8da352..1fbb2ca 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -2,9 +2,9 @@ Validation errors, and some surrounding helpers. """ from collections import defaultdict, deque +from pprint import pformat +from textwrap import dedent, indent import itertools -import pprint -import textwrap import attr @@ -57,7 +57,7 @@ class _Error(Exception): error.parent = self def __repr__(self): - return "<%s: %r>" % (self.__class__.__name__, self.message) + return f"<{self.__class__.__name__}: {self.message!r}>" def __str__(self): essential_for_verbose = ( @@ -66,24 +66,24 @@ class _Error(Exception): if any(m is _unset for m in essential_for_verbose): return self.message - pschema = pprint.pformat(self.schema, width=72) - pinstance = pprint.pformat(self.instance, width=72) - return self.message + textwrap.dedent(""" + schema_word = self._word_for_schema_in_error_message + schema_path = _utils.format_as_index( + list(self.relative_schema_path)[:-1], + ) + instance_word = self._word_for_instance_in_error_message + instance_path = _utils.format_as_index(self.relative_path) + prefix = 16 * " " + + return dedent( + f"""\ + {self.message} - Failed validating %r in %s%s: - %s + Failed validating {self.validator!r} in {schema_word}{schema_path}: + {indent(pformat(self.schema, width=72), prefix).lstrip()} - On %s%s: - %s + On {instance_word}{instance_path}: + {indent(pformat(self.instance, width=72), prefix).lstrip()} """.rstrip(), - ) % ( - self.validator, - self._word_for_schema_in_error_message, - _utils.format_as_index(list(self.relative_schema_path)[:-1]), - textwrap.indent(pschema, " "), - self._word_for_instance_in_error_message, - _utils.format_as_index(self.relative_path), - textwrap.indent(pinstance, " "), ) @classmethod @@ -172,7 +172,7 @@ class UndefinedTypeCheck(Exception): self.type = type def __str__(self): - return "Type %r is unknown to this type checker" % self.type + return f"Type {self.type!r} is unknown to this type checker" class UnknownType(Exception): @@ -186,19 +186,16 @@ class UnknownType(Exception): self.schema = schema def __str__(self): - pschema = pprint.pformat(self.schema, width=72) - pinstance = pprint.pformat(self.instance, width=72) - return textwrap.dedent(""" - Unknown type %r for validator with schema: - %s + prefix = 16 * " " + + return dedent( + f"""\ + Unknown type {self.type!r} for validator with schema: + {indent(pformat(self.schema, width=72), prefix).lstrip()} While checking instance: - %s + {indent(pformat(self.instance, width=72), prefix).lstrip()} """.rstrip(), - ) % ( - self.type, - textwrap.indent(pschema, " "), - textwrap.indent(pinstance, " "), ) @@ -276,7 +273,7 @@ class ErrorTree(object): return self.total_errors def __repr__(self): - return "<%s (%s total errors)>" % (self.__class__.__name__, len(self)) + return f"<{self.__class__.__name__} ({len(self)} total errors)>" @property def total_errors(self): diff --git a/jsonschema/tests/_suite.py b/jsonschema/tests/_suite.py index 931dd69..e24e41e 100644 --- a/jsonschema/tests/_suite.py +++ b/jsonschema/tests/_suite.py @@ -183,7 +183,7 @@ class _Test(object): @property def method_name(self): delimiters = r"[\W\- ]+" - return "test_%s_%s_%s" % ( + return "test_{}_{}_{}".format( re.sub(delimiters, "_", self.subject), re.sub(delimiters, "_", self.case_description), re.sub(delimiters, "_", self.description), diff --git a/jsonschema/tests/test_exceptions.py b/jsonschema/tests/test_exceptions.py index a59fdc5..153b72e 100644 --- a/jsonschema/tests/test_exceptions.py +++ b/jsonschema/tests/test_exceptions.py @@ -284,6 +284,22 @@ class TestErrorTree(TestCase): tree = exceptions.ErrorTree([error]) self.assertIsInstance(tree["foo"], exceptions.ErrorTree) + def test_repr(self): + e1, e2 = ( + exceptions.ValidationError( + "1", + validator="foo", + path=["bar", "bar2"], + instance="i1"), + exceptions.ValidationError( + "2", + validator="quux", + path=["foobar", 2], + instance="i2"), + ) + tree = exceptions.ErrorTree([e1, e2]) + self.assertEqual(repr(tree), "") + class TestErrorInitReprStr(TestCase): def make_error(self, **kwargs): @@ -312,7 +328,7 @@ class TestErrorInitReprStr(TestCase): def test_repr(self): self.assertEqual( repr(exceptions.ValidationError(message="Hello!")), - "" % "Hello!", + "", ) def test_unset_error(self): diff --git a/jsonschema/tests/test_types.py b/jsonschema/tests/test_types.py index cdb5cb3..21e8312 100644 --- a/jsonschema/tests/test_types.py +++ b/jsonschema/tests/test_types.py @@ -54,7 +54,10 @@ class TestTypeChecker(TestCase): def test_is_unknown_type(self): with self.assertRaises(UndefinedTypeCheck) as context: TypeChecker().is_type(4, "foobar") - self.assertIn("foobar", str(context.exception)) + self.assertIn( + "'foobar' is unknown to this type checker", + str(context.exception), + ) def test_checks_can_be_added_at_init(self): checker = TypeChecker({"two": equals_2}) diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 09633c7..449ba93 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -198,9 +198,9 @@ class TestIterErrors(TestCase): got = (e.message for e in self.validator.iter_errors(instance, schema)) expected = [ - "%r is disallowed for [1, 2]" % (schema["disallow"],), + f"{schema['disallow']!r} is disallowed for [1, 2]", "[1, 2] is too short", - "[1, 2] is not one of %r" % (schema["enum"],), + f"[1, 2] is not one of {schema['enum']}", ] self.assertEqual(sorted(got), sorted(expected)) @@ -220,9 +220,10 @@ class TestIterErrors(TestCase): class TestValidationErrorMessages(TestCase): def message_for(self, instance, schema, *args, **kwargs): - kwargs.setdefault("cls", validators.Draft3Validator) + cls = kwargs.pop("cls", validators._LATEST_VERSION) + cls.check_schema(schema) with self.assertRaises(exceptions.ValidationError) as e: - validators.validate(instance, schema, *args, **kwargs) + cls(schema, *args, **kwargs).validate(instance) return e.exception.message def test_single_type_failure(self): @@ -240,13 +241,24 @@ class TestValidationErrorMessages(TestCase): def test_object_without_title_type_failure(self): type = {"type": [{"minimum": 3}]} - message = self.message_for(instance=1, schema={"type": [type]}) - self.assertEqual(message, "1 is less than the minimum of 3") + message = self.message_for( + instance=1, + schema={"type": [type]}, + cls=validators.Draft3Validator, + ) + self.assertEqual( + message, + "1 is not of type {'type': [{'minimum': 3}]}", + ) def test_object_with_named_type_failure(self): schema = {"type": [{"name": "Foo", "minimum": 3}]} - message = self.message_for(instance=1, schema=schema) - self.assertEqual(message, "1 is less than the minimum of 3") + message = self.message_for( + instance=1, + schema=schema, + cls=validators.Draft3Validator, + ) + self.assertEqual(message, "1 is not of type 'Foo'") def test_minimum(self): message = self.message_for(instance=1, schema={"minimum": 2}) @@ -264,7 +276,7 @@ class TestValidationErrorMessages(TestCase): schema=schema, cls=validators.Draft3Validator, ) - self.assertEqual(message, "%r is a dependency of %r" % (on, depend)) + self.assertEqual(message, "'foo' is a dependency of 'bar'") def test_dependencies_list_draft3(self): depend, on = "bar", "foo" @@ -274,7 +286,7 @@ class TestValidationErrorMessages(TestCase): schema=schema, cls=validators.Draft3Validator, ) - self.assertEqual(message, "%r is a dependency of %r" % (on, depend)) + self.assertEqual(message, "'foo' is a dependency of 'bar'") def test_dependencies_list_draft7(self): depend, on = "bar", "foo" @@ -284,12 +296,13 @@ class TestValidationErrorMessages(TestCase): schema=schema, cls=validators.Draft7Validator, ) - self.assertEqual(message, "%r is a dependency of %r" % (on, depend)) + self.assertEqual(message, "'foo' is a dependency of 'bar'") def test_additionalItems_single_failure(self): message = self.message_for( instance=[2], schema={"items": [], "additionalItems": False}, + cls=validators.Draft3Validator, ) self.assertIn("(2 was unexpected)", message) @@ -297,6 +310,7 @@ class TestValidationErrorMessages(TestCase): message = self.message_for( instance=[1, 2, 3], schema={"items": [], "additionalItems": False}, + cls=validators.Draft3Validator, ) self.assertIn("(1, 2, 3 were unexpected)", message) @@ -304,7 +318,7 @@ class TestValidationErrorMessages(TestCase): additional = "foo" schema = {"additionalProperties": False} message = self.message_for(instance={additional: 2}, schema=schema) - self.assertIn("(%r was unexpected)" % (additional,), message) + self.assertIn("('foo' was unexpected)", message) def test_additionalProperties_multiple_failures(self): schema = {"additionalProperties": False} @@ -322,20 +336,19 @@ class TestValidationErrorMessages(TestCase): message = self.message_for( instance={"foo": "bar"}, schema=schema, - cls=validators.Draft6Validator, ) self.assertIn("12 was expected", message) - def test_contains(self): + def test_contains_draft_6(self): schema = {"contains": {"const": 12}} message = self.message_for( instance=[2, {}, []], schema=schema, cls=validators.Draft6Validator, ) - self.assertIn( - "None of [2, {}, []] are valid under the given schema", + self.assertEqual( message, + "None of [2, {}, []] are valid under the given schema", ) def test_invalid_format_default_message(self): @@ -387,36 +400,188 @@ class TestValidationErrorMessages(TestCase): message = self.message_for( instance="something", schema=False, - cls=validators.Draft7Validator, ) - self.assertIn("False schema does not allow 'something'", message) + self.assertEqual(message, "False schema does not allow 'something'") - def test_unevaluated_properties(self): - schema = {"type": "object", "unevaluatedProperties": False} + def test_multipleOf(self): message = self.message_for( - instance={ - "foo": "foo", - "bar": "bar", + instance=3, + schema={"multipleOf": 2}, + ) + self.assertEqual(message, "3 is not a multiple of 2") + + def test_minItems(self): + message = self.message_for(instance=[], schema={"minItems": 2}) + self.assertEqual(message, "[] is too short") + + def test_maxItems(self): + message = self.message_for(instance=[1, 2, 3], schema={"maxItems": 2}) + self.assertEqual(message, "[1, 2, 3] is too long") + + def test_prefixItems(self): + message = self.message_for( + instance=[1, 2, "foo", 5], + schema={"items": False, "prefixItems": [{}, {}]}, + ) + self.assertEqual(message, "Expected at most 2 items, but found 4") + + def test_minLength(self): + message = self.message_for( + instance="", + schema={"minLength": 2}, + ) + self.assertEqual(message, "'' is too short") + + def test_maxLength(self): + message = self.message_for( + instance="abc", + schema={"maxLength": 2}, + ) + self.assertEqual(message, "'abc' is too long") + + def test_pattern(self): + message = self.message_for( + instance="bbb", + schema={"pattern": "^a*$"}, + ) + self.assertEqual(message, "'bbb' does not match '^a*$'") + + def test_does_not_contain(self): + message = self.message_for( + instance=[], + schema={"contains": {"type": "string"}}, + ) + self.assertEqual( + message, + "[] does not contain items matching the given schema", + ) + + def test_contains_too_few(self): + message = self.message_for( + instance=["foo", 1], + schema={"contains": {"type": "string"}, "minContains": 2}, + ) + self.assertEqual( + message, + "Too few items match the given schema " + "(expected 2 but only 1 matched)", + ) + + def test_contains_too_few_both_constrained(self): + message = self.message_for( + instance=["foo", 1], + schema={ + "contains": {"type": "string"}, + "minContains": 2, + "maxContains": 4, }, - schema=schema, - cls=validators.Draft202012Validator, ) - self.assertIn( - "Unevaluated properties are not allowed " - "('foo', 'bar' were unexpected)", + self.assertEqual( + message, + "Expected between 2 and 4 items to match the given schema but " + "only 1 matched", + ) + + def test_contains_too_many(self): + message = self.message_for( + instance=["foo", "bar", "baz", "quux"], + schema={"contains": {"type": "string"}, "maxContains": 2}, + ) + self.assertEqual( + message, + "Too many items match the given schema " + "(expected at most 2 but 4 matched)", + ) + + def test_contains_too_many_both_constrained(self): + message = self.message_for( + instance=["foo"] * 7, + schema={ + "contains": {"type": "string"}, + "minContains": 2, + "maxContains": 4, + }, + ) + self.assertEqual( message, + "Expected between 2 and 4 items to match the given schema but " + "7 matched", + ) + + def test_exclusiveMinimum(self): + message = self.message_for( + instance=3, + schema={"exclusiveMinimum": 5}, + ) + self.assertEqual( + message, + "3 is less than or equal to the minimum of 5", + ) + + def test_exclusiveMaximum(self): + message = self.message_for(instance=3, schema={"exclusiveMaximum": 2}) + self.assertEqual( + message, + "3 is greater than or equal to the maximum of 2", + ) + + def test_required(self): + message = self.message_for(instance={}, schema={"required": ["foo"]}) + self.assertEqual(message, "'foo' is a required property") + + def test_dependentRequired(self): + message = self.message_for( + instance={"foo": {}}, + schema={"dependentRequired": {"foo": ["bar"]}}, ) + self.assertEqual(message, "'bar' is a dependency of 'foo'") + + def test_minProperties(self): + message = self.message_for(instance={}, schema={"minProperties": 2}) + self.assertEqual(message, "{} does not have enough properties") + + def test_maxProperties(self): + message = self.message_for( + instance={"a": {}, "b": {}, "c": {}}, + schema={"maxProperties": 2}, + ) + self.assertEqual( + message, + "{'a': {}, 'b': {}, 'c': {}} has too many properties", + ) + + def test_oneOf_matches_none(self): + message = self.message_for(instance={}, schema={"oneOf": [False]}) + self.assertEqual( + message, + "{} is not valid under any of the given schemas", + ) + + def test_oneOf_matches_too_many(self): + message = self.message_for(instance={}, schema={"oneOf": [True, True]}) + self.assertEqual(message, "{} is valid under each of True, True") def test_unevaluated_items(self): schema = {"type": "array", "unevaluatedItems": False} + message = self.message_for(instance=["foo", "bar"], schema=schema) + self.assertIn( + message, + "Unevaluated items are not allowed ('foo', 'bar' were unexpected)", + ) + + def test_unevaluated_properties(self): + schema = {"type": "object", "unevaluatedProperties": False} message = self.message_for( - instance=["foo", "bar"], + instance={ + "foo": "foo", + "bar": "bar", + }, schema=schema, - cls=validators.Draft202012Validator, ) - self.assertIn( - "Unevaluated items are not allowed ('foo', 'bar' were unexpected)", + self.assertEqual( message, + "Unevaluated properties are not allowed " + "('foo', 'bar' were unexpected)", ) @@ -859,7 +1024,7 @@ class TestValidationErrorDetails(TestCase): self.assertEqual(error.validator, "not") self.assertEqual( error.message, - "%r is not allowed for %r" % ({"const": "foo"}, "foo"), + "{'const': 'foo'} is not allowed for 'foo'", ) self.assertEqual(error.path, deque([])) self.assertEqual(error.json_path, "$") @@ -1479,7 +1644,7 @@ class TestValidate(SynchronousTestCase): validators.validate(12, {"type": "string"}) self.assertRegex( str(e.exception), - "(?s)Failed validating u?'.*' in schema.*On instance", + "(?s)Failed validating '.*' in schema.*On instance", ) def test_schema_error_message(self): @@ -1487,7 +1652,7 @@ class TestValidate(SynchronousTestCase): validators.validate(12, {"type": 12}) self.assertRegex( str(e.exception), - "(?s)Failed validating u?'.*' in metaschema.*On schema", + "(?s)Failed validating '.*' in metaschema.*On schema", ) def test_it_uses_best_match(self): diff --git a/jsonschema/validators.py b/jsonschema/validators.py index c3f7ff9..371c6f1 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -166,7 +166,7 @@ def create( return elif _schema is False: yield exceptions.ValidationError( - "False schema does not allow %r" % (instance,), + f"False schema does not allow {instance!r}", validator=None, validator_value=None, instance=instance, @@ -804,7 +804,7 @@ class RefResolver(object): document = document[part] except (TypeError, LookupError): raise exceptions.RefResolutionError( - "Unresolvable JSON pointer: %r" % fragment, + f"Unresolvable JSON pointer: {fragment!r}", ) return document -- cgit v1.2.1