• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

deepset-ai / haystack / 15113278803

19 May 2025 12:42PM UTC coverage: 90.425% (-0.02%) from 90.445%
15113278803

Pull #9345

github

web-flow
Merge 5f892e200 into 707573d96
Pull Request #9345: feat: add serialization to `State` / move `State` to utils

11002 of 12167 relevant lines covered (90.42%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.02
haystack/utils/base_serialization.py
1
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
#
3
# SPDX-License-Identifier: Apache-2.0
4

5
from typing import Any, Dict
1✔
6

7
from haystack.core.errors import DeserializationError, SerializationError
1✔
8
from haystack.core.serialization import generate_qualified_class_name, import_class_by_name
1✔
9

10

11
def serialize_class_instance(obj: Any) -> Dict[str, Any]:
1✔
12
    """
13
    Serializes an object that has a `to_dict` method into a dictionary.
14

15
    :param obj:
16
        The object to be serialized.
17
    :returns:
18
        A dictionary representation of the object.
19
    :raises SerializationError:
20
        If the object does not have a `to_dict` method.
21
    """
22
    if not hasattr(obj, "to_dict"):
1✔
23
        raise SerializationError(f"Object of class '{type(obj).__name__}' does not have a 'to_dict' method")
1✔
24

25
    output = obj.to_dict()
1✔
26
    return {"type": generate_qualified_class_name(type(obj)), "data": output}
1✔
27

28

29
def deserialize_class_instance(data: Dict[str, Any]) -> Any:
1✔
30
    """
31
    Deserializes an object from a dictionary representation generated by `auto_serialize_class_instance`.
32

33
    :param data:
34
        The dictionary to deserialize from.
35
    :returns:
36
        The deserialized object.
37
    :raises DeserializationError:
38
        If the serialization data is malformed, the class type cannot be imported, or the
39
        class does not have a `from_dict` method.
40
    """
41
    if "type" not in data:
1✔
42
        raise DeserializationError("Missing 'type' in serialization data")
1✔
43
    if "data" not in data:
1✔
44
        raise DeserializationError("Missing 'data' in serialization data")
1✔
45

46
    try:
1✔
47
        obj_class = import_class_by_name(data["type"])
1✔
48
    except ImportError as e:
1✔
49
        raise DeserializationError(f"Class '{data['type']}' not correctly imported") from e
1✔
50

51
    if not hasattr(obj_class, "from_dict"):
1✔
52
        raise DeserializationError(f"Class '{data['type']}' does not have a 'from_dict' method")
1✔
53

54
    return obj_class.from_dict(data["data"])
1✔
55

56

57
def serialize_value(value: Any) -> Any:
1✔
58
    """
59
    Serializes a value into a format suitable for storage or transmission.
60

61
    Handles various types including:
62
    - Haystack dataclass objects (Answer, Document, etc.)
63
    - Primitive types (returned as is)
64
    - Lists of primitives (returned as is)
65
    - Lists of complex types (recursively serialized)
66
    - Dictionaries (recursively serialized)
67

68
    :param value: The value to serialize
69
    :returns: The serialized representation of the value
70
    """
71
    if hasattr(value, "to_dict") and callable(value.to_dict):
1✔
72
        serialized_value = value.to_dict()
1✔
73
        serialized_value["_type"] = generate_qualified_class_name(type(value))
1✔
74
        return serialized_value
1✔
75

76
    # this is a hack to serialize inputs that don't have a to_dict
77
    elif hasattr(value, "__dict__"):
1✔
78
        return {
×
79
            "_type": generate_qualified_class_name(type(value)),
80
            "attributes": {k: serialize_value(v) for k, v in value.__dict__.items()},
81
        }
82

83
    # recursively serialize all inputs in a dict
84
    elif isinstance(value, dict):
1✔
85
        return {k: serialize_value(v) for k, v in value.items()}
1✔
86

87
    # recursively serialize all inputs in lists or tuples
88
    elif isinstance(value, (list, tuple, set)):
1✔
89
        return type(value)(serialize_value(v) for v in value)
1✔
90

91
    # None and primitives fall through
92
    return value
1✔
93

94

95
def deserialize_value(value: Any) -> Any:
1✔
96
    """
97
    Deserializes a value from its serialized representation.
98

99
    - Primitives and None are returned as-is.
100
    - dicts with a "_type" key are turned back into instances:
101
        * If the target class has a `from_dict`, it's used.
102
        * Otherwise, if an "attributes" sub-dict exists, we reconstruct
103
          by creating a blank instance and setting attributes.
104
    - Other dicts, lists, tuples, and sets are recursively deserialized.
105

106
    :param value: The serialized value to deserialize
107
    :returns: The deserialized value
108
    """
109
    # 1) Primitives & None
110
    if value is None or isinstance(value, (str, int, float, bool)):
1✔
111
        return value
1✔
112

113
    # 2) Typed objects
114
    if isinstance(value, dict) and "_type" in value:
1✔
115
        type_name = value.pop("_type")  # remove without mutating original
1✔
116
        obj_class = import_class_by_name(type_name)
1✔
117

118
        # a) Preferred: class method
119
        if hasattr(obj_class, "from_dict") and callable(obj_class.from_dict):
1✔
120
            deserialized_payload = {k: deserialize_value(v) for k, v in value.items()}
1✔
121
            return obj_class.from_dict(deserialized_payload)
1✔
122

123
        # b) Fallback: if attributes are present, we reconstruct the object
124
        if "attributes" in value:
×
125
            raw_attrs = value["attributes"]
×
126
            deserialized_attrs = {k: deserialize_value(v) for k, v in raw_attrs.items()}
×
127
            instance = obj_class.__new__(obj_class)
×
128
            for attr_name, attr_value in deserialized_attrs.items():
×
129
                setattr(instance, attr_name, attr_value)
×
130
            return instance
×
131

132
    # 3) Generic dict
133
    if isinstance(value, dict):
1✔
134
        return {k: deserialize_value(v) for k, v in value.items()}
1✔
135

136
    # 4) Collections
137
    if isinstance(value, (list, tuple, set)):
1✔
138
        return type(value)(deserialize_value(v) for v in value)
1✔
139

140
    # 5) Anything else
141
    return value
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc