Coverage for tests / test_router_events.py: 100%
274 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1from collections.abc import AsyncGenerator 1jkml
2from contextlib import asynccontextmanager 1jkml
3from typing import Union 1jkml
5import pytest 1jkml
6from fastapi import APIRouter, FastAPI, Request 1jkml
7from fastapi.testclient import TestClient 1jkml
8from pydantic import BaseModel 1jkml
11class State(BaseModel): 1jkml
12 app_startup: bool = False 1jkml
13 app_shutdown: bool = False 1jkml
14 router_startup: bool = False 1jkml
15 router_shutdown: bool = False 1jkml
16 sub_router_startup: bool = False 1jkml
17 sub_router_shutdown: bool = False 1jkml
20@pytest.fixture 1jkml
21def state() -> State: 1jkml
22 return State() 1jkl
25@pytest.mark.filterwarnings( 1jkml
26 r"ignore:\s*on_event is deprecated, use lifespan event handlers instead.*:DeprecationWarning"
27)
28def test_router_events(state: State) -> None: 1jkml
29 app = FastAPI() 1abc
31 @app.get("/") 1abc
32 def main() -> dict[str, str]: 1abc
33 return {"message": "Hello World"} 1abc
35 @app.on_event("startup") 1abc
36 def app_startup() -> None: 1abc
37 state.app_startup = True 1abc
39 @app.on_event("shutdown") 1abc
40 def app_shutdown() -> None: 1abc
41 state.app_shutdown = True 1abc
43 router = APIRouter() 1abc
45 @router.on_event("startup") 1abc
46 def router_startup() -> None: 1abc
47 state.router_startup = True 1abc
49 @router.on_event("shutdown") 1abc
50 def router_shutdown() -> None: 1abc
51 state.router_shutdown = True 1abc
53 sub_router = APIRouter() 1abc
55 @sub_router.on_event("startup") 1abc
56 def sub_router_startup() -> None: 1abc
57 state.sub_router_startup = True 1abc
59 @sub_router.on_event("shutdown") 1abc
60 def sub_router_shutdown() -> None: 1abc
61 state.sub_router_shutdown = True 1abc
63 router.include_router(sub_router) 1abc
64 app.include_router(router) 1abc
66 assert state.app_startup is False 1abc
67 assert state.router_startup is False 1abc
68 assert state.sub_router_startup is False 1abc
69 assert state.app_shutdown is False 1abc
70 assert state.router_shutdown is False 1abc
71 assert state.sub_router_shutdown is False 1abc
72 with TestClient(app) as client: 1abc
73 assert state.app_startup is True 1abc
74 assert state.router_startup is True 1abc
75 assert state.sub_router_startup is True 1abc
76 assert state.app_shutdown is False 1abc
77 assert state.router_shutdown is False 1abc
78 assert state.sub_router_shutdown is False 1abc
79 response = client.get("/") 1abc
80 assert response.status_code == 200, response.text 1abc
81 assert response.json() == {"message": "Hello World"} 1abc
82 assert state.app_startup is True 1abc
83 assert state.router_startup is True 1abc
84 assert state.sub_router_startup is True 1abc
85 assert state.app_shutdown is True 1abc
86 assert state.router_shutdown is True 1abc
87 assert state.sub_router_shutdown is True 1abc
90def test_app_lifespan_state(state: State) -> None: 1jkml
91 @asynccontextmanager 1nop
92 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1nop
93 state.app_startup = True 1nop
94 yield 1nop
95 state.app_shutdown = True 1nop
97 app = FastAPI(lifespan=lifespan) 1nop
99 @app.get("/") 1nop
100 def main() -> dict[str, str]: 1nop
101 return {"message": "Hello World"} 1nop
103 assert state.app_startup is False 1nop
104 assert state.app_shutdown is False 1nop
105 with TestClient(app) as client: 1nop
106 assert state.app_startup is True 1nop
107 assert state.app_shutdown is False 1nop
108 response = client.get("/") 1nop
109 assert response.status_code == 200, response.text 1nop
110 assert response.json() == {"message": "Hello World"} 1nop
111 assert state.app_startup is True 1nop
112 assert state.app_shutdown is True 1nop
115def test_router_nested_lifespan_state(state: State) -> None: 1jkml
116 @asynccontextmanager 1def
117 async def lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1def
118 state.app_startup = True 1def
119 yield {"app": True} 1def
120 state.app_shutdown = True 1def
122 @asynccontextmanager 1def
123 async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1def
124 state.router_startup = True 1def
125 yield {"router": True} 1def
126 state.router_shutdown = True 1def
128 @asynccontextmanager 1def
129 async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1def
130 state.sub_router_startup = True 1def
131 yield {"sub_router": True} 1def
132 state.sub_router_shutdown = True 1def
134 sub_router = APIRouter(lifespan=subrouter_lifespan) 1def
136 router = APIRouter(lifespan=router_lifespan) 1def
137 router.include_router(sub_router) 1def
139 app = FastAPI(lifespan=lifespan) 1def
140 app.include_router(router) 1def
142 @app.get("/") 1def
143 def main(request: Request) -> dict[str, str]: 1def
144 assert request.state.app 1def
145 assert request.state.router 1def
146 assert request.state.sub_router 1def
147 return {"message": "Hello World"} 1def
149 assert state.app_startup is False 1def
150 assert state.router_startup is False 1def
151 assert state.sub_router_startup is False 1def
152 assert state.app_shutdown is False 1def
153 assert state.router_shutdown is False 1def
154 assert state.sub_router_shutdown is False 1def
156 with TestClient(app) as client: 1def
157 assert state.app_startup is True 1def
158 assert state.router_startup is True 1def
159 assert state.sub_router_startup is True 1def
160 assert state.app_shutdown is False 1def
161 assert state.router_shutdown is False 1def
162 assert state.sub_router_shutdown is False 1def
163 response = client.get("/") 1def
164 assert response.status_code == 200, response.text 1def
165 assert response.json() == {"message": "Hello World"} 1def
167 assert state.app_startup is True 1def
168 assert state.router_startup is True 1def
169 assert state.sub_router_startup is True 1def
170 assert state.app_shutdown is True 1def
171 assert state.router_shutdown is True 1def
172 assert state.sub_router_shutdown is True 1def
175def test_router_nested_lifespan_state_overriding_by_parent() -> None: 1jkml
176 @asynccontextmanager 1CDE
177 async def lifespan( 1CDE
178 app: FastAPI,
179 ) -> AsyncGenerator[dict[str, Union[str, bool]], None]:
180 yield { 1CDE
181 "app_specific": True,
182 "overridden": "app",
183 }
185 @asynccontextmanager 1CDE
186 async def router_lifespan( 1CDE
187 app: FastAPI,
188 ) -> AsyncGenerator[dict[str, Union[str, bool]], None]:
189 yield { 1CDE
190 "router_specific": True,
191 "overridden": "router", # should override parent
192 }
194 router = APIRouter(lifespan=router_lifespan) 1CDE
195 app = FastAPI(lifespan=lifespan) 1CDE
196 app.include_router(router) 1CDE
198 with TestClient(app) as client: 1CDE
199 assert client.app_state == { 1CDE
200 "app_specific": True,
201 "router_specific": True,
202 "overridden": "app",
203 }
206def test_merged_no_return_lifespans_return_none() -> None: 1jkml
207 @asynccontextmanager 1FGH
208 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1FGH
209 yield 1FGH
211 @asynccontextmanager 1FGH
212 async def router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1FGH
213 yield 1FGH
215 router = APIRouter(lifespan=router_lifespan) 1FGH
216 app = FastAPI(lifespan=lifespan) 1FGH
217 app.include_router(router) 1FGH
219 with TestClient(app) as client: 1FGH
220 assert not client.app_state 1FGH
223def test_merged_mixed_state_lifespans() -> None: 1jkml
224 @asynccontextmanager 1wxy
225 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1wxy
226 yield 1wxy
228 @asynccontextmanager 1wxy
229 async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1wxy
230 yield {"router": True} 1wxy
232 @asynccontextmanager 1wxy
233 async def sub_router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1wxy
234 yield 1wxy
236 sub_router = APIRouter(lifespan=sub_router_lifespan) 1wxy
237 router = APIRouter(lifespan=router_lifespan) 1wxy
238 app = FastAPI(lifespan=lifespan) 1wxy
239 router.include_router(sub_router) 1wxy
240 app.include_router(router) 1wxy
242 with TestClient(app) as client: 1wxy
243 assert client.app_state == {"router": True} 1wxy
246@pytest.mark.filterwarnings( 1jkml
247 r"ignore:\s*on_event is deprecated, use lifespan event handlers instead.*:DeprecationWarning"
248)
249def test_router_async_shutdown_handler(state: State) -> None: 1jkml
250 """Test that async on_shutdown event handlers are called correctly, for coverage."""
251 app = FastAPI() 1zAB
253 @app.get("/") 1zAB
254 def main() -> dict[str, str]: 1zAB
255 return {"message": "Hello World"} 1zAB
257 @app.on_event("shutdown") 1zAB
258 async def app_shutdown() -> None: 1zAB
259 state.app_shutdown = True 1zAB
261 assert state.app_shutdown is False 1zAB
262 with TestClient(app) as client: 1zAB
263 assert state.app_shutdown is False 1zAB
264 response = client.get("/") 1zAB
265 assert response.status_code == 200, response.text 1zAB
266 assert state.app_shutdown is True 1zAB
269def test_router_sync_generator_lifespan(state: State) -> None: 1jkml
270 """Test that a sync generator lifespan works via _wrap_gen_lifespan_context."""
271 from collections.abc import Generator 1qrs
273 def lifespan(app: FastAPI) -> Generator[None, None, None]: 1qrs
274 state.app_startup = True 1qrs
275 yield 1qrs
276 state.app_shutdown = True 1qrs
278 app = FastAPI(lifespan=lifespan) # type: ignore[arg-type] 1qrs
280 @app.get("/") 1qrs
281 def main() -> dict[str, str]: 1qrs
282 return {"message": "Hello World"} 1qrs
284 assert state.app_startup is False 1qrs
285 assert state.app_shutdown is False 1qrs
286 with TestClient(app) as client: 1qrs
287 assert state.app_startup is True 1qrs
288 assert state.app_shutdown is False 1qrs
289 response = client.get("/") 1qrs
290 assert response.status_code == 200, response.text 1qrs
291 assert response.json() == {"message": "Hello World"} 1qrs
292 assert state.app_startup is True 1qrs
293 assert state.app_shutdown is True 1qrs
296def test_router_async_generator_lifespan(state: State) -> None: 1jkml
297 """Test that an async generator lifespan (not wrapped) works."""
299 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1tuv
300 state.app_startup = True 1tuv
301 yield 1tuv
302 state.app_shutdown = True 1tuv
304 app = FastAPI(lifespan=lifespan) # type: ignore[arg-type] 1tuv
306 @app.get("/") 1tuv
307 def main() -> dict[str, str]: 1tuv
308 return {"message": "Hello World"} 1tuv
310 assert state.app_startup is False 1tuv
311 assert state.app_shutdown is False 1tuv
312 with TestClient(app) as client: 1tuv
313 assert state.app_startup is True 1tuv
314 assert state.app_shutdown is False 1tuv
315 response = client.get("/") 1tuv
316 assert response.status_code == 200, response.text 1tuv
317 assert response.json() == {"message": "Hello World"} 1tuv
318 assert state.app_startup is True 1tuv
319 assert state.app_shutdown is True 1tuv
322def test_startup_shutdown_handlers_as_parameters(state: State) -> None: 1jkml
323 """Test that startup/shutdown handlers passed as parameters to FastAPI are called correctly."""
325 def app_startup() -> None: 1ghi
326 state.app_startup = True 1ghi
328 def app_shutdown() -> None: 1ghi
329 state.app_shutdown = True 1ghi
331 app = FastAPI(on_startup=[app_startup], on_shutdown=[app_shutdown]) 1ghi
333 @app.get("/") 1ghi
334 def main() -> dict[str, str]: 1ghi
335 return {"message": "Hello World"} 1ghi
337 def router_startup() -> None: 1ghi
338 state.router_startup = True 1ghi
340 def router_shutdown() -> None: 1ghi
341 state.router_shutdown = True 1ghi
343 router = APIRouter(on_startup=[router_startup], on_shutdown=[router_shutdown]) 1ghi
345 def sub_router_startup() -> None: 1ghi
346 state.sub_router_startup = True 1ghi
348 def sub_router_shutdown() -> None: 1ghi
349 state.sub_router_shutdown = True 1ghi
351 sub_router = APIRouter( 1ghi
352 on_startup=[sub_router_startup], on_shutdown=[sub_router_shutdown]
353 )
355 router.include_router(sub_router) 1ghi
356 app.include_router(router) 1ghi
358 assert state.app_startup is False 1ghi
359 assert state.router_startup is False 1ghi
360 assert state.sub_router_startup is False 1ghi
361 assert state.app_shutdown is False 1ghi
362 assert state.router_shutdown is False 1ghi
363 assert state.sub_router_shutdown is False 1ghi
364 with TestClient(app) as client: 1ghi
365 assert state.app_startup is True 1ghi
366 assert state.router_startup is True 1ghi
367 assert state.sub_router_startup is True 1ghi
368 assert state.app_shutdown is False 1ghi
369 assert state.router_shutdown is False 1ghi
370 assert state.sub_router_shutdown is False 1ghi
371 response = client.get("/") 1ghi
372 assert response.status_code == 200, response.text 1ghi
373 assert response.json() == {"message": "Hello World"} 1ghi
374 assert state.app_startup is True 1ghi
375 assert state.router_startup is True 1ghi
376 assert state.sub_router_startup is True 1ghi
377 assert state.app_shutdown is True 1ghi
378 assert state.router_shutdown is True 1ghi
379 assert state.sub_router_shutdown is True 1ghi