aio-libs / aiohttp
1 10
import asyncio
2 10
import logging
3 10
import warnings
4 10
from functools import partial, update_wrapper
5 10
from typing import (  # noqa
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
    Tuple,
20
    Type,
21
    Union,
22
    cast,
23
)
24

25 10
from aiosignal import Signal
26 10
from frozenlist import FrozenList
27 10
from typing_extensions import final
28

29 10
from . import hdrs
30 10
from .log import web_logger
31 10
from .web_middlewares import _fix_request_current_app
32 10
from .web_request import Request
33 10
from .web_response import StreamResponse
34 10
from .web_routedef import AbstractRouteDef
35 10
from .web_urldispatcher import (
36
    AbstractResource,
37
    AbstractRoute,
38
    Domain,
39
    MaskDomain,
40
    MatchedSubAppResource,
41
    PrefixedSubAppResource,
42
    UrlDispatcher,
43
)
44

45 10
__all__ = ("Application", "CleanupError")
46

47

48
if TYPE_CHECKING:  # pragma: no cover
49
    _AppSignal = Signal[Callable[["Application"], Awaitable[None]]]
50
    _RespPrepareSignal = Signal[Callable[[Request, StreamResponse], Awaitable[None]]]
51
    _Handler = Callable[[Request], Awaitable[StreamResponse]]
52
    _Middleware = Callable[[Request, _Handler], Awaitable[StreamResponse]]
53
    _Middlewares = FrozenList[_Middleware]
54
    _MiddlewaresHandlers = Sequence[_Middleware]
55
    _Subapps = List["Application"]
56
else:
57
    # No type checker mode, skip types
58 10
    _AppSignal = Signal
59 10
    _RespPrepareSignal = Signal
60 10
    _Handler = Callable
61 10
    _Middleware = Callable
62 10
    _Middlewares = FrozenList
63 10
    _MiddlewaresHandlers = Sequence
64 10
    _Subapps = List
65

66

67 10
@final
68 10
class Application(MutableMapping[str, Any]):
69 10
    __slots__ = (
70
        "logger",
71
        "_debug",
72
        "_router",
73
        "_loop",
74
        "_handler_args",
75
        "_middlewares",
76
        "_middlewares_handlers",
77
        "_run_middlewares",
78
        "_state",
79
        "_frozen",
80
        "_pre_frozen",
81
        "_subapps",
82
        "_on_response_prepare",
83
        "_on_startup",
84
        "_on_shutdown",
85
        "_on_cleanup",
86
        "_client_max_size",
87
        "_cleanup_ctx",
88
    )
89

90 10
    def __init__(
91
        self,
92
        *,
93
        logger: logging.Logger = web_logger,
94
        middlewares: Iterable[_Middleware] = (),
95
        handler_args: Optional[Mapping[str, Any]] = None,
96
        client_max_size: int = 1024 ** 2,
97
        debug: Any = ...,  # mypy doesn't support ellipsis
98
    ) -> None:
99

100 10
        if debug is not ...:
101 0
            warnings.warn(
102
                "debug argument is no-op since 4.0 " "and scheduled for removal in 5.0",
103
                DeprecationWarning,
104
                stacklevel=2,
105
            )
106 10
        self._router = UrlDispatcher()
107 10
        self._handler_args = handler_args
108 10
        self.logger = logger
109

110 10
        self._middlewares = FrozenList(middlewares)  # type: _Middlewares
111

112
        # initialized on freezing
113 10
        self._middlewares_handlers = tuple()  # type: _MiddlewaresHandlers
114
        # initialized on freezing
115 10
        self._run_middlewares = None  # type: Optional[bool]
116

117 10
        self._state = {}  # type: Dict[str, Any]
118 10
        self._frozen = False
119 10
        self._pre_frozen = False
120 10
        self._subapps = []  # type: _Subapps
121

122 10
        self._on_response_prepare = Signal(self)  # type: _RespPrepareSignal
123 10
        self._on_startup = Signal(self)  # type: _AppSignal
124 10
        self._on_shutdown = Signal(self)  # type: _AppSignal
125 10
        self._on_cleanup = Signal(self)  # type: _AppSignal
126 10
        self._cleanup_ctx = CleanupContext()
127 10
        self._on_startup.append(self._cleanup_ctx._on_startup)
128 10
        self._on_cleanup.append(self._cleanup_ctx._on_cleanup)
129 10
        self._client_max_size = client_max_size
130

131 10
    def __init_subclass__(cls: Type["Application"]) -> None:
132 10
        raise TypeError(
133
            "Inheritance class {} from web.Application "
134
            "is forbidden".format(cls.__name__)
135
        )
136

137
    # MutableMapping API
138

139 10
    def __eq__(self, other: object) -> bool:
140 10
        return self is other
141

142 10
    def __getitem__(self, key: str) -> Any:
