Skip to content

Commit a011f64

Browse files
fix(anthropic): Preserve cached _iterator property
1 parent 7ad4e1a commit a011f64

1 file changed

Lines changed: 52 additions & 11 deletions

File tree

‎sentry_sdk/integrations/anthropic.py‎

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import sys
33
from collections.abc import Iterable
4-
from functools import wraps
4+
from functools import cached_property, wraps
55
from typing import TYPE_CHECKING, cast
66

77
import sentry_sdk
@@ -220,6 +220,7 @@ def __exit__(
220220
class AnthropicIntegration(Integration):
221221
identifier = "anthropic"
222222
origin = f"auto.ai.{identifier}"
223+
version = None
223224

224225
def __init__(self: "AnthropicIntegration", include_prompts: bool = True) -> None:
225226
self.include_prompts = include_prompts
@@ -228,6 +229,7 @@ def __init__(self: "AnthropicIntegration", include_prompts: bool = True) -> None
228229
def setup_once() -> None:
229230
version = package_version("anthropic")
230231
_check_minimum_version(AnthropicIntegration, version)
232+
AnthropicIntegration.version = version
231233

232234
"""
233235
client.messages.create(stream=True) can return an instance of the Stream class, which implements the iterator protocol.
@@ -272,6 +274,35 @@ def setup_once() -> None:
272274
if not issubclass(AsyncMessageStream, AsyncStream):
273275
AsyncMessageStream.close = _wrap_async_close(AsyncMessageStream.close) # type: ignore[method-assign]
274276

277+
# In newer anthropic SDK versions, _iterator is a cached_property instead of being eagerly assigned in __init__.
278+
# This is good for us, since we do a global patch instead of patching _iterator on every instance.
279+
# We also should apply a global patch to keep the cached "_iterator" lazy.
280+
if isinstance(Stream.__dict__.get("_iterator"), cached_property):
281+
_original_stream_iterator = Stream._iterator.func # type: ignore[attr-defined]
282+
283+
def _sentry_patched_stream_iterator(self: "Any") -> "Any":
284+
original = _original_stream_iterator(self)
285+
if not hasattr(self, "_span"):
286+
return original
287+
return _wrap_synchronous_message_iterator(self, original)
288+
289+
patched = cached_property(_sentry_patched_stream_iterator)
290+
patched.__set_name__(Stream, "_iterator")
291+
Stream._iterator = patched # type: ignore[assignment,method-assign]
292+
293+
if isinstance(AsyncStream.__dict__.get("_iterator"), cached_property):
294+
_original_async_stream_iterator = AsyncStream._iterator.func # type: ignore[attr-defined]
295+
296+
def _sentry_patched_async_stream_iterator(self: "Any") -> "Any":
297+
original = _original_async_stream_iterator(self)
298+
if not hasattr(self, "_span"):
299+
return original
300+
return _wrap_asynchronous_message_iterator(self, original)
301+
302+
patched = cached_property(_sentry_patched_async_stream_iterator)
303+
patched.__set_name__(AsyncStream, "_iterator")
304+
AsyncStream._iterator = patched # type: ignore[assignment,method-assign]
305+
275306

276307
def _capture_exception(exc: "Any") -> None:
277308
event, hint = event_from_exception(
@@ -783,10 +814,15 @@ def _sentry_patched_create_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any
783814
result._integration = integration # type: ignore[attr-defined]
784815

785816
_initialize_data_accumulation_state(result)
786-
result._iterator = _wrap_synchronous_message_iterator(
787-
cast("_PatchedRawMessageStream", result),
788-
result._iterator,
789-
)
817+
# New versions use global patch since _iterator was changed to a cached property.
818+
if AnthropicIntegration.version is not None and AnthropicIntegration.version < (
819+
1,
820+
14,
821+
):
822+
result._iterator = _wrap_synchronous_message_iterator( # type: ignore[misc]
823+
cast("_PatchedRawMessageStream", result),
824+
result._iterator,
825+
)
790826

791827
return result
792828

@@ -881,10 +917,15 @@ async def _sentry_patched_create_async(
881917
result._integration = integration # type: ignore[attr-defined]
882918

883919
_initialize_data_accumulation_state(result)
884-
result._iterator = _wrap_asynchronous_message_iterator(
885-
cast("_PatchedRawAsyncMessageStream", result),
886-
result._iterator,
887-
)
920+
# New versions use global patch since _iterator was changed to a cached property.
921+
if AnthropicIntegration.version is not None and AnthropicIntegration.version < (
922+
1,
923+
14,
924+
):
925+
result._iterator = _wrap_asynchronous_message_iterator( # type: ignore[misc]
926+
cast("_PatchedRawAsyncMessageStream", result),
927+
result._iterator,
928+
)
888929

889930
return result
890931

@@ -1134,7 +1175,7 @@ def _sentry_patched_enter(self: "MessageStreamManager") -> "MessageStream":
11341175
)
11351176

11361177
try:
1137-
stream = f(self)
1178+
stream: "_PatchedMessageStream" = f(self)
11381179
except Exception as exc:
11391180
exc_info = sys.exc_info()
11401181
with capture_internal_exceptions():
@@ -1247,7 +1288,7 @@ async def _sentry_patched_aenter(
12471288
)
12481289

12491290
try:
1250-
stream = await f(self)
1291+
stream: "_PatchedAsyncMessageStream" = await f(self)
12511292
except Exception as exc:
12521293
exc_info = sys.exc_info()
12531294
with capture_internal_exceptions():

0 commit comments

Comments
 (0)