From cb84e92fb63e1ba4291fa69e0258b2fd94b47f7b Mon Sep 17 00:00:00 2001 From: Dibbu-cell Date: Sat, 11 Oct 2025 00:18:36 +0530 Subject: [PATCH 1/4] python 3.16 --- sentry_sdk/_compat.py | 21 +++++++++++++++ sentry_sdk/ai/monitoring.py | 3 ++- sentry_sdk/integrations/asgi.py | 5 ++-- sentry_sdk/integrations/django/asgi.py | 16 ++--------- sentry_sdk/integrations/fastapi.py | 3 ++- sentry_sdk/integrations/google_genai/utils.py | 3 ++- sentry_sdk/integrations/quart.py | 3 ++- sentry_sdk/integrations/starlette.py | 5 ++-- sentry_sdk/tracing_utils.py | 3 ++- tests/integrations/httpx/test_httpx.py | 27 ++++++++++--------- 10 files changed, 53 insertions(+), 36 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index a811cf2120..d2858969e3 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -1,12 +1,16 @@ import sys +import asyncio +import inspect from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Any from typing import TypeVar + from typing import Callable T = TypeVar("T") + _F = TypeVar("_F", bound=Callable[..., Any]) PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7 @@ -15,6 +19,23 @@ PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11 +# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for +# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. +# The latter is replaced with the inspect.markcoroutinefunction decorator. +# Until 3.12 is the minimum supported Python version, provide a shim. +# This was adapted from https://github.com/django/asgiref/blob/main/asgiref/sync.py +if hasattr(inspect, "markcoroutinefunction"): + iscoroutinefunction = inspect.iscoroutinefunction + markcoroutinefunction = inspect.markcoroutinefunction +else: + iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment] + + def markcoroutinefunction(func): + # type: (_F) -> _F + func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore + return func + + def with_metaclass(meta, *bases): # type: (Any, *Any) -> Any class MetaClass(type): diff --git a/sentry_sdk/ai/monitoring.py b/sentry_sdk/ai/monitoring.py index 9dd1aa132c..16b1836baa 100644 --- a/sentry_sdk/ai/monitoring.py +++ b/sentry_sdk/ai/monitoring.py @@ -1,6 +1,7 @@ import inspect from functools import wraps +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import SPANDATA import sentry_sdk.utils from sentry_sdk import start_span @@ -89,7 +90,7 @@ async def async_wrapped(*args, **kwargs): _ai_pipeline_name.set(None) return res - if inspect.iscoroutinefunction(f): + if iscoroutinefunction(f): return wraps(f)(async_wrapped) # type: ignore else: return wraps(f)(sync_wrapped) # type: ignore diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 28b44cc7ab..a583bd4743 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -10,6 +10,7 @@ from functools import partial import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.api import continue_trace from sentry_sdk.consts import OP from sentry_sdk.integrations._asgi_common import ( @@ -76,10 +77,10 @@ def _looks_like_asgi3(app): if inspect.isclass(app): return hasattr(app, "__await__") elif inspect.isfunction(app): - return asyncio.iscoroutinefunction(app) + return iscoroutinefunction(app) else: call = getattr(app, "__call__", None) # noqa - return asyncio.iscoroutinefunction(call) + return iscoroutinefunction(call) class SentryAsgiMiddleware: diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 773c538045..e4f5fe1db3 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -13,6 +13,7 @@ from django.core.handlers.wsgi import WSGIRequest import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction, markcoroutinefunction from sentry_sdk.consts import OP from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -35,20 +36,7 @@ _F = TypeVar("_F", bound=Callable[..., Any]) -# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for -# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. -# The latter is replaced with the inspect.markcoroutinefunction decorator. -# Until 3.12 is the minimum supported Python version, provide a shim. -# This was copied from https://github.com/django/asgiref/blob/main/asgiref/sync.py -if hasattr(inspect, "markcoroutinefunction"): - iscoroutinefunction = inspect.iscoroutinefunction - markcoroutinefunction = inspect.markcoroutinefunction -else: - iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment] - - def markcoroutinefunction(func: "_F") -> "_F": - func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore - return func + def _make_asgi_request_event_processor(request): diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 1473cbcab7..b6e952f4e8 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -3,6 +3,7 @@ from functools import wraps import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource @@ -75,7 +76,7 @@ def _sentry_get_request_handler(*args, **kwargs): if ( dependant and dependant.call is not None - and not asyncio.iscoroutinefunction(dependant.call) + and not iscoroutinefunction(dependant.call) ): old_call = dependant.call diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index ff973b02d9..a106c8c546 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -15,6 +15,7 @@ ) import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.ai.utils import set_data_normalized from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import should_send_default_pii @@ -318,7 +319,7 @@ def wrapped_tool(tool): tool_name = getattr(tool, "__name__", "unknown") tool_doc = tool.__doc__ - if inspect.iscoroutinefunction(tool): + if iscoroutinefunction(tool): # Async function @wraps(tool) async def async_wrapped(*args, **kwargs): diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 64f7e0bcd2..3cb2c3c0bd 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -3,6 +3,7 @@ from functools import wraps import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -113,7 +114,7 @@ def _sentry_route(*args, **kwargs): def decorator(old_func): # type: (Any) -> Any - if inspect.isfunction(old_func) and not asyncio.iscoroutinefunction( + if inspect.isfunction(old_func) and not iscoroutinefunction( old_func ): diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index f1a0e360bb..c59a5df575 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -6,6 +6,7 @@ from json import JSONDecodeError import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import OP from sentry_sdk.integrations import ( DidNotEnable, @@ -415,8 +416,8 @@ def _is_async_callable(obj): while isinstance(obj, functools.partial): obj = obj.func - return asyncio.iscoroutinefunction(obj) or ( - callable(obj) and asyncio.iscoroutinefunction(obj.__call__) + return iscoroutinefunction(obj) or ( + callable(obj) and iscoroutinefunction(obj.__call__) ) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 587133ad67..8a04121413 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -11,6 +11,7 @@ import uuid import sentry_sdk +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS, SPANTEMPLATE from sentry_sdk.utils import ( capture_internal_exceptions, @@ -912,7 +913,7 @@ def sync_wrapper(*args, **kwargs): except Exception: pass - if inspect.iscoroutinefunction(f): + if iscoroutinefunction(f): return async_wrapper else: return sync_wrapper diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index 1f30fdf945..4a162c6c1d 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -9,6 +9,7 @@ import sentry_sdk from sentry_sdk import capture_message, start_transaction +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import MATCH_ALL, SPANDATA from sentry_sdk.integrations.httpx import HttpxIntegration from tests.conftest import ApproxDict @@ -32,7 +33,7 @@ def before_breadcrumb(crumb, hint): with start_transaction(): events = capture_events() - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -86,7 +87,7 @@ def test_crumb_capture_client_error( with start_transaction(): events = capture_events() - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -137,7 +138,7 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, httpx_mock): op="greeting.sniff", trace_id="01234567890123456789012345678901", ) as transaction: - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -180,7 +181,7 @@ def test_outgoing_trace_headers_append_to_baggage( op="greeting.sniff", trace_id="01234567890123456789012345678901", ) as transaction: - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url, headers={"baGGage": "custom=data"}) ) @@ -333,7 +334,7 @@ def test_option_trace_propagation_targets( # Must be in a transaction to propagate headers with sentry_sdk.start_transaction(): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -420,7 +421,7 @@ def test_request_source_disabled( url = "http://example.com/" with start_transaction(name="test_transaction"): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -457,7 +458,7 @@ def test_request_source_enabled(sentry_init, capture_events, httpx_client, httpx url = "http://example.com/" with start_transaction(name="test_transaction"): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -494,7 +495,7 @@ def test_request_source(sentry_init, capture_events, httpx_client, httpx_mock): url = "http://example.com/" with start_transaction(name="test_transaction"): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -547,7 +548,7 @@ def test_request_source_with_module_in_search_path( url = "http://example.com/" with start_transaction(name="test_transaction"): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): from httpx_helpers.helpers import async_get_request_with_client asyncio.get_event_loop().run_until_complete( @@ -578,7 +579,7 @@ def test_request_source_with_module_in_search_path( is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep assert is_relative_path - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): assert data.get(SPANDATA.CODE_FUNCTION) == "async_get_request_with_client" else: assert data.get(SPANDATA.CODE_FUNCTION) == "get_request_with_client" @@ -618,7 +619,7 @@ def fake_start_span(*args, **kwargs): "sentry_sdk.integrations.httpx.start_span", fake_start_span, ): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -670,7 +671,7 @@ def fake_start_span(*args, **kwargs): "sentry_sdk.integrations.httpx.start_span", fake_start_span, ): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -720,7 +721,7 @@ def test_span_origin(sentry_init, capture_events, httpx_client, httpx_mock): url = "http://example.com/" with start_transaction(name="test_transaction"): - if asyncio.iscoroutinefunction(httpx_client.get): + if iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) From 0b840144f41e2559966d88e6420cb90e7108b387 Mon Sep 17 00:00:00 2001 From: Dibbu-cell Date: Mon, 13 Oct 2025 17:37:09 +0530 Subject: [PATCH 2/4] change2 --- sentry_sdk/_compat.py | 25 +++++++++++++++-- sentry_sdk/ai/monitoring.py | 3 +- sentry_sdk/integrations/django/asgi.py | 3 -- sentry_sdk/integrations/google_genai/utils.py | 3 +- sentry_sdk/integrations/quart.py | 4 +-- sentry_sdk/tracing_utils.py | 3 +- tests/integrations/httpx/test_httpx.py | 28 +++++++++---------- 7 files changed, 40 insertions(+), 29 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index d2858969e3..4712e2f868 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -2,7 +2,7 @@ import asyncio import inspect -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Callable if TYPE_CHECKING: from typing import Any @@ -12,6 +12,10 @@ T = TypeVar("T") _F = TypeVar("_F", bound=Callable[..., Any]) +# Public shim symbols with precise types so mypy accepts branch assignments +iscoroutinefunction: "Callable[[Any], bool]" +markcoroutinefunction: "Callable[[ _F ], _F]" + PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7 PY38 = sys.version_info[0] == 3 and sys.version_info[1] >= 8 @@ -28,11 +32,26 @@ iscoroutinefunction = inspect.iscoroutinefunction markcoroutinefunction = inspect.markcoroutinefunction else: - iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment] + iscoroutinefunction = asyncio.iscoroutinefunction def markcoroutinefunction(func): # type: (_F) -> _F - func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore + # Prior to Python 3.12, asyncio exposed a private `_is_coroutine` + # marker used by asyncio.iscoroutinefunction(). This attribute was + # removed in Python 3.11. If it's not available, fall back to a no-op, + # which preserves behavior of inspect.iscoroutinefunction for our + # supported versions while avoiding AttributeError. + try: + marker = getattr(asyncio.coroutines, "_is_coroutine") + except Exception: + # No marker available on this Python version; return function as-is. + return func + + try: # pragma: no cover - defensive + func._is_coroutine = marker # type: ignore[attr-defined] + except Exception: + # If assignment fails for any reason, leave func unchanged. + pass return func diff --git a/sentry_sdk/ai/monitoring.py b/sentry_sdk/ai/monitoring.py index 16b1836baa..9dd1aa132c 100644 --- a/sentry_sdk/ai/monitoring.py +++ b/sentry_sdk/ai/monitoring.py @@ -1,7 +1,6 @@ import inspect from functools import wraps -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import SPANDATA import sentry_sdk.utils from sentry_sdk import start_span @@ -90,7 +89,7 @@ async def async_wrapped(*args, **kwargs): _ai_pipeline_name.set(None) return res - if iscoroutinefunction(f): + if inspect.iscoroutinefunction(f): return wraps(f)(async_wrapped) # type: ignore else: return wraps(f)(sync_wrapped) # type: ignore diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index e4f5fe1db3..03c456f24f 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -36,9 +36,6 @@ _F = TypeVar("_F", bound=Callable[..., Any]) - - - def _make_asgi_request_event_processor(request): # type: (ASGIRequest) -> EventProcessor def asgi_request_event_processor(event, hint): diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index a106c8c546..ff973b02d9 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -15,7 +15,6 @@ ) import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.ai.utils import set_data_normalized from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import should_send_default_pii @@ -319,7 +318,7 @@ def wrapped_tool(tool): tool_name = getattr(tool, "__name__", "unknown") tool_doc = tool.__doc__ - if iscoroutinefunction(tool): + if inspect.iscoroutinefunction(tool): # Async function @wraps(tool) async def async_wrapped(*args, **kwargs): diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 3cb2c3c0bd..307dd7caf8 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -114,9 +114,7 @@ def _sentry_route(*args, **kwargs): def decorator(old_func): # type: (Any) -> Any - if inspect.isfunction(old_func) and not iscoroutinefunction( - old_func - ): + if inspect.isfunction(old_func) and not iscoroutinefunction(old_func): @wraps(old_func) @ensure_integration_enabled(QuartIntegration, old_func) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 8a04121413..587133ad67 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -11,7 +11,6 @@ import uuid import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS, SPANTEMPLATE from sentry_sdk.utils import ( capture_internal_exceptions, @@ -913,7 +912,7 @@ def sync_wrapper(*args, **kwargs): except Exception: pass - if iscoroutinefunction(f): + if inspect.iscoroutinefunction(f): return async_wrapper else: return sync_wrapper diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index 4a162c6c1d..0df310a4c6 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -1,6 +1,7 @@ import os import datetime import asyncio +import inspect from unittest import mock import httpx @@ -9,7 +10,6 @@ import sentry_sdk from sentry_sdk import capture_message, start_transaction -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import MATCH_ALL, SPANDATA from sentry_sdk.integrations.httpx import HttpxIntegration from tests.conftest import ApproxDict @@ -33,7 +33,7 @@ def before_breadcrumb(crumb, hint): with start_transaction(): events = capture_events() - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -87,7 +87,7 @@ def test_crumb_capture_client_error( with start_transaction(): events = capture_events() - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -138,7 +138,7 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, httpx_mock): op="greeting.sniff", trace_id="01234567890123456789012345678901", ) as transaction: - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -181,7 +181,7 @@ def test_outgoing_trace_headers_append_to_baggage( op="greeting.sniff", trace_id="01234567890123456789012345678901", ) as transaction: - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url, headers={"baGGage": "custom=data"}) ) @@ -334,7 +334,7 @@ def test_option_trace_propagation_targets( # Must be in a transaction to propagate headers with sentry_sdk.start_transaction(): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -421,7 +421,7 @@ def test_request_source_disabled( url = "http://example.com/" with start_transaction(name="test_transaction"): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -458,7 +458,7 @@ def test_request_source_enabled(sentry_init, capture_events, httpx_client, httpx url = "http://example.com/" with start_transaction(name="test_transaction"): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -495,7 +495,7 @@ def test_request_source(sentry_init, capture_events, httpx_client, httpx_mock): url = "http://example.com/" with start_transaction(name="test_transaction"): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -548,7 +548,7 @@ def test_request_source_with_module_in_search_path( url = "http://example.com/" with start_transaction(name="test_transaction"): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): from httpx_helpers.helpers import async_get_request_with_client asyncio.get_event_loop().run_until_complete( @@ -579,7 +579,7 @@ def test_request_source_with_module_in_search_path( is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep assert is_relative_path - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): assert data.get(SPANDATA.CODE_FUNCTION) == "async_get_request_with_client" else: assert data.get(SPANDATA.CODE_FUNCTION) == "get_request_with_client" @@ -619,7 +619,7 @@ def fake_start_span(*args, **kwargs): "sentry_sdk.integrations.httpx.start_span", fake_start_span, ): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -671,7 +671,7 @@ def fake_start_span(*args, **kwargs): "sentry_sdk.integrations.httpx.start_span", fake_start_span, ): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -721,7 +721,7 @@ def test_span_origin(sentry_init, capture_events, httpx_client, httpx_mock): url = "http://example.com/" with start_transaction(name="test_transaction"): - if iscoroutinefunction(httpx_client.get): + if inspect.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) From f6996fa280ae4ec94e8ae70a7a20bde061458331 Mon Sep 17 00:00:00 2001 From: Dibbu-cell Date: Wed, 15 Oct 2025 17:41:28 +0530 Subject: [PATCH 3/4] Sentry --- sentry_sdk/_compat.py | 52 +++++++++--------------- sentry_sdk/client.py | 17 ++++++-- sentry_sdk/integrations/django/asgi.py | 43 ++++++++++++++++++-- sentry_sdk/performance.py | 55 ++++++++++++++++++++++++++ tests/tracing/test_allow_n_plus_one.py | 27 +++++++++++++ 5 files changed, 152 insertions(+), 42 deletions(-) create mode 100644 sentry_sdk/performance.py create mode 100644 tests/tracing/test_allow_n_plus_one.py diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 4712e2f868..80540ccf8d 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -2,7 +2,7 @@ import asyncio import inspect -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast if TYPE_CHECKING: from typing import Any @@ -12,47 +12,31 @@ T = TypeVar("T") _F = TypeVar("_F", bound=Callable[..., Any]) -# Public shim symbols with precise types so mypy accepts branch assignments -iscoroutinefunction: "Callable[[Any], bool]" -markcoroutinefunction: "Callable[[ _F ], _F]" +# Use a conservative cutoff (Python 3.13+) before switching to +# inspect.iscoroutinefunction. See contributor discussion and +# references to Starlette/Uvicorn behavior for rationale. +PY313 = sys.version_info[0] == 3 and sys.version_info[1] >= 13 +# Backwards-compatible version flags expected across the codebase PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7 PY38 = sys.version_info[0] == 3 and sys.version_info[1] >= 8 PY310 = sys.version_info[0] == 3 and sys.version_info[1] >= 10 PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11 -# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for -# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. -# The latter is replaced with the inspect.markcoroutinefunction decorator. -# Until 3.12 is the minimum supported Python version, provide a shim. -# This was adapted from https://github.com/django/asgiref/blob/main/asgiref/sync.py -if hasattr(inspect, "markcoroutinefunction"): - iscoroutinefunction = inspect.iscoroutinefunction - markcoroutinefunction = inspect.markcoroutinefunction -else: - iscoroutinefunction = asyncio.iscoroutinefunction - - def markcoroutinefunction(func): - # type: (_F) -> _F - # Prior to Python 3.12, asyncio exposed a private `_is_coroutine` - # marker used by asyncio.iscoroutinefunction(). This attribute was - # removed in Python 3.11. If it's not available, fall back to a no-op, - # which preserves behavior of inspect.iscoroutinefunction for our - # supported versions while avoiding AttributeError. - try: - marker = getattr(asyncio.coroutines, "_is_coroutine") - except Exception: - # No marker available on this Python version; return function as-is. - return func - - try: # pragma: no cover - defensive - func._is_coroutine = marker # type: ignore[attr-defined] - except Exception: - # If assignment fails for any reason, leave func unchanged. - pass - return func +# Public shim symbol so other modules can import a stable API. For Python +# 3.13+ prefer inspect.iscoroutinefunction, otherwise fall back to +# asyncio.iscoroutinefunction to preserve historical behavior on older Pythons. +iscoroutinefunction: Callable[[Any], bool] = cast( + Callable[[Any], bool], inspect.iscoroutinefunction if PY313 else asyncio.iscoroutinefunction +) + + +# We intentionally do not export `markcoroutinefunction` here. The decorator +# is only used by the Django ASGI integration and historically may be applied +# to middleware instances (non-callables). Keeping the marker local to the +# integration reduces import surface and avoids potential circular imports. def with_metaclass(meta, *bases): diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index d17f922642..5b7429788e 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -577,7 +577,10 @@ def _prepare_event( for key in "release", "environment", "server_name", "dist": if event.get(key) is None and self.options[key] is not None: - event[key] = str(self.options[key]).strip() + # `event` is a TypedDict (Event). mypy doesn't allow assignment + # with a non-literal key, so cast to a plain dict for this + # dynamic assignment. + cast(Dict[str, Any], event)[key] = str(self.options[key]).strip() if event.get("sdk") is None: sdk_info = dict(SDK_INFO) sdk_info["integrations"] = sorted(self.integrations.keys()) @@ -642,7 +645,9 @@ def _prepare_event( if event.get("exception"): DedupeIntegration.reset_last_seen() - event = new_event + # `new_event` has type Any | None at runtime; cast to Event for mypy + # now that we've checked it's not None. + event = cast("Event", new_event) before_send_transaction = self.options["before_send_transaction"] if ( @@ -666,13 +671,17 @@ def _prepare_event( quantity=spans_before + 1, # +1 for the transaction itself ) else: - spans_delta = spans_before - len(new_event.get("spans", [])) + # new_event is not None here, but mypy doesn't narrow the type + # from Any | None, so cast to Event for safe attribute access. + new_event_cast = cast("Event", new_event) + spans_delta = spans_before - len(new_event_cast.get("spans", [])) if spans_delta > 0 and self.transport is not None: self.transport.record_lost_event( reason="before_send", data_category="span", quantity=spans_delta ) - event = new_event + # `new_event` may be Any | None; cast to Event for mypy. + event = cast("Event", new_event) return event diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 03c456f24f..d448dd202f 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -6,6 +6,7 @@ `django.core.handlers.asgi`. """ +import sys import asyncio import functools import inspect @@ -13,7 +14,7 @@ from django.core.handlers.wsgi import WSGIRequest import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction, markcoroutinefunction +from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import OP from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -200,7 +201,41 @@ def _async_check(self): Taken from django.utils.deprecation::MiddlewareMixin._async_check """ if iscoroutinefunction(self.get_response): - markcoroutinefunction(self) + # The stdlib moved the coroutine marker to + # `inspect.markcoroutinefunction` and removed the private + # `_is_coroutine` marker on newer Pythons. Historically some + # code (Django middleware) has relied on attaching that + # marker to middleware instances. We implement a conservative + # local helper which uses `inspect.markcoroutinefunction` on + # sufficiently new Pythons (3.13+), otherwise falls back to a + # best-effort shim that sets the old private marker when + # available. Keep this logic local to the integration to avoid + # widening import dependencies. + + # Conservative cutoff mirroring Starlette/Uvicorn: prefer + # `inspect.iscoroutinefunction` on Python 3.13+ to avoid + # historical stdlib edge-cases. + PY313 = sys.version_info[0] == 3 and sys.version_info[1] >= 13 + + if hasattr(inspect, "markcoroutinefunction") and PY313: + try: + inspect.markcoroutinefunction(self) + except Exception: + # Best-effort: don't fail the application if this fails. + pass + else: + # Fallback for older Pythons: try to set the historical + # private marker used by asyncio.iscoroutinefunction(). + try: + marker = getattr(asyncio.coroutines, "_is_coroutine") + except Exception: + marker = None + + if marker is not None: + try: + setattr(self, "_is_coroutine", marker) + except Exception: + pass def async_route_check(self): # type: () -> bool @@ -222,9 +257,9 @@ async def __acall__(self, *args, **kwargs): middleware_span = _check_middleware_span(old_method=f) if middleware_span is None: - return await f(*args, **kwargs) # type: ignore + return await f(*args, **kwargs) with middleware_span: - return await f(*args, **kwargs) # type: ignore + return await f(*args, **kwargs) return SentryASGIMixin diff --git a/sentry_sdk/performance.py b/sentry_sdk/performance.py new file mode 100644 index 0000000000..7b1ee14f81 --- /dev/null +++ b/sentry_sdk/performance.py @@ -0,0 +1,55 @@ +from contextlib import contextmanager +from typing import Generator, Optional + +# Import the helper that returns the current active span/transaction without +# importing the top-level package to avoid circular imports. +from sentry_sdk.tracing_utils import get_current_span + + +@contextmanager +def allow_n_plus_one(reason: Optional[str] = None) -> Generator[None, None, None]: + """Context manager to mark the current span and its root transaction as + intentionally allowed N+1. + + This sets tags on the active span and its containing transaction so that + server-side N+1 detectors (if updated to honor these tags) can ignore the + transaction. This helper is best-effort and will not raise if there is no + active span/transaction. + + Usage: + with allow_n_plus_one("expected loop"): + for x in queryset: + ... + """ + span = get_current_span() + if span is not None: + try: + # Tag the active span + span.set_tag("sentry.n_plus_one.ignore", True) + if reason: + span.set_tag("sentry.n_plus_one.reason", reason) + + # Also tag the containing transaction if available + try: + tx = span.containing_transaction + except Exception: + tx = None + + if tx is not None: + try: + tx.set_tag("sentry.n_plus_one.ignore", True) + if reason: + tx.set_tag("sentry.n_plus_one.reason", reason) + except Exception: + # best-effort: do not fail if transaction tagging fails + pass + except Exception: + # best-effort: silence any unexpected errors + pass + + try: + yield + finally: + # keep tags; no cleanup required + pass + diff --git a/tests/tracing/test_allow_n_plus_one.py b/tests/tracing/test_allow_n_plus_one.py new file mode 100644 index 0000000000..0faaeb51be --- /dev/null +++ b/tests/tracing/test_allow_n_plus_one.py @@ -0,0 +1,27 @@ +import sentry_sdk +from typing import Any +from sentry_sdk.performance import allow_n_plus_one + + +def test_allow_n_plus_one_sets_tag(sentry_init: Any) -> None: + # Initialize SDK with test fixture + sentry_init() + + with sentry_sdk.start_transaction(name="tx") as tx: + with allow_n_plus_one("expected"): + # no-op loop simulated + pass + + # The tag should be set on the transaction and the active span + assert tx._tags.get("sentry.n_plus_one.ignore") + assert tx._tags.get("sentry.n_plus_one.reason") == "expected" + + # if a span was active, it should have been tagged as well; start a span + # to verify tagging of the active span + with sentry_sdk.start_span(op="db", name="q") as span: + with allow_n_plus_one("inner"): + pass + + assert span._tags.get("sentry.n_plus_one.ignore") + assert span._tags.get("sentry.n_plus_one.reason") == "inner" + From 24e2fdcf074a4008b008c9c46d767d136fa0e374 Mon Sep 17 00:00:00 2001 From: Dibbu-cell Date: Wed, 15 Oct 2025 19:14:03 +0530 Subject: [PATCH 4/4] Revert unrelated changes; keep only iscoroutinefunction change in _compat.py --- sentry_sdk/client.py | 17 ++------ sentry_sdk/integrations/asgi.py | 5 +-- sentry_sdk/integrations/django/asgi.py | 58 +++++++++----------------- sentry_sdk/integrations/fastapi.py | 3 +- sentry_sdk/integrations/quart.py | 5 ++- sentry_sdk/integrations/starlette.py | 5 +-- sentry_sdk/performance.py | 55 ------------------------ tests/integrations/httpx/test_httpx.py | 27 ++++++------ tests/tracing/test_allow_n_plus_one.py | 27 ------------ 9 files changed, 44 insertions(+), 158 deletions(-) delete mode 100644 sentry_sdk/performance.py delete mode 100644 tests/tracing/test_allow_n_plus_one.py diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 5b7429788e..d17f922642 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -577,10 +577,7 @@ def _prepare_event( for key in "release", "environment", "server_name", "dist": if event.get(key) is None and self.options[key] is not None: - # `event` is a TypedDict (Event). mypy doesn't allow assignment - # with a non-literal key, so cast to a plain dict for this - # dynamic assignment. - cast(Dict[str, Any], event)[key] = str(self.options[key]).strip() + event[key] = str(self.options[key]).strip() if event.get("sdk") is None: sdk_info = dict(SDK_INFO) sdk_info["integrations"] = sorted(self.integrations.keys()) @@ -645,9 +642,7 @@ def _prepare_event( if event.get("exception"): DedupeIntegration.reset_last_seen() - # `new_event` has type Any | None at runtime; cast to Event for mypy - # now that we've checked it's not None. - event = cast("Event", new_event) + event = new_event before_send_transaction = self.options["before_send_transaction"] if ( @@ -671,17 +666,13 @@ def _prepare_event( quantity=spans_before + 1, # +1 for the transaction itself ) else: - # new_event is not None here, but mypy doesn't narrow the type - # from Any | None, so cast to Event for safe attribute access. - new_event_cast = cast("Event", new_event) - spans_delta = spans_before - len(new_event_cast.get("spans", [])) + spans_delta = spans_before - len(new_event.get("spans", [])) if spans_delta > 0 and self.transport is not None: self.transport.record_lost_event( reason="before_send", data_category="span", quantity=spans_delta ) - # `new_event` may be Any | None; cast to Event for mypy. - event = cast("Event", new_event) + event = new_event return event diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index a583bd4743..28b44cc7ab 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -10,7 +10,6 @@ from functools import partial import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.api import continue_trace from sentry_sdk.consts import OP from sentry_sdk.integrations._asgi_common import ( @@ -77,10 +76,10 @@ def _looks_like_asgi3(app): if inspect.isclass(app): return hasattr(app, "__await__") elif inspect.isfunction(app): - return iscoroutinefunction(app) + return asyncio.iscoroutinefunction(app) else: call = getattr(app, "__call__", None) # noqa - return iscoroutinefunction(call) + return asyncio.iscoroutinefunction(call) class SentryAsgiMiddleware: diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index d448dd202f..773c538045 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -6,7 +6,6 @@ `django.core.handlers.asgi`. """ -import sys import asyncio import functools import inspect @@ -14,7 +13,6 @@ from django.core.handlers.wsgi import WSGIRequest import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import OP from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -37,6 +35,22 @@ _F = TypeVar("_F", bound=Callable[..., Any]) +# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for +# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. +# The latter is replaced with the inspect.markcoroutinefunction decorator. +# Until 3.12 is the minimum supported Python version, provide a shim. +# This was copied from https://github.com/django/asgiref/blob/main/asgiref/sync.py +if hasattr(inspect, "markcoroutinefunction"): + iscoroutinefunction = inspect.iscoroutinefunction + markcoroutinefunction = inspect.markcoroutinefunction +else: + iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment] + + def markcoroutinefunction(func: "_F") -> "_F": + func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore + return func + + def _make_asgi_request_event_processor(request): # type: (ASGIRequest) -> EventProcessor def asgi_request_event_processor(event, hint): @@ -201,41 +215,7 @@ def _async_check(self): Taken from django.utils.deprecation::MiddlewareMixin._async_check """ if iscoroutinefunction(self.get_response): - # The stdlib moved the coroutine marker to - # `inspect.markcoroutinefunction` and removed the private - # `_is_coroutine` marker on newer Pythons. Historically some - # code (Django middleware) has relied on attaching that - # marker to middleware instances. We implement a conservative - # local helper which uses `inspect.markcoroutinefunction` on - # sufficiently new Pythons (3.13+), otherwise falls back to a - # best-effort shim that sets the old private marker when - # available. Keep this logic local to the integration to avoid - # widening import dependencies. - - # Conservative cutoff mirroring Starlette/Uvicorn: prefer - # `inspect.iscoroutinefunction` on Python 3.13+ to avoid - # historical stdlib edge-cases. - PY313 = sys.version_info[0] == 3 and sys.version_info[1] >= 13 - - if hasattr(inspect, "markcoroutinefunction") and PY313: - try: - inspect.markcoroutinefunction(self) - except Exception: - # Best-effort: don't fail the application if this fails. - pass - else: - # Fallback for older Pythons: try to set the historical - # private marker used by asyncio.iscoroutinefunction(). - try: - marker = getattr(asyncio.coroutines, "_is_coroutine") - except Exception: - marker = None - - if marker is not None: - try: - setattr(self, "_is_coroutine", marker) - except Exception: - pass + markcoroutinefunction(self) def async_route_check(self): # type: () -> bool @@ -257,9 +237,9 @@ async def __acall__(self, *args, **kwargs): middleware_span = _check_middleware_span(old_method=f) if middleware_span is None: - return await f(*args, **kwargs) + return await f(*args, **kwargs) # type: ignore with middleware_span: - return await f(*args, **kwargs) + return await f(*args, **kwargs) # type: ignore return SentryASGIMixin diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index b6e952f4e8..1473cbcab7 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -3,7 +3,6 @@ from functools import wraps import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource @@ -76,7 +75,7 @@ def _sentry_get_request_handler(*args, **kwargs): if ( dependant and dependant.call is not None - and not iscoroutinefunction(dependant.call) + and not asyncio.iscoroutinefunction(dependant.call) ): old_call = dependant.call diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 307dd7caf8..64f7e0bcd2 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -3,7 +3,6 @@ from functools import wraps import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -114,7 +113,9 @@ def _sentry_route(*args, **kwargs): def decorator(old_func): # type: (Any) -> Any - if inspect.isfunction(old_func) and not iscoroutinefunction(old_func): + if inspect.isfunction(old_func) and not asyncio.iscoroutinefunction( + old_func + ): @wraps(old_func) @ensure_integration_enabled(QuartIntegration, old_func) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index c59a5df575..f1a0e360bb 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -6,7 +6,6 @@ from json import JSONDecodeError import sentry_sdk -from sentry_sdk._compat import iscoroutinefunction from sentry_sdk.consts import OP from sentry_sdk.integrations import ( DidNotEnable, @@ -416,8 +415,8 @@ def _is_async_callable(obj): while isinstance(obj, functools.partial): obj = obj.func - return iscoroutinefunction(obj) or ( - callable(obj) and iscoroutinefunction(obj.__call__) + return asyncio.iscoroutinefunction(obj) or ( + callable(obj) and asyncio.iscoroutinefunction(obj.__call__) ) diff --git a/sentry_sdk/performance.py b/sentry_sdk/performance.py deleted file mode 100644 index 7b1ee14f81..0000000000 --- a/sentry_sdk/performance.py +++ /dev/null @@ -1,55 +0,0 @@ -from contextlib import contextmanager -from typing import Generator, Optional - -# Import the helper that returns the current active span/transaction without -# importing the top-level package to avoid circular imports. -from sentry_sdk.tracing_utils import get_current_span - - -@contextmanager -def allow_n_plus_one(reason: Optional[str] = None) -> Generator[None, None, None]: - """Context manager to mark the current span and its root transaction as - intentionally allowed N+1. - - This sets tags on the active span and its containing transaction so that - server-side N+1 detectors (if updated to honor these tags) can ignore the - transaction. This helper is best-effort and will not raise if there is no - active span/transaction. - - Usage: - with allow_n_plus_one("expected loop"): - for x in queryset: - ... - """ - span = get_current_span() - if span is not None: - try: - # Tag the active span - span.set_tag("sentry.n_plus_one.ignore", True) - if reason: - span.set_tag("sentry.n_plus_one.reason", reason) - - # Also tag the containing transaction if available - try: - tx = span.containing_transaction - except Exception: - tx = None - - if tx is not None: - try: - tx.set_tag("sentry.n_plus_one.ignore", True) - if reason: - tx.set_tag("sentry.n_plus_one.reason", reason) - except Exception: - # best-effort: do not fail if transaction tagging fails - pass - except Exception: - # best-effort: silence any unexpected errors - pass - - try: - yield - finally: - # keep tags; no cleanup required - pass - diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index 0df310a4c6..1f30fdf945 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -1,7 +1,6 @@ import os import datetime import asyncio -import inspect from unittest import mock import httpx @@ -33,7 +32,7 @@ def before_breadcrumb(crumb, hint): with start_transaction(): events = capture_events() - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -87,7 +86,7 @@ def test_crumb_capture_client_error( with start_transaction(): events = capture_events() - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -138,7 +137,7 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, httpx_mock): op="greeting.sniff", trace_id="01234567890123456789012345678901", ) as transaction: - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url) ) @@ -181,7 +180,7 @@ def test_outgoing_trace_headers_append_to_baggage( op="greeting.sniff", trace_id="01234567890123456789012345678901", ) as transaction: - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): response = asyncio.get_event_loop().run_until_complete( httpx_client.get(url, headers={"baGGage": "custom=data"}) ) @@ -334,7 +333,7 @@ def test_option_trace_propagation_targets( # Must be in a transaction to propagate headers with sentry_sdk.start_transaction(): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -421,7 +420,7 @@ def test_request_source_disabled( url = "http://example.com/" with start_transaction(name="test_transaction"): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -458,7 +457,7 @@ def test_request_source_enabled(sentry_init, capture_events, httpx_client, httpx url = "http://example.com/" with start_transaction(name="test_transaction"): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -495,7 +494,7 @@ def test_request_source(sentry_init, capture_events, httpx_client, httpx_mock): url = "http://example.com/" with start_transaction(name="test_transaction"): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -548,7 +547,7 @@ def test_request_source_with_module_in_search_path( url = "http://example.com/" with start_transaction(name="test_transaction"): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): from httpx_helpers.helpers import async_get_request_with_client asyncio.get_event_loop().run_until_complete( @@ -579,7 +578,7 @@ def test_request_source_with_module_in_search_path( is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep assert is_relative_path - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): assert data.get(SPANDATA.CODE_FUNCTION) == "async_get_request_with_client" else: assert data.get(SPANDATA.CODE_FUNCTION) == "get_request_with_client" @@ -619,7 +618,7 @@ def fake_start_span(*args, **kwargs): "sentry_sdk.integrations.httpx.start_span", fake_start_span, ): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -671,7 +670,7 @@ def fake_start_span(*args, **kwargs): "sentry_sdk.integrations.httpx.start_span", fake_start_span, ): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) @@ -721,7 +720,7 @@ def test_span_origin(sentry_init, capture_events, httpx_client, httpx_mock): url = "http://example.com/" with start_transaction(name="test_transaction"): - if inspect.iscoroutinefunction(httpx_client.get): + if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: httpx_client.get(url) diff --git a/tests/tracing/test_allow_n_plus_one.py b/tests/tracing/test_allow_n_plus_one.py deleted file mode 100644 index 0faaeb51be..0000000000 --- a/tests/tracing/test_allow_n_plus_one.py +++ /dev/null @@ -1,27 +0,0 @@ -import sentry_sdk -from typing import Any -from sentry_sdk.performance import allow_n_plus_one - - -def test_allow_n_plus_one_sets_tag(sentry_init: Any) -> None: - # Initialize SDK with test fixture - sentry_init() - - with sentry_sdk.start_transaction(name="tx") as tx: - with allow_n_plus_one("expected"): - # no-op loop simulated - pass - - # The tag should be set on the transaction and the active span - assert tx._tags.get("sentry.n_plus_one.ignore") - assert tx._tags.get("sentry.n_plus_one.reason") == "expected" - - # if a span was active, it should have been tagged as well; start a span - # to verify tagging of the active span - with sentry_sdk.start_span(op="db", name="q") as span: - with allow_n_plus_one("inner"): - pass - - assert span._tags.get("sentry.n_plus_one.ignore") - assert span._tags.get("sentry.n_plus_one.reason") == "inner" -