diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 2e5d4ca08..9398a72e4 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from collections import defaultdict, deque +from collections import deque from pprint import pformat from textwrap import dedent, indent from typing import TYPE_CHECKING, Any, ClassVar @@ -321,12 +321,15 @@ class ErrorTree: def __init__(self, errors: Iterable[ValidationError] = ()): self.errors: MutableMapping[str, ValidationError] = {} - self._contents: Mapping[str, ErrorTree] = defaultdict(self.__class__) + self._contents: MutableMapping[str | int, ErrorTree] = {} for error in errors: container = self for element in error.path: - container = container[element] + container = container._contents.setdefault( + element, + self.__class__(), + ) container.errors[error.validator] = error container._instance = error.instance @@ -346,9 +349,15 @@ def __getitem__(self, index): by ``instance.__getitem__`` will be propagated (usually this is some subclass of `LookupError`. """ - if self._instance is not _unset and index not in self: + try: + return self._contents[index] + except KeyError: + pass + + if self._instance is not _unset: self._instance[index] - return self._contents[index] + + return self.__class__() def __setitem__(self, index: str | int, value: ErrorTree): """ diff --git a/jsonschema/tests/test_exceptions.py b/jsonschema/tests/test_exceptions.py index 358b92425..0c17aa28e 100644 --- a/jsonschema/tests/test_exceptions.py +++ b/jsonschema/tests/test_exceptions.py @@ -404,6 +404,23 @@ def test_it_does_not_contain_an_item_if_the_item_had_no_error(self): tree = exceptions.ErrorTree(errors) self.assertNotIn("foo", tree) + def test_getting_item_without_errors_does_not_create_a_subtree(self): + schema = { + "type": "array", + "items": {"type": "number", "enum": [1, 2, 3]}, + "minItems": 3, + } + instance = ["spam", 2] + tree = exceptions.ErrorTree( + _LATEST_VERSION(schema).iter_errors(instance), + ) + + self.assertEqual(list(tree), [0]) + self.assertNotIn(1, tree) + self.assertIsInstance(tree[1], exceptions.ErrorTree) + self.assertEqual(list(tree), [0]) + self.assertNotIn(1, tree) + def test_keywords_that_failed_appear_in_errors_dict(self): error = exceptions.ValidationError("a message", validator="foo") tree = exceptions.ErrorTree([error])