Skip to content

Commit ae39b35

Browse files
committed
Added documentation for lifespan
1 parent 5ef2219 commit ae39b35

File tree

3 files changed

+115
-6
lines changed

3 files changed

+115
-6
lines changed

docs/basics/lifespan.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# **Lifespan**
2+
Ellar applications registers a lifespan manager. The manager handles lifespan handler registered in the configuration under the variable name
3+
**`DEFAULT_LIFESPAN_HANDLER`**.
4+
5+
It also executes code that needs to run before the application starts up, or when the application is shutting down.
6+
The lifespan manager must be run before ellar starts serving incoming request.
7+
8+
```python
9+
import uvicorn
10+
import contextlib
11+
from ellar.core import App, AppFactory
12+
13+
@contextlib.asynccontextmanager
14+
async def some_async_resource():
15+
print("running some-async-resource function")
16+
yield
17+
print("existing some-async-resource function")
18+
19+
20+
@contextlib.asynccontextmanager
21+
async def lifespan(app: App):
22+
async with some_async_resource():
23+
print("Run at startup!")
24+
yield
25+
print("Run on shutdown!")
26+
27+
28+
application = AppFactory.create_app(config_module=dict(
29+
DEFAULT_LIFESPAN_HANDLER=lifespan
30+
))
31+
32+
if __name__ == "__main__":
33+
uvicorn.run(application, port=5000, log_level="info")
34+
```
35+
The construct above will generate the output below:
36+
```shell
37+
INFO: Started server process [11772]
38+
INFO: Waiting for application startup.
39+
INFO: Application startup complete.
40+
INFO: Uvicorn running on http://127.0.0.1:5000 (Press CTRL+C to quit)
41+
42+
running some-async-resource function
43+
Run at startup!
44+
45+
INFO: Shutting down
46+
INFO: Waiting for application shutdown.
47+
INFO: Application shutdown complete.
48+
INFO: Finished server process [11772]
49+
50+
Run on shutdown!
51+
existing some-async-resource function
52+
```
53+
54+
## **Modules and Lifespan**
55+
Any module that wants to engage in application lifespan must inherit `IApplicationStartup` for startup actions or `IApplicationShutdown` for shutdown actions
56+
or inherit both for startup and shutdown actions.
57+
58+
`IApplicationStartup` has an abstractmethod `on_startup` function and `IApplicationShutdown` has an abstractmethod `on_shutdown` function.
59+
60+
```python
61+
from abc import abstractmethod
62+
63+
64+
class IApplicationStartup:
65+
@abstractmethod
66+
async def on_startup(self, app: "App") -> None:
67+
...
68+
69+
70+
class IApplicationShutdown:
71+
@abstractmethod
72+
async def on_shutdown(self) -> None:
73+
...
74+
```
75+
Let's assume we have a module that extends both `IApplicationStartup` and `IApplicationShutdown` to execute some actions on startup and on shutdown as shown below:
76+
77+
```python
78+
from ellar.common import IApplicationShutdown, IApplicationStartup, Module
79+
80+
@Module()
81+
class SampleModule(IApplicationShutdown, IApplicationStartup):
82+
83+
async def on_startup(self, app) -> None:
84+
print("Run at startup! in SampleModule")
85+
86+
async def on_shutdown(self) -> None:
87+
print("Run on shutdown! in SampleModule")
88+
89+
```
90+
91+
## **Running lifespan in tests**
92+
You should use `TestClient` as a context manager, to ensure that the lifespan is called.
93+
94+
```python
95+
from ellar.testing import Test
96+
from .main import SampleModule
97+
98+
test_module = Test.create_test_module(modules=[SampleModule])
99+
100+
def test_lifespan():
101+
with test_module.get_test_client() as client:
102+
# Application's lifespan is called on entering the block.
103+
response = client.get("/")
104+
assert response.status_code == 200
105+
106+
# And the lifespan's teardown is run when exiting the block.
107+
108+
```

ellar/core/lifespan.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ async def lifespan(self, app: "App") -> t.AsyncIterator[t.Any]:
4949
async with create_task_group() as tg:
5050
tg.start_soon(self.run_all_startup_actions, app)
5151

52-
async with self._lifespan_context(app) as ctx: # type:ignore[union-attr]
53-
yield ctx
54-
55-
async with create_task_group() as tg:
56-
tg.start_soon(self.run_all_shutdown_actions, app)
52+
try:
53+
async with self._lifespan_context(app) as ctx: # type:ignore[union-attr]
54+
yield ctx
55+
finally:
56+
async with create_task_group() as tg:
57+
tg.start_soon(self.run_all_shutdown_actions, app)

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ nav:
9898
- Versioning: techniques/versioning.md
9999
- Caching: techniques/caching.md
100100
- Templating:
101-
- html: techniques/templating.md
101+
- Html: techniques/templating.md
102102
- Static Files: techniques/staticfiles.md
103103
- Mount: techniques/mount.md
104104
- Background Tasks: techniques/background-task.md

0 commit comments

Comments
 (0)