Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

_deepcopy_with_exceptions turns dict subclasses (defaultdict, OrderedDict, Counter) into plain dict, so pipeline inputs lose their type

Closed Beginner friendly
#13,092 1 comment 0 reactions 1 assignee View on GitHub

Maintainers usually reply within 1 day

@anakin87 is already working on this.

Since Oct 3, 2026.

  • #13094 by @inchang-ing — closed without merging
  • #13096 by @hahahahahayesyeseys — closed without merging

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
backend

Research direction

Start in haystack/core/pipeline/utils.py at _deepcopy_with_exceptions, then inspect the input-copying calls in pipeline.py and base.py. Add focused coverage for OrderedDict, defaultdict, and Counter, including defaultdict missing-key behavior, and run the relevant pipeline and utility tests. Done means dict subclasses retain their type and state when passed through Pipeline.run.

Written by the indexing model from the issue text.

Description

Describe the bug

_deepcopy_with_exceptions in haystack/core/pipeline/utils.py rebuilds every dict as a plain dict:

if isinstance(obj, dict):
    return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()}

For list, tuple and set the function keeps the original type (type(obj)(...)), and #11981 added the same care for namedtuples. Dict subclasses do not get that treatment. A defaultdict, OrderedDict or Counter comes back as a plain dict, and a defaultdict loses its default_factory.

Pipeline.run copies its inputs with this function (pipeline.py and base.py call it on the input data), so a component receives a different type than the caller passed in.

Error message and reproduction

Direct call:

from collections import OrderedDict, defaultdict
from haystack.core.pipeline.utils import _deepcopy_with_exceptions as d

print(type(d(OrderedDict(a=1))).__name__)           # dict
print(type(d(defaultdict(list, a=[1]))).__name__)   # dict
d(defaultdict(list))["missing"]                     # KeyError: 'missing'

Through a pipeline:

from collections import defaultdict
from haystack import Pipeline, component

@component
class Lookup:
    @component.output_types(v=int)
    def run(self, d: dict):
        return {"v": len(d["absent"])}

p = Pipeline()
p.add_component("d", Lookup())
p.run({"d": {"d": defaultdict(list)}})
# PipelineRuntimeError ... Error: 'absent'

Calling Lookup().run(d=defaultdict(list)) directly returns {"v": 0}. Only the pipeline run fails, because the input was converted to a plain dict on the way in.

I ran both snippets twice and got identical output each time.

Expected behavior

The copy keeps the type of a dict subclass, the same way it already does for lists, tuples, sets and namedtuples.

Possible fix

For dict subclasses, shallow copy the container so its type and state are kept, then fill it with copied values:

if isinstance(obj, dict):
    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()}

I tried this on a local copy. OrderedDict, defaultdict and Counter all keep their type, compare equal to the original, and the defaultdict still creates missing keys. I did not run the full test suite. Given the current Haystack contribution state I am leaving this as an issue and am happy to send a PR if a maintainer wants one.

Additional context

Checked against main at 87dba65; haystack/core/pipeline/utils.py there is identical to the file I tested. Python 3.14 locally, Haystack from main. I searched open and closed issues and PRs for this function and for dict subclass copying and found only #11981 (namedtuples), which this is the sibling of.

Disclosure: I used an AI coding assistant to help find and write this up. I ran the reproductions myself and checked the output.

Dominant language
Python
Stars
26.8k
Forks
3.3k
Avg merge
1d 10h
Merged PRs (30d)
260

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from deepset-ai/haystack

All issues in deepset-ai/haystack

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.