Skip to content

Commit f63bbd9

Browse files
merge master
2 parents c280241 + 8af6a69 commit f63bbd9

24 files changed

Lines changed: 868 additions & 414 deletions

File tree

‎.github/workflows/test-integrations-network.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ jobs:
4141
run: |
4242
set -x # print commands that are executed
4343
./scripts/runtox.sh "py${{ matrix.python-version }}-grpc"
44-
- name: Test httpx
44+
- name: Test httpx0
4545
run: |
4646
set -x # print commands that are executed
47-
./scripts/runtox.sh "py${{ matrix.python-version }}-httpx"
47+
./scripts/runtox.sh "py${{ matrix.python-version }}-httpx0"
4848
- name: Test httpx2
4949
run: |
5050
set -x # print commands that are executed

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ typing = [
7777
"anthropic>=1.2.0",
7878
"google-genai>=2.21.0",
7979
"openai>=3.7.0",
80+
"tiktoken>=0.14.0",
81+
"aiohttp>=3.14.3",
82+
"openai-agents>=0.22.0",
8083
]
8184
test = [
8285
"dataclasses ; python_full_version < '3.7'",

‎scripts/populate_tox/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ a Python version older than 3.9 if the HTTPX version is 0.28 or higher, you can
146146
say:
147147

148148
```python
149-
"httpx": {
149+
"httpx0": {
150150
"python": {
151151
# run the test suite for httpx v0.28+ on Python 3.9+ only
152152
">=0.28": ">=3.9",

‎scripts/populate_tox/config.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,9 @@
205205
},
206206
"python": ">=3.7",
207207
},
208-
"httpx": {
208+
"httpx0": {
209209
"package": "httpx",
210+
"integration_name": "httpx",
210211
"deps": {
211212
"*": ["anyio>=3,<5", "pytest-asyncio"],
212213
"<0.24": ["anyio<4"],

‎scripts/split_tox_gh_actions/split_tox_gh_actions.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
],
129129
"Network": [
130130
"grpc",
131-
"httpx",
131+
"httpx0",
132132
"httpx2",
133133
"pyreqwest",
134134
"requests",

‎sentry_sdk/integrations/aiohttp.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ async def sentry_app_handle(
310310

311311
return response
312312

313-
Application._handle = sentry_app_handle
313+
Application._handle = sentry_app_handle # type: ignore[method-assign]
314314

315315
old_urldispatcher_resolve = UrlDispatcher.resolve
316316

@@ -350,7 +350,7 @@ async def sentry_urldispatcher_resolve(
350350

351351
return rv
352352

353-
UrlDispatcher.resolve = sentry_urldispatcher_resolve
353+
UrlDispatcher.resolve = sentry_urldispatcher_resolve # type: ignore[method-assign]
354354

355355
old_client_session_init = ClientSession.__init__
356356

@@ -363,7 +363,7 @@ def init(*args: "Any", **kwargs: "Any") -> None:
363363
kwargs["trace_configs"] = client_trace_configs
364364
return old_client_session_init(*args, **kwargs)
365365

366-
ClientSession.__init__ = init
366+
ClientSession.__init__ = init # type: ignore[method-assign]
367367

368368

369369
def create_trace_config() -> "TraceConfig":

‎sentry_sdk/integrations/langchain.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from sentry_sdk.utils import (
2828
capture_internal_exceptions,
29+
event_from_exception,
2930
has_data_collection_enabled,
3031
logger,
3132
)
@@ -266,6 +267,15 @@ def setup_once() -> None:
266267
_patch_embeddings_provider(OllamaEmbeddings)
267268

268269

270+
def _capture_exception(exc: "Any", scope: "Optional[Any]" = None) -> None:
271+
event, hint = event_from_exception(
272+
exc,
273+
client_options=sentry_sdk.get_client().options,
274+
mechanism={"type": "langchain", "handled": False},
275+
)
276+
sentry_sdk.capture_event(event, hint=hint, scope=scope)
277+
278+
269279
class SentryLangchainCallback(BaseCallbackHandler):
270280
"""Callback handler that creates Sentry spans."""
271281

@@ -294,7 +304,7 @@ def _handle_error(self, run_id: "UUID", error: "Any") -> None:
294304
if is_ignored:
295305
span.__exit__(None, None, None)
296306
else:
297-
sentry_sdk.capture_exception(
307+
_capture_exception(
298308
error, span._scope if isinstance(span, StreamedSpan) else span.scope
299309
)
300310
span.__exit__(type(error), error, error.__traceback__)

‎sentry_sdk/integrations/mcp.py‎

Lines changed: 55 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from sentry_sdk.traces import StreamedSpan
2222
from sentry_sdk.tracing_utils import has_span_streaming_enabled
2323
from sentry_sdk.utils import (
24+
capture_internal_exceptions,
25+
event_from_exception,
2426
has_data_collection_enabled,
2527
nullcontext,
2628
package_version,
@@ -100,6 +102,15 @@ def setup_once() -> None:
100102
_patch_fastmcp()
101103

102104

105+
def _capture_exception(exc: "Any") -> None:
106+
event, hint = event_from_exception(
107+
exc,
108+
client_options=sentry_sdk.get_client().options,
109+
mechanism={"type": "mcp", "handled": False},
110+
)
111+
sentry_sdk.capture_event(event, hint=hint)
112+
113+
103114
@contextmanager
104115
def _active_http_scopes(
105116
ctx: "Any",
@@ -259,57 +270,6 @@ def _extract_text_from_content_blocks(content_blocks: "Any") -> "Any":
259270
return " ".join(texts) if texts else content_blocks
260271

261272

262-
def _extract_handler_data_from_args(
263-
handler_type: str,
264-
original_args: "tuple[Any, ...]",
265-
original_kwargs: "Optional[dict[str, Any]]" = None,
266-
) -> "tuple[str, dict[str, Any]]":
267-
"""
268-
Extract handler name and arguments from v1 positional args.
269-
270-
In MCP SDK v1, handlers receive positional args:
271-
- Tool: (tool_name, arguments)
272-
- Prompt: (name, arguments)
273-
- Resource: (uri,)
274-
"""
275-
original_kwargs = original_kwargs or {}
276-
277-
if handler_type == "tool":
278-
if original_args:
279-
handler_name = original_args[0]
280-
elif original_kwargs.get("name"):
281-
handler_name = original_kwargs["name"]
282-
283-
arguments = {}
284-
if len(original_args) > 1:
285-
arguments = original_args[1]
286-
elif original_kwargs.get("arguments"):
287-
arguments = original_kwargs["arguments"]
288-
289-
elif handler_type == "prompt":
290-
if original_args:
291-
handler_name = original_args[0]
292-
elif original_kwargs.get("name"):
293-
handler_name = original_kwargs["name"]
294-
295-
arguments = {}
296-
if len(original_args) > 1:
297-
arguments = original_args[1]
298-
elif original_kwargs.get("arguments"):
299-
arguments = original_kwargs["arguments"]
300-
301-
else: # resource
302-
handler_name = "unknown"
303-
if original_args:
304-
handler_name = str(original_args[0])
305-
elif original_kwargs.get("uri"):
306-
handler_name = str(original_kwargs["uri"])
307-
308-
arguments = {}
309-
310-
return handler_name, arguments
311-
312-
313273
async def _tool_handler_wrapper(
314274
func: "Callable[..., Awaitable[Union[CallToolResult, InputRequiredResult]]]",
315275
original_args: "tuple[Any, ...]",
@@ -329,12 +289,18 @@ async def _tool_handler_wrapper(
329289
"""
330290
client = sentry_sdk.get_client()
331291

332-
if original_kwargs is None:
333-
original_kwargs = {}
292+
original_kwargs = original_kwargs or {}
334293

335-
handler_name, arguments = _extract_handler_data_from_args(
336-
"tool", original_args, original_kwargs
337-
)
294+
if original_args:
295+
handler_name = original_args[0]
296+
elif original_kwargs.get("name"):
297+
handler_name = original_kwargs["name"]
298+
299+
arguments = {}
300+
if len(original_args) > 1 and original_args[1] is not None:
301+
arguments = original_args[1]
302+
elif original_kwargs.get("arguments"):
303+
arguments = original_kwargs["arguments"]
338304

339305
if has_data_collection_enabled(client.options):
340306
if not client.options["data_collection"]["gen_ai"]["inputs"]:
@@ -394,7 +360,8 @@ async def _tool_handler_wrapper(
394360
result = await result
395361

396362
except Exception as e:
397-
sentry_sdk.capture_exception(e)
363+
with capture_internal_exceptions():
364+
_capture_exception(e)
398365
raise
399366

400367
if result is None:
@@ -490,7 +457,8 @@ async def _instrument_v2_tool_call(
490457
result = await call_next(ctx)
491458

492459
except Exception as e:
493-
sentry_sdk.capture_exception(e)
460+
with capture_internal_exceptions():
461+
_capture_exception(e)
494462
raise
495463

496464
if not isinstance(result, dict):
@@ -549,13 +517,20 @@ async def _prompt_handler_wrapper(
549517
original_kwargs: Original keyword arguments passed to the handler
550518
self: Optional instance for bound methods
551519
"""
552-
if original_kwargs is None:
553-
original_kwargs = {}
554-
555520
client = sentry_sdk.get_client()
556-
handler_name, arguments = _extract_handler_data_from_args(
557-
"prompt", original_args, original_kwargs
558-
)
521+
522+
original_kwargs = original_kwargs or {}
523+
524+
if original_args:
525+
handler_name = original_args[0]
526+
elif original_kwargs.get("name"):
527+
handler_name = original_kwargs["name"]
528+
529+
arguments = {}
530+
if len(original_args) > 1 and original_args[1] is not None:
531+
arguments = original_args[1]
532+
elif original_kwargs.get("arguments"):
533+
arguments = original_kwargs["arguments"]
559534

560535
if has_data_collection_enabled(client.options):
561536
if not client.options["data_collection"]["gen_ai"]["inputs"]:
@@ -615,7 +590,8 @@ async def _prompt_handler_wrapper(
615590
result = await result
616591

617592
except Exception as e:
618-
sentry_sdk.capture_exception(e)
593+
with capture_internal_exceptions():
594+
_capture_exception(e)
619595
raise
620596

621597
if result is None:
@@ -764,7 +740,8 @@ async def _instrument_v2_prompt_get(
764740
try:
765741
result = await call_next(ctx)
766742
except Exception as e:
767-
sentry_sdk.capture_exception(e)
743+
with capture_internal_exceptions():
744+
_capture_exception(e)
768745
raise
769746

770747
if not isinstance(result, dict):
@@ -847,12 +824,15 @@ async def _resource_handler_wrapper(
847824
original_kwargs: Original keyword arguments passed to the handler
848825
self: Optional instance for bound methods
849826
"""
850-
if original_kwargs is None:
851-
original_kwargs = {}
827+
original_kwargs = original_kwargs or {}
852828

853-
handler_name, arguments = _extract_handler_data_from_args(
854-
"resource", original_args, original_kwargs
855-
)
829+
handler_name = "unknown"
830+
if original_args:
831+
handler_name = str(original_args[0])
832+
elif original_kwargs.get("uri"):
833+
handler_name = str(original_kwargs["uri"])
834+
835+
arguments: "dict[str, Any]" = {}
856836

857837
ctx = None
858838
try:
@@ -919,7 +899,8 @@ async def _resource_handler_wrapper(
919899
result = await result
920900

921901
except Exception as e:
922-
sentry_sdk.capture_exception(e)
902+
with capture_internal_exceptions():
903+
_capture_exception(e)
923904
raise
924905

925906
return result
@@ -984,7 +965,8 @@ async def _instrument_v2_resource_read(
984965
result = await call_next(ctx)
985966

986967
except Exception as e:
987-
sentry_sdk.capture_exception(e)
968+
with capture_internal_exceptions():
969+
_capture_exception(e)
988970
raise
989971

990972
return result

‎sentry_sdk/integrations/openai.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def __init__(
130130

131131
self.tiktoken_encoding = None
132132
if tiktoken_encoding_name is not None:
133-
import tiktoken # type: ignore
133+
import tiktoken
134134

135135
self.tiktoken_encoding = tiktoken.get_encoding(tiktoken_encoding_name)
136136

0 commit comments

Comments
 (0)