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

deepset-ai / haystack / 13109457248

03 Feb 2025 08:37AM UTC coverage: 91.362% (+0.01%) from 91.352%
13109457248

Pull #8788

github

web-flow
Merge 86505eb46 into 379711f63
Pull Request #8788: fix: callables can be deserialized from fully qualified import path

8874 of 9713 relevant lines covered (91.36%)

0.91 hits per line

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

95.35
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 Any, 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
    parts = callable_handle.split(".")
1✔
54

55
    for i in range(len(parts), 0, -1):
1✔
56
        module_name = ".".join(parts[:i])
1✔
57
        try:
1✔
58
            mod: Any = thread_safe_import(module_name)
1✔
59
        except Exception:
1✔
60
            # keep reducing i until we find a valid module import
61
            continue
1✔
62

63
        attr_value = mod
1✔
64
        for part in parts[i:]:
1✔
65
            try:
1✔
66
                attr_value = getattr(attr_value, part)
1✔
67
            except AttributeError as e:
1✔
68
                raise DeserializationError(f"Could not find attribute '{part}' in {attr_value.__name__}") from e
1✔
69

70
        # when the attribute is a classmethod, we need the underlying function
71
        if isinstance(attr_value, (classmethod, staticmethod)):
1✔
72
            attr_value = attr_value.__func__
×
73

74
        if not callable(attr_value):
1✔
75
            raise DeserializationError(f"The final attribute is not callable: {attr_value}")
1✔
76

77
        return attr_value
1✔
78

79
    # Fallback if we never find anything
80
    raise DeserializationError(f"Could not import '{callable_handle}' as a module or callable.")
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

© 2025 Coveralls, Inc