summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2021-08-18 16:53:02 +0100
committerJulian Berman <Julian@GrayVines.com>2021-08-18 16:53:02 +0100
commiteb633b216bad1acb1a7739279c5ab83bdd63d7a1 (patch)
tree60afc7f5262fc91baf405e53724419220b4c35c5
parent4737847a3d07d5d5eafd039c4c3c32d77f392188 (diff)
downloadjsonschema-eb633b216bad1acb1a7739279c5ab83bdd63d7a1.tar.gz
Tidy up validation error messages, particularly using f-strings.
-rw-r--r--jsonschema/_format.py4
-rw-r--r--jsonschema/_legacy_validators.py31
-rw-r--r--jsonschema/_utils.py4
-rw-r--r--jsonschema/_validators.py112
-rw-r--r--jsonschema/exceptions.py57
-rw-r--r--jsonschema/tests/_suite.py2
-rw-r--r--jsonschema/tests/test_exceptions.py18
-rw-r--r--jsonschema/tests/test_types.py5
-rw-r--r--jsonschema/tests/test_validators.py235
-rw-r--r--jsonschema/validators.py4
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), "<ErrorTree (2 total errors)>")
+
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!")),
- "<ValidationError: %r>" % "Hello!",
+ "<ValidationError: '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