diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index dcb590fcfa..fbd5a1d830 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -13,6 +13,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 +PY313 = sys.version_info[0] == 3 and sys.version_info[1] >= 13 def with_metaclass(meta: "Any", *bases: "Any") -> "Any": diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index 525ca4b5b5..5a94a41d64 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -1,5 +1,8 @@ +import asyncio +import inspect import urllib +from sentry_sdk._compat import PY313 from sentry_sdk.scope import should_send_default_pii from sentry_sdk.integrations._wsgi_common import _filter_headers @@ -14,6 +17,10 @@ from sentry_sdk.utils import AnnotatedValue +_iscoroutinefunction = ( + inspect.iscoroutinefunction if PY313 else asyncio.iscoroutinefunction +) + 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..78396d7dba 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -16,6 +16,7 @@ from sentry_sdk.consts import OP from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +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, @@ -41,10 +42,8 @@ # 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 @@ -215,7 +214,7 @@ 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): + if _iscoroutinefunction(self.get_response): markcoroutinefunction(self) def async_route_check(self) -> bool: @@ -223,7 +222,7 @@ 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 3572b1c07f..61703207f6 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -1,9 +1,9 @@ -import asyncio 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.traces import NoOpStreamedSpan, StreamedSpan from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource @@ -74,7 +74,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 _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 dac9887e2f..55a2091fa8 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -1,4 +1,3 @@ -import asyncio 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 asyncio.iscoroutinefunction(obj) or ( - callable(obj) and asyncio.iscoroutinefunction(obj.__call__) + return _iscoroutinefunction(obj) or ( + callable(obj) and _iscoroutinefunction(obj.__call__) )