Skip to content

Commit 3bc4aaf

Browse files
ritikraj2425julian-rischclaude
authored
fix: telemetry decorator overwrites original function metadata (#11569)
Co-authored-by: Julian Risch <julian.risch@deepset.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4dc0c7d commit 3bc4aaf

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

haystack/telemetry/_telemetry.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
import datetime
6+
import functools
67
import logging
78
import os
89
import uuid
@@ -121,7 +122,7 @@ def send_telemetry(func: Callable[..., Any]) -> Callable[..., None]:
121122
The wrapped function is actually called only if telemetry is enabled.
122123
"""
123124

124-
# FIXME? Somehow, functools.wraps makes `telemetry` out of scope. Let's take care of it later.
125+
@functools.wraps(func)
125126
def send_telemetry_wrapper(*args: Any, **kwargs: Any) -> None:
126127
try:
127128
if telemetry:

test/test_telemetry.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from haystack import AsyncPipeline, Pipeline, component
1212
from haystack.core.serialization import generate_qualified_class_name
13-
from haystack.telemetry._telemetry import pipeline_running
13+
from haystack.telemetry._telemetry import pipeline_running, tutorial_running
1414
from haystack.utils.auth import Secret, TokenSecret
1515

1616

@@ -114,3 +114,27 @@ def run(self):
114114
with caplog.at_level(logging.DEBUG):
115115
pipeline_running(pipe)
116116
assert "TypeError: Telemetry data for component my_component must be a dictionary" in caplog.text
117+
118+
119+
def test_send_telemetry_preserves_function_metadata():
120+
"""
121+
Regression test for https://github.com/deepset-ai/haystack/issues/11568.
122+
123+
The ``send_telemetry`` decorator must use ``functools.wraps`` so that decorated functions such as
124+
``pipeline_running`` and ``tutorial_running`` keep their own metadata (``__name__``, ``__doc__`` and
125+
``__annotations__``) instead of exposing the ``send_telemetry_wrapper`` wrapper's.
126+
"""
127+
# ``__name__`` comes from the wrapped function, not the wrapper.
128+
assert pipeline_running.__name__ == "pipeline_running"
129+
assert tutorial_running.__name__ == "tutorial_running"
130+
131+
# ``__doc__`` is preserved.
132+
assert pipeline_running.__doc__ is not None
133+
assert "Collects telemetry data for a pipeline run" in pipeline_running.__doc__
134+
135+
# ``__annotations__`` are preserved, e.g. the wrapped functions' parameters.
136+
assert "pipeline" in pipeline_running.__annotations__
137+
assert "tutorial_id" in tutorial_running.__annotations__
138+
139+
# ``functools.wraps`` also exposes the undecorated function through ``__wrapped__``.
140+
assert pipeline_running.__wrapped__.__name__ == "pipeline_running"

0 commit comments

Comments
 (0)