앞선 예제에서 보았듯이 서로 연관된 여러 모델을 사용하는 경우가 많다.
아래의 경우가 대표적인 케이스이다
- input model은 패스워드가 필요하다
- output model은 패스워드를 포함하면 안된다.
- database model은 hash로 된 패스워드가 필요하다
위 케이스를 어떻게 해결하면 좋을까?
1. Multiple Models
가장 기초적인 해결방법이다. 각 케이스마다 모델을 따로 작성한다.
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: Optional[str] = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
하지만 당연 비효율적으로 보인다.
**user_in.dict()
해결법을 배우기전에 Pydantic의 dict() 함수에 대해 배우고 가자
dict()함수는 모델을 사전형으로 형변환 해주어 리턴해주는 함수이다.
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
user_dict = user_in.dict()
print(user_dict)
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
dict unwrapping 하기
만약에 dict를 함수에 **user_dict 형태로 전달한다면, 파이썬은 이를 unwrap한뒤 key-value argument로 전달한다
UserInDB(**user_dict)
위 코드는 아래와 동일하다.
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
좀더 정확히 적다면 아래의 코드를 풀어서 쓴게 위에 최종결과물이다.
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
아래와 같이 extra keywords도 추가할 수 있다.
UserInDB(**user_in.dict(), hashed_password=hashed_password)
이는 아래와 같다.
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
2. Reduce duplication, 중복 제거
자 이제 처음에 얘기했던 문제에 대해 다시 돌아가자. 서로 연관된 모델을 처리하기 위해 모든 모델에 대한 모델을 작성하였다.
허나 중복된 내용이 많아 분명 비효율적이다. 그리고 각기 모델을 따로 설정하면 코드관리의 어려움과 버그에 대한 문제소지도 있다.
중복된 내용을 제거하는 방법은 간단하다. 상속을 이용하는 것이다.
중복되는 내용만 따로 모아 UserBase라는 모델을 만든다. 그리고 이를 상속하여 연관된 모델을 생성한다.
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
Union
만약 reponse model이 여러모델중 하나만 되도 상관없다면 Union 키워드를 이용한다.
(미래애 anyOf 키워드로 바뀐다 한다.)
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type = "car"
class PlaneItem(BaseItem):
type = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car"},
"item2": {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
},
}
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]
response_model=Union[PlaneItem, CarItem]
List, dict of Models
앞서배운 다른 파라미터들과 같은 방법으로 reponse model도 list와 dict 값을 가질 수 있다.
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=Dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
'Fast API > fastapi배우기' 카테고리의 다른 글
Fast API 배우기 14부 - Form Data (0) | 2021.11.02 |
---|---|
Fast API 배우기 13부 - Response Status Code (0) | 2021.11.01 |
Fast API 배우기 11부 - Response Model (0) | 2021.11.01 |
Fast API 배우기 10부 - Cookie 파라미터, Header 파라미터 (1) | 2021.11.01 |
Fast API 배우기 9부 - Extra Data Types (0) | 2021.11.01 |
댓글