중첩 모델
중첩 모델로 복잡한 본문 다루기 (Body - Nested Models)
FastAPI 덕분에 우리는 깊고 깊게 중첩된 모델을 얼마든지 정의하고, 검증하고, 문서화하고, 직접 사용할 수 있어요. 전부 Pydantic 덕분이죠. list, set, dict 같은 파이썬 표준 타입에 내부 타입을 담는 문법만 알면 반복 구조가 복잡한 JSON 본문도 쉽게 표현해요.
출처: https://fastapi.tiangolo.com/tutorial/body-nested-models/
본문
리스트 필드
모델 속성에 리스트 타입을 하나 붙여 볼게요. 그냥 list라고만 선언하면 요소의 타입은 정해지지 않아요.
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list = []
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, "item": item}
타입 파라미터와 함께 쓰는 리스트 필드
요소의 타입까지 정하고 싶다면 대괄호 [ ] 안에 내부 타입을 "타입 파라미터"로 넣어요. list[str]처럼요. 이건 그냥 표준 파이썬 문법이에요.
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
집합(Set) 타입
태그는 중복되지 않아야 하니까 중복을 허용하지 않는 set으로 바꿔 볼게요. 요청에 중복된 데이터가 와도 자동으로 고유한 값들의 집합으로 변환돼요. 데이터를 내보낼 때도 마찬가지로 중복 없이 나가요.
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
중첩 모델
Pydantic 모델의 각 속성은 타입을 하나씩 갖는데, 그 타입이 또 다른 Pydantic 모델일 수 있어요. 그래서 이름과 타입과 검증 규칙을 갖춘 JSON 객체를 아주 깊게 중첩해서 선언할 수 있죠. 아래처럼 Image 모델을 정의해 볼게요.
from fastapi import FastAPI
from pydantic import BaseModel
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
image 속성에 Image를 타입으로 쓰면 FastAPI는 이런 본문을 기대해요.
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
선언만 해도 중첩 모델까지 에디터 지원, 데이터 변환, 검증, 자동 문서화를 그대로 받을 수 있어요.
특별한 타입과 검증
str을 상속받는 더 복잡한 타입도 쓸 수 있어요. 예를 들어 Image의 url 필드를 평범한 str 대신 Pydantic의 HttpUrl 인스턴스로 선언하면, 값이 꼭 유효한 URL이어야 하고 문서에도 그렇게 표시돼요.
from pydantic import BaseModel, HttpUrl
class Image(BaseModel):
url: HttpUrl
name: str
서브모델의 리스트
Pydantic 모델을 또 list나 set의 내부 타입으로도 쓸 수 있어요. images 필드에 Image들의 리스트를 담으면, 요청 본문이 "이미지 객체들의 배열"을 받게 돼요.
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
깊게 중첩된 모델
이런 조합이면 마음껏 깊게 중첩할 수 있어요. 예를 들어 Offer가 Item들의 리스트를, 각 Item이 또 옵션으로 Image들의 리스트를 갖는 구조도 가능하죠.
순수 리스트 본문
최상위 값이 JSON 배열이라면 함수 파라미터 타입을 list[Image]처럼 선언해 주면 돼요. 그래도 에디터 지원과 변환, 검증을 그대로 받을 수 있어요.
임의의 dict 본문
키와 값을 다른 타입으로 갖는 dict로도 선언할 수 있어요. 이러면 Pydantic 모델처럼 필드 이름을 미리 알지 못해도 임의의 키를 받아들일 수 있죠. 예를 들어 int 키에 float 값을 요구하는 dict[int, float]로 선언하면, 그 형식만 맞으면 아무 dict나 받아요.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
참고로 JSON은 키로 문자열만 지원하지만, Pydantic이 자동 변환을 해 주기 때문에 클라이언트가 "순수한 정수"로만 된 문자열 키를 보내면 정수 키로 바꿔 검증·변환해 줘요. 결국 weights는 실제로 int 키와 float 값만 갖는 dict가 돼요.
더 알아보기
- 요청 본문의 기본기: Request Body
- 모델 필드에 예시를 넣기: Schema Extra / Example
- 추가 모델 다루기: Extra Models