Skip to content

Commit cc20c86

Browse files
Kludexadriangb
andauthored
Deprecate on_startup and on_shutdown events (#2070)
* Revert "Support lifespan state (#2060)" This reverts commit da6461b. * new implementation * Deprecate `on_startup` and `on_shutdown` events * Rename `events.md` by `lifespan.md` --------- Co-authored-by: Adrian Garcia Badaracco <[email protected]>
1 parent 92ab71e commit cc20c86

File tree

10 files changed

+152
-174
lines changed

10 files changed

+152
-174
lines changed

docs/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def client():
240240
Make a 'client' fixture available to test cases.
241241
"""
242242
# Our fixture is created within a context manager. This ensures that
243-
# application startup and shutdown run for every test case.
243+
# application lifespan runs for every test case.
244244
with TestClient(app) as test_client:
245245
yield test_client
246246
```

docs/database.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ DATABASE_URL=sqlite:///test.db
1818
**app.py**
1919

2020
```python
21+
import contextlib
22+
2123
import databases
2224
import sqlalchemy
2325
from starlette.applications import Starlette
@@ -44,6 +46,11 @@ notes = sqlalchemy.Table(
4446

4547
database = databases.Database(DATABASE_URL)
4648

49+
@contextlib.asynccontextmanager
50+
async def lifespan(app):
51+
await database.connect()
52+
yield
53+
await database.disconnect()
4754

4855
# Main application code.
4956
async def list_notes(request):
@@ -77,8 +84,7 @@ routes = [
7784

7885
app = Starlette(
7986
routes=routes,
80-
on_startup=[database.connect],
81-
on_shutdown=[database.disconnect]
87+
lifespan=lifespan,
8288
)
8389
```
8490

docs/events.md

Lines changed: 0 additions & 147 deletions
This file was deleted.

docs/lifespan.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
Starlette applications can register a lifespan handler for dealing with
3+
code that needs to run before the application starts up, or when the application
4+
is shutting down.
5+
6+
```python
7+
import contextlib
8+
9+
from starlette.applications import Starlette
10+
11+
12+
@contextlib.asynccontextmanager
13+
async def lifespan(app):
14+
async with some_async_resource():
15+
print("Run at startup!")
16+
yield
17+
print("Run on shutdown!")
18+
19+
20+
routes = [
21+
...
22+
]
23+
24+
app = Starlette(routes=routes, lifespan=lifespan)
25+
```
26+
27+
Starlette will not start serving any incoming requests until the lifespan has been run.
28+
29+
The lifespan teardown will run once all connections have been closed, and
30+
any in-process background tasks have completed.
31+
32+
Consider using [`anyio.create_task_group()`](https://anyio.readthedocs.io/en/stable/tasks.html)
33+
for managing asynchronous tasks.
34+
35+
## Lifespan State
36+
37+
The lifespan has the concept of `state`, which is a dictionary that
38+
can be used to share the objects between the lifespan, and the requests.
39+
40+
```python
41+
import contextlib
42+
from typing import TypedDict
43+
44+
import httpx
45+
from starlette.applications import Starlette
46+
from starlette.responses import PlainTextResponse
47+
from starlette.routing import Route
48+
49+
50+
class State(TypedDict):
51+
http_client: httpx.AsyncClient
52+
53+
54+
@contextlib.asynccontextmanager
55+
async def lifespan(app: Starlette) -> State:
56+
async with httpx.AsyncClient() as client:
57+
yield {"http_client": client}
58+
59+
60+
async def homepage(request):
61+
client = request.state.http_client
62+
response = await client.get("https://www.example.com")
63+
return PlainTextResponse(response.text)
64+
65+
66+
app = Starlette(
67+
lifespan=lifespan,
68+
routes=[Route("/", homepage)]
69+
)
70+
```
71+
72+
The `state` received on the requests is a **shallow** copy of the state received on the
73+
lifespan handler.
74+
75+
## Running lifespan in tests
76+
77+
You should use `TestClient` as a context manager, to ensure that the lifespan is called.
78+
79+
```python
80+
from example import app
81+
from starlette.testclient import TestClient
82+
83+
84+
def test_homepage():
85+
with TestClient(app) as client:
86+
# Application's lifespan is called on entering the block.
87+
response = client.get("/")
88+
assert response.status_code == 200
89+
90+
# And the lifespan's teardown is run when exiting the block.
91+
```

docs/testclient.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ case you should use `client = TestClient(app, raise_server_exceptions=False)`.
6464

6565
!!! note
6666

67-
If you want the `TestClient` to run `lifespan` events (`on_startup`, `on_shutdown`, or `lifespan`),
68-
you will need to use the `TestClient` as a context manager. Otherwise, the events
69-
will not be triggered when the `TestClient` is instantiated. You can learn more about it
70-
[here](/events/#running-event-handlers-in-tests).
67+
If you want the `TestClient` to run the `lifespan` handler,
68+
you will need to use the `TestClient` as a context manager. It will
69+
not be triggered when the `TestClient` is instantiated. You can learn more about it
70+
[here](/lifespan/#running-lifespan-in-tests).
7171

7272
### Selecting the Async backend
7373

starlette/applications.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ class Starlette:
3737
* **on_shutdown** - A list of callables to run on application shutdown.
3838
Shutdown handler callables do not take any arguments, and may be be either
3939
standard functions, or async functions.
40+
* **lifespan** - A lifespan context function, which can be used to perform
41+
startup and shutdown tasks. This is a newer style that replaces the
42+
`on_startup` and `on_shutdown` handlers. Use one or the other, not both.
4043
"""
4144

4245
def __init__(

starlette/routing.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,14 @@ def __init__(
588588
self.on_startup = [] if on_startup is None else list(on_startup)
589589
self.on_shutdown = [] if on_shutdown is None else list(on_shutdown)
590590

591+
if on_startup or on_shutdown:
592+
warnings.warn(
593+
"The on_startup and on_shutdown parameters are deprecated, and they "
594+
"will be removed on version 1.0. Use the lifespan parameter instead. "
595+
"See more about it on https://www.starlette.io/lifespan/.",
596+
DeprecationWarning,
597+
)
598+
591599
if lifespan is None:
592600
self.lifespan_context: Lifespan = _DefaultLifespan(self)
593601

@@ -841,7 +849,7 @@ def add_event_handler(
841849
def on_event(self, event_type: str) -> typing.Callable:
842850
warnings.warn(
843851
"The `on_event` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
844-
"Refer to https://www.starlette.io/events/#registering-events for recommended approach.", # noqa: E501
852+
"Refer to https://www.starlette.io/lifespan/ for recommended approach.",
845853
DeprecationWarning,
846854
)
847855

tests/test_applications.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,13 @@ def run_cleanup():
345345
nonlocal cleanup_complete
346346
cleanup_complete = True
347347

348-
app = Starlette(
349-
on_startup=[run_startup],
350-
on_shutdown=[run_cleanup],
351-
)
348+
with pytest.deprecated_call(
349+
match="The on_startup and on_shutdown parameters are deprecated"
350+
):
351+
app = Starlette(
352+
on_startup=[run_startup],
353+
on_shutdown=[run_cleanup],
354+
)
352355

353356
assert not startup_complete
354357
assert not cleanup_complete

0 commit comments

Comments
 (0)