From c6484dd8206fdbc7d0536dfcbe59e7adb2947fc7 Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:47:43 +0300 Subject: [PATCH 1/2] Fix ErrorTree polluting contents on index access ErrorTree used a defaultdict for _contents, which meant accessing a non-existent index via __getitem__ would silently create an empty entry. This caused __iter__ and __contains__ to return incorrect results after accessing an index with no errors. Replaced defaultdict with a regular dict and changed __init__ to explicitly create subtrees. __getitem__ now returns a new empty ErrorTree for valid indices without storing it in _contents. --- commit_msg.txt | 10 ++++++++++ jsonschema/exceptions.py | 12 ++++++++---- jsonschema/tests/test_exceptions.py | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 commit_msg.txt diff --git a/commit_msg.txt b/commit_msg.txt new file mode 100644 index 00000000..493fcbe7 --- /dev/null +++ b/commit_msg.txt @@ -0,0 +1,10 @@ +Fix ErrorTree polluting contents on index access + +ErrorTree used a defaultdict for _contents, which meant accessing +a non-existent index via __getitem__ would silently create an empty +entry. This caused __iter__ and __contains__ to return incorrect +results after accessing an index with no errors. + +Replaced defaultdict with a regular dict and changed __init__ to +explicitly create subtrees. __getitem__ now returns a new empty +ErrorTree for valid indices without storing it in _contents. 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): From 23c8c8b979690a57fcab84ed775e1609409ef7fd Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:44:19 +0300 Subject: [PATCH 2/2] Delete commit_msg.txt --- commit_msg.txt | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 commit_msg.txt diff --git a/commit_msg.txt b/commit_msg.txt deleted file mode 100644 index 493fcbe7..00000000 --- a/commit_msg.txt +++ /dev/null @@ -1,10 +0,0 @@ -Fix ErrorTree polluting contents on index access - -ErrorTree used a defaultdict for _contents, which meant accessing -a non-existent index via __getitem__ would silently create an empty -entry. This caused __iter__ and __contains__ to return incorrect -results after accessing an index with no errors. - -Replaced defaultdict with a regular dict and changed __init__ to -explicitly create subtrees. __getitem__ now returns a new empty -ErrorTree for valid indices without storing it in _contents.