@ -171,6 +171,137 @@ def test_router_nested_lifespan_state(state: State) -> None:
assert state . sub_router_shutdown is True
def test_router_nested_lifespan_state_discard_default_lifespan_app (
state : State ,
) - > None :
@asynccontextmanager
async def router_lifespan ( app : FastAPI ) - > AsyncGenerator [ dict [ str , bool ] , None ] :
state . router_startup = True
yield { " router " : True }
state . router_shutdown = True
@asynccontextmanager
async def subrouter_lifespan ( app : FastAPI ) - > AsyncGenerator [ dict [ str , bool ] , None ] :
state . sub_router_startup = True
yield { " sub_router " : True }
state . sub_router_shutdown = True
sub_router = APIRouter ( lifespan = subrouter_lifespan )
sub_router_lifespan_ctx = sub_router . lifespan_context
router = APIRouter ( lifespan = router_lifespan )
router_lifespan_ctx = router . lifespan_context
router . include_router ( sub_router )
assert router . lifespan_context is not router_lifespan_ctx , (
" Including a sub-router with a lifespan should change the router ' s lifespan context "
)
assert router . lifespan_context is not sub_router_lifespan_ctx , (
" New router lifespan context should not be the same as the sub-router ' s lifespan context, since the router should merge the lifespan contexts of all included sub-routers "
)
app = FastAPI ( )
app_lifespan_ctx = app . router . lifespan_context
app . include_router ( router )
assert app . router . lifespan_context is not app_lifespan_ctx , (
" Including a router with a lifespan should change the app ' s lifespan context "
)
assert app . router . lifespan_context is router . lifespan_context , (
" New app lifespan context should be the same as the router ' s lifespan context, since the app has a default lifespan "
)
assert app . router . lifespan_context is not sub_router_lifespan_ctx , (
" New app lifespan context should not be the same as the sub-router ' s lifespan context, since the app should merge the lifespan contexts of all included routers "
)
@app . get ( " / " )
def main ( request : Request ) - > dict [ str , str ] :
assert request . state . router
assert request . state . sub_router
return { " message " : " Hello World " }
assert state . router_startup is False
assert state . sub_router_startup is False
assert state . router_shutdown is False
assert state . sub_router_shutdown is False
with TestClient ( app ) as client :
assert state . router_startup is True
assert state . sub_router_startup is True
assert state . router_shutdown is False
assert state . sub_router_shutdown is False
response = client . get ( " / " )
assert response . status_code == 200 , response . text
assert response . json ( ) == { " message " : " Hello World " }
assert state . router_startup is True
assert state . sub_router_startup is True
assert state . router_shutdown is True
assert state . sub_router_shutdown is True
def test_router_nested_lifespan_state_discard_default_lifespan_child (
state : State ,
) - > None :
@asynccontextmanager
async def lifespan ( app : FastAPI ) - > AsyncGenerator [ dict [ str , bool ] , None ] :
state . app_startup = True
yield { " app " : True }
state . app_shutdown = True
router = APIRouter ( )
app = FastAPI ( lifespan = lifespan )
app_lifespan_ctx = app . router . lifespan_context
app . include_router ( router )
assert app . router . lifespan_context is app_lifespan_ctx , (
" Including a router without a lifespan should not change the app ' s lifespan context "
)
@app . get ( " / " )
def main ( request : Request ) - > dict [ str , str ] :
assert request . state . app
return { " message " : " Hello World " }
assert state . app_startup is False
assert state . app_shutdown is False
with TestClient ( app ) as client :
assert state . app_startup is True
response = client . get ( " / " )
assert response . status_code == 200 , response . text
assert response . json ( ) == { " message " : " Hello World " }
assert state . app_shutdown is True
def test_router_nested_lifespan_state_no_lifespans (
state : State ,
) - > None :
""" Test that if no lifespans are provided, the app still works and the state is empty. """
router = APIRouter ( )
app = FastAPI ( )
app_lifespan_ctx = app . router . lifespan_context
app . include_router ( router )
assert app . router . lifespan_context is app_lifespan_ctx , (
" Including a router without a lifespan should not change the app ' s lifespan context "
)
@app . get ( " / " )
def main ( request : Request ) - > dict [ str , str ] :
return { " message " : " Hello World " }
with TestClient ( app ) as client :
response = client . get ( " / " )
assert response . status_code == 200 , response . text
assert response . json ( ) == { " message " : " Hello World " }
def test_router_nested_lifespan_state_overriding_by_parent ( ) - > None :
@asynccontextmanager
async def lifespan (