Skip to content

Commit

Permalink
fix(django): fix Django ASGI integration on Python 3.12
Browse files Browse the repository at this point in the history
  • Loading branch information
bellini666 committed Apr 27, 2024
1 parent 5dc2b9a commit 1c2881e
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 5 deletions.
28 changes: 23 additions & 5 deletions sentry_sdk/integrations/django/asgi.py
Expand Up @@ -8,6 +8,7 @@

import asyncio
import functools
import inspect

from django.core.handlers.wsgi import WSGIRequest

Expand All @@ -25,14 +26,31 @@


if TYPE_CHECKING:
from collections.abc import Callable
from typing import Any, 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"):
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
Expand Down Expand Up @@ -181,16 +199,16 @@ def _async_check(self):
a thread is not consumed during a whole request.
Taken from django.utils.deprecation::MiddlewareMixin._async_check
"""
if asyncio.iscoroutinefunction(self.get_response):
self._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore
if iscoroutinefunction(self.get_response):
markcoroutinefunction(self)

def async_route_check(self):
# type: () -> bool
"""
Function that checks if we are in async mode,
and if we are forwards the handling of requests to __acall__
"""
return asyncio.iscoroutinefunction(self.get_response)
return iscoroutinefunction(self.get_response)

async def __acall__(self, *args, **kwargs):
# type: (*Any, **Any) -> Any
Expand Down
66 changes: 66 additions & 0 deletions tests/integrations/django/asgi/test_asgi.py
@@ -1,5 +1,8 @@
import base64
import sys
import json
import inspect
import asyncio
import os
from unittest import mock

Expand All @@ -8,6 +11,7 @@
from channels.testing import HttpCommunicator
from sentry_sdk import capture_message
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.django.asgi import _asgi_middleware_mixin_factory
from tests.integrations.django.myapp.asgi import channels_application

try:
Expand Down Expand Up @@ -526,3 +530,65 @@ async def test_asgi_request_body(
assert event["request"]["data"] == expected_data
else:
assert "data" not in event["request"]


@pytest.mark.asyncio
@pytest.mark.skipif(
sys.version_info >= (3, 12),
reason=(
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
),
)
async def test_asgi_mixin_iscoroutinefunction_before_3_12():
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)

async def get_response(): ...

instance = sentry_asgi_mixin(get_response)
assert asyncio.iscoroutinefunction(instance)


@pytest.mark.skipif(
sys.version_info >= (3, 12),
reason=(
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
),
)
def test_asgi_mixin_iscoroutinefunction_when_not_async_before_3_12():
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)

def get_response(): ...

instance = sentry_asgi_mixin(get_response)
assert not asyncio.iscoroutinefunction(instance)


@pytest.mark.asyncio
@pytest.mark.skipif(
sys.version_info < (3, 12),
reason=(
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
),
)
async def test_asgi_mixin_iscoroutinefunction_after_3_12():
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)

async def get_response(): ...

instance = sentry_asgi_mixin(get_response)
assert inspect.iscoroutinefunction(instance)


@pytest.mark.skipif(
sys.version_info < (3, 12),
reason=(
"asyncio.iscoroutinefunction has been replaced in 3.12 by inspect.iscoroutinefunction"
),
)
def test_asgi_mixin_iscoroutinefunction_when_not_async_after_3_12():
sentry_asgi_mixin = _asgi_middleware_mixin_factory(lambda: None)

def get_response(): ...

instance = sentry_asgi_mixin(get_response)
assert not inspect.iscoroutinefunction(instance)

0 comments on commit 1c2881e

Please sign in to comment.