143 10
        return self._state[key]
144

145 10
    def _check_frozen(self) -> None:
146 10
        if self._frozen:
147 10
            raise RuntimeError(
148
                "Changing state of started or joined " "application is forbidden"
149
            )
150

151 10
    def __setitem__(self, key: str, value: Any) -> None:
152 10
        self._check_frozen()
153 10
        self._state[key] = value
154

155 10
    def __delitem__(self, key: str) -> None:
156 10
        self._check_frozen()
157 10
        del self._state[key]
158

159 10
    def __len__(self) -> int:
160 10
        return len(self._state)
161

162 10
    def __iter__(self) -> Iterator[str]:
163 10
        return iter(self._state)
164

165
    ########
166 10
    def _set_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None:
167 0
        warnings.warn(
168
            "_set_loop() is no-op since 4.0 " "and scheduled for removal in 5.0",
169
            DeprecationWarning,
170
            stacklevel=2,
171
        )
172

173 10
    @property
174 10
    def pre_frozen(self) -> bool:
175 10
        return self._pre_frozen
176

177 10
    def pre_freeze(self) -> None:
178 10
        if self._pre_frozen:
179 10
            return
180

181 10
        self._pre_frozen = True
182 10
        self._middlewares.freeze()
183 10
        self._router.freeze()
184 10
        self._on_response_prepare.freeze()
185 10
        self._cleanup_ctx.freeze()
186 10
        self._on_startup.freeze()
187 10
        self._on_shutdown.freeze()
188 10
        self._on_cleanup.freeze()
189 10
        self._middlewares_handlers = tuple(self._prepare_middleware())
190

191
        # If current app and any subapp do not have middlewares avoid run all
192
        # of the code footprint that it implies, which have a middleware
193
        # hardcoded per app that sets up the current_app attribute. If no
194
        # middlewares are configured the handler will receive the proper
195
        # current_app without needing all of this code.
196 10
        self._run_middlewares = True if self.middlewares else False
197

198 10
        for subapp in self._subapps:
199 10
            subapp.pre_freeze()
200 10
            self._run_middlewares = self._run_middlewares or subapp._run_middlewares
201

202 10
    @property
203 10
    def frozen(self) -> bool:
204 10
        return self._frozen
205

206 10
    def freeze(self) -> None:
207 10
        if self._frozen:
208 10
            return
209

210 10
        self.pre_freeze()
211 10
        self._frozen = True
212 10
        for subapp in self._subapps:
213 10
            subapp.freeze()
214

215 10
    @property
216 10
    def debug(self) -> bool:
217 0
        warnings.warn(
218
            "debug property is deprecated since 4.0" "and scheduled for removal in 5.0",
219
            DeprecationWarning,
220
            stacklevel=2,
221
        )
222 0
        return asyncio.get_event_loop().get_debug()
223

224 10
    def _reg_subapp_signals(self, subapp: "Application") -> None:
225 10
        def reg_handler(signame: str) -> None:
226 10
            subsig = getattr(subapp, signame)
227

228 10
            async def handler(app: "Application") -> None:
229 10
                await subsig.send(subapp)
230

231 10
            appsig = getattr(self, signame)
232 10
            appsig.append(handler)
233

234 10
        reg_handler("on_startup")
235 10
        reg_handler("on_shutdown")
236 10
        reg_handler("on_cleanup")
237

238 10
    def add_subapp(self, prefix: str, subapp: "Application") -> AbstractResource:
239 10
        if not isinstance(prefix, str):
240 10
            raise TypeError("Prefix must be str")
241 10
        prefix = prefix.rstrip("/")
242 10
        if not prefix:
243 10
            raise ValueError("Prefix cannot be empty")
244 10
        factory = partial(PrefixedSubAppResource, prefix, subapp)
245 10
        return self._add_subapp(factory, subapp)
246

247 10
    def _add_subapp(
248
        self, resource_factory: Callable[[], AbstractResource], subapp: "Application"
249
    ) -> AbstractResource:
250 10
        if self.frozen:
251 10
            raise RuntimeError("Cannot add sub application to frozen application")
252 10
        if subapp.frozen:
253 10
            raise RuntimeError("Cannot add frozen application")
254 10
        resource = resource_factory()
255 10
        self.router.register_resource(resource)
256 10
        self._reg_subapp_signals(subapp)
257 10
        self._subapps.append(subapp)
258 10
        subapp.pre_freeze()
259 10
        return resource
260

261 10
    def add_domain(self, domain: str, subapp: "Application") -> AbstractResource:
262 10
        if not isinstance(domain, str):
263 10
            raise TypeError("Domain must be str")
264 10
        elif "*" in domain:
265 10
            rule = MaskDomain(domain)  # type: Domain
266
        else:
267 10
            rule = Domain(domain)
