Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/web_app.py: 38%

252 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-27 06:09 +0000

1import asyncio 

2import logging 

3import warnings 

4from functools import partial, update_wrapper 

5from typing import ( 

6 TYPE_CHECKING, 

7 Any, 

8 AsyncIterator, 

9 Awaitable, 

10 Callable, 

11 Dict, 

12 Iterable, 

13 Iterator, 

14 List, 

15 Mapping, 

16 MutableMapping, 

17 Optional, 

18 Sequence, 

19 Type, 

20 TypeVar, 

21 Union, 

22 cast, 

23 final, 

24 overload, 

25) 

26 

27from aiosignal import Signal 

28from frozenlist import FrozenList 

29 

30from . import hdrs 

31from .helpers import AppKey 

32from .log import web_logger 

33from .typedefs import Middleware 

34from .web_middlewares import _fix_request_current_app 

35from .web_request import Request 

36from .web_response import StreamResponse 

37from .web_routedef import AbstractRouteDef 

38from .web_urldispatcher import ( 

39 AbstractResource, 

40 AbstractRoute, 

41 Domain, 

42 MaskDomain, 

43 MatchedSubAppResource, 

44 PrefixedSubAppResource, 

45 UrlDispatcher, 

46) 

47 

48__all__ = ("Application", "CleanupError") 

49 

50 

51if TYPE_CHECKING: # pragma: no cover 

52 _AppSignal = Signal[Callable[["Application"], Awaitable[None]]] 

53 _RespPrepareSignal = Signal[Callable[[Request, StreamResponse], Awaitable[None]]] 

54 _Middlewares = FrozenList[Middleware] 

55 _MiddlewaresHandlers = Sequence[Middleware] 

56 _Subapps = List["Application"] 

57else: 

58 # No type checker mode, skip types 

59 _AppSignal = Signal 

60 _RespPrepareSignal = Signal 

61 _Handler = Callable 

62 _Middlewares = FrozenList 

63 _MiddlewaresHandlers = Sequence 

64 _Subapps = List 

65 

66_T = TypeVar("_T") 

67_U = TypeVar("_U") 

68 

69 

70@final 

71class Application(MutableMapping[Union[str, AppKey[Any]], Any]): 

72 __slots__ = ( 

73 "logger", 

74 "_debug", 

75 "_router", 

76 "_loop", 

77 "_handler_args", 

78 "_middlewares", 

79 "_middlewares_handlers", 

80 "_run_middlewares", 

81 "_state", 

82 "_frozen", 

83 "_pre_frozen", 

84 "_subapps", 

85 "_on_response_prepare", 

86 "_on_startup", 

87 "_on_shutdown", 

88 "_on_cleanup", 

89 "_client_max_size", 

90 "_cleanup_ctx", 

91 ) 

92 

93 def __init__( 

94 self, 

95 *, 

96 logger: logging.Logger = web_logger, 

97 middlewares: Iterable[Middleware] = (), 

98 handler_args: Optional[Mapping[str, Any]] = None, 

99 client_max_size: int = 1024**2, 

100 debug: Any = ..., # mypy doesn't support ellipsis 

101 ) -> None: 

102 if debug is not ...: 

103 warnings.warn( 

104 "debug argument is no-op since 4.0 " "and scheduled for removal in 5.0", 

105 DeprecationWarning, 

106 stacklevel=2, 

107 ) 

108 self._router = UrlDispatcher() 

109 self._handler_args = handler_args 

110 self.logger = logger 

111 

112 self._middlewares: _Middlewares = FrozenList(middlewares) 

113 

114 # initialized on freezing 

115 self._middlewares_handlers: _MiddlewaresHandlers = tuple() 

116 # initialized on freezing 

117 self._run_middlewares: Optional[bool] = None 

118 

119 self._state: Dict[Union[AppKey[Any], str], object] = {} 

120 self._frozen = False 

121 self._pre_frozen = False 

122 self._subapps: _Subapps = [] 

123 

124 self._on_response_prepare: _RespPrepareSignal = Signal(self) 

125 self._on_startup: _AppSignal = Signal(self) 

126 self._on_shutdown: _AppSignal = Signal(self) 

127 self._on_cleanup: _AppSignal = Signal(self) 

128 self._cleanup_ctx = CleanupContext() 

129 self._on_startup.append(self._cleanup_ctx._on_startup) 

130 self._on_cleanup.append(self._cleanup_ctx._on_cleanup) 

131 self._client_max_size = client_max_size 

132 

133 def __init_subclass__(cls: Type["Application"]) -> None: 

134 raise TypeError( 

135 "Inheritance class {} from web.Application " 

136 "is forbidden".format(cls.__name__) 

137 ) 

138 

139 # MutableMapping API 

140 

141 def __eq__(self, other: object) -> bool: 

142 return self is other 

143 

144 @overload # type: ignore[override] 

145 def __getitem__(self, key: AppKey[_T]) -> _T: 

146 ... 

147 

148 @overload 

149 def __getitem__(self, key: str) -> Any: 

