본문 - 중첩 모델¶
FastAPI를 이용하면 (Pydantic 덕분에) 단독으로 깊이 중첩된 모델을 정의, 검증, 문서화하며 사용할 수 있습니다.
리스트 필드¶
어트리뷰트를 서브타입으로 정의할 수 있습니다. 예를 들어 파이썬 list
는:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
이는 tags
를 항목 리스트로 만듭니다. 각 항목의 타입을 선언하지 않더라도요.
타입 매개변수가 있는 리스트 필드¶
하지만 파이썬은 내부의 타입이나 "타입 매개변수"를 선언할 수 있는 특정 방법이 있습니다:
typing의 List
임포트¶
먼저, 파이썬 표준 typing
모듈에서 List
를 임포트합니다:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
타입 매개변수로 List
선언¶
list
, dict
, tuple
과 같은 타입 매개변수(내부 타입)를 갖는 타입을 선언하려면:
typing
모듈에서 임포트- 대괄호를 사용하여 "타입 매개변수"로 내부 타입 전달:
[
및]
from typing import List
my_list: List[str]
이 모든 것은 타입 선언을 위한 표준 파이썬 문법입니다.
내부 타입을 갖는 모델 어트리뷰트에 대해 동일한 표준 문법을 사용하세요.
마찬가지로 예제에서 tags
를 구체적으로 "문자열의 리스트"로 만들 수 있습니다:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
집합 타입¶
그런데 생각해보니 태그는 반복되면 안 돼고, 고유한(Unique) 문자열이어야 할 것 같습니다.
그리고 파이썬은 집합을 위한 특별한 데이터 타입 set
이 있습니다.
그렇다면 Set
을 임포트 하고 tags
를 str
의 set
으로 선언할 수 있습니다:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
덕분에 중복 데이터가 있는 요청을 수신하더라도 고유한 항목들의 집합으로 변환됩니다.
그리고 해당 데이터를 출력 할 때마다 소스에 중복이 있더라도 고유한 항목들의 집합으로 출력됩니다.
또한 그에 따라 주석이 생기고 문서화됩니다.
중첩 모델¶
Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다.
그런데 해당 타입 자체로 또다른 Pydantic 모델의 타입이 될 수 있습니다.
그러므로 특정한 어트리뷰트의 이름, 타입, 검증을 사용하여 깊게 중첩된 JSON "객체"를 선언할 수 있습니다.
모든 것이 단독으로 중첩됩니다.
서브모델 정의¶
예를 들어, Image
모델을 선언할 수 있습니다:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
서브모듈을 타입으로 사용¶
그리고 어트리뷰트의 타입으로 사용할 수 있습니다:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
이는 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"
}
}
다시 한번, FastAPI를 사용하여 해당 선언을 함으로써 얻는 것은:
- 중첩 모델도 편집기 지원(자동완성 등)
- 데이터 변환
- 데이터 검증
- 자동 문서화
특별한 타입과 검증¶
str
, int
, float
등과 같은 단일 타입과는 별개로, str
을 상속하는 더 복잡한 단일 타입을 사용할 수 있습니다.
모든 옵션을 보려면, Pydantic's exotic types 문서를 확인하세요. 다음 장에서 몇가지 예제를 볼 수 있습니다.
예를 들어 Image
모델 안에 url
필드를 str
대신 Pydantic의 HttpUrl
로 선언할 수 있습니다:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
이 문자열이 유효한 URL인지 검사하고 JSON 스키마/OpenAPI로 문서화 됩니다.
서브모델 리스트를 갖는 어트리뷰트¶
list
, set
등의 서브타입으로 Pydantic 모델을 사용할 수도 있습니다:
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
아래와 같은 JSON 본문으로 예상(변환, 검증, 문서화 등을)합니다:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
정보
images
키가 어떻게 이미지 객체 리스트를 갖는지 주목하세요.
깊게 중첩된 모델¶
단독으로 깊게 중첩된 모델을 정의할 수 있습니다:
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: List[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
정보
Offer
가 선택사항 Image
리스트를 차례로 갖는 Item
리스트를 어떻게 가지고 있는지 주목하세요
순수 리스트의 본문¶
예상되는 JSON 본문의 최상위 값이 JSON array
(파이썬 list
)면, Pydantic 모델에서와 마찬가지로 함수의 매개변수에서 타입을 선언할 수 있습니다:
images: List[Image]
이를 아래처럼:
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: List[Image]):
return images
어디서나 편집기 지원¶
그리고 어디서나 편집기 지원을 받을수 있습니다.
리스트 내부 항목의 경우에도:
Pydantic 모델 대신에 dict
를 직접 사용하여 작업할 경우, 이러한 편집기 지원을 받을수 없습니다.
하지만 수신한 딕셔너리가 자동으로 변환되고 출력도 자동으로 JSON으로 변환되므로 걱정할 필요는 없습니다.
단독 dict
의 본문¶
일부 타입의 키와 다른 타입의 값을 사용하여 dict
로 본문을 선언할 수 있습니다.
(Pydantic을 사용한 경우처럼) 유효한 필드/어트리뷰트 이름이 무엇인지 알 필요가 없습니다.
아직 모르는 키를 받으려는 경우 유용합니다.
다른 유용한 경우는 다른 타입의 키를 가질 때입니다. 예. int
.
여기서 그 경우를 볼 것입니다.
이 경우, float
값을 가진 int
키가 있는 모든 dict
를 받아들입니다:
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
return weights
팁
JSON은 오직 str
형 키만 지원한다는 것을 염두에 두세요.
하지만 Pydantic은 자동 데이터 변환이 있습니다.
즉, API 클라이언트가 문자열을 키로 보내더라도 해당 문자열이 순수한 정수를 포함하는한 Pydantic은 이를 변환하고 검증합니다.
그러므로 weights
로 받은 dict
는 실제로 int
키와 float
값을 가집니다.
요약¶
FastAPI를 사용하면 Pydantic 모델이 제공하는 최대 유연성을 확보하면서 코드를 간단하고 짧게, 그리고 우아하게 유지할 수 있습니다.
물론 아래의 이점도 있습니다:
- 편집기 지원 (자동완성이 어디서나!)
- 데이터 변환 (일명 파싱/직렬화)
- 데이터 검증
- 스키마 문서화
- 자동 문서