Skip to content

Commit 918bebe

Browse files
chuenchen309claude
andauthored
fix: rebuild namedtuples in _deepcopy_with_exceptions (#11981)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b1031bf commit 918bebe

3 files changed

Lines changed: 29 additions & 0 deletions

File tree

haystack/core/pipeline/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ def _deepcopy_with_exceptions(obj: Any) -> Any:
3333
from haystack.tools.tool import Tool
3434
from haystack.tools.toolset import Toolset
3535

36+
# namedtuples are tuple subclasses whose __new__ takes the fields positionally,
37+
# so they must be rebuilt by unpacking rather than from a single iterable.
38+
if isinstance(obj, tuple) and hasattr(obj, "_fields"):
39+
return type(obj)(*(_deepcopy_with_exceptions(v) for v in obj))
40+
3641
if isinstance(obj, (list, tuple, set)):
3742
return type(obj)(_deepcopy_with_exceptions(v) for v in obj)
3843

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed ``_deepcopy_with_exceptions`` crashing when a value is a ``namedtuple``
5+
(or any ``typing.NamedTuple``). The list/tuple/set branch rebuilt the
6+
container with ``type(obj)(<generator>)``, but a namedtuple's ``__new__``
7+
expects its fields as positional arguments rather than a single iterable, so
8+
copying a component input or parameter that contained a namedtuple raised a
9+
``TypeError``. Namedtuples are now rebuilt by unpacking their deep-copied
10+
fields.

test/core/pipeline/test_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import logging
66
import warnings
7+
from collections import namedtuple
78

89
import pytest
910

@@ -255,6 +256,19 @@ def test_deepcopy_with_fallback_component(self, monkeypatch):
255256
res = _deepcopy_with_exceptions(original)
256257
assert res["component"] is original["component"]
257258

259+
def test_deepcopy_with_fallback_namedtuple(self):
260+
Point = namedtuple("Point", ["x", "y"])
261+
inner = Copyable()
262+
original = {"point": Point(inner, 2)}
263+
copy = _deepcopy_with_exceptions(original)
264+
# A namedtuple must be reconstructed as its own type. Its __new__ takes
265+
# positional fields, so the plain ``type(obj)(<generator>)`` path used for
266+
# lists/tuples/sets would raise a TypeError instead of copying it.
267+
assert isinstance(copy["point"], Point)
268+
assert copy["point"].y == 2
269+
# Its contents are deep-copied, matching how plain tuples are handled.
270+
assert copy["point"].x is not original["point"].x
271+
258272

259273
class TestArgsDeprecated:
260274
@pytest.fixture

0 commit comments

Comments
 (0)