268 10
        factory = partial(MatchedSubAppResource, rule, subapp)
269 10
        return self._add_subapp(factory, subapp)
270

271 10
    def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]:
272 10
        return self.router.add_routes(routes)
273

274 10
    @property
275 10
    def on_response_prepare(self) -> _RespPrepareSignal:
276 10
        return self._on_response_prepare
277

278 10
    @property
279 10
    def on_startup(self) -> _AppSignal:
280 10
        return self._on_startup
281

282 10
    @property
283 10
    def on_shutdown(self) -> _AppSignal:
284 10
        return self._on_shutdown
285

286 10
    @property
287 10
    def on_cleanup(self) -> _AppSignal:
288 10
        return self._on_cleanup
289

290 10
    @property
291 10
    def cleanup_ctx(self) -> "CleanupContext":
292 10
        return self._cleanup_ctx
293

294 10
    @property
295 10
    def router(self) -> UrlDispatcher:
296 10
        return self._router
297

298 10
    @property
299 10
    def middlewares(self) -> _Middlewares:
300 10
        return self._middlewares
301

302 10
    async def startup(self) -> None:
303
        """Causes on_startup signal
304

305
        Should be called in the event loop along with the request handler.
306
        """
307 10
        await self.on_startup.send(self)
308

309 10
    async def shutdown(self) -> None:
310
        """Causes on_shutdown signal
311

312
        Should be called before cleanup()
313
        """
314 10
        await self.on_shutdown.send(self)
315

316 10
    async def cleanup(self) -> None:
317
        """Causes on_cleanup signal
318

319
        Should be called after shutdown()
320
        """
321 10
        if self.on_cleanup.frozen:
322 10
            await self.on_cleanup.send(self)
323
        else:
324
            # If an exception occurs in startup, ensure cleanup contexts are completed.
325 10
            await self._cleanup_ctx._on_cleanup(self)
326

327 10
    def _prepare_middleware(self) -> Iterator[_Middleware]:
328 10
        yield from reversed(self._middlewares)
329 10
        yield _fix_request_current_app(self)
330

331 10
    async def _handle(self, request: Request) -> StreamResponse:
332 10
        match_info = await self._router.resolve(request)
333 10
        match_info.add_app(self)
334 10
        match_info.freeze()
335

336 10
        resp = None
337 10
        request._match_info = match_info  # type: ignore[assignment]
338 10
        expect = request.headers.get(hdrs.EXPECT)
339 10
        if expect:
340 10
            resp = await match_info.expect_handler(request)
341 10
            await request.writer.drain()
342

343 10
        if resp is None:
344 10
            handler = match_info.handler
345

346 10
            if self._run_middlewares:
347 10
                for app in match_info.apps[::-1]:
348 10
                    assert app.pre_frozen, "middleware handlers are not ready"
349 10
                    for m in app._middlewares_handlers:
350 10
                        handler = update_wrapper(partial(m, handler=handler), handler)
351

352 10
            resp = await handler(request)
353

354 10
        return resp
355

356 10
    def __call__(self) -> "Application":
357
        """gunicorn compatibility"""
358 10
        return self
359

360 10
    def __repr__(self) -> str:
361 10
        return "<Application 0x{:x}>".format(id(self))
362

363 10
    def __bool__(self) -> bool:
364 10
        return True
365

366

367 10
class CleanupError(RuntimeError):
368 10
    @property
369 10
    def exceptions(self) -> List[BaseException]:
370 10
        return cast(List[BaseException], self.args[1])
371

372

373
if TYPE_CHECKING:  # pragma: no cover
374
    _CleanupContextBase = FrozenList[Callable[[Application], AsyncIterator[None]]]
375
else:
376 10
    _CleanupContextBase = FrozenList
377

378

379 10
class CleanupContext(_CleanupContextBase):
380 10
    def __init__(self) -> None:
381 10
        super().__init__()
382 10
        self._exits = []  # type: List[AsyncIterator[None]]
383

384 10
    async def _on_startup(self, app: Application) -> None:
385 10
        for cb in self:
386 10
            it = cb(app).__aiter__()
387 10
            await it.__anext__()
388 10
            self._exits.append(it)
389

390 10
    async def _on_cleanup(self, app: Application) -> None:
391 10
        errors = []
392 10
        for it in reversed(self._exits):
393 10
            try:
394 10
                await it.__anext__()
395 10
            except StopAsyncIteration:
396 10
                pass
397 10
            except Exception as exc:
398 10
                errors.append(exc)
399
            else:
400 10
                errors.append(RuntimeError(f"{it!r} has more than one 'yield'"))
401 10
        if errors:
402 10
            if len(errors) == 1:
403 10
                raise errors[0]
404
            else:
405 10
                raise CleanupError("Multiple errors on cleanup stage", errors)

Read our documentation on viewing source code .

Loading