22from __future__ import absolute_import
33
44import sys
5+ import threading
56import weakref
67
78from django import VERSION as DJANGO_VERSION # type: ignore
8- from django .db .models .query import QuerySet # type: ignore
99from django .core import signals # type: ignore
1010
1111if False :
@@ -116,6 +116,8 @@ def sentry_patched_get_response(self, request):
116116 hub = Hub .current
117117 integration = hub .get_integration (DjangoIntegration )
118118 if integration is not None :
119+ _patch_drf ()
120+
119121 with hub .configure_scope () as scope :
120122 scope .add_event_processor (
121123 _make_event_processor (weakref .ref (request ), integration )
@@ -124,25 +126,6 @@ def sentry_patched_get_response(self, request):
124126
125127 BaseHandler .get_response = sentry_patched_get_response
126128
127- try :
128- from rest_framework .views import APIView # type: ignore
129- except ImportError :
130- pass
131- else :
132- # DRF's request type (which wraps the Django request and proxies
133- # all attrs) has some attributes such as `data` which buffer
134- # request data. We want to use those in the RequestExtractor to
135- # get body data more reliably.
136- old_drf_initial = APIView .initial
137-
138- def sentry_patched_drf_initial (self , request , * args , ** kwargs ):
139- with capture_internal_exceptions ():
140- request ._request ._sentry_drf_request_backref = weakref .ref (request )
141- pass
142- return old_drf_initial (self , request , * args , ** kwargs )
143-
144- APIView .initial = sentry_patched_drf_initial
145-
146129 signals .got_request_exception .connect (_got_request_exception )
147130
148131 @add_global_event_processor
@@ -190,6 +173,17 @@ def process_django_templates(event, hint):
190173
191174 @add_global_repr_processor
192175 def _django_queryset_repr (value , hint ):
176+ try :
177+ # Django 1.6 can fail to import `QuerySet` when Django settings
178+ # have not yet been initialized.
179+ #
180+ # If we fail to import, return `NotImplemented`. It's at least
181+ # unlikely that we have a query set in `value` when importing
182+ # `QuerySet` fails.
183+ from django .db .models .query import QuerySet # type: ignore
184+ except Exception :
185+ return NotImplemented
186+
193187 if not isinstance (value , QuerySet ) or value ._result_cache :
194188 return NotImplemented
195189
@@ -205,6 +199,63 @@ def _django_queryset_repr(value, hint):
205199 )
206200
207201
202+ _DRF_PATCHED = False
203+ _DRF_PATCH_LOCK = threading .Lock ()
204+
205+
206+ def _patch_drf ():
207+ """
208+ Patch Django Rest Framework for more/better request data. DRF's request
209+ type is a wrapper around Django's request type. The attribute we're
210+ interested in is `request.data`, which is a cached property containing a
211+ parsed request body. Reading a request body from that property is more
212+ reliable than reading from any of Django's own properties, as those don't
213+ hold payloads in memory and therefore can only be accessed once.
214+
215+ We patch the Django request object to include a weak backreference to the
216+ DRF request object, such that we can later use either in
217+ `DjangoRequestExtractor`.
218+
219+ This function is not called directly on SDK setup, because importing almost
220+ any part of Django Rest Framework will try to access Django settings (where
221+ `sentry_sdk.init()` might be called from in the first place). Instead we
222+ run this function on every request and do the patching on the first
223+ request.
224+ """
225+
226+ global _DRF_PATCHED
227+
228+ if _DRF_PATCHED :
229+ # Double-checked locking
230+ return
231+
232+ with _DRF_PATCH_LOCK :
233+ if _DRF_PATCHED :
234+ return
235+
236+ # We set this regardless of whether the code below succeeds or fails.
237+ # There is no point in trying to patch again on the next request.
238+ _DRF_PATCHED = True
239+
240+ with capture_internal_exceptions ():
241+ try :
242+ from rest_framework .views import APIView # type: ignore
243+ except ImportError :
244+ pass
245+ else :
246+ old_drf_initial = APIView .initial
247+
248+ def sentry_patched_drf_initial (self , request , * args , ** kwargs ):
249+ with capture_internal_exceptions ():
250+ request ._request ._sentry_drf_request_backref = weakref .ref (
251+ request
252+ )
253+ pass
254+ return old_drf_initial (self , request , * args , ** kwargs )
255+
256+ APIView .initial = sentry_patched_drf_initial
257+
258+
208259def _make_event_processor (weak_request , integration ):
209260 # type: (Callable[[], WSGIRequest], DjangoIntegration) -> Callable
210261 def event_processor (event , hint ):
0 commit comments