diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 2e5d4ca0..4bc9349a 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,14 @@ class ErrorTree: def __init__(self, errors: Iterable[ValidationError] = ()): self.errors: MutableMapping[str, ValidationError] = {} - self._contents: Mapping[str, ErrorTree] = defaultdict(self.__class__) + self._contents: dict[str | int, ErrorTree] = {} for error in errors: container = self for element in error.path: - container = container[element] + if element not in container._contents: + container._contents[element] = self.__class__() + container = container._contents[element] container.errors[error.validator] = error container._instance = error.instance @@ -348,7 +350,9 @@ def __getitem__(self, index): """ if self._instance is not _unset and index not in self: self._instance[index] - return self._contents[index] + if index in self._contents: + 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 358b9242..f4a99cbf 100644 --- a/jsonschema/tests/test_exceptions.py +++ b/jsonschema/tests/test_exceptions.py @@ -506,6 +506,23 @@ def test_repr_empty(self): tree = exceptions.ErrorTree([]) self.assertEqual(repr(tree), "") + def test_accessing_index_without_error_does_not_pollute_contents(self): + error = exceptions.ValidationError( + "not a number", + validator="type", + path=[0], + instance=["spam", 2], + ) + tree = exceptions.ErrorTree([error]) + self.assertEqual(list(tree), [0]) + self.assertNotIn(1, tree) + + # accessing an index with no error should not add it to the tree + tree[1] + + self.assertEqual(list(tree), [0]) + self.assertNotIn(1, tree) + class TestErrorInitReprStr(TestCase): def make_error(self, **kwargs):