[FastAPI] Routing (Path Operations)
정의
FastAPI Routing 은 HTTP method + URL 을 Python 함수 (path operation) 에 매핑하는 시스템입니다. Starlette 의 라우팅 위에 type hint 기반 파라미터 파싱, 자동 검증, 문서화, 응답 직렬화 를 얹었습니다.
기본 데코레이터
@app.get("/items/")
@app.post("/items/")
@app.put("/items/{id}")
@app.patch("/items/{id}")
@app.delete("/items/{id}")
@app.head("/items/{id}")
@app.options("/items/")
@app.trace("/items/")
각 데코레이터의 인자:
@app.get(
"/items/{id}",
response_model=Item, # 응답 스키마
status_code=200,
tags=["items"],
summary="아이템 조회",
description="ID 로 아이템을 가져옵니다.",
response_description="아이템 상세",
responses={
404: {"description": "Not Found"},
},
deprecated=False,
include_in_schema=True,
)
Path Parameter
경로에 중괄호 {name} 로 선언, 함수 인자로 받음.
@app.get("/users/{user_id}")
def get_user(user_id: int): # int 로 자동 파싱 + 검증
return {"id": user_id}
user_id: int 이므로 /users/abc 는 자동으로 422 (Unprocessable Entity) 반환.
Path 순서
경로 매칭은 선언 순서 로 수행. 더 구체적인 경로를 먼저:
@app.get("/users/me") # 이 순서로 선언
def get_current_user():
...
@app.get("/users/{user_id}") # {user_id} = "me" 로 잡히지 않도록
def get_user(user_id: int):
...
경로 컨버터
Starlette 의 컨버터 사용:
@app.get("/files/{file_path:path}") # 슬래시 포함
def read_file(file_path: str):
return {"file_path": file_path}
:path 는 나머지 전체를 매칭. /files/home/user/x.txt 에서 file_path = "home/user/x.txt".
Enum 컨버터
from enum import Enum
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
@app.get("/models/{name}")
def get_model(name: ModelName):
...
값이 enum 에 없으면 422 반환.
Query Parameter
함수 인자 중 path 에 없는 것 은 자동으로 query.
@app.get("/items/")
def list_items(
skip: int = 0,
limit: int = 10,
q: str | None = None,
):
...
기본값 있으면 optional, 없으면 required.
Query 클래스로 상세 지정
from fastapi import Query
@app.get("/items/")
def list_items(
q: str | None = Query(
None,
min_length=3,
max_length=50,
pattern=r"^[a-zA-Z0-9]+$",
title="Query string",
description="검색어",
alias="search", # 실제 query key = search
deprecated=False,
include_in_schema=True,
)
):
return {"q": q}
List query parameter
@app.get("/items/")
def list_items(tags: list[str] = Query([])):
return {"tags": tags}
URL: /items/?tags=a&tags=b&tags=c -> tags=["a","b","c"].
Request Body
Pydantic BaseModel 로 선언.
from pydantic import BaseModel
class ItemCreate(BaseModel):
name: str
price: float
tax: float | None = None
@app.post("/items/")
def create_item(item: ItemCreate):
return item
Body 는 JSON 파싱 후 Pydantic 검증. 실패 시 422.
여러 body 파라미터
class User(BaseModel): ...
class Item(BaseModel): ...
@app.post("/items/")
def create_item(user: User, item: Item):
...
Body 는 {"user": {...}, "item": {...}} 형태.
단일 필드 body: Body() 로 명시:
from fastapi import Body
@app.post("/items/")
def create_item(importance: int = Body(...)):
...
Form 데이터
from fastapi import Form
@app.post("/login/")
def login(username: str = Form(...), password: str = Form(...)):
...
multipart/form-data 를 파싱하려면 python-multipart 설치 필요.
파일 업로드
from fastapi import File, UploadFile
@app.post("/uploadfile/")
def upload_file(file: UploadFile = File(...)):
return {"filename": file.filename, "content_type": file.content_type}
UploadFile 은 SpooledTemporaryFile (메모리 소량 후 디스크). 큰 파일에 유리.
여러 파일:
@app.post("/uploadfiles/")
def upload_files(files: list[UploadFile] = File(...)):
return {"filenames": [f.filename for f in files]}
Header, Cookie
from fastapi import Header, Cookie
@app.get("/items/")
def read_items(
user_agent: str | None = Header(None),
x_token: str = Header(...),
session_id: str | None = Cookie(None),
):
...
Header 이름은 자동으로 _ -> - 변환. user_agent -> User-Agent.
Response Model
response_model 은 응답 스키마 + 필터링.
class UserIn(BaseModel):
username: str
password: str # 요청에만
email: str
class UserOut(BaseModel):
username: str
email: str
@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
return user # password 필드는 자동으로 응답에서 제거
response_model_exclude / include
@app.get(
"/items/{id}",
response_model=Item,
response_model_exclude={"password", "internal_field"},
)
response_model_exclude_unset / _none / _defaults
exclude_unset=True: 요청에 명시된 필드만 응답exclude_none=True: None 값 필드 제거exclude_defaults=True: 기본값과 같은 필드 제거
Status Code
from fastapi import status
@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(item: Item):
...
@app.delete("/items/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(id: int):
... # 204 는 body 없음, return 값 무시
APIRouter
여러 endpoint 를 모듈로 분할.
# routers/users.py
from fastapi import APIRouter, Depends
router = APIRouter(
prefix="/users",
tags=["users"],
dependencies=[Depends(verify_token)], # 라우터 전체에 DI 적용
responses={404: {"description": "Not found"}},
)
@router.get("/{id}")
def read_user(id: int):
...
@router.post("/")
def create_user(user: UserCreate):
...
# main.py
from fastapi import FastAPI
from myapp.routers import users, items
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router, prefix="/api/v1")
응답 변형
JSONResponse (기본)
from fastapi.responses import JSONResponse
@app.get("/items/{id}")
def read_item(id: int):
return JSONResponse({"id": id}, status_code=200)
HTMLResponse, PlainTextResponse
from fastapi.responses import HTMLResponse
@app.get("/", response_class=HTMLResponse)
def read_root():
return "<h1>Hello</h1>"
StreamingResponse
from fastapi.responses import StreamingResponse
@app.get("/large-file")
def large_file():
def iterfile():
with open("/path/to/large.bin", "rb") as f:
yield from f
return StreamingResponse(iterfile(), media_type="application/octet-stream")
FileResponse
from fastapi.responses import FileResponse
@app.get("/download")
def download():
return FileResponse("/path/to/file.pdf", filename="report.pdf")
RedirectResponse
from fastapi.responses import RedirectResponse
@app.get("/old")
def old_endpoint():
return RedirectResponse("/new")
Response Headers
from fastapi import Response
@app.get("/items/{id}")
def read_item(id: int, response: Response):
response.headers["X-Custom"] = "value"
response.set_cookie(key="session", value="abc")
return {"id": id}
Path Operation 데코레이터 순서
여러 데코레이터를 겹치면 아래부터 위 순서로 적용:
@app.get("/items/", tags=["items"])
@some_other_decorator # 데코레이터 순서 주의
def read_items():
...
FastAPI 데코레이터는 라우팅에 대한 등록이므로 다른 데코레이터 (예: 캐시) 는 그 아래에 위치해야 등록에 적용됨.
OpenAPI 커스터마이제이션
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
schema = get_openapi(
title="My API",
version="1.0.0",
description="...",
routes=app.routes,
)
schema["info"]["x-logo"] = {"url": "https://.../logo.png"}
app.openapi_schema = schema
return schema
app.openapi = custom_openapi
함정
WARNING
Path 순서. /users/{id} 가 /users/me 보다 먼저 선언되면 /users/me 가 id=me 로 파싱되어 422.
CAUTION
Trailing slash. FastAPI 는 /items 와 /items/ 를 다른 경로로 취급. Starlette 의 redirect_slashes=True (기본) 로 307 리디렉트되지만, 프로덕션에서는 명시적으로 하나만 사용.
WARNING
response_model 과 return type hint 는 다를 수 있음. return type hint 는 Python 검사용, response_model 은 FastAPI 필터/문서화용. 일관되게 유지하려면 둘 다 지정.
IMPORTANT
include_router 의 prefix 는 라우터 자체 prefix 와 결합. router = APIRouter(prefix="/users") 에 app.include_router(router, prefix="/api/v1") 하면 최종 /api/v1/users/....
CAUTION
Path parameter 이름 중복 되면 뒤의 것이 앞을 덮음 (Python 함수 인자 규칙). 다른 이름 사용.
관련 위키
- FastAPI - 상위 개요
- FastAPI + Pydantic - Body validation
- FastAPI DI - 라우터 dependencies
- FastAPI Middleware - 요청/응답 훅
- FastAPI Async - async endpoint
- Pydantic - 모델 정의
이 글의 용어 (6개)
- [FastAPI] Async / Sync Endpointsfastapi
- 정의 FastAPI 는 ASGI 프레임워크 이므로 endpoint 를 로 정의해 이벤트 루프에서 실행하거나, 로 정의해 스레드풀에서 실행할 수 있습니다. 어느 쪽을 선택하는지가 …
- [FastAPI] Dependency Injectionfastapi
- 정의 FastAPI Dependency Injection 은 로 함수를 endpoint 에 자동 주입하는 시스템입니다. DB 세션, 인증 사용자, 설정, 서비스 계층 등을 end…
- [FastAPI] Middlewarefastapi
- 정의 FastAPI Middleware 는 모든 요청과 응답을 가로채 로깅, 인증, 압축, CORS 등 공통 처리를 하는 계층입니다. Starlette 의 ASGI middlew…
- [FastAPI] Pydantic Integrationfastapi
- 정의 FastAPI + Pydantic 은 요청/응답의 타입 검증, 직렬화, JSON Schema 생성 을 위한 통합입니다. Pydantic v2 는 Rust 코어 ( ) 로 v…
- [Python] FastAPIfastapi
- 정의 FastAPI 는 Python 3.8+ 을 위한 ASGI 기반 현대 웹/API 프레임워크 입니다. Sebastián Ramírez (tiangolo) 가 2018년 발표했고…
- [Python] pydantic: 런타임 타입 검증과 직렬화python
- 정의 은 타입 힌트 기반 런타임 검증 + 직렬화 라이브러리. FastAPI, LangChain, Hugging Face 등 수많은 라이브러리의 기반. v2부터 Rust 코어로 빠…
💬 댓글