Skip to content

16. Extra Models


1. Extra Models

  • 다음과 같은 이유로 사용자 모델의 경우 하나 이상의 관련 모델들이 있는 것이 일반적이다.


1] 입력 모델은 비밀번호를 가질 수 있어야 한다.

2] 출력 모델에는 비밀번호가 없어야 한다.

3] 데이터베이스 모델에는 해시된 비밀번호가 있어야 한다.


2. 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


1) About user_in.dict()

(1) Pydantic's .dict()

  • user_inUserIn 클래스의 Pydantic 모델이다.
  • Pydantic 모델은 데이터가 있는 dict를 반환하는 .dict() 메소드를 가지고 있다.


  • 예를 들어, 다음과 같이 Pydantic 객체인 user_in을 생성한다고 가정해 보자.


user_in = UserIn(username="john", password="secret", email="john.doe@example.com")


  • 위와 같이 생성된 user_in.dict() 메소드를 이용하여 다음과 같이 데이터가 있는 dict인 변수 user_dict를 생성할 수 있다.


user_dict = user_in.dict()


  • user_dict를 다음과 같이 호출할 수 있다.


print(user_dict)


  • 호출된 user_dict는 Python dict의 형태로 다음과 같이 반환된다.


{
    "username": "john",
    "password": "secret",
    "email": "john.doe@example.com",
    "full_name": None,
}


(2) Unwrapping a dict

  • user_dict와 같이 언래핑을 통해 다음과 같이 user_dict의 키와 값을 키-값 인수로 직접 전달할 수 있다.


UserInDB(user_dict)


  • 위의 코드는 다음의 코드와 같은 것이다.


UserInDB(
    username="john",
    password="secret",
    email="john.doe@example.com",
    full_name=None,
)


  • 또는 다음의 코드처럼 직접 user_dict의 항목에 접근하여 전달하는 것과 같은 것이다.


UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
)


(3) A Pydantic model from the contents of another

  • 위의 예제에서 user_in.dict()를 통해 user_dict를 생성했으며, 이를 다음과 같이 언래핑할 수 있다.


user_dict = user_in.dict()

UserInDB(user_dict)


  • 또는 다음과 같이 언래핑할 수 있다.


UserInDB(user_in.dict())


  • 위의 코드의 결과로 또 다른 Pydantic 모델을 얻게 된다.


(4) Unwrapping a dict and extra keywords

  • 다음과 같이 별도의 키워드 인수 hashed_password=hashed_password를 추가할 수 있다.


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,
)


3. Reduce duplication

  • 코드의 중복을 줄이는 것은 매우 중요하다.
  • 코드 중복으로 인해 버그, 보안 문제, 코드 비동기화 문제 등이 발생할 가능성이 증가한다.
  • 또한 많은 데이터를 생성해야 하며, 속성 이름과 타입을 복제해서 사용하게 되므로 복잡해진다.


  • 코드 중복을 줄이기 위해 다음과 같이 작성할 수 있다.


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


4. Union or anyOf

  • response를 두 가지 타입의 Union으로 선언할 수 있다.
  • 즉, response는 둘 중 하나가 된다는 것을 의미한다.


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]


Note

  • Union을 정의할 때는 가장 구체적인 타입을 먼저 포함하고, 덜 구체적인 타입을 그 다음에 포함한다.
  • 위의 예제의 Union[PlaneItem, CarItem]에서는 더 구체적인 PlaneItemCarItem보다 먼저 포함된다.


5. List of models

  • 위의 예제와 같은 방법으로 response를 객체들의 리스트로 선언할 수 있다.


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


6. Response with arbitrary dict

  • response를 Pydantic 모델을 사용하지 않고 키와 값의 타입만 선언하는 임의의 dict로 선언할 수 있다.
  • 이런 방식은 유효한 필드/속성 이름을 모르는 경우에 유용하다.


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}

References