본문 바로가기
Fast API/fastapi배우기

Fast API 배우기 12부 - Extra Model

by 붕어사랑 티스토리 2021. 11. 1.
반응형

앞선 예제에서 보았듯이 서로 연관된 여러 모델을 사용하는 경우가 많다.

 

아래의 경우가 대표적인 케이스이다

 

  • 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}
반응형

댓글