본문으로 건너뛰기
김신건의 로그

[FastAPI] Routing (Path Operations)

· 수정 · 📖 약 2분 · 745자/단어 #python #fastapi #web-framework #routing
FastAPI Routing, FastAPI Path Operations, FastAPI 라우팅, APIRouter, fastapi include_router, path parameter, query parameter, response_model

정의

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]}
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/meid=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_routerprefix 는 라우터 자체 prefix 와 결합. router = APIRouter(prefix="/users")app.include_router(router, prefix="/api/v1") 하면 최종 /api/v1/users/....

CAUTION

Path parameter 이름 중복 되면 뒤의 것이 앞을 덮음 (Python 함수 인자 규칙). 다른 이름 사용.

관련 위키

이 글의 용어 (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 코어로 빠…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기