데이터 검증
FastAPI는 데이터 검증을 알아서 해준다. 정확히는 pydantic 라이브러리가 해준다. 지금까지는 타입 검증이나 필수 값이냐 아니냐만을 봤지만 좀 더 세밀한 제어를 할 수 있다.
Path, Query 함수
함수를 정의할 때 각 매개변수가 경로 매개변수인지 아니면 쿼리 매개변수 인지 명시하지 않아도 FastAPI가 알아서 인지했다. 하지만 Path(), Query() 함수를 이용하면 매개변수를 명시적으로 정의할 수 있고, 다양한 옵션을 추가할 수 있다.
Path(…)은 필수 값이다라는 표현이고, Query(None)은 선택값이다라는 표현이다. Path(…, gt=0)은 user_id가 0보다 큰 값이어야 한다는 뜻이다. title과 description은 데이터를 표현하는 옵션은 아니고 문서에서 어떻게 보여지는지 표현하는 파라미터 이다. Query(min_length=1, max_length=2) 는 name이 최소 1글자 최대 2글자 여야 한다는 뜻이다.
이제 user의 인벤토리에서 특정 아이템을 꺼내오는 앱을 만들어보자.
from typing import List
from fastapi import FastAPI, Query, Path
from pydantic import BaseModel, parse_obj_as
app = FastAPI()
inventory = (
{
"id": 1,
"user_id": 1,
"name": "레전드포션",
"price": 2500.0,
"amount": 100,
},
{
"id": 2,
"user_id": 1,
"name": "포션",
"price": 300.0,
"amount": 50,
},
)
class Item(BaseModel):
name: str
price: float
amount: int = 0
@app.get("/users/{user_id}/inventory", response_model=List[Item])
def get_item(
user_id: int = Path(..., gt=0, title="사용자 id", description="DB의 user.id"),
name: str = Query(None, min_length=1, max_length=2, title="아이템 이름"),
):
user_items = []
for item in inventory:
if item["user_id"] == user_id:
user_items.append(item)
response = []
for item in user_items:
if name is None:
response = user_items
break
if item["name"] == name:
response.append(item)
return response
swagger에서 보면 user_id의 description을 확인할 수 있고, 지정했던 옵션들이 표현되는 것을 볼 수 있다.
터미널에서도 잘 실행되는 것을 볼 수 있다.
(fastapi) PS D:\workspace> http :8000/users/1/inventory?name=레전드포션
HTTP/1.1 422 Unprocessable Entity
content-length: 152
content-type: application/json
date: Sat, 13 Aug 2022 11:13:39 GMT
server: uvicorn
{
"detail": [
{
"ctx": {
"limit_value": 2
},
"loc": [
"query",
"name"
],
"msg": "ensure this value has at most 2 characters",
"type": "value_error.any_str.max_length"
}
]
}
레전드포션 name은 max_length인 2 보다 크기 때문에 에러를 뱉어준다.
pydantic 클래스에서 데이터 정의
위에서는 앱의 함수의 데이터 검증을 정의해 줬지만 json 바디를 넘겨주는 경우에는 pydantic의 field를 사용하면 된다.
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100, title="이름")
price: float = Field(None, ge=0)
amount: int = Field(default=1, gt=0, le=100, title="수량", description="아이템 갯수. 1~100개 소지 가능")
@app.post("/users/{user_id}/item")
def create_item(item: Item):
return item