diff --git a/pyproject.toml b/pyproject.toml index f744e2ae6a..6f14401690 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ typing = [ "pydantic-ai-slim>=2.23.0", "langchain-core>=1.5.3", "huggingface-hub>=1.26.1", + "anthropic>=1.2.0", "google-genai>=2.21.0", ] test = [ diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 2ca633c4c7..761784d012 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -2,7 +2,7 @@ import sys from collections.abc import Iterable from functools import wraps -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import sentry_sdk from sentry_sdk.ai.monitoring import record_token_usage @@ -36,12 +36,12 @@ try: from anthropic import NotGiven except ImportError: - NotGiven = None + NotGiven = None # type: ignore[misc,assignment] try: from anthropic import Omit except ImportError: - Omit = None + Omit = None # type: ignore[misc,assignment] from anthropic import AsyncStream, Stream from anthropic.lib.streaming import ( @@ -69,13 +69,15 @@ from typing import ( Any, AsyncIterator, - Awaitable, Callable, + Coroutine, Iterator, Optional, + TypeVar, Union, ) + from anthropic.lib.streaming import ParsedMessageStreamEvent from anthropic.types import ( MessageParam, ModelParam, @@ -86,6 +88,74 @@ from sentry_sdk._types import TextPart + class _PatchedRawMessageStream(Stream[RawMessageStreamEvent]): + _span: Span + _integration: "AnthropicIntegration" + + _model: Optional[ModelParam] + _usage: "_RecordedUsage" + _content_blocks: list[Any] + _response_id: Optional[str] + _finish_reason: Optional[str] + + class _PatchedMessageStream(MessageStream): + _span: Span + _integration: "AnthropicIntegration" + + _model: Optional[ModelParam] + _usage: "_RecordedUsage" + _content_blocks: list[Any] + _response_id: Optional[str] + _finish_reason: Optional[str] + + class _PatchedRawAsyncMessageStream(AsyncStream[RawMessageStreamEvent]): + _span: Span + _integration: "AnthropicIntegration" + + _model: Optional[ModelParam] + _usage: "_RecordedUsage" + _content_blocks: list[Any] + _response_id: Optional[str] + _finish_reason: Optional[str] + + class _PatchedAsyncMessageStream(AsyncMessageStream): + _span: Span + _integration: "AnthropicIntegration" + + _model: Optional[ModelParam] + _usage: "_RecordedUsage" + _content_blocks: list[Any] + _response_id: Optional[str] + _finish_reason: Optional[str] + + class _PatchedMessageStreamManager(MessageStreamManager): + _span: Union[Span, StreamedSpan] + _integration: "AnthropicIntegration" + + _max_tokens: int + _messages: Iterable[MessageParam] + _model: Optional[ModelParam] + _system: Optional[Union[str, Iterable[TextBlockParam]]] + _temperature: Optional[float] + _top_k: Optional[int] + _top_p: Optional[float] + _tools: Optional[Iterable[ToolUnionParam]] + + class _PatchedAsyncMessageStreamManager(AsyncMessageStreamManager[Any]): + _span: Union[Span, StreamedSpan] + _integration: "AnthropicIntegration" + + _max_tokens: int + _messages: Iterable[MessageParam] + _model: Optional[ModelParam] + _system: Optional[Union[str, Iterable[TextBlockParam]]] + _temperature: Optional[float] + _top_k: Optional[int] + _top_p: Optional[float] + _tools: Optional[Iterable[ToolUnionParam]] + + _EventT = TypeVar("_EventT", RawMessageStreamEvent, "ParsedMessageStreamEvent[Any]") + class _RecordedUsage: output_tokens: int = 0 @@ -102,7 +172,7 @@ class _StreamSpanContext: def __init__( self, - stream: "Union[Stream, MessageStream, AsyncStream, AsyncMessageStream]", + stream: "Union[Stream[RawMessageStreamEvent], MessageStream, AsyncStream[RawMessageStreamEvent], AsyncMessageStream]", # Flag to avoid unreachable branches when the stream state is known to be initialized (stream._model, etc. are set). guaranteed_streaming_state: bool = False, ) -> None: @@ -122,25 +192,30 @@ def __exit__( if not hasattr(self._stream, "_span"): return + patched_stream = cast( + "Union[_PatchedRawMessageStream, _PatchedMessageStream, _PatchedRawAsyncMessageStream, _PatchedAsyncMessageStream]", + self._stream, + ) + if not self._guaranteed_streaming_state and not hasattr( - self._stream, "_model" + patched_stream, "_model" ): - self._stream._span.__exit__(exc_type, exc_val, exc_tb) - del self._stream._span + patched_stream._span.__exit__(exc_type, exc_val, exc_tb) + del patched_stream._span return _set_streaming_output_data( - span=self._stream._span, - integration=self._stream._integration, - model=self._stream._model, - usage=self._stream._usage, - content_blocks=self._stream._content_blocks, - response_id=self._stream._response_id, - finish_reason=self._stream._finish_reason, + span=patched_stream._span, + integration=patched_stream._integration, + model=patched_stream._model, + usage=patched_stream._usage, + content_blocks=patched_stream._content_blocks, + response_id=patched_stream._response_id, + finish_reason=patched_stream._finish_reason, ) - self._stream._span.__exit__(exc_type, exc_val, exc_tb) - del self._stream._span + patched_stream._span.__exit__(exc_type, exc_val, exc_tb) + del patched_stream._span class AnthropicIntegration(Integration): @@ -167,27 +242,27 @@ def setup_once() -> None: Both paths may run. For example, the context manager exit can follow iterator exhaustion. """ - Messages.create = _wrap_message_create(Messages.create) - Stream.close = _wrap_close(Stream.close) + Messages.create = _wrap_message_create(Messages.create) # type: ignore[method-assign] + Stream.close = _wrap_close(Stream.close) # type: ignore[method-assign] - AsyncMessages.create = _wrap_message_create_async(AsyncMessages.create) - AsyncStream.close = _wrap_async_close(AsyncStream.close) + AsyncMessages.create = _wrap_message_create_async(AsyncMessages.create) # type: ignore[method-assign] + AsyncStream.close = _wrap_async_close(AsyncStream.close) # type: ignore[method-assign] """ client.messages.stream() patches are analogous to the patches for client.messages.create(stream=True) described above. """ - Messages.stream = _wrap_message_stream(Messages.stream) - MessageStreamManager.__enter__ = _wrap_message_stream_manager_enter( + Messages.stream = _wrap_message_stream(Messages.stream) # type: ignore[method-assign] + MessageStreamManager.__enter__ = _wrap_message_stream_manager_enter( # type: ignore[method-assign] MessageStreamManager.__enter__ ) # Before https://github.com/anthropics/anthropic-sdk-python/commit/b1a1c0354a9aca450a7d512fdbdeb59c0ead688a # MessageStream inherits from Stream, so patching Stream is sufficient on these versions. if not issubclass(MessageStream, Stream): - MessageStream.close = _wrap_close(MessageStream.close) + MessageStream.close = _wrap_close(MessageStream.close) # type: ignore[method-assign] - AsyncMessages.stream = _wrap_async_message_stream(AsyncMessages.stream) - AsyncMessageStreamManager.__aenter__ = ( + AsyncMessages.stream = _wrap_async_message_stream(AsyncMessages.stream) # type: ignore[method-assign] + AsyncMessageStreamManager.__aenter__ = ( # type: ignore[method-assign] _wrap_async_message_stream_manager_aenter( AsyncMessageStreamManager.__aenter__ ) @@ -196,7 +271,7 @@ def setup_once() -> None: # Before https://github.com/anthropics/anthropic-sdk-python/commit/b1a1c0354a9aca450a7d512fdbdeb59c0ead688a # AsyncMessageStream inherits from AsyncStream, so patching Stream is sufficient on these versions. if not issubclass(AsyncMessageStream, AsyncStream): - AsyncMessageStream.close = _wrap_async_close(AsyncMessageStream.close) + AsyncMessageStream.close = _wrap_async_close(AsyncMessageStream.close) # type: ignore[method-assign] def _capture_exception(exc: "Any") -> None: @@ -376,7 +451,7 @@ def _set_common_input_data( integration: "AnthropicIntegration", max_tokens: "int", messages: "Iterable[MessageParam]", - model: "ModelParam", + model: "Optional[ModelParam]", system: "Optional[Union[str, Iterable[TextBlockParam]]]", temperature: "Optional[float]", top_k: "Optional[int]", @@ -434,7 +509,7 @@ def _set_common_input_data( json.dumps(_transform_system_instructions(system)), ) - normalized_messages = [] + normalized_messages: "list[Any]" = [] for message in messages: if ( message.get("role") == GEN_AI_ALLOWED_MESSAGE_ROLES.USER @@ -465,7 +540,7 @@ def _set_common_input_data( ) else: # Transform content for non-list messages or assistant messages - transformed_message = message.copy() + transformed_message: "dict[str, Any]" = dict(message) if "content" in transformed_message: content = transformed_message["content"] if isinstance(content, (list, tuple)): @@ -524,9 +599,9 @@ def _set_create_input_data( def _wrap_synchronous_message_iterator( - stream: "Union[Stream, MessageStream]", - iterator: "Iterator[Union[RawMessageStreamEvent, MessageStreamEvent]]", -) -> "Iterator[Union[RawMessageStreamEvent, MessageStreamEvent]]": + stream: "Union[_PatchedRawMessageStream, _PatchedMessageStream]", + iterator: "Iterator[_EventT]", +) -> "Iterator[_EventT]": """ Sets information received while iterating the response stream on the AI Client Span. Responsible for closing the AI Client Span unless the span has already been closed in the close() patch. @@ -554,9 +629,9 @@ def _wrap_synchronous_message_iterator( async def _wrap_asynchronous_message_iterator( - stream: "Union[AsyncStream, AsyncMessageStream]", - iterator: "AsyncIterator[Union[RawMessageStreamEvent, MessageStreamEvent]]", -) -> "AsyncIterator[Union[RawMessageStreamEvent, MessageStreamEvent]]": + stream: "Union[_PatchedRawAsyncMessageStream, _PatchedAsyncMessageStream]", + iterator: "AsyncIterator[_EventT]", +) -> "AsyncIterator[_EventT]": """ Sets information received while iterating the response stream on the AI Client Span. Responsible for closing the AI Client Span unless the span has already been closed in the close() patch. @@ -695,12 +770,12 @@ def _sentry_patched_create_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any reraise(*exc_info) if isinstance(result, Stream): - result._span = span - result._integration = integration + result._span = span # type: ignore[attr-defined] + result._integration = integration # type: ignore[attr-defined] _initialize_data_accumulation_state(result) result._iterator = _wrap_synchronous_message_iterator( - result, + cast("_PatchedRawMessageStream", result), result._iterator, ) @@ -793,12 +868,12 @@ async def _sentry_patched_create_async( reraise(*exc_info) if isinstance(result, AsyncStream): - result._span = span - result._integration = integration + result._span = span # type: ignore[attr-defined] + result._integration = integration # type: ignore[attr-defined] _initialize_data_accumulation_state(result) result._iterator = _wrap_asynchronous_message_iterator( - result, + cast("_PatchedRawAsyncMessageStream", result), result._iterator, ) @@ -853,21 +928,23 @@ def _sentry_wrapped_create_sync(*args: "Any", **kwargs: "Any") -> "Any": return _sentry_wrapped_create_sync -def _initialize_data_accumulation_state(stream: "Union[Stream, MessageStream]") -> None: +def _initialize_data_accumulation_state( + stream: "Union[Stream[RawMessageStreamEvent], MessageStream, AsyncStream[RawMessageStreamEvent], AsyncMessageStream]", +) -> None: """ Initialize fields for accumulating output on the Stream instance. """ if not hasattr(stream, "_model"): - stream._model = None - stream._usage = _RecordedUsage() - stream._content_blocks = [] - stream._response_id = None - stream._finish_reason = None + stream._model = None # type: ignore[union-attr] + stream._usage = _RecordedUsage() # type: ignore[union-attr] + stream._content_blocks = [] # type: ignore[union-attr] + stream._response_id = None # type: ignore[union-attr] + stream._finish_reason = None # type: ignore[union-attr] def _accumulate_event_data( - stream: "Union[Stream, MessageStream]", - event: "Union[RawMessageStreamEvent, MessageStreamEvent]", + stream: "Union[_PatchedRawMessageStream, _PatchedMessageStream, _PatchedRawAsyncMessageStream, _PatchedAsyncMessageStream]", + event: "RawMessageStreamEvent", ) -> None: """ Update accumulated output from a single stream event. @@ -929,7 +1006,7 @@ def _wrap_close( Closes the AI Client Span unless the finally block in `_wrap_synchronous_message_iterator()` runs first. """ - def close(self: "Union[Stream, MessageStream]") -> None: + def close(self: "Union[Stream[RawMessageStreamEvent], MessageStream]") -> None: with _StreamSpanContext(self): return f(self) @@ -948,13 +1025,13 @@ async def _sentry_wrapped_create_async(*args: "Any", **kwargs: "Any") -> "Any": def _wrap_async_close( - f: "Callable[..., Awaitable[None]]", -) -> "Callable[..., Awaitable[None]]": + f: "Callable[..., Coroutine[Any, Any, None]]", +) -> "Callable[..., Coroutine[Any, Any, None]]": """ Closes the AI Client Span unless the finally block in `_wrap_asynchronous_message_iterator()` runs first. """ - async def close(self: "AsyncStream") -> None: + async def close(self: "AsyncStream[RawMessageStreamEvent]") -> None: with _StreamSpanContext(self): return await f(self) @@ -995,23 +1072,27 @@ def _sentry_patched_enter(self: "MessageStreamManager") -> "MessageStream": if not hasattr(self, "_max_tokens"): return f(self) + patched_self = cast("_PatchedMessageStreamManager", self) + client = sentry_sdk.get_client() integration = client.get_integration(AnthropicIntegration) if integration is None: return f(self) - if self._messages is None: + if patched_self._messages is None: return f(self) try: - iter(self._messages) + iter(patched_self._messages) except TypeError: return f(self) if has_span_streaming_enabled(client.options): span = sentry_sdk.traces.start_span( - name="chat" if self._model is None else f"chat {self._model}".strip(), + name="chat" + if patched_self._model is None + else f"chat {patched_self._model}".strip(), attributes={ "sentry.op": OP.GEN_AI_CHAT, "sentry.origin": AnthropicIntegration.origin, @@ -1021,7 +1102,9 @@ def _sentry_patched_enter(self: "MessageStreamManager") -> "MessageStream": else: span = get_start_span_function()( op=OP.GEN_AI_CHAT, - name="chat" if self._model is None else f"chat {self._model}".strip(), + name="chat" + if patched_self._model is None + else f"chat {patched_self._model}".strip(), origin=AnthropicIntegration.origin, ) span.__enter__() @@ -1031,14 +1114,14 @@ def _sentry_patched_enter(self: "MessageStreamManager") -> "MessageStream": _set_common_input_data( span=span, integration=integration, - max_tokens=self._max_tokens, - messages=self._messages, - model=self._model, - system=self._system, - temperature=self._temperature, - top_k=self._top_k, - top_p=self._top_p, - tools=self._tools, + max_tokens=patched_self._max_tokens, + messages=patched_self._messages, + model=patched_self._model, + system=patched_self._system, + temperature=patched_self._temperature, + top_k=patched_self._top_k, + top_p=patched_self._top_p, + tools=patched_self._tools, ) try: @@ -1102,23 +1185,27 @@ async def _sentry_patched_aenter( if not hasattr(self, "_max_tokens"): return await f(self) + patched_self = cast("_PatchedAsyncMessageStreamManager", self) + client = sentry_sdk.get_client() integration = client.get_integration(AnthropicIntegration) if integration is None: return await f(self) - if self._messages is None: + if patched_self._messages is None: return await f(self) try: - iter(self._messages) + iter(patched_self._messages) except TypeError: return await f(self) if has_span_streaming_enabled(client.options): span = sentry_sdk.traces.start_span( - name="chat" if self._model is None else f"chat {self._model}".strip(), + name="chat" + if patched_self._model is None + else f"chat {patched_self._model}".strip(), attributes={ "sentry.op": OP.GEN_AI_CHAT, "sentry.origin": AnthropicIntegration.origin, @@ -1128,7 +1215,9 @@ async def _sentry_patched_aenter( else: span = get_start_span_function()( op=OP.GEN_AI_CHAT, - name="chat" if self._model is None else f"chat {self._model}".strip(), + name="chat" + if patched_self._model is None + else f"chat {patched_self._model}".strip(), origin=AnthropicIntegration.origin, ) span.__enter__() @@ -1138,14 +1227,14 @@ async def _sentry_patched_aenter( _set_common_input_data( span=span, integration=integration, - max_tokens=self._max_tokens, - messages=self._messages, - model=self._model, - system=self._system, - temperature=self._temperature, - top_k=self._top_k, - top_p=self._top_p, - tools=self._tools, + max_tokens=patched_self._max_tokens, + messages=patched_self._messages, + model=patched_self._model, + system=patched_self._system, + temperature=patched_self._temperature, + top_k=patched_self._top_k, + top_p=patched_self._top_p, + tools=patched_self._tools, ) try: diff --git a/uv.lock b/uv.lock index 835748c9df..c31dc21a50 100644 --- a/uv.lock +++ b/uv.lock @@ -40,6 +40,7 @@ toxgen = [ { name = "requests" }, ] typing = [ + { name = "anthropic", specifier = ">=1.2.0" }, { name = "botocore-stubs" }, { name = "certifi" }, { name = "google-genai", specifier = ">=2.21.0" }, @@ -100,6 +101,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, ] +[[package]] +name = "anthropic" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "docstring-parser" }, + { name = "httpx2" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/1a/b5af41cc1fa14da277ec20ca5554dd2fcbc09b8523ac59b7a97fbb88e452/anthropic-1.2.0.tar.gz", hash = "sha256:12f8eedee7b7fb5685837b1371b7bfae1b281703f62355f4632598ec2fc53b34", size = 1137443, upload-time = "2026-08-27T20:29:12.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/78/3f8b52708b03309e511990700bb8d0ec7a0c9db3d2a1e0d1c3ca417a4604/anthropic-1.2.0-py3-none-any.whl", hash = "sha256:b60642b3e3cd6b8e3e328a2d3f2863ad2b6e743f1037e42cc0143f7df99f63c6", size = 1289535, upload-time = "2026-08-27T20:29:11.01Z" }, +] + [[package]] name = "anyio" version = "4.14.2" @@ -630,6 +649,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] +[[package]] +name = "docstring-parser" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -1119,6 +1147,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jiter" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239, upload-time = "2026-06-29T13:03:57.205Z" }, + { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928, upload-time = "2026-06-29T13:03:58.643Z" }, + { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998, upload-time = "2026-06-29T13:04:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112, upload-time = "2026-06-29T13:04:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807, upload-time = "2026-06-29T13:04:03.214Z" }, + { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181, upload-time = "2026-06-29T13:04:04.629Z" }, + { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927, upload-time = "2026-06-29T13:04:06.067Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754, upload-time = "2026-06-29T13:04:07.477Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553, upload-time = "2026-06-29T13:04:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900, upload-time = "2026-06-29T13:04:10.407Z" }, + { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754, upload-time = "2026-06-29T13:04:12.046Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381, upload-time = "2026-06-29T13:04:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578, upload-time = "2026-06-29T13:04:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154, upload-time = "2026-06-29T13:04:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458, upload-time = "2026-06-29T13:04:17.707Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739, upload-time = "2026-06-29T13:04:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911, upload-time = "2026-06-29T13:04:21.257Z" }, + { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747, upload-time = "2026-06-29T13:04:22.677Z" }, + { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225, upload-time = "2026-06-29T13:04:24.441Z" }, + { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169, upload-time = "2026-06-29T13:04:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332, upload-time = "2026-06-29T13:04:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377, upload-time = "2026-06-29T13:04:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746, upload-time = "2026-06-29T13:04:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292, upload-time = "2026-06-29T13:04:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259, upload-time = "2026-06-29T13:04:33.721Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" }, +] + [[package]] name = "jsonpatch" version = "1.33"