From fb6386768a5749301ef1d552de78192651e86db4 Mon Sep 17 00:00:00 2001 From: darrenhuai <60621295+darrenhuai@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:01:13 -0700 Subject: [PATCH 1/2] test: add coverage for tomlkit._types wrapper operators _CustomList.__add__/__iadd__, _CustomDict.__or__/__ior__, and the wrap_method helper had no direct tests, leaving tomlkit/_types.py at 60% coverage despite backing the operator overloads on Array and every table type. Notably, __add__ silently returns a plain list (loses the Array wrapper) while __or__ preserves the Table wrapper - this asymmetry is now pinned down by a test instead of being implicit. wrap_method itself is unused elsewhere in the codebase but is part of the public API (exported via __all__), so it's tested directly against a minimal wrapper class. tomlkit/_types.py coverage: 60% -> 100%. Agent: Claude Code Model: claude-sonnet-5 Notes: repo research, coverage-gap analysis, tests, and verification (pytest/ruff/mypy) all done by the agent; reviewed before pushing. --- tests/test_types.py | 83 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/test_types.py diff --git a/tests/test_types.py b/tests/test_types.py new file mode 100644 index 00000000..614ecf47 --- /dev/null +++ b/tests/test_types.py @@ -0,0 +1,83 @@ +from typing import Any + +import tomlkit + +from tomlkit._types import wrap_method +from tomlkit.items import Array +from tomlkit.items import Table + + +def test_custom_list_add_returns_plain_list_with_combined_items() -> None: + arr = tomlkit.array() + arr.extend([1, 2, 3]) + + result = arr + [4, 5] # noqa: RUF005 (exercising __add__ itself) + + assert result == [1, 2, 3, 4, 5] + assert not isinstance(result, Array) + + +def test_custom_list_iadd_mutates_in_place_and_keeps_wrapper_type() -> None: + arr = tomlkit.array() + arr.extend([1, 2]) + original_id = id(arr) + + arr += [9] + + assert arr == [1, 2, 9] + assert isinstance(arr, Array) + assert id(arr) == original_id + + +def test_custom_dict_or_returns_new_table_with_merged_items() -> None: + table = tomlkit.table() + table["a"] = 1 + table["b"] = 2 + + result = table | {"c": 3} + + assert dict(result) == {"a": 1, "b": 2, "c": 3} + assert isinstance(result, Table) + assert result is not table + + +def test_custom_dict_ior_mutates_in_place() -> None: + table = tomlkit.table() + table["x"] = 1 + original_id = id(table) + + table |= {"y": 2} + + assert dict(table) == {"x": 1, "y": 2} + assert id(table) == original_id + + +class _Wrapped: + def __init__(self, value: int) -> None: + self.value = value + + def _new(self, value: int) -> "_Wrapped": + return _Wrapped(value) + + +def test_wrap_method_wraps_result_via_new() -> None: + def raw_add(self: _Wrapped, other: int) -> int: + return self.value + other + + wrapped_add = wrap_method(raw_add) + wrapped = _Wrapped(5) + + result = wrapped_add(wrapped, 3) + + assert isinstance(result, _Wrapped) + assert result.value == 8 + + +def test_wrap_method_passes_through_not_implemented() -> None: + def raw_not_implemented(self: _Wrapped, other: int) -> Any: + return NotImplemented + + wrapped_method = wrap_method(raw_not_implemented) + wrapped = _Wrapped(5) + + assert wrapped_method(wrapped, 3) is NotImplemented From 4e9df466e4aeb0b4ff80cfb0f313c639c45ab215 Mon Sep 17 00:00:00 2001 From: darrenhuai <60621295+darrenhuai@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:57:59 -0700 Subject: [PATCH 2/2] test: drop wrap_method coverage, stop asserting Array type on __add__ dimbleby and davidpavlovschi both flagged the same thing from two angles: wrap_method has been dead since #460, so testing it just locks in code nobody calls. Removed it from _types.py along with the Callable/Concatenate/ParamSpec plumbing that existed only for its signature, and dropped the corresponding tests. Also backed off the __add__ test's `assert not isinstance(result, Array)`. That was asserting an implementation detail the PR description itself called out as possibly unintended, and davidpavlovschi's right that a coverage PR shouldn't be the thing that decides whether __add__ is supposed to return a plain list or a wrapped Array. The test now just checks the combined values. --- tests/test_types.py | 37 +------------------------------------ tomlkit/_types.py | 18 ------------------ 2 files changed, 1 insertion(+), 54 deletions(-) diff --git a/tests/test_types.py b/tests/test_types.py index 614ecf47..a035dde0 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,20 +1,16 @@ -from typing import Any - import tomlkit -from tomlkit._types import wrap_method from tomlkit.items import Array from tomlkit.items import Table -def test_custom_list_add_returns_plain_list_with_combined_items() -> None: +def test_custom_list_add_returns_combined_items() -> None: arr = tomlkit.array() arr.extend([1, 2, 3]) result = arr + [4, 5] # noqa: RUF005 (exercising __add__ itself) assert result == [1, 2, 3, 4, 5] - assert not isinstance(result, Array) def test_custom_list_iadd_mutates_in_place_and_keeps_wrapper_type() -> None: @@ -50,34 +46,3 @@ def test_custom_dict_ior_mutates_in_place() -> None: assert dict(table) == {"x": 1, "y": 2} assert id(table) == original_id - - -class _Wrapped: - def __init__(self, value: int) -> None: - self.value = value - - def _new(self, value: int) -> "_Wrapped": - return _Wrapped(value) - - -def test_wrap_method_wraps_result_via_new() -> None: - def raw_add(self: _Wrapped, other: int) -> int: - return self.value + other - - wrapped_add = wrap_method(raw_add) - wrapped = _Wrapped(5) - - result = wrapped_add(wrapped, 3) - - assert isinstance(result, _Wrapped) - assert result.value == 8 - - -def test_wrap_method_passes_through_not_implemented() -> None: - def raw_not_implemented(self: _Wrapped, other: int) -> Any: - return NotImplemented - - wrapped_method = wrap_method(raw_not_implemented) - wrapped = _Wrapped(5) - - assert wrapped_method(wrapped, 3) is NotImplemented diff --git a/tomlkit/_types.py b/tomlkit/_types.py index 3585d56c..5f5f66c1 100644 --- a/tomlkit/_types.py +++ b/tomlkit/_types.py @@ -12,7 +12,6 @@ "_CustomFloat", "_CustomInt", "_CustomList", - "wrap_method", ] if TYPE_CHECKING: # pragma: no cover @@ -31,13 +30,8 @@ from builtins import float as _CustomFloat from builtins import int as _CustomInt from builtins import list as _CustomList - from typing import Callable - from typing import Concatenate - from typing import ParamSpec from typing import Protocol - P = ParamSpec("P") - class WrapperType(Protocol): def _new(self: WT, value: Any) -> WT: ... @@ -76,15 +70,3 @@ class _CustomInt(Integral, int): class _CustomFloat(Real, float): """Adds Real mixin while pretending to be a builtin float""" - - -def wrap_method( - original_method: Callable[Concatenate[WT, P], Any], -) -> Callable[Concatenate[WT, P], Any]: - def wrapper(self: WT, /, *args: P.args, **kwargs: P.kwargs) -> Any: - result = original_method(self, *args, **kwargs) - if result is NotImplemented: - return result - return self._new(result) - - return wrapper