11import json
22import sys
33from collections .abc import Iterable
4- from functools import wraps
4+ from functools import cached_property , wraps
55from typing import TYPE_CHECKING , cast
66
77import sentry_sdk
@@ -220,6 +220,7 @@ def __exit__(
220220class 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
276307def _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