Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3a4ac24

Browse files
Lancetnikpre-commit-ci[bot]Kludextiangolo
authored
🐛 Ensure thatapp.include_router merges nested lifespans (#9630)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
1 parent22bf988 commit3a4ac24

File tree

2 files changed

+158
-4
lines changed

2 files changed

+158
-4
lines changed

‎fastapi/routing.py‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
importemail.message
44
importinspect
55
importjson
6-
fromcontextlibimportAsyncExitStack
6+
fromcontextlibimportAsyncExitStack,asynccontextmanager
77
fromenumimportEnum,IntEnum
88
fromtypingimport (
99
Any,
10+
AsyncIterator,
1011
Callable,
1112
Coroutine,
1213
Dict,
1314
List,
15+
Mapping,
1416
Optional,
1517
Sequence,
1618
Set,
@@ -67,7 +69,7 @@
6769
websocket_session,
6870
)
6971
fromstarlette.routingimportMountasMount# noqa
70-
fromstarlette.typesimportASGIApp,Lifespan,Scope
72+
fromstarlette.typesimportAppType,ASGIApp,Lifespan,Scope
7173
fromstarlette.websocketsimportWebSocket
7274
fromtyping_extensionsimportAnnotated,Doc,deprecated
7375

@@ -119,6 +121,23 @@ def _prepare_response_content(
119121
returnres
120122

121123

124+
def_merge_lifespan_context(
125+
original_context:Lifespan[Any],nested_context:Lifespan[Any]
126+
)->Lifespan[Any]:
127+
@asynccontextmanager
128+
asyncdefmerged_lifespan(
129+
app:AppType,
130+
)->AsyncIterator[Optional[Mapping[str,Any]]]:
131+
asyncwithoriginal_context(app)asmaybe_original_state:
132+
asyncwithnested_context(app)asmaybe_nested_state:
133+
ifmaybe_nested_stateisNoneandmaybe_original_stateisNone:
134+
yieldNone# old ASGI compatibility
135+
else:
136+
yield {**(maybe_nested_stateor {}),**(maybe_original_stateor {})}
137+
138+
returnmerged_lifespan# type: ignore[return-value]
139+
140+
122141
asyncdefserialize_response(
123142
*,
124143
field:Optional[ModelField]=None,
@@ -1308,6 +1327,10 @@ def read_users():
13081327
self.add_event_handler("startup",handler)
13091328
forhandlerinrouter.on_shutdown:
13101329
self.add_event_handler("shutdown",handler)
1330+
self.lifespan_context=_merge_lifespan_context(
1331+
self.lifespan_context,
1332+
router.lifespan_context,
1333+
)
13111334

13121335
defget(
13131336
self,

‎tests/test_router_events.py‎

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
fromcontextlibimportasynccontextmanager
2-
fromtypingimportAsyncGenerator,Dict
2+
fromtypingimportAsyncGenerator,Dict,Union
33

44
importpytest
5-
fromfastapiimportAPIRouter,FastAPI
5+
fromfastapiimportAPIRouter,FastAPI,Request
66
fromfastapi.testclientimportTestClient
77
frompydanticimportBaseModel
88

@@ -109,3 +109,134 @@ def main() -> Dict[str, str]:
109109
assertresponse.json()== {"message":"Hello World"}
110110
assertstate.app_startupisTrue
111111
assertstate.app_shutdownisTrue
112+
113+
114+
deftest_router_nested_lifespan_state(state:State)->None:
115+
@asynccontextmanager
116+
asyncdeflifespan(app:FastAPI)->AsyncGenerator[Dict[str,bool],None]:
117+
state.app_startup=True
118+
yield {"app":True}
119+
state.app_shutdown=True
120+
121+
@asynccontextmanager
122+
asyncdefrouter_lifespan(app:FastAPI)->AsyncGenerator[Dict[str,bool],None]:
123+
state.router_startup=True
124+
yield {"router":True}
125+
state.router_shutdown=True
126+
127+
@asynccontextmanager
128+
asyncdefsubrouter_lifespan(app:FastAPI)->AsyncGenerator[Dict[str,bool],None]:
129+
state.sub_router_startup=True
130+
yield {"sub_router":True}
131+
state.sub_router_shutdown=True
132+
133+
sub_router=APIRouter(lifespan=subrouter_lifespan)
134+
135+
router=APIRouter(lifespan=router_lifespan)
136+
router.include_router(sub_router)
137+
138+
app=FastAPI(lifespan=lifespan)
139+
app.include_router(router)
140+
141+
@app.get("/")
142+
defmain(request:Request)->Dict[str,str]:
143+
assertrequest.state.app
144+
assertrequest.state.router
145+
assertrequest.state.sub_router
146+
return {"message":"Hello World"}
147+
148+
assertstate.app_startupisFalse
149+
assertstate.router_startupisFalse
150+
assertstate.sub_router_startupisFalse
151+
assertstate.app_shutdownisFalse
152+
assertstate.router_shutdownisFalse
153+
assertstate.sub_router_shutdownisFalse
154+
155+
withTestClient(app)asclient:
156+
assertstate.app_startupisTrue
157+
assertstate.router_startupisTrue
158+
assertstate.sub_router_startupisTrue
159+
assertstate.app_shutdownisFalse
160+
assertstate.router_shutdownisFalse
161+
assertstate.sub_router_shutdownisFalse
162+
response=client.get("/")
163+
assertresponse.status_code==200,response.text
164+
assertresponse.json()== {"message":"Hello World"}
165+
166+
assertstate.app_startupisTrue
167+
assertstate.router_startupisTrue
168+
assertstate.sub_router_startupisTrue
169+
assertstate.app_shutdownisTrue
170+
assertstate.router_shutdownisTrue
171+
assertstate.sub_router_shutdownisTrue
172+
173+
174+
deftest_router_nested_lifespan_state_overriding_by_parent()->None:
175+
@asynccontextmanager
176+
asyncdeflifespan(
177+
app:FastAPI,
178+
)->AsyncGenerator[Dict[str,Union[str,bool]],None]:
179+
yield {
180+
"app_specific":True,
181+
"overridden":"app",
182+
}
183+
184+
@asynccontextmanager
185+
asyncdefrouter_lifespan(
186+
app:FastAPI,
187+
)->AsyncGenerator[Dict[str,Union[str,bool]],None]:
188+
yield {
189+
"router_specific":True,
190+
"overridden":"router",# should override parent
191+
}
192+
193+
router=APIRouter(lifespan=router_lifespan)
194+
app=FastAPI(lifespan=lifespan)
195+
app.include_router(router)
196+
197+
withTestClient(app)asclient:
198+
assertclient.app_state== {
199+
"app_specific":True,
200+
"router_specific":True,
201+
"overridden":"app",
202+
}
203+
204+
205+
deftest_merged_no_return_lifespans_return_none()->None:
206+
@asynccontextmanager
207+
asyncdeflifespan(app:FastAPI)->AsyncGenerator[None,None]:
208+
yield
209+
210+
@asynccontextmanager
211+
asyncdefrouter_lifespan(app:FastAPI)->AsyncGenerator[None,None]:
212+
yield
213+
214+
router=APIRouter(lifespan=router_lifespan)
215+
app=FastAPI(lifespan=lifespan)
216+
app.include_router(router)
217+
218+
withTestClient(app)asclient:
219+
assertnotclient.app_state
220+
221+
222+
deftest_merged_mixed_state_lifespans()->None:
223+
@asynccontextmanager
224+
asyncdeflifespan(app:FastAPI)->AsyncGenerator[None,None]:
225+
yield
226+
227+
@asynccontextmanager
228+
asyncdefrouter_lifespan(app:FastAPI)->AsyncGenerator[Dict[str,bool],None]:
229+
yield {"router":True}
230+
231+
@asynccontextmanager
232+
asyncdefsub_router_lifespan(app:FastAPI)->AsyncGenerator[None,None]:
233+
yield
234+
235+
sub_router=APIRouter(lifespan=sub_router_lifespan)
236+
router=APIRouter(lifespan=router_lifespan)
237+
app=FastAPI(lifespan=lifespan)
238+
router.include_router(sub_router)
239+
app.include_router(router)
240+
241+
withTestClient(app)asclient:
242+
assertclient.app_state== {"router":True}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2026 Movatter.jp