지난 3편의 FastAPI 구조 살펴보기 글을 통해서 ASGI 서버가 어떻게 동작하고, FastAPI의 기반이 되는 Starlette가 요청을 어떻게 처리하는지 알아보았습니다. 이번에는 FastAPI가 Starlette를 어떻게 확장하여 추가적인 기능들을 제공하는지 간단하게 정리해보겠습니다.
지난 3편의 FastAPI 구조 살펴보기 글을 통해서 ASGI 서버가 어떻게 동작하고, FastAPI의 기반이 되는 Starlette가 요청을 어떻게 처리하는지 알아보았습니다. 이번 글에서는 FastAPI의 OpenAPI 기반 API 문서가 언제 생성되고, 어떤 구조로 동작하는지 코드를 기반으로 구체적으로 살펴보겠습니다.
GitDiagram
이라는 github repo를 빠르게 시각화하여 이해하는데 도움을 주는 도구가 있습니다.
위 그림은 GitDiagram로 확인한 FastAPI
의 Diagram 입니다.
구조 파악을 위해서 diagram 형식으로 보면 파악하기 좋은 것 같아서 가져와 보았습니다.
FastAPI 구조 살펴보기 1에서 설명드렸던 것 처럼 FastAPI는 ASGI 표준 위에서 동작합니다.
Diagram을 확인하여 보면 대표적인 ASGI 서버인 Uvicorn이 요청을 받고 FastAPI 애플리케이션에 전달함을 확인할 수 있습니다.
그리고 diagram에 표시되어 있지는 않지만, FastAPI 구조 살펴보기 2, 3에서 설명드렸던 것 처럼 Uvicorn이 전달하는 FastAPI Application은 Starlette 클래스를 상속받아 생성되어 있습니다.
API Documentation은 Documentation 단락에서 OpenAPI Schema와 Swagger UI, ReDoc을 사용함을 파악할 수 있습니다.
이제 Swagger UI, ReDoc 같은 기능이 어디서 어떻게 생성되는지 하나씩 짚어보겠습니다.
FastAPI 인스턴스를 생성할 때, 내부적으로 자동으로 문서 생성 경로를 설정합니다. 다음은 FastAPI의 생성자 내부를 살펴본 코드입니다.
fastapi/application.py : __init__
class FastAPI(Starlette):
def __init__(
self,
...,
openapi_url="/openapi.json", #L205 ~ 227
docs_url="/docs", #L399 ~ 422
redoc_url="/redoc", #L423 ~ 446
...) -> None:
...
self.openapi_url = openapi_url #L831
self.docs_url = docs_url #L834
self.redoc_url = redoc_url #L835
...
self.setup()
openapi_url
: OpenAPI JSON 정의를 제공하는 경로 (/openapi.json
)docs_url
: Swagger UI 경로 (/docs
)redoc_url
: ReDoc 경로 (/redoc
)즉, 앱 인스턴스를 만드는 시점에 문서 경로들이 이미 등록되고 있습니다.
문서 관련 경로들을 실제로 추가하는 코드는 setup
함수 안에 있습니다.
fastapi/application.py : setup()
def setup(self) -> None:
...
if self.openapi_url:
self.add_route(self.openapi_url, openapi, include_in_schema=False)
if self.openapi_url and self.docs_url:
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
if self.openapi_url and self.redoc_url:
self.add_route(self.redoc_url, redoc_html, include_in_schema=False)
/openapi.json
→ self.openapi_json
핸들러/docs
→ Swagger UI HTML/redoc
→ ReDoc HTML이처럼 FastAPI는 문서 관련 핸들러를 기본 라우터에 등록하여, 별도 설정 없이도 자동으로 문서가 제공됩니다.
get_openapi
FastAPI의 OpenAPI 스펙(JSON)은 다음 함수에서 동적으로 생성됩니다.
fastapi/openapi/utils.py : get_openapi()
def get_openapi(
title: str,
version: str,
routes: Sequence[BaseRoute],
... # 기타 설정들
) -> Dict[str, Any]:
...
info: Dict[str, Any] = {"title": title, "version": version}
output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
...
for route in routes or []:
if isinstance(route, routing.APIRoute):
result = get_openapi_path(
route=route,
operation_ids=operation_ids,
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
)
if result:
path, security_schemes, path_definitions = result
if path:
paths.setdefault(route.path_format, {}).update(path)
if security_schemes:
components.setdefault("securitySchemes", {}).update(
security_schemes
)
if path_definitions:
definitions.update(path_definitions)
APIRoute
를 탐색하여 OpenAPI의 paths
에 자동으로 추가합니다.response_model
, status_code
, dependencies
, summary
등도 이 과정에서 함께 문서화됩니다.결과적으로 get_openapi()
는 FastAPI 라우터에서 정의된 모든 정보를 수집해 OpenAPI 표준 JSON으로 변환합니다.
Swagger와 ReDoc은 각각 swagger_ui_html
, redoc_html
메서드를 통해 HTML이 제공됩니다.
fastapi/application.py : swagger_ui_html()
async def swagger_ui_html(req: Request) -> HTMLResponse:
return get_swagger_ui_html(
openapi_url=openapi_url,
title=f"{self.title} - Swagger UI",
...
)
fastapi/openapi/utils.py : get_swagger_ui_html()
def get_swagger_ui_html(...) -> HTMLResponse:
html = f"""
<!DOCTYPE html>
<html>
<head>
...
<<script src="{swagger_js_url}"></script>
...
</head>
<body>
<div id="swagger-ui"></div>
<script>
const ui = SwaggerUIBundle({{
url: "{openapi_url}",
...
}})
</script>
</body>
</html>
"""
return HTMLResponse(html)
이 코드를 통해 FastAPI는 Swagger UI와 ReDoc의 CDN을 포함한 HTML을 동적으로 렌더링하여 /docs
, /redoc
에서 제공하게 됩니다.
물론 가능합니다. FastAPI의 생성자에서 다음과 같은 인자들을 조정할 수 있습니다:
app = FastAPI(
docs_url="/swagger",
redoc_url=None,
openapi_url="/custom-openapi.json"
)
이렇게 하면:
/swagger
에서 접근 가능/custom-openapi.json
에서 제공또한, app.openapi_schema
에 접근하거나 app.openapi = custom_openapi
로 오버라이딩하면 문서 스펙 자체도 직접 커스터마이징할 수 있습니다.
이번 글에서는 FastAPI가 API 문서를 어떻게 처리하고 자동으로 생성하는지를 GitHub 코드를 바탕으로 살펴보았습니다. 정리하면 다음과 같습니다.
/docs
, /redoc
, /openapi.json
은 기본 경로로 설정됨get_openapi()
함수가 핵심 로직오늘은 아래 글을 참고하여 작성했습니다: