From 545d2e5327aab89c4415357a058870a5055d5ff9 Mon Sep 17 00:00:00 2001 From: Thomas Foutrein Date: Sat, 13 Jun 2026 14:28:25 +0200 Subject: [PATCH] Add a native OutOfOrderTableProxy.__contains__ OutOfOrderTableProxy was the last mapping type still inheriting the MutableMapping.__contains__ mixin, which implements `key in proxy` as `try: proxy[key]` -- routing through __getitem__, which resolves the value (and builds a NonExistentKey on every absent key) only to discard it. Add a native __contains__ that returns `key in self._internal_container`, exactly the predicate __getitem__ already uses as its guard and itself a native Container.__contains__ (which still rebuilds the proxy for an out-of-order key so its validation runs as before). This completes #483, which gave Container, Table and InlineTable native __contains__. No behaviour change: the membership result, the raised exception type and document (non-)mutation are all identical to the inherited path. Roughly 2.1x faster on a present key and 1.3x on an absent one. --- CHANGELOG.md | 1 + tests/test_items.py | 22 ++++++++++++++++++++++ tomlkit/container.py | 9 +++++++++ 3 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ef9c455..576ea6e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Speed up parsing by removing the internal `TOMLChar` wrapper: the parser now reads plain `str` characters from `Source` and detects end-of-input positionally, avoiding a per-character object construction and method dispatch. ([#492](https://github.com/python-poetry/tomlkit/pull/492)) - Speed up parsing by comparing `StringType` members by identity (`is`) instead of building a set on every `is_basic`/`is_literal`/`is_singleline`/`is_multiline` call, avoiding millions of enum hashes while parsing. ([#502](https://github.com/python-poetry/tomlkit/pull/502)) - Speed up merging super tables by merging in place instead of deep-copying the growing target on every merge, turning the parse of documents with many subtables under a shared super table (e.g. consecutive `[a.b.c]` / `[a.b.d]` headers) from O(n²) into O(n). ([#503](https://github.com/python-poetry/tomlkit/pull/503)) +- Speed up membership tests (`key in ...`) on out-of-order tables with a native `OutOfOrderTableProxy.__contains__`, completing [#483](https://github.com/python-poetry/tomlkit/issues/483) for the last mapping type that still inherited the slow `MutableMapping` mixin (which resolves the value and builds an exception on every absent key). ([#515](https://github.com/python-poetry/tomlkit/pull/515)) ### Fixed diff --git a/tests/test_items.py b/tests/test_items.py index ed53cf7c..3336b9ec 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -19,6 +19,7 @@ from tests.util import elementary_test from tomlkit import api from tomlkit import parse +from tomlkit.container import OutOfOrderTableProxy from tomlkit.exceptions import NonExistentKey from tomlkit.items import Array from tomlkit.items import Bool @@ -1345,3 +1346,24 @@ def test_out_of_order_table_membership() -> None: assert "b" in table assert "missing" not in table assert doc.as_string() == content + + +def test_out_of_order_table_proxy_membership() -> None: + # A top-level table split by another table (``a`` interrupted by ``foo``) + # resolves to an ``OutOfOrderTableProxy``; exercise its native + # ``__contains__`` directly (the test above goes through ``Table``). + content = "[a.x]\np = 1\n[foo]\nbar = 2\n[a.y]\nq = 3\n" + doc = parse(content) + table = doc["a"] + assert isinstance(table, OutOfOrderTableProxy) + + assert "x" in table + assert "y" in table + assert "missing" not in table + # a Key is accepted just like __getitem__ does + assert Key("x") in table + # a non-str/non-Key argument is rejected like the other mapping types + with pytest.raises(TypeError): + _ = 123 in table + # membership must not resolve values or mutate the document + assert doc.as_string() == content diff --git a/tomlkit/container.py b/tomlkit/container.py index 3f8363d6..45a7fde8 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -954,6 +954,15 @@ def unwrap(self) -> dict[str, Any]: def value(self) -> dict[str, Any]: return self._internal_container.value + def __contains__(self, key: object) -> bool: + # Native membership test. The inherited ``MutableMapping.__contains__`` + # resolves the value via ``__getitem__`` (and builds a ``NonExistentKey`` + # on every absent key) only to discard it. Probe the internal container + # directly -- the same predicate ``__getitem__`` already uses -- which is + # itself a native ``_map`` lookup that still rebuilds the proxy for an + # out-of-order key so its validation runs exactly as before. + return key in self._internal_container + def __getitem__(self, key: Key | str) -> Any: if key not in self._internal_container: raise NonExistentKey(key)