@@ -78,6 +78,16 @@ def wrap_attributeerrors():
7878 raise exc .with_traceback (info [2 ])
7979
8080
81+ def safe_property (func ):
82+ """Property decorator to ensure AttributeErrors raised in properties are properly handled"""
83+
84+ @property
85+ def new_func (self ):
86+ with wrap_attributeerrors ():
87+ return func (self )
88+
89+ return new_func
90+
8191class Empty :
8292 """
8393 Placeholder for unset attributes.
@@ -193,12 +203,12 @@ def __class_getitem__(cls, *args, **kwargs):
193203 def _default_negotiator (self ):
194204 return api_settings .DEFAULT_CONTENT_NEGOTIATION_CLASS ()
195205
196- @property
206+ @safe_property
197207 def content_type (self ):
198208 meta = self ._request .META
199209 return meta .get ('CONTENT_TYPE' , meta .get ('HTTP_CONTENT_TYPE' , '' ))
200210
201- @property
211+ @safe_property
202212 def stream (self ):
203213 """
204214 Returns an object that may be used to stream the request content.
@@ -207,28 +217,27 @@ def stream(self):
207217 self ._load_stream ()
208218 return self ._stream
209219
210- @property
220+ @safe_property
211221 def query_params (self ):
212222 """
213223 More semantically correct name for request.GET.
214224 """
215225 return self ._request .GET
216226
217- @property
227+ @safe_property
218228 def data (self ):
219229 if not _hasattr (self , '_full_data' ):
220230 self ._load_data_and_files ()
221231 return self ._full_data
222232
223- @property
233+ @safe_property
224234 def user (self ):
225235 """
226236 Returns the user associated with the current request, as authenticated
227237 by the authentication classes provided to the request.
228238 """
229239 if not hasattr (self , '_user' ):
230- with wrap_attributeerrors ():
231- self ._authenticate ()
240+ self ._authenticate ()
232241 return self ._user
233242
234243 @user .setter
@@ -244,15 +253,14 @@ def user(self, value):
244253 self ._user = value
245254 self ._request .user = value
246255
247- @property
256+ @safe_property
248257 def auth (self ):
249258 """
250259 Returns any non-user authentication information associated with the
251260 request, such as an authentication token.
252261 """
253262 if not hasattr (self , '_auth' ):
254- with wrap_attributeerrors ():
255- self ._authenticate ()
263+ self ._authenticate ()
256264 return self ._auth
257265
258266 @auth .setter
@@ -264,15 +272,14 @@ def auth(self, value):
264272 self ._auth = value
265273 self ._request .auth = value
266274
267- @property
275+ @safe_property
268276 def successful_authenticator (self ):
269277 """
270278 Return the instance of the authentication instance class that was used
271279 to authenticate the request, or `None`.
272280 """
273281 if not hasattr (self , '_authenticator' ):
274- with wrap_attributeerrors ():
275- self ._authenticate ()
282+ self ._authenticate ()
276283 return self ._authenticator
277284
278285 def _load_data_and_files (self ):
@@ -420,9 +427,9 @@ def __getattr__(self, attr):
420427 _request = self .__getattribute__ ("_request" )
421428 return getattr (_request , attr )
422429 except AttributeError :
423- return self .__getattribute__ ( attr )
430+ raise AttributeError ( f"' { self .__class__ . __name__ } ' object has no attribute ' { attr } '" )
424431
425- @property
432+ @safe_property
426433 def POST (self ):
427434 # Ensure that request.POST uses our request parsing.
428435 if not _hasattr (self , '_data' ):
@@ -431,7 +438,7 @@ def POST(self):
431438 return self ._data
432439 return QueryDict ('' , encoding = self ._request ._encoding )
433440
434- @property
441+ @safe_property
435442 def FILES (self ):
436443 # Leave this one alone for backwards compat with Django's request.FILES
437444 # Different from the other two cases, which are not valid property
0 commit comments