10. Body - Nested Models
1. Body - Nested Models
- FastAPI를 사용하면 임의로 nested 모델을 정의, 검증 및 문서화할 수 있다.
2. List fields
- 속성을 하위 타입으로 정의할 수 있다.
- 예를 들어, 다음과 같이 Python
list
를 작성할 수 있다.
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = 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
는 항목들이 존재하는 리스트로서 사용될 수 있다.
3. List fields with type parameter
- 하지만 위의 예제와 같이 작성하면, 타입에 대한 모호성이 발생할 수 있다.
- 이를 해결하기 위해 Python에는 "내부 타입" 또는 "타입 매개변수"가 있는 리스트를 선언하는 방법을 사용한다.
1) Import typing's List
- 먼저, 표준 Python
typing
모듈의List
를 임포트한다.
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = 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
2) Declare a List
with a type parameter
list
,dict
,tuple
과 같은 내부 타입을 포함하는 타입은 다음과 같이 선언한다.
1] typing
모듈에서 임포트한다.
2] 대괄호 [
와 ]
를 사용하여 내부 타입으로 전달한다.
- 다음과 같이 작성할 수 있다.
- 위의 예제처럼 하는 것이 타입 선언에 대한 표준 Python 구문이다.
- 내부 타입이 있는 모델 속성에 대해 동일하게 사용하면 된다.
- 따라서 다음과 같이 문자열 타입에 대한 리스트
tags
를 만들 수 있다.
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = 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
4. Set types
tags
라는 것이 가지고 있는 의미를 생각해보면,tags
안에 있는 항목들은 중복되어서는 안된다.- 그러므로
List
가 아니라 중복되지 않는 집합인Set
을 사용해야 한다.
- 다음과 같이
Set
을 임포트한 후tags
를str
의 집합으로 선언할 수 있다.
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = 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
- 위의 예제와 같이 작성하면, 중복 데이터가 포함된 request를 수신하더라도 중복되지 않는 집합으로 변환된다.
- 이와 마찬가지로 해당 데이터를 출력할 때마다 소스가 중복되더라도 고유한 항목의 집합으로 출력된다.
5. Nested Models
- Pydantic 모델의 각 속성은 타입을 가지고 있다.
- 하지만 그 타입 자체가 또 다른 Pydantic 모델이 될 수 있다.
- 따라서 특정 속성 이름, 타입 및 유효성 검사를 사용하여 nested JSON "객체"를 선언할 수 있다.
1) Define a submodel
Image
모델을 정의해 보자.
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
image: Optional[Image] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
2) Use the submodel as a type
- 그런 다음 속성의 타입으로 사용할 수 있다.
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
image: Optional[Image] = 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는 다음과 같은 body를 예상하게 된다.
{
"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"
}
}
6. Special types and validation
str
,int
,float
등과 같은 일반적인 단일 타입과는 별도로str
에서 상속되는 더 복잡한 단일 타입을 사용할 수 있다.
- 예를 들어, 다음과 같이
Image
모델에서url
필드의str
타입 대신 Pydantic의HttpUrl
타입으로 선언할 수 있다.
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
image: Optional[Image] = 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인지 확인하게 된다.
7. Attributes with lists of submodels
- Pydantic 모델을
list
,set
등의 하위 타입으로 사용할 수도 있다.
from typing import Optional, Set, List
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
images: Optional[List[Image]] = 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는 다음과 같은 body를 예상하게 된다.
{
"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"
}
]
}
8. Deeply nested models
- deeply nested 모델을 정의할 수도 있다.
from typing import Optional, Set, List
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
images: Optional[List[Image]] = None
class Offer(BaseModel):
name: str
description: Optional[str] = None
price: float
items: List[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
9. Bodies of pure lists
- 예상하는 JSON body의 최상위 값이 JSON
array
(Pythonlist
)인 경우 Pydantic 모델에서와 같이 함수의 매개변수에서 타입을 선언할 수 있다.
- 다음과 같이 작성할 수 있다.
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
10. Bodies of arbitrary dict
s
- 특정 타입의 키와 특정 타입의 값을 사용하여 body를
dict
로 선언할 수 있다. - 이런 경우 유효한 필드/속성 이름이 무엇인지 미리 알 필요 없거나 아직 모르는 키를 수신하려는 경우에 유용하다.
- 다음의 예제는
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
Note
- 원래 JSON은 키의 타입으로
str
타입만 지원한다. - 하지만 Pydantic의 자동 데이터 변환으로 인해, 클라이언트가 보낸 키에
str
타입이 아닌 타입이 포함되어 있으면 Pydantic이 자동으로 변환하고 유효성을 검사한다.