◾️OpenAPIでスキーマ管理
app/main.py
app = FastAPI()
fastapi/applications.py
Class FastAPI(Starlette):
・・・
def openapi(self) -> dict[str, Any]:
◾️DI(依存注入)
・アプリ全体に適用する共通の依存関係を登録。
fastapi/applications.py
Class FastAPI(Starlette):
・・・
def __init__():
dependencies: Annotated[
Sequence[Depends] | None,
Doc(),] = None
・Dependsオブジェクトをリストとして受け取り、内部の router に引き渡す。
fastapi/params.py
@dataclass(frozen=True)
class Depends:
dependency: Callable[..., Any] | None = None
use_cache: bool = True
scope: Literal["function", "request"] | None = None
fastapi/applications.py
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
dependencies=dependencies
)
fastapi/routing.py
class APIRouter(routing.Router):
def __init__():
~~~~~~~
self.dependant = get_dependant(
path=self.path_format, call=self.endpoint, scope="function"
)
FastAPIの起動時に、Dependant オブジェクトを作る。
fastapi/dependencies/utils.py
def get_dependant(
*,
path: str,
call: Callable[..., Any],
scope: Literal["function", "request"] | None = None,
) -> Dependant:
dependant = Dependant(
call=call,
name=name,
path=path,
models.pyに定義してある。
fastapi/dependencies/models.py
@dataclass
class Dependant:
path_params: list[ModelField] = field(default_factory=list)
query_params: list[ModelField] = field(default_factory=list)
header_params: list[ModelField] = field(default_factory=list)
cookie_params: list[ModelField] = field(default_factory=list)
body_params: list[ModelField] = field(default_factory=list)
~~~~
scope: Literal["function", "request"] | None = None
fastapi/routing.py
class _DefaultLifespan:
~~~~~
def __init__(self, router: "APIRouter") -> None:
self._router = router
~~~~~
solved_result = await solve_dependencies(
request=websocket,
dependant=dependant,
~~~~~
リクエストが来た瞬間、この関数が動く。
fastapi/dependencies/utils.py
# utils.py 内の solve_dependencies 関数
async def solve_dependencies(..., dependant: Dependant, ...) -> SolvedDependency:
values: dict[str, Any] = {}
# 依存先を再帰的に実行して解決
for sub_dependant in dependant.dependencies:
solved_result = await solve_dependencies(dependant=sub_dependant, ...)
# 依存関数を実行して値を手に入れる
solved = await call(**solved_result.values)
# 辞書にkeyとvalueを追加
if sub_dependant.name:
values[sub_dependant.name] = solved
完成した辞書を関数に叩き込む。
fastapi/applications.py
class FastAPI(Starlette):
def __init__():
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
dependencies=dependencies
)
fastapi/routing.py
class _DefaultLifespan:
def __init__(self, router: "APIRouter") -> None:
self._router = router
def get_request_handler():
~~~
async def app(request: Request) -> Response:
~~~
raw_response = await run_endpoint_function(
dependant=dependant,
fastapi/routing.py
async def run_endpoint_function(
*, dependant: Dependant, values: dict[str, Any], is_coroutine: bool
) -> Any:
◾️Pydantic(バリデーションチェック)
fastapi/applications.py
Class FastAPI(Starlette):
・・・
def openapi(self) -> dict[str, Any]:
fastapi/applications.py
Class FastAPI(Starlette):
def __init__():
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
dependencies=dependencies
)
fastapi/routing.py
class APIRouter(routing.Router):
def get_request_handler(
dependant: Dependant,
body_field: ModelField | None = None,
status_code: int | None = None,
~~~
for sub_dependant in dependant.dependencies:
~~~
solved_result = await solve_dependencies(
request=request,
dependant=dependant, ←Pydanticの情報が詰まっている
body=body,
)
fastapi/dependencies/utils.py
async def solve_dependencies(
*,
request: Request | WebSocket,
dependant: Dependant,
~~~
def _validate_value_with_model_field(
*, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]
) -> tuple[Any, list[Any]]:
if value is None:
if field.field_info.is_required():
return None, [get_missing_field_error(loc=loc)]
else:
return deepcopy(field.default), []
return field.validate(value, values, loc=loc)
fastapi/_compat/v2.py
class ModelField:
def validate(
self,
value: Any,
values: dict[str, Any] = {}, # noqa: B006
*,
loc: tuple[int | str, ...] = (),
) -> tuple[Any, list[dict[str, Any]]]:
try:
return (
self._type_adapter.validate_python(value, from_attributes=True),
[],
)
except ValidationError as exc:
return None, _regenerate_error_with_loc(
errors=exc.errors(include_url=False), loc_prefix=loc
)
型が違えば errors リストに詰め込み、合っていれば辞書(solved_result.values)に追加する。
Starlette の役割: HTTP 通信、WebSocket、ミドルウェアのスタック管理など
fastapi/applications.py
class FastAPI(Starlette):
def __init__(
self: AppType,
*,
debug: Annotated[
bool
):
〜〜〜
Middleware: リクエストが FastAPI に届く直前で、共通のログを吐いたり、ヘッダーを書き換えたりする仕組み。
starlette>applications.py
class Starlette:
def __init__(
self: AppType,
~~~
middleware: Sequence[Middleware] | None = None,
~~~
lifespan: Lifespan[AppType] | None = None,
) -> None:
~~~
self.user_middleware = [] if middleware is None else list(middleware)
self.middleware_stack: ASGIApp | None = None
__call__:Uvicorn などのサーバーからリクエストが届いたとき、最初に叩かれる。
starlette>applications.py
class Starlette:
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
scope["app"] = self
if self.middleware_stack is None:
self.middleware_stack = self.build_middleware_stack()
await self.middleware_stack(scope, receive, send)
starlette>applications.py
def build_middleware_stack(self) -> ASGIApp:
debug = self.debug
error_handler = None
exception_handlers: dict[Any, ExceptionHandler] = {}
for key, value in self.exception_handlers.items():
if key in (500, Exception):
error_handler = value
else:
exception_handlers[key] = value
middleware = (
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
+ self.user_middleware
+ [Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)]
)
app = self.router
for cls, args, kwargs in reversed(middleware):
app = cls(app, *args, **kwargs)
return app
WebSocket: リアルタイムな通信をする。
特徴:一度つないだら、やり取りが双方向(Receive/Send)でループし続ける
starlette>applications.py
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
@app.websocket("/ws") と書いたとき、内部では APIWebSocketRoute が生成されます。
fastapi/routing.py
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.add_api_websocket_route(
path,
func,
name=name,
dependencies=dependencies,
)
return func
return decorator
APIWebSocketRoute オブジェクトが作られ、Starlette のルーターに登録されます。
fastapi/routing.py
def add_api_websocket_route(
self,
path: str,
endpoint: Callable[..., Any],
name: str | None = None,
*,
dependencies: Sequence[Depends] | None = None,
) -> None:
self.router.add_api_websocket_route(
path,
endpoint,
name=name,
dependencies=dependencies,
)
実際に WebSocket の接続が来た時、Uvicorn から呼ばれる。
fastapi/applications.py
class FastAPI(Starlette):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if self.root_path:
scope["root_path"] = self.root_path
await super().__call__(scope, receive, send) ← 親(Starlette)へ
starlette/applications.py
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
scope["app"] = self
if self.middleware_stack is None:
self.middleware_stack = self.build_middleware_stack()
await self.middleware_stack(scope, receive, send)
starlette/applications.py
class Starlette:
def build_middleware_stack(self) -> ASGIApp:
debug = self.debug
error_handler = None
exception_handlers: dict[Any, ExceptionHandler] = {}
middleware = (
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
+ self.user_middleware
+ [Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)]
)
app = self.router
starlette/applications.py
class Starlette:
def __init__(
self.debug = debug
self.state = State()
self.router = Router(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan)
starlette/routing.py
class Router:
def __init__(
self,
routes: Sequence[BaseRoute] | None = None,
redirect_slashes: bool = True,
default: ASGIApp | None = None,
on_startup: Sequence[Callable[[], Any]] | None = None,
on_shutdown: Sequence[Callable[[], Any]] | None = None,
async def __call__(self, scope: Scope, receive: Receive, send: Send) ->
await self.middleware_stack(scope, receive, send)
starlette/routing.py
class Router:
async def app(self, scope: Scope, receive: Receive, send: Send) -> None:
assert scope["type"] in ("http", "websocket", "lifespan")
if "router" not in scope:
scope["router"] = self
if scope["type"] == "lifespan":
await self.lifespan(scope, receive, send)
return
for route in self.routes:
match, child_scope = route.matches(scope)
if match == Match.FULL: # URL は /ws/battle か?タイプは websocket か?
scope.update(child_scope)
await route.handle(scope, receive, send) # WebSocketRouteへ遷移
return
starlette/routing.py
async def handle(self, scope: Scope, receive: Receive, send: Send) ->
~~~~
await self.app(scope, receive, send)
starlette/routing.py
class Router:
def __init__():
~~~
self.middleware_stack = self.app
if middleware:
for cls, args, kwargs in reversed(middleware):
self.middleware_stack = cls(self.middleware_stack, *args, **kwargs)