150 ... 

151 

152 def __getitem__(self, key: Union[str, AppKey[_T]]) -> Any: 

153 return self._state[key] 

154 

155 def _check_frozen(self) -> None: 

156 if self._frozen: 

157 raise RuntimeError( 

158 "Changing state of started or joined " "application is forbidden" 

159 ) 

160 

161 @overload # type: ignore[override] 

162 def __setitem__(self, key: AppKey[_T], value: _T) -> None: 

163 ... 

164 

165 @overload 

166 def __setitem__(self, key: str, value: Any) -> None: 

167 ... 

168 

169 def __setitem__(self, key: Union[str, AppKey[_T]], value: Any) -> None: 

170 self._check_frozen() 

171 if not isinstance(key, AppKey): 

172 warnings.warn( 

173 "It is recommended to use web.AppKey instances for keys.\n" 

174 + "https://docs.aiohttp.org/en/stable/web_advanced.html" 

175 + "#application-s-config", 

176 stacklevel=2, 

177 ) 

178 self._state[key] = value 

179 

180 def __delitem__(self, key: Union[str, AppKey[_T]]) -> None: 

181 self._check_frozen() 

182 del self._state[key] 

183 

184 def __len__(self) -> int: 

185 return len(self._state) 

186 

187 def __iter__(self) -> Iterator[Union[str, AppKey[Any]]]: 

188 return iter(self._state) 

189 

190 @overload # type: ignore[override] 

191 def get(self, key: AppKey[_T], default: None = ...) -> Optional[_T]: 

192 ... 

193 

194 @overload 

195 def get(self, key: AppKey[_T], default: _U) -> Union[_T, _U]: 

196 ... 

197 

198 @overload 

199 def get(self, key: str, default: Any = ...) -> Any: 

200 ... 

201 

202 def get(self, key: Union[str, AppKey[_T]], default: Any = None) -> Any: 

203 return self._state.get(key, default) 

204 

205 ######## 

206 def _set_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None: 

207 warnings.warn( 

208 "_set_loop() is no-op since 4.0 " "and scheduled for removal in 5.0", 

209 DeprecationWarning, 

210 stacklevel=2, 

211 ) 

212 

213 @property 

214 def pre_frozen(self) -> bool: 

215 return self._pre_frozen 

216 

217 def pre_freeze(self) -> None: 

218 if self._pre_frozen: 

219 return 

220 

221 self._pre_frozen = True 

222 self._middlewares.freeze() 

223 self._router.freeze() 

224 self._on_response_prepare.freeze() 

225 self._cleanup_ctx.freeze() 

226 self._on_startup.freeze() 

227 self._on_shutdown.freeze() 

228 self._on_cleanup.freeze() 

229 self._middlewares_handlers = tuple(self._prepare_middleware()) 

230 

231 # If current app and any subapp do not have middlewares avoid run all 

232 # of the code footprint that it implies, which have a middleware 

233 # hardcoded per app that sets up the current_app attribute. If no 

234 # middlewares are configured the handler will receive the proper 

235 # current_app without needing all of this code. 

236 self._run_middlewares = True if self.middlewares else False 

237 

238 for subapp in self._subapps: 

239 subapp.pre_freeze() 

240 self._run_middlewares = self._run_middlewares or subapp._run_middlewares 

241 

242 @property 

243 def frozen(self) -> bool: 

244 return self._frozen 

245 

246 def freeze(self) -> None: 

247 if self._frozen: 

248 return 

249 

250 self.pre_freeze() 

251 self._frozen = True 

252 for subapp in self._subapps: 

253 subapp.freeze() 

254 

255 @property 

256 def debug(self) -> bool: 

257 warnings.warn( 

258 "debug property is deprecated since 4.0" "and scheduled for removal in 5.0", 

259 DeprecationWarning, 

260 stacklevel=2, 

261 ) 

262 return asyncio.get_event_loop().get_debug() 

263 

264 def _reg_subapp_signals(self, subapp: "Application") -> None: 

265 def reg_handler(signame: str) -> None: 

266 subsig = getattr(subapp, signame) 

267 

268 async def handler(app: "Application") -> None: 

269 await subsig.send(subapp) 

270 

271 appsig = getattr(self, signame) 

272 appsig.append(handler) 

273 

274 reg_handler("on_startup") 

275 reg_handler("on_shutdown") 

276 reg_handler("on_cleanup") 

277 

278 def add_subapp(self, prefix: str, subapp: "Application") -> AbstractResource: 

279 if not isinstance(prefix, str): 

280 raise TypeError("Prefix must be str") 

281 prefix = prefix.rstrip("/") 

282 if not prefix: 

283 raise ValueError("Prefix cannot be empty") 

284 factory = partial(PrefixedSubAppResource, prefix, subapp) 

285 return self._add_subapp(factory, subapp) 

286 

287 def _add_subapp( 

288 self, resource_factory: Callable[[], AbstractResource], subapp: "Application" 

289 ) -> AbstractResource: 

290 if self.frozen: 

