fix: rebuild namedtuples in _deepcopy_with_exceptions (#11981)

Co-authored-by: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Andrew Chen
2026-07-17 09:28:45 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent b1031bfb69
commit 918bebe65a
3 changed files with 29 additions and 0 deletions
+5
View File
@@ -33,6 +33,11 @@ def _deepcopy_with_exceptions(obj: Any) -> Any:
from haystack.tools.tool import Tool
from haystack.tools.toolset import Toolset
# namedtuples are tuple subclasses whose __new__ takes the fields positionally,
# so they must be rebuilt by unpacking rather than from a single iterable.
if isinstance(obj, tuple) and hasattr(obj, "_fields"):
return type(obj)(*(_deepcopy_with_exceptions(v) for v in obj))
if isinstance(obj, (list, tuple, set)):
return type(obj)(_deepcopy_with_exceptions(v) for v in obj)
@@ -0,0 +1,10 @@
---
fixes:
- |
Fixed ``_deepcopy_with_exceptions`` crashing when a value is a ``namedtuple``
(or any ``typing.NamedTuple``). The list/tuple/set branch rebuilt the
container with ``type(obj)(<generator>)``, but a namedtuple's ``__new__``
expects its fields as positional arguments rather than a single iterable, so
copying a component input or parameter that contained a namedtuple raised a
``TypeError``. Namedtuples are now rebuilt by unpacking their deep-copied
fields.
+14
View File
@@ -4,6 +4,7 @@
import logging
import warnings
from collections import namedtuple
import pytest
@@ -255,6 +256,19 @@ class TestDeepcopyWithFallback:
res = _deepcopy_with_exceptions(original)
assert res["component"] is original["component"]
def test_deepcopy_with_fallback_namedtuple(self):
Point = namedtuple("Point", ["x", "y"])
inner = Copyable()
original = {"point": Point(inner, 2)}
copy = _deepcopy_with_exceptions(original)
# A namedtuple must be reconstructed as its own type. Its __new__ takes
# positional fields, so the plain ``type(obj)(<generator>)`` path used for
# lists/tuples/sets would raise a TypeError instead of copying it.
assert isinstance(copy["point"], Point)
assert copy["point"].y == 2
# Its contents are deep-copied, matching how plain tuples are handled.
assert copy["point"].x is not original["point"].x
class TestArgsDeprecated:
@pytest.fixture