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
« 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)
27from aiosignal import Signal
28from frozenlist import FrozenList
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)
48__all__ = ("Application", "CleanupError")
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
66_T = TypeVar("_T")
67_U = TypeVar("_U")
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 )
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
112 self._middlewares: _Middlewares = FrozenList(middlewares)
114 # initialized on freezing
115 self._middlewares_handlers: _MiddlewaresHandlers = tuple()
116 # initialized on freezing
117 self._run_middlewares: Optional[bool] = None
119 self._state: Dict[Union[AppKey[Any], str], object] = {}
120 self._frozen = False
121 self._pre_frozen = False
122 self._subapps: _Subapps = []
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
133 def __init_subclass__(cls: Type["Application"]) -> None:
134 raise TypeError(
135 "Inheritance class {} from web.Application "
136 "is forbidden".format(cls.__name__)
137 )
139 # MutableMapping API
141 def __eq__(self, other: object) -> bool:
142 return self is other
144 @overload # type: ignore[override]
145 def __getitem__(self, key: AppKey[_T]) -> _T:
146 ...
148 @overload
149 def __getitem__(self, key: str) -> Any:
150 ...
152 def __getitem__(self, key: Union[str, AppKey[_T]]) -> Any:
153 return self._state[key]
155 def _check_frozen(self) -> None:
156 if self._frozen:
157 raise RuntimeError(
158 "Changing state of started or joined " "application is forbidden"
159 )
161 @overload # type: ignore[override]
162 def __setitem__(self, key: AppKey[_T], value: _T) -> None:
163 ...
165 @overload
166 def __setitem__(self, key: str, value: Any) -> None:
167 ...
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
180 def __delitem__(self, key: Union[str, AppKey[_T]]) -> None:
181 self._check_frozen()
182 del self._state[key]
184 def __len__(self) -> int:
185 return len(self._state)
187 def __iter__(self) -> Iterator[Union[str, AppKey[Any]]]:
188 return iter(self._state)
190 @overload # type: ignore[override]
191 def get(self, key: AppKey[_T], default: None = ...) -> Optional[_T]:
192 ...
194 @overload
195 def get(self, key: AppKey[_T], default: _U) -> Union[_T, _U]:
196 ...
198 @overload
199 def get(self, key: str, default: Any = ...) -> Any:
200 ...
202 def get(self, key: Union[str, AppKey[_T]], default: Any = None) -> Any:
203 return self._state.get(key, default)
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 )
213 @property
214 def pre_frozen(self) -> bool:
215 return self._pre_frozen
217 def pre_freeze(self) -> None:
218 if self._pre_frozen:
219 return
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())
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
238 for subapp in self._subapps:
239 subapp.pre_freeze()
240 self._run_middlewares = self._run_middlewares or subapp._run_middlewares
242 @property
243 def frozen(self) -> bool:
244 return self._frozen
246 def freeze(self) -> None:
247 if self._frozen:
248 return
250 self.pre_freeze()
251 self._frozen = True
252 for subapp in self._subapps:
253 subapp.freeze()
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()
264 def _reg_subapp_signals(self, subapp: "Application") -> None:
265 def reg_handler(signame: str) -> None:
266 subsig = getattr(subapp, signame)
268 async def handler(app: "Application") -> None:
269 await subsig.send(subapp)
271 appsig = getattr(self, signame)
272 appsig.append(handler)
274 reg_handler("on_startup")
275 reg_handler("on_shutdown")
276 reg_handler("on_cleanup")
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)
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
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)
311 def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]:
312 return self.router.add_routes(routes)
314 @property
315 def on_response_prepare(self) -> _RespPrepareSignal:
316 return self._on_response_prepare
318 @property
319 def on_startup(self) -> _AppSignal:
320 return self._on_startup
322 @property
323 def on_shutdown(self) -> _AppSignal:
324 return self._on_shutdown
326 @property
327 def on_cleanup(self) -> _AppSignal:
328 return self._on_cleanup
330 @property
331 def cleanup_ctx(self) -> "CleanupContext":
332 return self._cleanup_ctx
334 @property
335 def router(self) -> UrlDispatcher:
336 return self._router
338 @property
339 def middlewares(self) -> _Middlewares:
340 return self._middlewares
342 async def startup(self) -> None:
343 """Causes on_startup signal
345 Should be called in the event loop along with the request handler.
346 """
347 await self.on_startup.send(self)
349 async def shutdown(self) -> None:
350 """Causes on_shutdown signal
352 Should be called before cleanup()
353 """
354 await self.on_shutdown.send(self)
356 async def cleanup(self) -> None:
357 """Causes on_cleanup signal
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)
367 def _prepare_middleware(self) -> Iterator[Middleware]:
368 yield from reversed(self._middlewares)
369 yield _fix_request_current_app(self)
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()
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()
383 if resp is None:
384 handler = match_info.handler
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)
392 resp = await handler(request)
394 return resp
396 def __call__(self) -> "Application":
397 """gunicorn compatibility"""
398 return self
400 def __repr__(self) -> str:
401 return f"<Application 0x{id(self):x}>"
403 def __bool__(self) -> bool:
404 return True
407class CleanupError(RuntimeError):
408 @property
409 def exceptions(self) -> List[BaseException]:
410 return cast(List[BaseException], self.args[1])
413if TYPE_CHECKING: # pragma: no cover
414 _CleanupContextBase = FrozenList[Callable[[Application], AsyncIterator[None]]]
415else:
416 _CleanupContextBase = FrozenList
419class CleanupContext(_CleanupContextBase):
420 def __init__(self) -> None:
421 super().__init__()
422 self._exits: List[AsyncIterator[None]] = []
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)
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)