Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds before_shutdown/after_startup lifespan hooks #3598

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ repos:
- id: unasyncd
additional_dependencies: ["ruff"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.4.4"
rev: "v0.4.10"
hooks:
- id: ruff
args: ["--fix"]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.3.0
hooks:
- id: codespell
exclude: "tests/openapi/typescript_converter/test_converter|README.md"
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/responses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ in:
Set-Cookie: router-cookie=router value; Path=/; SameSite=lax
Set-Cookie: app-cookie=app value; Path=/; SameSite=lax

You can easily override cookies declared in higher levels by re-declaring a cookie with the same key in a lower level,
You can easily override cookies declared in higher levels by redeclaring a cookie with the same key in a lower level,
e.g.:

.. literalinclude:: /examples/responses/response_cookies_2.py
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/routing/parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ declared in the route handler. Now, examine these more closely.
* ``controller_param`` is a query param with the key ``controller_param``. It has an :paramref:`~.params.Parameter.lt`
set to ``100`` defined on the controller, which means the provided value must be less than 100.

Yet the route handler re-declares it with an :paramref:`~.params.Parameter.lt` set to ``50``,
Yet the route handler redeclares it with an :paramref:`~.params.Parameter.lt` set to ``50``,
which means for the route handler this value must be less than 50.
* ``local_param`` is a route handler local :ref:`query parameter <usage/routing/parameters:query parameters>`, and
``path_param`` is a :ref:`path parameter <usage/routing/parameters:path parameters>`.
Expand Down
17 changes: 17 additions & 0 deletions litestar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ class Litestar(Router):
"logger",
"logging_config",
"multipart_form_part_limit",
"before_shutdown",
"on_shutdown",
"on_startup",
"after_startup",
"openapi_config",
"response_cache_config",
"route_map",
Expand Down Expand Up @@ -193,8 +195,10 @@ def __init__(
middleware: Sequence[Middleware] | None = None,
multipart_form_part_limit: int = 1000,
on_app_init: Sequence[OnAppInitHandler] | None = None,
before_shutdown: Sequence[LifespanHook] | None = None,
on_shutdown: Sequence[LifespanHook] | None = None,
on_startup: Sequence[LifespanHook] | None = None,
after_startup: Sequence[LifespanHook] | None = None,
openapi_config: OpenAPIConfig | None = DEFAULT_OPENAPI_CONFIG,
opt: Mapping[str, Any] | None = None,
parameters: ParametersMap | None = None,
Expand Down Expand Up @@ -268,10 +272,14 @@ def __init__(
an instance of :class:`AppConfig <.config.app.AppConfig>` that will have been initially populated with
the parameters passed to :class:`Litestar <litestar.app.Litestar>`, and must return an instance of same.
If more than one handler is registered they are called in the order they are provided.
before_shutdown: A sequence of :class:`LifespanHook <.types.LifespanHook>` called first during application
shutdown.
on_shutdown: A sequence of :class:`LifespanHook <.types.LifespanHook>` called during application
shutdown.
on_startup: A sequence of :class:`LifespanHook <litestar.types.LifespanHook>` called during
application startup.
after_startup: A sequence of :class:`LifespanHook <litestar.types.LifespanHook>` called during
after application startup.
openapi_config: Defaults to :attr:`DEFAULT_OPENAPI_CONFIG`
opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or
wherever you have access to :class:`Request <litestar.connection.request.Request>` or
Expand Down Expand Up @@ -351,8 +359,10 @@ def __init__(
logging_config=logging_config,
middleware=list(middleware or []),
multipart_form_part_limit=multipart_form_part_limit,
before_shutdown=list(before_shutdown or []),
on_shutdown=list(on_shutdown or []),
on_startup=list(on_startup or []),
after_startup=list(after_startup or []),
openapi_config=openapi_config,
opt=dict(opt or {}),
path=path or "",
Expand Down Expand Up @@ -422,8 +432,10 @@ def __init__(
self.event_emitter = config.event_emitter_backend(listeners=config.listeners)
self.logging_config = config.logging_config
self.multipart_form_part_limit = config.multipart_form_part_limit
self.before_shutdown = config.before_shutdown
self.on_shutdown = config.on_shutdown
self.on_startup = config.on_startup
self.after_startup = config.after_startup
self.openapi_config = config.openapi_config
self.request_class: type[Request] = config.request_class or Request
self.response_cache_config = config.response_cache_config
Expand Down Expand Up @@ -606,6 +618,9 @@ async def lifespan(self) -> AsyncGenerator[None, None]:
custom lifespan managers.
"""
async with AsyncExitStack() as exit_stack:
for hook in self.before_shutdown[::-1]:
exit_stack.push_async_callback(partial(self._call_lifespan_hook, hook))

for hook in self.on_shutdown[::-1]:
exit_stack.push_async_callback(partial(self._call_lifespan_hook, hook))

Expand All @@ -619,6 +634,8 @@ async def lifespan(self) -> AsyncGenerator[None, None]:
for hook in self.on_startup:
await self._call_lifespan_hook(hook)

for hook in self.after_startup:
await self._call_lifespan_hook(hook)
yield

@property
Expand Down
2 changes: 1 addition & 1 deletion litestar/channels/backends/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ async def stream_events(self) -> AsyncGenerator[tuple[str, Any], None]:

channel: str = message["channel"].decode()
data: bytes = message["data"]
# redis handles the unsubscibes with a queue; Unsubscribing doesn't mean the
# redis handles the unsubscribes with a queue; Unsubscribing doesn't mean the
# unsubscribe will happen immediately after requesting it, so we could
# receive a message on a channel that, from a client's perspective, it's not
# subscribed to anymore
Expand Down
4 changes: 4 additions & 0 deletions litestar/config/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,14 @@ class AppConfig:
"""An instance of :class:`BaseLoggingConfig <.logging.config.BaseLoggingConfig>` subclass."""
middleware: list[Middleware] = field(default_factory=list)
"""A list of :class:`Middleware <.types.Middleware>`."""
before_shutdown: list[LifespanHook] = field(default_factory=list)
"""A list of :class:`LifespanHook <.types.LifespanHook>` called first during the application shutdown."""
on_shutdown: list[LifespanHook] = field(default_factory=list)
"""A list of :class:`LifespanHook <.types.LifespanHook>` called during application shutdown."""
on_startup: list[LifespanHook] = field(default_factory=list)
"""A list of :class:`LifespanHook <.types.LifespanHook>` called during application startup."""
after_startup: list[LifespanHook] = field(default_factory=list)
"""A list of :class:`LifespanHook <.types.LifespanHook>` called immediately after the ``on_startup`` hooks."""
openapi_config: OpenAPIConfig | None = field(default=None)
"""Defaults to :data:`DEFAULT_OPENAPI_CONFIG <litestar.app.DEFAULT_OPENAPI_CONFIG>`"""
opt: dict[str, Any] = field(default_factory=dict)
Expand Down
16 changes: 16 additions & 0 deletions litestar/testing/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ def create_test_client(
middleware: Sequence[Middleware] | None = None,
multipart_form_part_limit: int = 1000,
on_app_init: Sequence[OnAppInitHandler] | None = None,
before_shutdown: Sequence[LifespanHook] | None = None,
on_shutdown: Sequence[LifespanHook] | None = None,
on_startup: Sequence[LifespanHook] | None = None,
after_startup: Sequence[LifespanHook] | None = None,
openapi_config: OpenAPIConfig | None = DEFAULT_OPENAPI_CONFIG,
opt: Mapping[str, Any] | None = None,
parameters: ParametersMap | None = None,
Expand Down Expand Up @@ -192,10 +194,14 @@ def test_my_handler() -> None:
an instance of :class:`AppConfig <.config.app.AppConfig>` that will have been initially populated with
the parameters passed to :class:`Litestar <litestar.app.Litestar>`, and must return an instance of same.
If more than one handler is registered they are called in the order they are provided.
before_shutdown: A sequence of :class:`LifespanHook <.types.LifespanHook>` called first during application
shutdown.
on_shutdown: A sequence of :class:`LifespanHook <.types.LifespanHook>` called during application
shutdown.
on_startup: A sequence of :class:`LifespanHook <litestar.types.LifespanHook>` called during
application startup.
after_startup: A sequence of :class:`LifespanHook <litestar.types.LifespanHook>` called during
after application startup.
openapi_config: Defaults to :attr:`DEFAULT_OPENAPI_CONFIG`
opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or
wherever you have access to :class:`Request <litestar.connection.request.Request>` or
Expand Down Expand Up @@ -273,8 +279,10 @@ def test_my_handler() -> None:
middleware=middleware,
multipart_form_part_limit=multipart_form_part_limit,
on_app_init=on_app_init,
before_shutdown=before_shutdown,
on_shutdown=on_shutdown,
on_startup=on_startup,
after_startup=after_startup,
openapi_config=openapi_config,
opt=opt,
parameters=parameters,
Expand Down Expand Up @@ -343,8 +351,10 @@ def create_async_test_client(
middleware: Sequence[Middleware] | None = None,
multipart_form_part_limit: int = 1000,
on_app_init: Sequence[OnAppInitHandler] | None = None,
before_shutdown: Sequence[LifespanHook] | None = None,
on_shutdown: Sequence[LifespanHook] | None = None,
on_startup: Sequence[LifespanHook] | None = None,
after_startup: Sequence[LifespanHook] | None = None,
openapi_config: OpenAPIConfig | None = DEFAULT_OPENAPI_CONFIG,
opt: Mapping[str, Any] | None = None,
parameters: ParametersMap | None = None,
Expand Down Expand Up @@ -453,10 +463,14 @@ async def test_my_handler() -> None:
an instance of :class:`AppConfig <.config.app.AppConfig>` that will have been initially populated with
the parameters passed to :class:`Litestar <litestar.app.Litestar>`, and must return an instance of same.
If more than one handler is registered they are called in the order they are provided.
before_shutdown: A sequence of :class:`LifespanHook <.types.LifespanHook>` called first during application
shutdown.
on_shutdown: A sequence of :class:`LifespanHook <.types.LifespanHook>` called during application
shutdown.
on_startup: A sequence of :class:`LifespanHook <litestar.types.LifespanHook>` called during
application startup.
after_startup: A sequence of :class:`LifespanHook <litestar.types.LifespanHook>` called during
after application startup.
openapi_config: Defaults to :attr:`DEFAULT_OPENAPI_CONFIG`
opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or
wherever you have access to :class:`Request <litestar.connection.request.Request>` or
Expand Down Expand Up @@ -533,8 +547,10 @@ async def test_my_handler() -> None:
middleware=middleware,
multipart_form_part_limit=multipart_form_part_limit,
on_app_init=on_app_init,
before_shutdown=before_shutdown,
on_shutdown=on_shutdown,
on_startup=on_startup,
after_startup=after_startup,
openapi_config=openapi_config,
opt=opt,
parameters=parameters,
Expand Down
Loading
Loading