Skip to content

Commit 181cf14

Browse files
committed
fix(django): fix Django ASGI integration on Python 3.12
1 parent 5dc2b9a commit 181cf14

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

sentry_sdk/integrations/django/asgi.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import asyncio
1010
import functools
11+
import inspect
12+
from typing import Callable, TypeVar
1113

1214
from django.core.handlers.wsgi import WSGIRequest
1315

@@ -25,14 +27,31 @@
2527

2628

2729
if TYPE_CHECKING:
28-
from collections.abc import Callable
2930
from typing import Any, Union
3031

3132
from django.core.handlers.asgi import ASGIRequest
3233
from django.http.response import HttpResponse
3334

3435
from sentry_sdk._types import Event, EventProcessor
3536

37+
_F = TypeVar("_F", bound=Callable[..., Any])
38+
39+
40+
# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for
41+
# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker.
42+
# The latter is replaced with the inspect.markcoroutinefunction decorator.
43+
# Until 3.12 is the minimum supported Python version, provide a shim.
44+
# This was copied from https://github.com/django/asgiref/blob/main/asgiref/sync.py
45+
if hasattr(inspect, "markcoroutinefunction"):
46+
iscoroutinefunction = inspect.iscoroutinefunction
47+
markcoroutinefunction = inspect.markcoroutinefunction
48+
else:
49+
iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment]
50+
51+
def markcoroutinefunction(func: _F) -> _F:
52+
func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore
53+
return func
54+
3655

3756
def _make_asgi_request_event_processor(request):
3857
# type: (ASGIRequest) -> EventProcessor
@@ -181,16 +200,16 @@ def _async_check(self):
181200
a thread is not consumed during a whole request.
182201
Taken from django.utils.deprecation::MiddlewareMixin._async_check
183202
"""
184-
if asyncio.iscoroutinefunction(self.get_response):
185-
self._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore
203+
if iscoroutinefunction(self.get_response):
204+
markcoroutinefunction(self)
186205

187206
def async_route_check(self):
188207
# type: () -> bool
189208
"""
190209
Function that checks if we are in async mode,
191210
and if we are forwards the handling of requests to __acall__
192211
"""
193-
return asyncio.iscoroutinefunction(self.get_response)
212+
return iscoroutinefunction(self.get_response)
194213

195214
async def __acall__(self, *args, **kwargs):
196215
# type: (*Any, **Any) -> Any

tests/integrations/django/asgi/test_asgi.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import base64
2+
import sys
23
import json
4+
import inspect
5+
import asyncio
36
import os
47
from unittest import mock
58

@@ -8,6 +11,7 @@
811
from channels.testing import HttpCommunicator
912
from sentry_sdk import capture_message
1013
from sentry_sdk.integrations.django import DjangoIntegration
14+
from sentry_sdk.integrations.django.asgi import _asgi_middleware_mixin_factory
1115
from tests.integrations.django.myapp.asgi import channels_application
1216

1317
try:
@@ -526,3 +530,65 @@ async def test_asgi_request_body(
526530
assert event["request"]["data"] == expected_data
527531
else:
528532
assert "data" not in event["request"]
533+
534+
535+
@pytest.mark.asyncio
536+
@pytest.mark.skipif(
537+
sys.version_info >= (3, 12),
538+
reason=(
539+
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
540+
),
541+
)
542+
async def test_asgi_mixin_iscoroutinefunction_before_3_12():
543+
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)
544+
545+
async def get_response(): ...
546+
547+
instance = sentry_asgi_mixin(get_response)
548+
assert asyncio.iscoroutinefunction(instance)
549+
550+
551+
@pytest.mark.skipif(
552+
sys.version_info >= (3, 12),
553+
reason=(
554+
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
555+
),
556+
)
557+
def test_asgi_mixin_iscoroutinefunction_when_not_async_before_3_12():
558+
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)
559+
560+
def get_response(): ...
561+
562+
instance = sentry_asgi_mixin(get_response)
563+
assert not asyncio.iscoroutinefunction(instance)
564+
565+
566+
@pytest.mark.asyncio
567+
@pytest.mark.skipif(
568+
sys.version_info < (3, 12),
569+
reason=(
570+
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
571+
),
572+
)
573+
async def test_asgi_mixin_iscoroutinefunction_after_3_12():
574+
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)
575+
576+
async def get_response(): ...
577+
578+
instance = sentry_asgi_mixin(get_response)
579+
assert inspect.iscoroutinefunction(instance)
580+
581+
582+
@pytest.mark.skipif(
583+
sys.version_info < (3, 12),
584+
reason=(
585+
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
586+
),
587+
)
588+
def test_asgi_mixin_iscoroutinefunction_when_not_async_after_3_12():
589+
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)
590+
591+
def get_response(): ...
592+
593+
instance = sentry_asgi_mixin(get_response)
594+
assert not inspect.iscoroutinefunction(instance)

0 commit comments

Comments
 (0)