-
Notifications
You must be signed in to change notification settings - Fork 457
Description
Tracer Version(s)
3.12.2
Python Version(s)
3.13.5
Pip Version(s)
Using "uv" as package manager, version 0.7.13
Bug Report
Thank you for maintaining datadog and it's Python SDK, I am generally very happy with the tooling!
However, I noticed the SDK does not correctly wrap async "generators" (!). It only handles iterators correctly. A generator is a generalization of an iterator that can also receive values, not only yield them.
See the following code:
import asyncio
from collections.abc import AsyncGenerator
from ddtrace.trace import tracer
# @tracer.wrap()
async def accumulator() -> AsyncGenerator[int, int]:
total = 0
while True:
incoming = yield total
total += incoming
async def main() -> None:
acc = accumulator()
await anext(acc) # Start the generator
for i in range(5):
print(f"Current value: {await acc.asend(i)}")
asyncio.run(main())
If you run this as-is, it will sum up the values from range(5)
:
Current value: 0
Current value: 1
Current value: 3
Current value: 6
Current value: 10
However, if you uncomment the @tracer.wrap()
decorator, it will crash with:
Traceback (most recent call last):
File "/redacted/tmp.py", line 23, in <module>
asyncio.run(main())
~~~~~~~~~~~^^^^^^^^
File "~/.local/share/uv/python/cpython-3.13.5-macos-aarch64-none/lib/python3.13/asyncio/runners.py", line 195, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "~/.local/share/uv/python/cpython-3.13.5-macos-aarch64-none/lib/python3.13/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "~/.local/share/uv/python/cpython-3.13.5-macos-aarch64-none/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "/redacted/tmp.py", line 20, in main
print(f"Current value: {await acc.asend(i)}")
^^^^^^^^^^^^^^^^^^
File "/redacted/.venv/lib/python3.13/site-packages/ddtrace/_trace/tracer.py", line 793, in func_wrapper
async for value in f(*args, **kwargs):
yield value
File "/redacted/tmp.py", line 12, in accumulator
total += incoming
TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType'
Looking at the implementation, I saw that you only process iterators correctly (that don't receive values from the outside), but not generic generators:
dd-trace-py/ddtrace/_trace/tracer.py
Lines 732 to 748 in 77dc3ae
def _wrap_generator_async( | |
self, | |
f: AnyCallable, | |
span_name: str, | |
service: Optional[str] = None, | |
resource: Optional[str] = None, | |
span_type: Optional[str] = None, | |
) -> AnyCallable: | |
"""Wrap a generator function with tracing.""" | |
@functools.wraps(f) | |
async def func_wrapper(*args, **kwargs): | |
with self.trace(span_name, service=service, resource=resource, span_type=span_type): | |
async for value in f(*args, **kwargs): | |
yield value | |
return cast(AnyCallable, func_wrapper) |
Reproduction Code
No response
Error Logs
No response
Libraries in Use
No response
Operating System
No response