mirror of
https://github.com/deepset-ai/haystack.git
synced 2026-10-12 02:12:50 +00:00
fix: rebuild namedtuples in _deepcopy_with_exceptions (#11981)
Co-authored-by: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b1031bfb69
commit
918bebe65a
@@ -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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user