Programmatic startup/shutdown of ASGI apps.
Programmatically send startup/shutdown lifespan events into ASGI applications. When used in combination with an ASGI-capable HTTP client such as HTTPX, this allows mocking or testing ASGI applications without having to spin up an ASGI server.
- Send lifespan events to an ASGI app using
- Support for
- Fully type-annotated.
- 100% test coverage.
pip install 'asgi-lifespan==2.*'
asgi-lifespan provides a
LifespanManager to programmatically send ASGI lifespan events into an ASGI app. This can be used to programmatically startup/shutdown an ASGI app without having to spin up an ASGI server.
LifespanManager can run on either
trio, and will auto-detect the async library in use.
# example.py from contextlib import asynccontextmanager from asgi_lifespan import LifespanManager from starlette.applications import Starlette # Example lifespan-capable ASGI app. Any ASGI app that supports # the lifespan protocol will do, e.g. FastAPI, Quart, Responder, ... @asynccontextmanager async def lifespan(app): print("Starting up!") yield print("Shutting down!") app = Starlette(lifespan=lifespan) async def main(): async with LifespanManager(app) as manager: print("We're in!") # On asyncio: import asyncio; asyncio.run(main()) # On trio: # import trio; trio.run(main)
$ python example.py Starting up! We're in! Shutting down!
Sending lifespan events for testing
The example below demonstrates how to use
asgi-lifespan in conjunction with HTTPX and
pytest in order to send test requests into an ASGI app.
- Install dependencies:
pip install asgi-lifespan httpx starlette pytest pytest-asyncio
- Test script:
# test_app.py from contextlib import asynccontextmanager import httpx import pytest import pytest_asyncio from asgi_lifespan import LifespanManager from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.routing import Route @pytest_asyncio.fixture async def app(): @asynccontextmanager async def lifespan(app): print("Starting up") yield print("Shutting down") async def home(request): return PlainTextResponse("Hello, world!") app = Starlette( routes=[Route("/", home)], lifespan=lifespan, ) async with LifespanManager(app) as manager: print("We're in!") yield manager.app @pytest_asyncio.fixture async def client(app): async with httpx.AsyncClient(app=app, base_url="http://app.io") as client: print("Client is ready") yield client @pytest.mark.asyncio async def test_home(client): print("Testing") response = await client.get("/") assert response.status_code == 200 assert response.text == "Hello, world!" print("OK")
- Run the test suite:
$ pytest -s test_app.py ======================= test session starts ======================= test_app.py Starting up We're in! Client is ready Testing OK .Shutting down ======================= 1 passed in 0.88s =======================
LifespanManager provisions a lifespan state which persists data from the lifespan cycle for use in request/response handling.
For your app to be aware of it, be sure to use
manager.app instead of the
app itself when inside the context manager.
For example if using HTTPX as an async test client:
async with LifespanManager(app) as manager: async with httpx.AsyncClient(app=manager.app) as client: ...
def __init__( self, app: Callable, startup_timeout: Optional[float] = 5, shutdown_timeout: Optional[float] = 5, )
An asynchronous context manager that starts up an ASGI app on enter and shuts it down on exit.
- On enter, start a
appin the background, then send the
lifespan.startupevent and wait for the application to send
- On exit, send the
lifespan.shutdownevent and wait for the application to send
- If an exception occurs during startup, shutdown, or in the body of the
async withblock, it bubbles up and no shutdown is performed.
async with LifespanManager(app) as manager: # 'app' was started up. ... # 'app' was shut down.
Callable): an ASGI application.
Optional[float], defaults to 5): maximum number of seconds to wait for the application to startup. Use
Nonefor no timeout.
Optional[float], defaults to 5): maximum number of seconds to wait for the application to shutdown. Use
Nonefor no timeout.
LifespanManageritself. In case you use lifespan state, use
async with LifespanManager(app) as manager: ...then access
manager.appto get a reference to the state-aware app.
LifespanNotSupported: if the application does not seem to support the lifespan protocol. Based on the rationale that if the app supported the lifespan protocol then it would successfully receive the
lifespan.startupASGI event, unsupported lifespan protocol is detected in two situations:
- The application called
receive()for the first time.
- The application raised an exception during startup before making its first call to
receive(). For example, this may be because the application failed on a statement such as
assert scope["type"] == "http".
- The application called
TimeoutError: if startup or shutdown timed out.
Exception: any exception raised by the application (during startup, shutdown, or within the
async withbody) that does not indicate it does not support the lifespan protocol.
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
2.1.0 - 2023-03-28
- Add support for lifespan state. (Pull #59)
2.0.0 - 2022-11-11
- Drop support for Python 3.6. (Pull #55)
- Add official support for Python 3.11. (Pull #55)
- Add official support for Python 3.9 and 3.10. (Pull #46 - Thanks @euri10)
- Ensure compatibility with mypy 0.990+, which made
no_implicit_optionalthe default. (Pull #53 - Thanks @AllSeeingEyeTolledEweSew)
1.0.1 - 2020-06-08
- Update development status to
5 - Production/Stable. (Pull #32)
1.0.0 - 2020-02-02
LifespanMiddleware. Please use Starlette's built-in lifespan capabilities instead. (Pull #27)
sniffiofor auto-detecting the async environment. (Pull #28)
- Enforce 100% test coverage on CI. (Pull #29)
- Enforce importing from the top-level package by switching to private internal modules. (Pull #26)
0.6.0 - 2019-11-29
lifespanmodule. (Pull #21)
LifespanManagerto drop dependency on
asynccontextmanageron 3.6. (Pull #20)
0.5.0 - 2019-11-29
- Enter Beta development status.
curiosupport. (Pull #18)
- Ship binary distributions (wheels) alongside source distributions.
- Use custom concurrency backends instead of
anyiofor asyncio and trio support. (Pull #18)
0.4.2 - 2019-10-06
py.typedis bundled with the package so that type checkers can detect type annotations. (Pull #16)
0.4.1 - 2019-09-29
- Improve error handling in
- Exceptions raised in the context manager body or during shutdown are now properly propagated.
- Unsupported lifespan is now also detected when the app calls
send()before calling having called
receive()at least once.
0.4.0 - 2019-09-29
- Enter Alpha development status.
0.3.1 - 2019-09-29
- Add configurable timeouts to
LifespanManager. (Pull #10)
0.3.0 - 2019-09-29
LifespanManagerfor sending lifespan events into an ASGI app. (Pull #5)
0.2.0 - 2019-09-28
LifespanMiddleware, an ASGI middleware to add lifespan support to an ASGI app. (Pull #9)
0.1.0 - 2019-09-28
Lifespan, an ASGI app implementing the lifespan protocol with event handler registration support. (Pull #7)
0.0.2 - 2019-09-28
- Installation from PyPI used to fail due to missing
0.0.1 - 2019-09-28
- Empty package.
Release history Release notifications | RSS feed
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Hashes for asgi_lifespan-2.1.0-py3-none-any.whl