Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/core/handlers/base.py: 45%
184 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import asyncio
2import logging
3import types
5from asgiref.sync import async_to_sync, sync_to_async
7from django.conf import settings
8from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
9from django.core.signals import request_finished
10from django.db import connections, transaction
11from django.urls import get_resolver, set_urlconf
12from django.utils.log import log_response
13from django.utils.module_loading import import_string
15from .exception import convert_exception_to_response
17logger = logging.getLogger("django.request")
20class BaseHandler:
21 _view_middleware = None
22 _template_response_middleware = None
23 _exception_middleware = None
24 _middleware_chain = None
26 def load_middleware(self, is_async=False):
27 """
28 Populate middleware lists from settings.MIDDLEWARE.
30 Must be called after the environment is fixed (see __call__ in subclasses).
31 """
32 self._view_middleware = []
33 self._template_response_middleware = []
34 self._exception_middleware = []
36 get_response = self._get_response_async if is_async else self._get_response
37 handler = convert_exception_to_response(get_response)
38 handler_is_async = is_async
39 for middleware_path in reversed(settings.MIDDLEWARE):
40 middleware = import_string(middleware_path)
41 middleware_can_sync = getattr(middleware, "sync_capable", True)
42 middleware_can_async = getattr(middleware, "async_capable", False)
43 if not middleware_can_sync and not middleware_can_async: 43 ↛ 44line 43 didn't jump to line 44, because the condition on line 43 was never true
44 raise RuntimeError(
45 "Middleware %s must have at least one of "
46 "sync_capable/async_capable set to True." % middleware_path
47 )
48 elif not handler_is_async and middleware_can_sync: 48 ↛ 51line 48 didn't jump to line 51, because the condition on line 48 was never false
49 middleware_is_async = False
50 else:
51 middleware_is_async = middleware_can_async
52 try:
53 # Adapt handler, if needed.
54 adapted_handler = self.adapt_method_mode(
55 middleware_is_async,
56 handler,
57 handler_is_async,
58 debug=settings.DEBUG,
59 name="middleware %s" % middleware_path,
60 )
61 mw_instance = middleware(adapted_handler)
62 except MiddlewareNotUsed as exc:
63 if settings.DEBUG:
64 if str(exc):
65 logger.debug("MiddlewareNotUsed(%r): %s", middleware_path, exc)
66 else:
67 logger.debug("MiddlewareNotUsed: %r", middleware_path)
68 continue
69 else:
70 handler = adapted_handler
72 if mw_instance is None: 72 ↛ 73line 72 didn't jump to line 73, because the condition on line 72 was never true
73 raise ImproperlyConfigured(
74 "Middleware factory %s returned None." % middleware_path
75 )
77 if hasattr(mw_instance, "process_view"):
78 self._view_middleware.insert(
79 0,
80 self.adapt_method_mode(is_async, mw_instance.process_view),
81 )
82 if hasattr(mw_instance, "process_template_response"): 82 ↛ 83line 82 didn't jump to line 83, because the condition on line 82 was never true
83 self._template_response_middleware.append(
84 self.adapt_method_mode(
85 is_async, mw_instance.process_template_response
86 ),
87 )
88 if hasattr(mw_instance, "process_exception"): 88 ↛ 91line 88 didn't jump to line 91, because the condition on line 88 was never true
89 # The exception-handling stack is still always synchronous for
90 # now, so adapt that way.
91 self._exception_middleware.append(
92 self.adapt_method_mode(False, mw_instance.process_exception),
93 )
95 handler = convert_exception_to_response(mw_instance)
96 handler_is_async = middleware_is_async
98 # Adapt the top of the stack, if needed.
99 handler = self.adapt_method_mode(is_async, handler, handler_is_async)
100 # We only assign to this when initialization is complete as it is used
101 # as a flag for initialization being complete.
102 self._middleware_chain = handler
104 def adapt_method_mode(
105 self,
106 is_async,
107 method,
108 method_is_async=None,
109 debug=False,
110 name=None,
111 ):
112 """
113 Adapt a method to be in the correct "mode":
114 - If is_async is False:
115 - Synchronous methods are left alone
116 - Asynchronous methods are wrapped with async_to_sync
117 - If is_async is True:
118 - Synchronous methods are wrapped with sync_to_async()
119 - Asynchronous methods are left alone
120 """
121 if method_is_async is None:
122 method_is_async = asyncio.iscoroutinefunction(method)
123 if debug and not name: 123 ↛ 124line 123 didn't jump to line 124, because the condition on line 123 was never true
124 name = name or "method %s()" % method.__qualname__
125 if is_async: 125 ↛ 126line 125 didn't jump to line 126, because the condition on line 125 was never true
126 if not method_is_async:
127 if debug:
128 logger.debug("Synchronous %s adapted.", name)
129 return sync_to_async(method, thread_sensitive=True)
130 elif method_is_async: 130 ↛ 131line 130 didn't jump to line 131, because the condition on line 130 was never true
131 if debug:
132 logger.debug("Asynchronous %s adapted.", name)
133 return async_to_sync(method)
134 return method
136 def get_response(self, request):
137 """Return an HttpResponse object for the given HttpRequest."""
138 # Setup default url resolver for this thread
139 set_urlconf(settings.ROOT_URLCONF)
140 response = self._middleware_chain(request)
141 response._resource_closers.append(request.close)
142 if response.status_code >= 400:
143 log_response(
144 "%s: %s",
145 response.reason_phrase,
146 request.path,
147 response=response,
148 request=request,
149 )
150 return response
152 async def get_response_async(self, request):
153 """
154 Asynchronous version of get_response.
156 Funneling everything, including WSGI, into a single async
157 get_response() is too slow. Avoid the context switch by using
158 a separate async response path.
159 """
160 # Setup default url resolver for this thread.
161 set_urlconf(settings.ROOT_URLCONF)
162 response = await self._middleware_chain(request)
163 response._resource_closers.append(request.close)
164 if response.status_code >= 400:
165 await sync_to_async(log_response, thread_sensitive=False)(
166 "%s: %s",
167 response.reason_phrase,
168 request.path,
169 response=response,
170 request=request,
171 )
172 return response
174 def _get_response(self, request):
175 """
176 Resolve and call the view, then apply view, exception, and
177 template_response middleware. This method is everything that happens
178 inside the request/response middleware.
179 """
180 response = None
181 callback, callback_args, callback_kwargs = self.resolve_request(request)
183 # Apply view middleware
184 for middleware_method in self._view_middleware:
185 response = middleware_method(
186 request, callback, callback_args, callback_kwargs
187 )
188 if response: 188 ↛ 189line 188 didn't jump to line 189, because the condition on line 188 was never true
189 break
191 if response is None: 191 ↛ 204line 191 didn't jump to line 204, because the condition on line 191 was never false
192 wrapped_callback = self.make_view_atomic(callback)
193 # If it is an asynchronous view, run it in a subthread.
194 if asyncio.iscoroutinefunction(wrapped_callback): 194 ↛ 195line 194 didn't jump to line 195, because the condition on line 194 was never true
195 wrapped_callback = async_to_sync(wrapped_callback)
196 try:
197 response = wrapped_callback(request, *callback_args, **callback_kwargs)
198 except Exception as e:
199 response = self.process_exception_by_middleware(e, request)
200 if response is None:
201 raise
203 # Complain if the view returned None (a common error).
204 self.check_response(response, callback)
206 # If the response supports deferred rendering, apply template
207 # response middleware and then render the response
208 if hasattr(response, "render") and callable(response.render): 208 ↛ 226line 208 didn't jump to line 226, because the condition on line 208 was never false
209 for middleware_method in self._template_response_middleware: 209 ↛ 210line 209 didn't jump to line 210, because the loop on line 209 never started
210 response = middleware_method(request, response)
211 # Complain if the template response middleware returned None
212 # (a common error).
213 self.check_response(
214 response,
215 middleware_method,
216 name="%s.process_template_response"
217 % (middleware_method.__self__.__class__.__name__,),
218 )
219 try:
220 response = response.render()
221 except Exception as e:
222 response = self.process_exception_by_middleware(e, request)
223 if response is None:
224 raise
226 return response
228 async def _get_response_async(self, request):
229 """
230 Resolve and call the view, then apply view, exception, and
231 template_response middleware. This method is everything that happens
232 inside the request/response middleware.
233 """
234 response = None
235 callback, callback_args, callback_kwargs = self.resolve_request(request)
237 # Apply view middleware.
238 for middleware_method in self._view_middleware:
239 response = await middleware_method(
240 request, callback, callback_args, callback_kwargs
241 )
242 if response:
243 break
245 if response is None:
246 wrapped_callback = self.make_view_atomic(callback)
247 # If it is a synchronous view, run it in a subthread
248 if not asyncio.iscoroutinefunction(wrapped_callback):
249 wrapped_callback = sync_to_async(
250 wrapped_callback, thread_sensitive=True
251 )
252 try:
253 response = await wrapped_callback(
254 request, *callback_args, **callback_kwargs
255 )
256 except Exception as e:
257 response = await sync_to_async(
258 self.process_exception_by_middleware,
259 thread_sensitive=True,
260 )(e, request)
261 if response is None:
262 raise
264 # Complain if the view returned None or an uncalled coroutine.
265 self.check_response(response, callback)
267 # If the response supports deferred rendering, apply template
268 # response middleware and then render the response
269 if hasattr(response, "render") and callable(response.render):
270 for middleware_method in self._template_response_middleware:
271 response = await middleware_method(request, response)
272 # Complain if the template response middleware returned None or
273 # an uncalled coroutine.
274 self.check_response(
275 response,
276 middleware_method,
277 name="%s.process_template_response"
278 % (middleware_method.__self__.__class__.__name__,),
279 )
280 try:
281 if asyncio.iscoroutinefunction(response.render):
282 response = await response.render()
283 else:
284 response = await sync_to_async(
285 response.render, thread_sensitive=True
286 )()
287 except Exception as e:
288 response = await sync_to_async(
289 self.process_exception_by_middleware,
290 thread_sensitive=True,
291 )(e, request)
292 if response is None:
293 raise
295 # Make sure the response is not a coroutine
296 if asyncio.iscoroutine(response):
297 raise RuntimeError("Response is still a coroutine.")
298 return response
300 def resolve_request(self, request):
301 """
302 Retrieve/set the urlconf for the request. Return the view resolved,
303 with its args and kwargs.
304 """
305 # Work out the resolver.
306 if hasattr(request, "urlconf"): 306 ↛ 307line 306 didn't jump to line 307, because the condition on line 306 was never true
307 urlconf = request.urlconf
308 set_urlconf(urlconf)
309 resolver = get_resolver(urlconf)
310 else:
311 resolver = get_resolver()
312 # Resolve the view, and assign the match object back to the request.
313 resolver_match = resolver.resolve(request.path_info)
314 request.resolver_match = resolver_match
315 return resolver_match
317 def check_response(self, response, callback, name=None):
318 """
319 Raise an error if the view returned None or an uncalled coroutine.
320 """
321 if not (response is None or asyncio.iscoroutine(response)): 321 ↛ 323line 321 didn't jump to line 323, because the condition on line 321 was never false
322 return
323 if not name:
324 if isinstance(callback, types.FunctionType): # FBV
325 name = "The view %s.%s" % (callback.__module__, callback.__name__)
326 else: # CBV
327 name = "The view %s.%s.__call__" % (
328 callback.__module__,
329 callback.__class__.__name__,
330 )
331 if response is None:
332 raise ValueError(
333 "%s didn't return an HttpResponse object. It returned None "
334 "instead." % name
335 )
336 elif asyncio.iscoroutine(response):
337 raise ValueError(
338 "%s didn't return an HttpResponse object. It returned an "
339 "unawaited coroutine instead. You may need to add an 'await' "
340 "into your view." % name
341 )
343 # Other utility methods.
345 def make_view_atomic(self, view):
346 non_atomic_requests = getattr(view, "_non_atomic_requests", set())
347 for db in connections.all():
348 if ( 348 ↛ 352line 348 didn't jump to line 352
349 db.settings_dict["ATOMIC_REQUESTS"]
350 and db.alias not in non_atomic_requests
351 ):
352 if asyncio.iscoroutinefunction(view):
353 raise RuntimeError(
354 "You cannot use ATOMIC_REQUESTS with async views."
355 )
356 view = transaction.atomic(using=db.alias)(view)
357 return view
359 def process_exception_by_middleware(self, exception, request):
360 """
361 Pass the exception to the exception middleware. If no middleware
362 return a response for this exception, return None.
363 """
364 for middleware_method in self._exception_middleware:
365 response = middleware_method(request, exception)
366 if response:
367 return response
368 return None
371def reset_urlconf(sender, **kwargs):
372 """Reset the URLconf after each request is finished."""
373 set_urlconf(None)
376request_finished.connect(reset_urlconf)