|
3 | 3 | import copy
|
4 | 4 | import os
|
5 | 5 | import secrets
|
| 6 | +from contextlib import AsyncExitStack, asynccontextmanager |
6 | 7 | from inspect import signature
|
7 | 8 | from pathlib import Path
|
8 |
| -from typing import Any, Callable, Optional, cast |
| 9 | +from typing import Any, Callable, Optional, TypeVar, cast |
9 | 10 |
|
10 | 11 | import starlette.applications
|
11 | 12 | import starlette.exceptions
|
|
33 | 34 | from .http_staticfiles import FileResponse, StaticFiles
|
34 | 35 | from .session import Inputs, Outputs, Session, session_context
|
35 | 36 |
|
| 37 | +T = TypeVar("T") |
| 38 | + |
36 | 39 | # Default values for App options.
|
37 | 40 | LIB_PREFIX: str = "lib/"
|
38 | 41 | SANITIZE_ERRORS: bool = False
|
@@ -109,6 +112,10 @@ def __init__(
|
109 | 112 | static_assets: Optional["str" | "os.PathLike[str]" | dict[str, Path]] = None,
|
110 | 113 | debug: bool = False,
|
111 | 114 | ) -> None:
|
| 115 | + # Used to store callbacks to be called when the app is shutting down (according |
| 116 | + # to the ASGI lifespan protocol) |
| 117 | + self._exit_stack = AsyncExitStack() |
| 118 | + |
112 | 119 | if server is None:
|
113 | 120 | self.server = noop_server_fn
|
114 | 121 | elif len(signature(server).parameters) == 1:
|
@@ -208,10 +215,16 @@ def init_starlette_app(self) -> starlette.applications.Starlette:
|
208 | 215 | starlette_app = starlette.applications.Starlette(
|
209 | 216 | routes=routes,
|
210 | 217 | middleware=middleware,
|
| 218 | + lifespan=self._lifespan, |
211 | 219 | )
|
212 | 220 |
|
213 | 221 | return starlette_app
|
214 | 222 |
|
| 223 | + @asynccontextmanager |
| 224 | + async def _lifespan(self, app: starlette.applications.Starlette): |
| 225 | + async with self._exit_stack: |
| 226 | + yield |
| 227 | + |
215 | 228 | def _create_session(self, conn: Connection) -> Session:
|
216 | 229 | id = secrets.token_hex(32)
|
217 | 230 | session = Session(self, id, conn, debug=self._debug)
|
@@ -243,6 +256,27 @@ def run(self, **kwargs: object) -> None:
|
243 | 256 | async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
244 | 257 | await self.starlette_app(scope, receive, send)
|
245 | 258 |
|
| 259 | + def on_shutdown(self, callback: Callable[[], None]) -> Callable[[], None]: |
| 260 | + """ |
| 261 | + Register a callback to be called when the app is shutting down. This can be |
| 262 | + useful for cleaning up app-wide resources, like connection pools, temporary |
| 263 | + directories, worker threads/processes, etc. |
| 264 | +
|
| 265 | + Parameters |
| 266 | + ---------- |
| 267 | + callback |
| 268 | + The callback to call. It should take no arguments, and any return value will |
| 269 | + be ignored. Try not to raise an exception in the callback, as exceptions |
| 270 | + during cleanup can hide the original exception that caused the app to shut |
| 271 | + down. |
| 272 | +
|
| 273 | + Returns |
| 274 | + ------- |
| 275 | + : |
| 276 | + The callback, to allow this method to be used as a decorator. |
| 277 | + """ |
| 278 | + return self._exit_stack.callback(callback) |
| 279 | + |
246 | 280 | async def call_pyodide(self, scope: Scope, receive: Receive, send: Send) -> None:
|
247 | 281 | """
|
248 | 282 | Communicate with pyodide.
|
|
0 commit comments