From e0585e5b2285ede1129cdc5d732e6e579bb23217 Mon Sep 17 00:00:00 2001 From: Cooper Ellidge Date: Fri, 17 Apr 2026 09:06:30 +0930 Subject: [PATCH 1/3] change asyncio to inspect for iscoroutinefunction --- sentry_sdk/integrations/fastapi.py | 4 ++-- sentry_sdk/integrations/starlette.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 66f73ea4e0..30dd3e1f32 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -1,4 +1,4 @@ -import asyncio +import inspect from copy import deepcopy from functools import wraps @@ -73,7 +73,7 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any": if ( dependant and dependant.call is not None - and not asyncio.iscoroutinefunction(dependant.call) + and not inspect.iscoroutinefunction(dependant.call) ): old_call = dependant.call diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index dac9887e2f..3fc8c9ff97 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -1,4 +1,4 @@ -import asyncio +import inspect import functools import warnings from collections.abc import Set @@ -424,8 +424,8 @@ def _is_async_callable(obj: "Any") -> bool: while isinstance(obj, functools.partial): obj = obj.func - return asyncio.iscoroutinefunction(obj) or ( - callable(obj) and asyncio.iscoroutinefunction(obj.__call__) + return inspect.iscoroutinefunction(obj) or ( + callable(obj) and inspect.iscoroutinefunction(obj.__call__) ) From e8f494db871fccef1b6d0798223b8dbc3235bb00 Mon Sep 17 00:00:00 2001 From: Cooper Ellidge Date: Fri, 17 Apr 2026 23:28:42 +0930 Subject: [PATCH 2/3] extract iscoroutinefunction into common --- sentry_sdk/integrations/_asgi_common.py | 21 ++++++++++++++++ sentry_sdk/integrations/asgi.py | 6 ++--- sentry_sdk/integrations/django/asgi.py | 32 +++++++------------------ sentry_sdk/integrations/django/views.py | 13 +++------- sentry_sdk/integrations/fastapi.py | 4 ++-- sentry_sdk/integrations/quart.py | 6 ++--- sentry_sdk/integrations/starlette.py | 6 ++--- 7 files changed, 42 insertions(+), 46 deletions(-) diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index 525ca4b5b5..1c00f42b80 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -1,3 +1,5 @@ +import asyncio +import inspect import urllib from sentry_sdk.scope import should_send_default_pii @@ -9,11 +11,30 @@ from typing import Any from typing import Dict from typing import Optional + from typing import TypeVar from typing import Union from typing_extensions import Literal from sentry_sdk.utils import AnnotatedValue + _F = TypeVar("_F") + + +# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for +# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. +# The latter is replaced with inspect.markcoroutinefunction(). +# 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[attr-defined] + return func + def _get_headers(asgi_scope: "Any") -> "Dict[str, str]": """ diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 64dc3cc554..43ebe59b44 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -5,7 +5,6 @@ """ import sys -import asyncio import inspect from copy import deepcopy from functools import partial @@ -18,6 +17,7 @@ _get_request_attributes, _get_request_data, _get_url, + _iscoroutinefunction, ) from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, @@ -87,10 +87,10 @@ def _looks_like_asgi3(app: "Any") -> bool: 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 f3aff113d6..64b27b5c8c 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -6,9 +6,7 @@ `django.core.handlers.asgi`. """ -import asyncio import functools -import inspect from django.core.handlers.wsgi import WSGIRequest @@ -16,6 +14,10 @@ from sentry_sdk.consts import OP from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +from sentry_sdk.integrations._asgi_common import ( + _iscoroutinefunction, + _markcoroutinefunction, +) from sentry_sdk.scope import should_send_default_pii from sentry_sdk.utils import ( capture_internal_exceptions, @@ -25,31 +27,13 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Callable, Union, TypeVar + from typing import Any, Callable, Union from django.core.handlers.asgi import ASGIRequest from django.http.response import HttpResponse from sentry_sdk._types import Event, EventProcessor - _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: "ASGIRequest") -> "EventProcessor": def asgi_request_event_processor(event: "Event", hint: "dict[str, Any]") -> "Event": @@ -215,15 +199,15 @@ def _async_check(self) -> None: a thread is not consumed during a whole request. Taken from django.utils.deprecation::MiddlewareMixin._async_check """ - if iscoroutinefunction(self.get_response): - markcoroutinefunction(self) + if _iscoroutinefunction(self.get_response): + _markcoroutinefunction(self) def async_route_check(self) -> bool: """ Function that checks if we are in async mode, and if we are forwards the handling of requests to __acall__ """ - return iscoroutinefunction(self.get_response) + return _iscoroutinefunction(self.get_response) async def __acall__(self, *args: "Any", **kwargs: "Any") -> "Any": f = self._acall_method diff --git a/sentry_sdk/integrations/django/views.py b/sentry_sdk/integrations/django/views.py index c9e370029e..a2b5b7b87e 100644 --- a/sentry_sdk/integrations/django/views.py +++ b/sentry_sdk/integrations/django/views.py @@ -2,6 +2,7 @@ import sentry_sdk from sentry_sdk.consts import OP +from sentry_sdk.integrations._asgi_common import _iscoroutinefunction from typing import TYPE_CHECKING @@ -9,12 +10,6 @@ from typing import Any -try: - from asyncio import iscoroutinefunction -except ImportError: - iscoroutinefunction = None # type: ignore - - try: from sentry_sdk.integrations.django.asgi import wrap_async_view except (ImportError, SyntaxError): @@ -48,10 +43,8 @@ def sentry_patched_make_view_atomic( integration = sentry_sdk.get_client().get_integration(DjangoIntegration) if integration is not None: - is_async_view = ( - iscoroutinefunction is not None - and wrap_async_view is not None - and iscoroutinefunction(callback) + is_async_view = wrap_async_view is not None and _iscoroutinefunction( + callback ) if is_async_view: sentry_wrapped_callback = wrap_async_view(callback) diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 30dd3e1f32..f708dfaa94 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -1,9 +1,9 @@ -import inspect from copy import deepcopy from functools import wraps import sentry_sdk from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations._asgi_common import _iscoroutinefunction from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource from sentry_sdk.utils import transaction_from_function @@ -73,7 +73,7 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any": if ( dependant and dependant.call is not None - and not inspect.iscoroutinefunction(dependant.call) + and not _iscoroutinefunction(dependant.call) ): old_call = dependant.call diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index c1b8fca717..95de5995d7 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -1,9 +1,9 @@ -import asyncio import inspect from functools import wraps import sentry_sdk from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.integrations._asgi_common import _iscoroutinefunction from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from sentry_sdk.scope import should_send_default_pii @@ -106,9 +106,7 @@ def _sentry_route(*args: "Any", **kwargs: "Any") -> "Any": old_decorator = old_route(*args, **kwargs) def decorator(old_func: "Any") -> "Any": - if inspect.isfunction(old_func) and not asyncio.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/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 3fc8c9ff97..55a2091fa8 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -1,4 +1,3 @@ -import inspect import functools import warnings from collections.abc import Set @@ -12,6 +11,7 @@ Integration, _DEFAULT_FAILED_REQUEST_STATUS_CODES, ) +from sentry_sdk.integrations._asgi_common import _iscoroutinefunction from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, HttpCodeRangeContainer, @@ -424,8 +424,8 @@ def _is_async_callable(obj: "Any") -> bool: while isinstance(obj, functools.partial): obj = obj.func - return inspect.iscoroutinefunction(obj) or ( - callable(obj) and inspect.iscoroutinefunction(obj.__call__) + return _iscoroutinefunction(obj) or ( + callable(obj) and _iscoroutinefunction(obj.__call__) ) From 6da51e64e525d817488485f6adcc9c50271fb9cb Mon Sep 17 00:00:00 2001 From: Cooper Ellidge Date: Tue, 21 Apr 2026 09:03:07 +0930 Subject: [PATCH 3/3] move _markcoroutinefunction back to django asgi --- sentry_sdk/integrations/_asgi_common.py | 17 +++------------- sentry_sdk/integrations/django/asgi.py | 27 +++++++++++++++++++------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index 1c00f42b80..3a66a44f4a 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -11,30 +11,19 @@ from typing import Any from typing import Dict from typing import Optional - from typing import TypeVar from typing import Union from typing_extensions import Literal from sentry_sdk.utils import AnnotatedValue - _F = TypeVar("_F") - - # Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for -# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. -# The latter is replaced with inspect.markcoroutinefunction(). -# 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"): +# inspect.iscoroutinefunction(). Until 3.12 is the minimum supported Python +# version, provide a shim. +if hasattr(inspect, "iscoroutinefunction"): _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[attr-defined] - return func - def _get_headers(asgi_scope: "Any") -> "Dict[str, str]": """ diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 64b27b5c8c..78396d7dba 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -6,7 +6,9 @@ `django.core.handlers.asgi`. """ +import asyncio import functools +import inspect from django.core.handlers.wsgi import WSGIRequest @@ -14,10 +16,7 @@ from sentry_sdk.consts import OP from sentry_sdk.integrations.asgi import SentryAsgiMiddleware -from sentry_sdk.integrations._asgi_common import ( - _iscoroutinefunction, - _markcoroutinefunction, -) +from sentry_sdk.integrations._asgi_common import _iscoroutinefunction from sentry_sdk.scope import should_send_default_pii from sentry_sdk.utils import ( capture_internal_exceptions, @@ -27,13 +26,29 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Callable, Union + from typing import Any, Callable, Union, TypeVar from django.core.handlers.asgi import ASGIRequest from django.http.response import HttpResponse from sentry_sdk._types import Event, EventProcessor + _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"): + markcoroutinefunction = inspect.markcoroutinefunction +else: + + def markcoroutinefunction(func: "_F") -> "_F": + func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore + return func + def _make_asgi_request_event_processor(request: "ASGIRequest") -> "EventProcessor": def asgi_request_event_processor(event: "Event", hint: "dict[str, Any]") -> "Event": @@ -200,7 +215,7 @@ def _async_check(self) -> None: Taken from django.utils.deprecation::MiddlewareMixin._async_check """ if _iscoroutinefunction(self.get_response): - _markcoroutinefunction(self) + markcoroutinefunction(self) def async_route_check(self) -> bool: """