출시·고도화 중
FastAPI 안내서 · 3/6
FastAPI의 거의 모든 기능은 "타입 힌트로 선언하면 프레임워크가 처리한다"는 한 가지 생각에서 나옵니다. 이 장에서는 경로 작업, 경로 · 쿼리 · 본문 매개변수, Pydantic 모델과 검증, 의존성 주입, async def와 def의 차이, OpenAPI 문서를 차례로 살펴봅니다.
HTTP 메서드와 경로의 조합을 FastAPI에서는 경로 작업(path operation)이라고 부릅니다. @app.get, @app.post, @app.put, @app.patch, @app.delete 데코레이터로 선언하며, 응답 상태 코드와 응답 모델도 이 자리에서 지정합니다.
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
items: dict[int, str] = {1: "Keyboard"}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
del items[item_id]HTTPException을 발생시키면 그 자리에서 처리가 멈추고 지정한 상태 코드와 {"detail": ...} 형태의 JSON이 응답됩니다. 경로는 선언한 순서대로 비교되므로, /users/me 같은 고정 경로는 /users/{user_id}보다 먼저 선언해야 합니다.
함수 매개변수가 경로의 {이름}과 같으면 경로 매개변수, 그렇지 않은 단순 타입이면 쿼리 매개변수가 됩니다. 추가 검증 규칙은 Annotated와 Path, Query로 붙입니다.
from typing import Annotated
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/users/{user_id}/items")
def list_user_items(
user_id: Annotated[int, Path(ge=1)],
q: Annotated[str | None, Query(max_length=50)] = None,
skip: int = 0,
limit: Annotated[int, Query(le=100)] = 10,
):
return {"user_id": user_id, "q": q, "skip": skip, "limit": limit}기본값이 없으면 필수, 있으면 선택 매개변수입니다. ge, le, min_length, pattern 같은 제약은 검증에 쓰이는 동시에 OpenAPI 문서에도 그대로 나타납니다.
JSON 본문은 Pydantic의 BaseModel을 상속한 클래스로 선언합니다. 매개변수 타입이 Pydantic 모델이면 FastAPI는 이를 요청 본문으로 읽습니다.
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class ItemCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
price: float = Field(gt=0)
tags: list[str] = []
class ItemPublic(BaseModel):
id: int
name: str
price: float
@app.post("/items", response_model=ItemPublic, status_code=201)
def create_item(item: ItemCreate):
return ItemPublic(id=1, **item.model_dump())본문이 JSON이 아니거나 price가 음수이면 422 응답이 자동으로 돌아갑니다. response_model은 응답을 그 모델 모양으로 걸러 주므로, 입력 모델에만 있는 비밀번호 같은 필드가 응답에 섞여 나가지 않게 막는 데 유용합니다. 반환 타입 표기(-> ItemPublic)로 같은 효과를 낼 수도 있습니다.
Depends는 FastAPI의 의존성 주입 장치입니다. 함수(또는 클래스)를 의존성으로 선언하면 FastAPI가 요청마다 그것을 호출하고, 그 결과를 경로 작업 함수에 넘깁니다. 의존성도 경로 · 쿼리 매개변수를 가질 수 있고, 다른 의존성에 의존할 수도 있습니다.
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def pagination(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
Pagination = Annotated[dict, Depends(pagination)]
@app.get("/items")
def list_items(page: Pagination):
return {"page": page}
@app.get("/users")
def list_users(page: Pagination):
return {"page": page}Annotated로 타입 별칭을 만들어 두면 여러 경로에서 같은 의존성을 짧게 재사용할 수 있습니다. yield를 쓰는 의존성은 응답 뒤에 정리 코드를 실행할 수 있어서 데이터베이스 세션 관리에 많이 쓰입니다(4장). 인증, 권한 확인, 공통 매개변수 처리도 대부분 의존성으로 구현합니다.
경로 작업 함수는 async def와 일반 def 모두 쓸 수 있으며, FastAPI가 두 경우를 다르게 실행합니다.
| 선언 | 실행 방식 | 알맞은 경우 |
|---|---|---|
async def | 이벤트 루프에서 직접 실행 | await할 수 있는 비동기 라이브러리(HTTPX, asyncpg 등)를 쓸 때 |
def | 별도 스레드 풀에서 실행 | 동기 라이브러리(일반 DB 드라이버, 파일 처리 등)를 쓸 때 |
주의할 점은 async def 안에서 time.sleep()이나 동기 DB 호출처럼 막히는 코드를 부르면 이벤트 루프 전체가 멈춘다는 것입니다. 잘 모르겠다면 def로 선언하는 편이 안전합니다.
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/weather")
async def weather():
async with httpx.AsyncClient() as client:
response = await client.get("https://example.com/api/weather")
return response.json()FastAPI는 위의 모든 선언을 모아 OpenAPI 스키마를 만듭니다. summary, description, tags, deprecated 같은 인자로 문서를 더 친절하게 만들 수 있고, 함수의 독스트링도 설명으로 쓰입니다. 앱의 제목과 버전은 FastAPI(title=..., version=...)로 정합니다.
HTTPException으로 돌려줍니다.Annotated, Path, Query로 검증합니다.response_model로 걸러 냅니다.Depends로 분리하고, 막히는 코드는 def 함수에 둡니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.