Coverage for tests / test_router_events.py: 100%
273 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-04-06 01:24 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-04-06 01:24 +0000
1from collections.abc import AsyncGenerator 1mnop
2from contextlib import asynccontextmanager 1mnop
4import pytest 1mnop
5from fastapi import APIRouter, FastAPI, Request 1mnop
6from fastapi.testclient import TestClient 1mnop
7from pydantic import BaseModel 1mnop
10class State(BaseModel): 1mnop
11 app_startup: bool = False 1mnop
12 app_shutdown: bool = False 1mnop
13 router_startup: bool = False 1mnop
14 router_shutdown: bool = False 1mnop
15 sub_router_startup: bool = False 1mnop
16 sub_router_shutdown: bool = False 1mnop
19@pytest.fixture 1mnop
20def state() -> State: 1mnop
21 return State() 1STUVWXYZ0123456789!#$%'
24@pytest.mark.filterwarnings( 1mnop
25 r"ignore:\s*on_event is deprecated, use lifespan event handlers instead.*:DeprecationWarning"
26)
27def test_router_events(state: State) -> None: 1mnop
28 app = FastAPI() 1abcd
30 @app.get("/") 1abcd
31 def main() -> dict[str, str]: 1abcd
32 return {"message": "Hello World"} 1abcd
34 @app.on_event("startup") 1abcd
35 def app_startup() -> None: 1abcd
36 state.app_startup = True 1abcd
38 @app.on_event("shutdown") 1abcd
39 def app_shutdown() -> None: 1abcd
40 state.app_shutdown = True 1abcd
42 router = APIRouter() 1abcd
44 @router.on_event("startup") 1abcd
45 def router_startup() -> None: 1abcd
46 state.router_startup = True 1abcd
48 @router.on_event("shutdown") 1abcd
49 def router_shutdown() -> None: 1abcd
50 state.router_shutdown = True 1abcd
52 sub_router = APIRouter() 1abcd
54 @sub_router.on_event("startup") 1abcd
55 def sub_router_startup() -> None: 1abcd
56 state.sub_router_startup = True 1abcd
58 @sub_router.on_event("shutdown") 1abcd
59 def sub_router_shutdown() -> None: 1abcd
60 state.sub_router_shutdown = True 1abcd
62 router.include_router(sub_router) 1abcd
63 app.include_router(router) 1abcd
65 assert state.app_startup is False 1abcd
66 assert state.router_startup is False 1abcd
67 assert state.sub_router_startup is False 1abcd
68 assert state.app_shutdown is False 1abcd
69 assert state.router_shutdown is False 1abcd
70 assert state.sub_router_shutdown is False 1abcd
71 with TestClient(app) as client: 1abcd
72 assert state.app_startup is True 1abcd
73 assert state.router_startup is True 1abcd
74 assert state.sub_router_startup is True 1abcd
75 assert state.app_shutdown is False 1abcd
76 assert state.router_shutdown is False 1abcd
77 assert state.sub_router_shutdown is False 1abcd
78 response = client.get("/") 1abcd
79 assert response.status_code == 200, response.text 1abcd
80 assert response.json() == {"message": "Hello World"} 1abcd
81 assert state.app_startup is True 1abcd
82 assert state.router_startup is True 1abcd
83 assert state.sub_router_startup is True 1abcd
84 assert state.app_shutdown is True 1abcd
85 assert state.router_shutdown is True 1abcd
86 assert state.sub_router_shutdown is True 1abcd
89def test_app_lifespan_state(state: State) -> None: 1mnop
90 @asynccontextmanager 1qrst
91 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1qrst
92 state.app_startup = True 1qrst
93 yield 1qrst
94 state.app_shutdown = True 1qrst
96 app = FastAPI(lifespan=lifespan) 1qrst
98 @app.get("/") 1qrst
99 def main() -> dict[str, str]: 1qrst
100 return {"message": "Hello World"} 1qrst
102 assert state.app_startup is False 1qrst
103 assert state.app_shutdown is False 1qrst
104 with TestClient(app) as client: 1qrst
105 assert state.app_startup is True 1qrst
106 assert state.app_shutdown is False 1qrst
107 response = client.get("/") 1qrst
108 assert response.status_code == 200, response.text 1qrst
109 assert response.json() == {"message": "Hello World"} 1qrst
110 assert state.app_startup is True 1qrst
111 assert state.app_shutdown is True 1qrst
114def test_router_nested_lifespan_state(state: State) -> None: 1mnop
115 @asynccontextmanager 1efgh
116 async def lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1efgh
117 state.app_startup = True 1efgh
118 yield {"app": True} 1efgh
119 state.app_shutdown = True 1efgh
121 @asynccontextmanager 1efgh
122 async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1efgh
123 state.router_startup = True 1efgh
124 yield {"router": True} 1efgh
125 state.router_shutdown = True 1efgh
127 @asynccontextmanager 1efgh
128 async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1efgh
129 state.sub_router_startup = True 1efgh
130 yield {"sub_router": True} 1efgh
131 state.sub_router_shutdown = True 1efgh
133 sub_router = APIRouter(lifespan=subrouter_lifespan) 1efgh
135 router = APIRouter(lifespan=router_lifespan) 1efgh
136 router.include_router(sub_router) 1efgh
138 app = FastAPI(lifespan=lifespan) 1efgh
139 app.include_router(router) 1efgh
141 @app.get("/") 1efgh
142 def main(request: Request) -> dict[str, str]: 1efgh
143 assert request.state.app 1efgh
144 assert request.state.router 1efgh
145 assert request.state.sub_router 1efgh
146 return {"message": "Hello World"} 1efgh
148 assert state.app_startup is False 1efgh
149 assert state.router_startup is False 1efgh
150 assert state.sub_router_startup is False 1efgh
151 assert state.app_shutdown is False 1efgh
152 assert state.router_shutdown is False 1efgh
153 assert state.sub_router_shutdown is False 1efgh
155 with TestClient(app) as client: 1efgh
156 assert state.app_startup is True 1efgh
157 assert state.router_startup is True 1efgh
158 assert state.sub_router_startup is True 1efgh
159 assert state.app_shutdown is False 1efgh
160 assert state.router_shutdown is False 1efgh
161 assert state.sub_router_shutdown is False 1efgh
162 response = client.get("/") 1efgh
163 assert response.status_code == 200, response.text 1efgh
164 assert response.json() == {"message": "Hello World"} 1efgh
166 assert state.app_startup is True 1efgh
167 assert state.router_startup is True 1efgh
168 assert state.sub_router_startup is True 1efgh
169 assert state.app_shutdown is True 1efgh
170 assert state.router_shutdown is True 1efgh
171 assert state.sub_router_shutdown is True 1efgh
174def test_router_nested_lifespan_state_overriding_by_parent() -> None: 1mnop
175 @asynccontextmanager 1KLMN
176 async def lifespan( 1KLMN
177 app: FastAPI,
178 ) -> AsyncGenerator[dict[str, str | bool], None]:
179 yield { 1KLMN
180 "app_specific": True,
181 "overridden": "app",
182 }
184 @asynccontextmanager 1KLMN
185 async def router_lifespan( 1KLMN
186 app: FastAPI,
187 ) -> AsyncGenerator[dict[str, str | bool], None]:
188 yield { 1KLMN
189 "router_specific": True,
190 "overridden": "router", # should override parent
191 }
193 router = APIRouter(lifespan=router_lifespan) 1KLMN
194 app = FastAPI(lifespan=lifespan) 1KLMN
195 app.include_router(router) 1KLMN
197 with TestClient(app) as client: 1KLMN
198 assert client.app_state == { 1KLMN
199 "app_specific": True,
200 "router_specific": True,
201 "overridden": "app",
202 }
205def test_merged_no_return_lifespans_return_none() -> None: 1mnop
206 @asynccontextmanager 1OPQR
207 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1OPQR
208 yield 1OPQR
210 @asynccontextmanager 1OPQR
211 async def router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1OPQR
212 yield 1OPQR
214 router = APIRouter(lifespan=router_lifespan) 1OPQR
215 app = FastAPI(lifespan=lifespan) 1OPQR
216 app.include_router(router) 1OPQR
218 with TestClient(app) as client: 1OPQR
219 assert not client.app_state 1OPQR
222def test_merged_mixed_state_lifespans() -> None: 1mnop
223 @asynccontextmanager 1CDEF
224 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1CDEF
225 yield 1CDEF
227 @asynccontextmanager 1CDEF
228 async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: 1CDEF
229 yield {"router": True} 1CDEF
231 @asynccontextmanager 1CDEF
232 async def sub_router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1CDEF
233 yield 1CDEF
235 sub_router = APIRouter(lifespan=sub_router_lifespan) 1CDEF
236 router = APIRouter(lifespan=router_lifespan) 1CDEF
237 app = FastAPI(lifespan=lifespan) 1CDEF
238 router.include_router(sub_router) 1CDEF
239 app.include_router(router) 1CDEF
241 with TestClient(app) as client: 1CDEF
242 assert client.app_state == {"router": True} 1CDEF
245@pytest.mark.filterwarnings( 1mnop
246 r"ignore:\s*on_event is deprecated, use lifespan event handlers instead.*:DeprecationWarning"
247)
248def test_router_async_shutdown_handler(state: State) -> None: 1mnop
249 """Test that async on_shutdown event handlers are called correctly, for coverage."""
250 app = FastAPI() 1GHIJ
252 @app.get("/") 1GHIJ
253 def main() -> dict[str, str]: 1GHIJ
254 return {"message": "Hello World"} 1GHIJ
256 @app.on_event("shutdown") 1GHIJ
257 async def app_shutdown() -> None: 1GHIJ
258 state.app_shutdown = True 1GHIJ
260 assert state.app_shutdown is False 1GHIJ
261 with TestClient(app) as client: 1GHIJ
262 assert state.app_shutdown is False 1GHIJ
263 response = client.get("/") 1GHIJ
264 assert response.status_code == 200, response.text 1GHIJ
265 assert state.app_shutdown is True 1GHIJ
268def test_router_sync_generator_lifespan(state: State) -> None: 1mnop
269 """Test that a sync generator lifespan works via _wrap_gen_lifespan_context."""
270 from collections.abc import Generator 1uvwx
272 def lifespan(app: FastAPI) -> Generator[None, None, None]: 1uvwx
273 state.app_startup = True 1uvwx
274 yield 1uvwx
275 state.app_shutdown = True 1uvwx
277 app = FastAPI(lifespan=lifespan) # type: ignore[arg-type] 1uvwx
279 @app.get("/") 1uvwx
280 def main() -> dict[str, str]: 1uvwx
281 return {"message": "Hello World"} 1uvwx
283 assert state.app_startup is False 1uvwx
284 assert state.app_shutdown is False 1uvwx
285 with TestClient(app) as client: 1uvwx
286 assert state.app_startup is True 1uvwx
287 assert state.app_shutdown is False 1uvwx
288 response = client.get("/") 1uvwx
289 assert response.status_code == 200, response.text 1uvwx
290 assert response.json() == {"message": "Hello World"} 1uvwx
291 assert state.app_startup is True 1uvwx
292 assert state.app_shutdown is True 1uvwx
295def test_router_async_generator_lifespan(state: State) -> None: 1mnop
296 """Test that an async generator lifespan (not wrapped) works."""
298 async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 1yzAB
299 state.app_startup = True 1yzAB
300 yield 1yzAB
301 state.app_shutdown = True 1yzAB
303 app = FastAPI(lifespan=lifespan) # type: ignore[arg-type] 1yzAB
305 @app.get("/") 1yzAB
306 def main() -> dict[str, str]: 1yzAB
307 return {"message": "Hello World"} 1yzAB
309 assert state.app_startup is False 1yzAB
310 assert state.app_shutdown is False 1yzAB
311 with TestClient(app) as client: 1yzAB
312 assert state.app_startup is True 1yzAB
313 assert state.app_shutdown is False 1yzAB
314 response = client.get("/") 1yzAB
315 assert response.status_code == 200, response.text 1yzAB
316 assert response.json() == {"message": "Hello World"} 1yzAB
317 assert state.app_startup is True 1yzAB
318 assert state.app_shutdown is True 1yzAB
321def test_startup_shutdown_handlers_as_parameters(state: State) -> None: 1mnop
322 """Test that startup/shutdown handlers passed as parameters to FastAPI are called correctly."""
324 def app_startup() -> None: 1ijkl
325 state.app_startup = True 1ijkl
327 def app_shutdown() -> None: 1ijkl
328 state.app_shutdown = True 1ijkl
330 app = FastAPI(on_startup=[app_startup], on_shutdown=[app_shutdown]) 1ijkl
332 @app.get("/") 1ijkl
333 def main() -> dict[str, str]: 1ijkl
334 return {"message": "Hello World"} 1ijkl
336 def router_startup() -> None: 1ijkl
337 state.router_startup = True 1ijkl
339 def router_shutdown() -> None: 1ijkl
340 state.router_shutdown = True 1ijkl
342 router = APIRouter(on_startup=[router_startup], on_shutdown=[router_shutdown]) 1ijkl
344 def sub_router_startup() -> None: 1ijkl
345 state.sub_router_startup = True 1ijkl
347 def sub_router_shutdown() -> None: 1ijkl
348 state.sub_router_shutdown = True 1ijkl
350 sub_router = APIRouter( 1ijkl
351 on_startup=[sub_router_startup], on_shutdown=[sub_router_shutdown]
352 )
354 router.include_router(sub_router) 1ijkl
355 app.include_router(router) 1ijkl
357 assert state.app_startup is False 1ijkl
358 assert state.router_startup is False 1ijkl
359 assert state.sub_router_startup is False 1ijkl
360 assert state.app_shutdown is False 1ijkl
361 assert state.router_shutdown is False 1ijkl
362 assert state.sub_router_shutdown is False 1ijkl
363 with TestClient(app) as client: 1ijkl
364 assert state.app_startup is True 1ijkl
365 assert state.router_startup is True 1ijkl
366 assert state.sub_router_startup is True 1ijkl
367 assert state.app_shutdown is False 1ijkl
368 assert state.router_shutdown is False 1ijkl
369 assert state.sub_router_shutdown is False 1ijkl
370 response = client.get("/") 1ijkl
371 assert response.status_code == 200, response.text 1ijkl
372 assert response.json() == {"message": "Hello World"} 1ijkl
373 assert state.app_startup is True 1ijkl
374 assert state.router_startup is True 1ijkl
375 assert state.sub_router_startup is True 1ijkl
376 assert state.app_shutdown is True 1ijkl
377 assert state.router_shutdown is True 1ijkl
378 assert state.sub_router_shutdown is True 1ijkl