291 raise RuntimeError("Cannot add sub application to frozen application") 

292 if subapp.frozen: 

293 raise RuntimeError("Cannot add frozen application") 

294 resource = resource_factory() 

295 self.router.register_resource(resource) 

296 self._reg_subapp_signals(subapp) 

297 self._subapps.append(subapp) 

298 subapp.pre_freeze() 

299 return resource 

300 

301 def add_domain(self, domain: str, subapp: "Application") -> AbstractResource: 

302 if not isinstance(domain, str): 

303 raise TypeError("Domain must be str") 

304 elif "*" in domain: 

305 rule: Domain = MaskDomain(domain) 

306 else: 

307 rule = Domain(domain) 

308 factory = partial(MatchedSubAppResource, rule, subapp) 

309 return self._add_subapp(factory, subapp) 

310 

311 def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: 

312 return self.router.add_routes(routes) 

313 

314 @property 

315 def on_response_prepare(self) -> _RespPrepareSignal: 

316 return self._on_response_prepare 

317 

318 @property 

319 def on_startup(self) -> _AppSignal: 

320 return self._on_startup 

321 

322 @property 

323 def on_shutdown(self) -> _AppSignal: 

324 return self._on_shutdown 

325 

326 @property 

327 def on_cleanup(self) -> _AppSignal: 

328 return self._on_cleanup 

329 

330 @property 

331 def cleanup_ctx(self) -> "CleanupContext": 

332 return self._cleanup_ctx 

333 

334 @property 

335 def router(self) -> UrlDispatcher: 

336 return self._router 

337 

338 @property 

339 def middlewares(self) -> _Middlewares: 

340 return self._middlewares 

341 

342 async def startup(self) -> None: 

343 """Causes on_startup signal 

344 

345 Should be called in the event loop along with the request handler. 

346 """ 

347 await self.on_startup.send(self) 

348 

349 async def shutdown(self) -> None: 

350 """Causes on_shutdown signal 

351 

352 Should be called before cleanup() 

353 """ 

354 await self.on_shutdown.send(self) 

355 

356 async def cleanup(self) -> None: 

357 """Causes on_cleanup signal 

358 

359 Should be called after shutdown() 

360 """ 

361 if self.on_cleanup.frozen: 

362 await self.on_cleanup.send(self) 

363 else: 

364 # If an exception occurs in startup, ensure cleanup contexts are completed. 

365 await self._cleanup_ctx._on_cleanup(self) 

366 

367 def _prepare_middleware(self) -> Iterator[Middleware]: 

368 yield from reversed(self._middlewares) 

369 yield _fix_request_current_app(self) 

370 

371 async def _handle(self, request: Request) -> StreamResponse: 

372 match_info = await self._router.resolve(request) 

373 match_info.add_app(self) 

374 match_info.freeze() 

375 

376 resp = None 

377 request._match_info = match_info 

378 expect = request.headers.get(hdrs.EXPECT) 

379 if expect: 

380 resp = await match_info.expect_handler(request) 

381 await request.writer.drain() 

382 

383 if resp is None: 

384 handler = match_info.handler 

385 

386 if self._run_middlewares: 

387 for app in match_info.apps[::-1]: 

388 assert app.pre_frozen, "middleware handlers are not ready" 

389 for m in app._middlewares_handlers: 

390 handler = update_wrapper(partial(m, handler=handler), handler) 

391 

392 resp = await handler(request) 

393 

394 return resp 

395 

396 def __call__(self) -> "Application": 

397 """gunicorn compatibility""" 

398 return self 

399 

400 def __repr__(self) -> str: 

401 return f"<Application 0x{id(self):x}>" 

402 

403 def __bool__(self) -> bool: 

404 return True 

405 

406 

407class CleanupError(RuntimeError): 

408 @property 

409 def exceptions(self) -> List[BaseException]: 

410 return cast(List[BaseException], self.args[1]) 

411 

412 

413if TYPE_CHECKING: # pragma: no cover 

414 _CleanupContextBase = FrozenList[Callable[[Application], AsyncIterator[None]]] 

415else: 

416 _CleanupContextBase = FrozenList 

417 

418 

419class CleanupContext(_CleanupContextBase): 

420 def __init__(self) -> None: 

421 super().__init__() 

422 self._exits: List[AsyncIterator[None]] = [] 

423 

424 async def _on_startup(self, app: Application) -> None: 

425 for cb in self: 

426 it = cb(app).__aiter__() 

427 await it.__anext__() 

428 self._exits.append(it) 

429 

430 async def _on_cleanup(self, app: Application) -> None: 

431 errors = [] 

432 for it in reversed(self._exits): 

433 try: 

434 await it.__anext__() 

435 except StopAsyncIteration: 

436 pass 

437 except Exception as exc: 

438 errors.append(exc) 

439 else: 

440 errors.append(RuntimeError(f"{it!r} has more than one 'yield'")) 

441 if errors: 

442 if len(errors) == 1: 

443 raise errors[0] 

444 else: 

445 raise CleanupError("Multiple errors on cleanup stage", errors)