Browse Source

nest routers` lifespans

pull/9630/head
Lancetnik 2 years ago
parent
commit
5bdbcc3c89
  1. 25
      fastapi/routing.py
  2. 57
      tests/test_router_events.py

25
fastapi/routing.py

@ -3,9 +3,10 @@ import dataclasses
import email.message import email.message
import inspect import inspect
import json import json
from contextlib import AsyncExitStack from contextlib import AsyncExitStack, asynccontextmanager
from enum import Enum, IntEnum from enum import Enum, IntEnum
from typing import ( from typing import (
AsyncIterator,
Any, Any,
Callable, Callable,
Coroutine, Coroutine,
@ -17,6 +18,7 @@ from typing import (
Tuple, Tuple,
Type, Type,
Union, Union,
Mapping,
) )
from fastapi import params from fastapi import params
@ -57,7 +59,7 @@ from starlette.routing import (
websocket_session, websocket_session,
) )
from starlette.status import WS_1008_POLICY_VIOLATION from starlette.status import WS_1008_POLICY_VIOLATION
from starlette.types import ASGIApp, Lifespan, Scope from starlette.types import ASGIApp, Lifespan, Scope, AppType
from starlette.websockets import WebSocket from starlette.websockets import WebSocket
@ -107,6 +109,21 @@ def _prepare_response_content(
return res return res
def _merge_lifespan_context(
original_context: Lifespan[Any], nested_context: Lifespan[Any]
) -> Lifespan[Any]:
@asynccontextmanager
async def merged_lifespan(app: AppType) -> AsyncIterator[Mapping[str, Any]]:
async with original_context(app) as maybe_self_context:
async with nested_context(app) as maybe_nested_context:
context = maybe_self_context or {}
if maybe_nested_context:
context.update(maybe_nested_context)
yield context
return merged_lifespan
async def serialize_response( async def serialize_response(
*, *,
field: Optional[ModelField] = None, field: Optional[ModelField] = None,
@ -830,6 +847,10 @@ class APIRouter(routing.Router):
self.add_event_handler("startup", handler) self.add_event_handler("startup", handler)
for handler in router.on_shutdown: for handler in router.on_shutdown:
self.add_event_handler("shutdown", handler) self.add_event_handler("shutdown", handler)
self.lifespan_context = _merge_lifespan_context(
self.lifespan_context,
router.lifespan_context,
)
def get( def get(
self, self,

57
tests/test_router_events.py

@ -106,3 +106,60 @@ def test_app_lifespan_state(state: State) -> None:
assert response.json() == {"message": "Hello World"} assert response.json() == {"message": "Hello World"}
assert state.app_startup is True assert state.app_startup is True
assert state.app_shutdown is True assert state.app_shutdown is True
def test_router_nested_lifespan_state(state: State) -> None:
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
state.app_startup = True
yield
state.app_shutdown = True
@asynccontextmanager
async def router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
state.router_startup = True
yield
state.router_shutdown = True
@asynccontextmanager
async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
state.sub_router_startup = True
yield
state.sub_router_shutdown = True
sub_router = APIRouter(lifespan=subrouter_lifespan)
router = APIRouter(lifespan=router_lifespan)
router.include_router(sub_router)
app = FastAPI(lifespan=lifespan)
app.include_router(router)
@app.get("/")
def main() -> Dict[str, str]:
return {"message": "Hello World"}
assert state.app_startup is False
assert state.router_startup is False
assert state.sub_router_startup is False
assert state.app_shutdown is False
assert state.router_shutdown is False
assert state.sub_router_shutdown is False
with TestClient(app) as client:
assert state.app_startup is True
assert state.router_startup is True
assert state.sub_router_startup is True
assert state.app_shutdown is False
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.app_startup is True
assert state.router_startup is True
assert state.sub_router_startup is True
assert state.app_shutdown is True
assert state.router_shutdown is True
assert state.sub_router_shutdown is True

Loading…
Cancel
Save