From 351b2e445fe288652251b16ee1f69077dd5e77c7 Mon Sep 17 00:00:00 2001 From: Jah-yee <166608075+Jah-yee@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:29:55 +0800 Subject: [PATCH] fix: preserve dict subclass types in _deepcopy_with_exceptions When copying dict subclasses (defaultdict, OrderedDict, Counter), the function was returning a plain dict instead of preserving the type. A defaultdict would lose its default_factory, and an OrderedDict would become a regular dict. The fix checks if the dict is a plain dict (type is dict) and uses the fast path. For subclasses, it creates a shallow copy, clears it, and repopulates with deep-copied values, preserving the subclass type. --- haystack/core/pipeline/utils.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/haystack/core/pipeline/utils.py b/haystack/core/pipeline/utils.py index d1263dec973..efed945d749 100644 --- a/haystack/core/pipeline/utils.py +++ b/haystack/core/pipeline/utils.py @@ -4,7 +4,7 @@ import heapq from collections.abc import Callable -from copy import deepcopy +import copy from functools import wraps from itertools import count from typing import Any @@ -42,7 +42,16 @@ def _deepcopy_with_exceptions(obj: Any) -> Any: return type(obj)(_deepcopy_with_exceptions(v) for v in obj) if isinstance(obj, dict): - return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()} + if type(obj) is dict: + return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()} + try: + new = copy.copy(obj) + new.clear() + for k, v in obj.items(): + new[k] = _deepcopy_with_exceptions(v) + return new + except Exception: + return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()} # Components and Tools often contain objects that we do not want to deepcopy or are not deepcopyable # (e.g. models, clients, etc.). In this case we return the object as-is.