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

1import asyncio 

2import logging 

3import types 

4 

5from asgiref.sync import async_to_sync, sync_to_async 

6 

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 

14 

15from .exception import convert_exception_to_response 

16 

17logger = logging.getLogger("django.request") 

18 

19 

20class BaseHandler: 

21 _view_middleware = None 

22 _template_response_middleware = None 

23 _exception_middleware = None 

24 _middleware_chain = None 

25 

26 def load_middleware(self, is_async=False): 

27 """ 

28 Populate middleware lists from settings.MIDDLEWARE. 

29 

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 = [] 

35 

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 

71 

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 ) 

76 

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 ) 

94 

95 handler = convert_exception_to_response(mw_instance) 

96 handler_is_async = middleware_is_async 

97 

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 

103 

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 

135 

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 

151 

152 async def get_response_async(self, request): 

153 """ 

154 Asynchronous version of get_response. 

155 

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 

173 

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) 

182 

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 

190 

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 

202 

203 # Complain if the view returned None (a common error). 

204 self.check_response(response, callback) 

205 

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 

225 

226 return response 

227 

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) 

236 

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 

244 

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 

263 

264 # Complain if the view returned None or an uncalled coroutine. 

265 self.check_response(response, callback) 

266 

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 

294 

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 

299 

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 

316 

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 ) 

342 

343 # Other utility methods. 

344 

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 

358 

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 

369 

370 

371def reset_urlconf(sender, **kwargs): 

372 """Reset the URLconf after each request is finished.""" 

373 set_urlconf(None) 

374 

375 

376request_finished.connect(reset_urlconf)