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

deepset-ai / haystack / 12668719108

08 Jan 2025 10:28AM UTC coverage: 91.024% (+0.007%) from 91.017%
12668719108

push

github

web-flow
refactor: improve serialization/deserialization of callables (to handle class methods and static methods) (#8683)

* progress

* refinements

* tidy up

* release note

8569 of 9414 relevant lines covered (91.02%)

0.91 hits per line

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

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

5
import inspect
1✔
6
from typing import Callable
1✔
7

8
from haystack.core.errors import DeserializationError, SerializationError
1✔
9
from haystack.utils.type_serialization import thread_safe_import
1✔
10

11

12
def serialize_callable(callable_handle: Callable) -> str:
1✔
13
    """
14
    Serializes a callable to its full path.
15

16
    :param callable_handle: The callable to serialize
17
    :return: The full path of the callable
18
    """
19
    try:
1✔
20
        full_arg_spec = inspect.getfullargspec(callable_handle)
1✔
21
        is_instance_method = bool(full_arg_spec.args and full_arg_spec.args[0] == "self")
1✔
22
    except TypeError:
1✔
23
        is_instance_method = False
1✔
24
    if is_instance_method:
1✔
25
        raise SerializationError("Serialization of instance methods is not supported.")
1✔
26

27
    # __qualname__ contains the fully qualified path we need for classmethods and staticmethods
28
    qualname = getattr(callable_handle, "__qualname__", "")
1✔
29
    if "<lambda>" in qualname:
1✔
30
        raise SerializationError("Serialization of lambdas is not supported.")
1✔
31
    if "<locals>" in qualname:
1✔
32
        raise SerializationError("Serialization of nested functions is not supported.")
1✔
33

34
    name = qualname or callable_handle.__name__
1✔
35

36
    # Get the full package path of the function
37
    module = inspect.getmodule(callable_handle)
1✔
38
    if module is not None:
1✔
39
        full_path = f"{module.__name__}.{name}"
1✔
40
    else:
41
        full_path = name
×
42
    return full_path
1✔
43

44

45
def deserialize_callable(callable_handle: str) -> Callable:
1✔
46
    """
47
    Deserializes a callable given its full import path as a string.
48

49
    :param callable_handle: The full path of the callable_handle
50
    :return: The callable
51
    :raises DeserializationError: If the callable cannot be found
52
    """
53
    module_name, *attribute_chain = callable_handle.split(".")
1✔
54

55
    try:
1✔
56
        current = thread_safe_import(module_name)
1✔
57
    except Exception as e:
1✔
58
        raise DeserializationError(f"Could not locate the module: {module_name}") from e
1✔
59

60
    for attr in attribute_chain:
1✔
61
        try:
1✔
62
            attr_value = getattr(current, attr)
1✔
63
        except AttributeError as e:
1✔
64
            raise DeserializationError(f"Could not find attribute '{attr}' in {current.__name__}") from e
1✔
65

66
        # when the attribute is a classmethod, we need the underlying function
67
        if isinstance(attr_value, (classmethod, staticmethod)):
1✔
68
            attr_value = attr_value.__func__
×
69

70
        current = attr_value
1✔
71

72
    if not callable(current):
1✔
73
        raise DeserializationError(f"The final attribute is not callable: {current}")
1✔
74

75
    return current
1✔
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