Skip to content

Commit 44b0244

Browse files
authored
feat(integrations): Support Django 5.0 (#2490)
Fix the way we wrap signal receivers: Django 5.0 introduced async receivers and changed the signature of the `Signal._live_receivers` method to return both sync and async receivers. We'll need to change the Django version in tox.ini to 5.0 once it's been released. At the moment we're using the 5.0b1 release.
1 parent cb7299a commit 44b0244

File tree

4 files changed

+52
-30
lines changed

4 files changed

+52
-30
lines changed

sentry_sdk/integrations/django/asgi.py

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

99
import asyncio
1010

11+
from django.core.handlers.wsgi import WSGIRequest
12+
1113
from sentry_sdk import Hub, _functools
1214
from sentry_sdk._types import TYPE_CHECKING
1315
from sentry_sdk.consts import OP
@@ -16,26 +18,21 @@
1618
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
1719
from sentry_sdk.utils import capture_internal_exceptions
1820

19-
from django.core.handlers.wsgi import WSGIRequest
20-
2121

2222
if TYPE_CHECKING:
23-
from typing import Any
24-
from typing import Dict
25-
from typing import Union
26-
from typing import Callable
23+
from collections.abc import Callable
24+
from typing import Any, Union
2725

2826
from django.core.handlers.asgi import ASGIRequest
2927
from django.http.response import HttpResponse
3028

31-
from sentry_sdk.integrations.django import DjangoIntegration
3229
from sentry_sdk._types import EventProcessor
3330

3431

35-
def _make_asgi_request_event_processor(request, integration):
36-
# type: (ASGIRequest, DjangoIntegration) -> EventProcessor
32+
def _make_asgi_request_event_processor(request):
33+
# type: (ASGIRequest) -> EventProcessor
3734
def asgi_request_event_processor(event, hint):
38-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
35+
# type: (dict[str, Any], dict[str, Any]) -> dict[str, Any]
3936
# if the request is gone we are fine not logging the data from
4037
# it. This might happen if the processor is pushed away to
4138
# another thread.
@@ -103,9 +100,7 @@ def sentry_patched_create_request(self, *args, **kwargs):
103100
# (otherwise Django closes the body stream and makes it impossible to read it again)
104101
_ = request.body
105102

106-
scope.add_event_processor(
107-
_make_asgi_request_event_processor(request, integration)
108-
)
103+
scope.add_event_processor(_make_asgi_request_event_processor(request))
109104

110105
return request, error_response
111106

sentry_sdk/integrations/django/signals_handlers.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
from sentry_sdk._functools import wraps
88
from sentry_sdk._types import TYPE_CHECKING
99
from sentry_sdk.consts import OP
10+
from sentry_sdk.integrations.django import DJANGO_VERSION
1011

1112

1213
if TYPE_CHECKING:
13-
from typing import Any
14-
from typing import Callable
15-
from typing import List
14+
from collections.abc import Callable
15+
from typing import Any, Union
1616

1717

1818
def _get_receiver_name(receiver):
@@ -42,17 +42,27 @@ def _get_receiver_name(receiver):
4242

4343
def patch_signals():
4444
# type: () -> None
45-
"""Patch django signal receivers to create a span"""
45+
"""
46+
Patch django signal receivers to create a span.
47+
48+
This only wraps sync receivers. Django>=5.0 introduced async receivers, but
49+
since we don't create transactions for ASGI Django, we don't wrap them.
50+
"""
4651
from sentry_sdk.integrations.django import DjangoIntegration
4752

4853
old_live_receivers = Signal._live_receivers
4954

5055
def _sentry_live_receivers(self, sender):
51-
# type: (Signal, Any) -> List[Callable[..., Any]]
56+
# type: (Signal, Any) -> Union[tuple[list[Callable[..., Any]], list[Callable[..., Any]]], list[Callable[..., Any]]]
5257
hub = Hub.current
53-
receivers = old_live_receivers(self, sender)
5458

55-
def sentry_receiver_wrapper(receiver):
59+
if DJANGO_VERSION >= (5, 0):
60+
sync_receivers, async_receivers = old_live_receivers(self, sender)
61+
else:
62+
sync_receivers = old_live_receivers(self, sender)
63+
async_receivers = []
64+
65+
def sentry_sync_receiver_wrapper(receiver):
5666
# type: (Callable[..., Any]) -> Callable[..., Any]
5767
@wraps(receiver)
5868
def wrapper(*args, **kwargs):
@@ -69,9 +79,12 @@ def wrapper(*args, **kwargs):
6979

7080
integration = hub.get_integration(DjangoIntegration)
7181
if integration and integration.signals_spans:
72-
for idx, receiver in enumerate(receivers):
73-
receivers[idx] = sentry_receiver_wrapper(receiver)
82+
for idx, receiver in enumerate(sync_receivers):
83+
sync_receivers[idx] = sentry_sync_receiver_wrapper(receiver)
7484

75-
return receivers
85+
if DJANGO_VERSION >= (5, 0):
86+
return sync_receivers, async_receivers
87+
else:
88+
return sync_receivers
7689

7790
Signal._live_receivers = _sentry_live_receivers

tests/integrations/django/asgi/test_asgi.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
@pytest.mark.parametrize("application", APPS)
2828
@pytest.mark.asyncio
29+
@pytest.mark.forked
2930
async def test_basic(sentry_init, capture_events, application):
3031
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
3132

@@ -58,6 +59,7 @@ async def test_basic(sentry_init, capture_events, application):
5859

5960
@pytest.mark.parametrize("application", APPS)
6061
@pytest.mark.asyncio
62+
@pytest.mark.forked
6163
@pytest.mark.skipif(
6264
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
6365
)
@@ -85,6 +87,7 @@ async def test_async_views(sentry_init, capture_events, application):
8587
@pytest.mark.parametrize("application", APPS)
8688
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
8789
@pytest.mark.asyncio
90+
@pytest.mark.forked
8891
@pytest.mark.skipif(
8992
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
9093
)
@@ -119,6 +122,7 @@ async def test_active_thread_id(sentry_init, capture_envelopes, endpoint, applic
119122

120123

121124
@pytest.mark.asyncio
125+
@pytest.mark.forked
122126
@pytest.mark.skipif(
123127
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
124128
)
@@ -152,6 +156,7 @@ async def test_async_views_concurrent_execution(sentry_init, settings):
152156

153157

154158
@pytest.mark.asyncio
159+
@pytest.mark.forked
155160
@pytest.mark.skipif(
156161
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
157162
)
@@ -189,6 +194,7 @@ async def test_async_middleware_that_is_function_concurrent_execution(
189194

190195

191196
@pytest.mark.asyncio
197+
@pytest.mark.forked
192198
@pytest.mark.skipif(
193199
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
194200
)
@@ -238,6 +244,7 @@ async def test_async_middleware_spans(
238244

239245

240246
@pytest.mark.asyncio
247+
@pytest.mark.forked
241248
@pytest.mark.skipif(
242249
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
243250
)
@@ -267,6 +274,7 @@ async def test_has_trace_if_performance_enabled(sentry_init, capture_events):
267274

268275

269276
@pytest.mark.asyncio
277+
@pytest.mark.forked
270278
@pytest.mark.skipif(
271279
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
272280
)
@@ -293,6 +301,7 @@ async def test_has_trace_if_performance_disabled(sentry_init, capture_events):
293301

294302

295303
@pytest.mark.asyncio
304+
@pytest.mark.forked
296305
@pytest.mark.skipif(
297306
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
298307
)
@@ -328,6 +337,7 @@ async def test_trace_from_headers_if_performance_enabled(sentry_init, capture_ev
328337

329338

330339
@pytest.mark.asyncio
340+
@pytest.mark.forked
331341
@pytest.mark.skipif(
332342
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
333343
)
@@ -373,6 +383,7 @@ async def test_trace_from_headers_if_performance_disabled(sentry_init, capture_e
373383
],
374384
)
375385
@pytest.mark.asyncio
386+
@pytest.mark.forked
376387
@pytest.mark.skipif(
377388
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
378389
)

tox.ini

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ envlist =
7979
{py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-django-v{3.2}
8080
# - Django 4.x
8181
{py3.8,py3.9,py3.10,py3.11,py3.12}-django-v{4.0,4.1,4.2}
82+
# - Django 5.x
83+
{py3.10,py3.11,py3.12}-django-v{5.0}
8284

8385
# Falcon
8486
{py2.7,py3.5,py3.6,py3.7}-falcon-v{1.4}
@@ -288,17 +290,16 @@ deps =
288290
django: Werkzeug<2.1.0
289291
django-v{1.11,2.0,2.1,2.2,3.0,3.1,3.2}: djangorestframework>=3.0.0,<4.0.0
290292

291-
{py3.7,py3.8,py3.9,py3.10,py3.11}-django-v{1.11,2.0,2.1,2.2,3.0,3.1,3.2,4.0,4.1,4.2}: pytest-asyncio
292-
{py3.7,py3.8,py3.9,py3.10,py3.11}-django-v{1.11,2.0,2.1,2.2,3.0,3.1,3.2,4.0,4.1,4.2}: channels[daphne]>2
293+
{py3.7,py3.8,py3.9,py3.10,py3.11,py3.12}-django-v{1.11,2.0,2.1,2.2,3.0,3.1,3.2,4.0,4.1,4.2,5.0}: pytest-asyncio
294+
{py3.7,py3.8,py3.9,py3.10,py3.11,py3.12}-django-v{1.11,2.0,2.1,2.2,3.0,3.1,3.2,4.0,4.1,4.2,5.0}: channels[daphne]>2
293295

294296
django-v{1.8,1.9,1.10,1.11,2.0,2.1}: pytest-django<4.0
295297
django-v{2.2,3.0,3.1,3.2}: pytest-django>=4.0
296298
django-v{2.2,3.0,3.1,3.2}: Werkzeug<2.0
297-
298-
django-v{4.0,4.1,4.2}: djangorestframework
299-
django-v{4.0,4.1,4.2}: pytest-asyncio
300-
django-v{4.0,4.1,4.2}: pytest-django
301-
django-v{4.0,4.1,4.2}: Werkzeug
299+
django-v{4.0,4.1,4.2,5.0}: djangorestframework
300+
django-v{4.0,4.1,4.2,5.0}: pytest-asyncio
301+
django-v{4.0,4.1,4.2,5.0}: pytest-django
302+
django-v{4.0,4.1,4.2,5.0}: Werkzeug
302303

303304
django-v1.8: Django>=1.8,<1.9
304305
django-v1.9: Django>=1.9,<1.10
@@ -313,6 +314,8 @@ deps =
313314
django-v4.0: Django>=4.0,<4.1
314315
django-v4.1: Django>=4.1,<4.2
315316
django-v4.2: Django>=4.2,<4.3
317+
# TODO: change to final when available
318+
django-v5.0: Django==5.0b1
316319

317320
# Falcon
318321
falcon-v1.4: falcon>=1.4,<1.5

0 commit comments

Comments
 (0)