프레임워크/FastAPI

[FastAPI] FastAPI [16] Extra Models

:) :) 2023. 5. 2. 16:53

https://fastapi.tiangolo.com/ko/tutorial/extra-models/

 

Extra Models - FastAPI

Warning The current page still doesn't have a translation for this language. But you can help translating it: Contributing. Continuing with the previous example, it will be common to have more than one related model. This is especially the case for user mo

fastapi.tiangolo.com

<FastAPI 공식문서 참조>

 

1. Extra Models

 이전 포스팅의 user model은, 아래와 같은 상황에서 일반적으로 사용된다.

  • input model에 비밀번호를 가지는 것을 허용해야 할 때
  • output model이 비밀번호를 가지면 안될 때
  • database model이 hashed password를 아마도 가질 필요가 있을 때

 

1-1. Multiple models

 다음은 model이 암호 필드와 이것이 사용되는 위치에서 어떻게 보이는지에 대한 일반적인 아이디어다.

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


class UserInDB(BaseModel):
    username: str
    hashed_password: str
    email: EmailStr
    full_name: str | None = 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-1-1. Pydantic's .dict()

 user_in 은 UserIn 이라는 클래스의 Pydantic model이다.

Pydantic models 은 .dict() 이라는 메소드를 가지고 있어 모델이 가지고 있는 데이터를 딕셔너리 형태로 리턴할 수 있다.

이는 다음과 같이 호출한다.

user_dict = user_in.dict()

 

이를 print 했을 때,

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

와 같은 결과를 볼 수 있다.

 

 

1-1-2. Unwrapping a dict

  user_dict와 같은 dict를 함수(혹은 클래스)에 전달하면, python은 이를 unwrap 하게 된다.

user_dict의 key와 value를 키-값 인자로 각각 직접 전달하게 된다.

 

따라서 아래의 코드는

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

 

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

 

 

2. Reduce duplication

 코드 중복을 제거하는 것은 FastAPI의 핵심 아이디어 중 하나다.

코드 중복의 증가는 버그의 발생, 보안 이슈, 코드 비동기화 이슈(불일치성) 등의 문제를 야기시킨다.

또한 이런 모델들(중복이 된)은 많은 데이터를 공유하고 많은 속성 이름과 데이터형을 중복해서 가지고 있다.

줄여보자. 깔끔하게 만들 수 있다.

 

우리가 쓸 다른 모델들의 base를 위해 UserBase라는 model을 선언할 수 있다. 그리고 이 모델의 속성을 상속하는 서브클래스를 만들 수 있다.

이러면 모든 데이터 형변환, 유효성 검사, 문서화 등등은 정상적으로 여전히 작동할 것이다.

 

이러한 방법으로 우리는 약간의 차이만 주어 다양한 모델을 손쉽게 만들 수 있다.

 

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserBase(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = 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

UserBase를 생성후, 상속을 이용하여 UserIn에는 password를 추가, UserOut은 password 없이, UserInDB에는 일반 password가 아닌 hashed_password를 attribute로 가지게 구성하였다.

 

3. Union or anyOf

 두 가지 자료형의 합인 Union을 반응의 한 형태로 선언할 수 있다.

Union의 의미를 잘 살펴보면 이 response type은 두 자료형 중 하나 아무거나로 될 수 있음을 의미한다.

이는 OpenAI에서 anyOf로 정의된다.

 

표준 파이썬 타입은 typing.Union이다.

아래 예시를 보자.

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]

 

3-1. Union in Python 3.10

 위 예제의 마지막 문단을 보면, Union[PlaneItem, CarItem] 을 response_model의 인자값으로 전달하였다.

지금까지는 type annotation을 사용하여 | (vertical bar)를 사용하였는데, 이러면 에러가 난다.

파이썬이 PlaneItem과 CarItem 사이에서 invaild operation error를 발생시키기 때문이다(type annotation으로 해석하는 과정에서).

그래서 Union 키워드를 써야 제대로 작동한다.

 

4. List of models

 객체의 list들의 response를 선언할 수 있다.

표준 파이썬 typing.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

 

5. Response with arbitrary dict

 Pydantic모델을 사용하지 않고, 보통의 임의 dictonary를 reponse type으로 선언할 수도 있다. key와 value의 자료형도 같이 선언해줘야 한다.

 

이는 유효한 field / attribute 이름을 모를 때 유용하다.

 

typing.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}

 

 

6. Recap

 다중 Pydantic model을 상속을 사용하여 각 예시에서 자유롭게 이용하자.

각 엔티티가 다른 상태를 나타내야만 한다 해도(password가 있거나 없거나 hashed_password를 사용했던 예시처럼) 단일 데이터 모델을 사용할 필요가 없다.

 

7. Reference

https://fastapi.tiangolo.com/ko/tutorial/extra-models/

 

Extra Models - FastAPI

Warning The current page still doesn't have a translation for this language. But you can help translating it: Contributing. Continuing with the previous example, it will be common to have more than one related model. This is especially the case for user mo

fastapi.tiangolo.com