FastAPI 공식문서 따라하기[17] - Extra Models
https://fastapi.tiangolo.com/tutorial/extra-models/ 공식문서 따라하는 글
☑️ Extra Models
쉬어가는 챕터로, 이전 예제에서 추가적인 개발을 하는 문서 같다
input model
은password
를 포함하고 있다.output model
은password
를 포함하면 안된다.database model
은hashed password
를 포함할 것이다.
☑️ Multiple models
모델을 설계할 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
여기서의 문제점은 password
를 그대로 가져오는것과 해시함수로 표현하지 않은것들인데, 예제에서는 복잡하지 않기 위해서 일단 그냥 넘기기로 했다.
하나 신기한 문법이 있다.
☑️ About **user_in.dict()
Pydantics’s .dict()
user_in
은 UserIn
라는 클래스의 Pydantic
모델이다.
이 모델은 .dict()
이라는 함수를 가지고 있는데, 모델을 dict
자료형으로 바꾸는 기능을 한다.
그렇기에
1
user_in = UserIn(username="kms", password="secret", email="minseokkang@gmail,com")
라고 생성된 Pydantic
object가 있다면 user_dict = user_in.dict()
을 통해서 dict
자료형으로 바꿀 수 있다.
1
2
3
4
5
6
7
#바뀐 자료형
{
'username': 'kms',
'password': 'secret',
'email': 'minseokkang@gmail,com',
'full_name': None,
}
이렇게 생성된 데이터를 함수에 인자를 통해 넘기고 싶으면 **user_dict
을 통해 넘길 수 있다. 이를 Python
에서는 “unwrap”이라고 한다. 그렇기에 위의 함수에서 처럼 UserInDB(**user_dict)
로 넘길 수 있다.
이는 다음과 동등한 결과라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
UserInDB(
username="kms",
password="secret",
email = 'minseokkang@gmail,com',
full_name=None,
)
또한
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
또한
UserInDB(**user_in.dict())
: user_in.dict()호출 -> `**`를 통한 "unwrap"
하지만 위의 예제에서는 두 번째 인자가 있다. 바로 hashed_password=hashed_password
인데,
위의 코드는 다음과 동일하다고 한다.
1
2
3
4
5
6
7
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
☑️ Reduce duplication
FastAPI
가 코드의 중복을 줄여준다고 한다.
코드가 중복되면 예상치 못한 버그가 발생할 수 있고, 보안적인 이슈나 코드 동기화 문제도 있을 수 있다.
위의 코드에는 데이터가 많고 속성값과 타입도 각 클래스마다 중복되는 것들이 있다.
UserBase
를 다른 클래스에서 상속받으면 해결이 될 것이다.
password
와 관련된 속성을 제외하면 모두 중복된 코드이므로 다음과 같이 작성할 수 있을 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
를 제외한 모든 클래스들은 UserBase
를 상속받고 있음을 알자.
☑️ Union
or anyOf
Response
모델에 Union
이란 키워드를 통해 두가지 타입을 지정할 수 있는데, 그 둘 중 하나의 클래스의 오브젝트타입으로 리턴한다는 것이다.
Union
을 사용할 때 첫번째 타입은, 두 번째 타입보다 좀 더 중요하고 명시하고 싶은 타입을 선언한다. 밑의 예제는PlaneItem
이CarItem
보다 더 강조하고 싶다는 뜻이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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": "planekms!!!!",
"size": 5,
},
}
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]
http://localhost:8000/items/item2
로 접근하면
1
2
3
4
5
{
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane2",
"size": 5
}
라는 결과를 얻을 수 있다.
참고로
Union[PlanItem, CarItem]
은repsonse_model = PlaneItem | CarItem
으로 바꿔 쓸 수 있다. 공식 문서에서는 이는 에러를 발생시킨다고 되어있지만.. 실제로 해보면 된다. 다음 내용은 사실 쓰지 말라는 내용인데, 나는 잘 되어서 그 부분에 대한 내용은 추가하지 않겠다.
☑️ List of models
respone_model = List[Item]
으로 선언이 가능하다는 얘기다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Father", "description": "There comes my hero"},
{"name": "Job", "description": "It's my Life"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
보아하면 반환타입은 Item
형태를 가진 List
다.
결과
1
2
3
4
5
6
7
8
9
10
[
{
"name": 123.4,
"description": "There comes my hero"
},
{
"name": 43.2,
"description": "It's my Life"
}
]
☑️ Response with arbitray dict
마찬가지로 dict
타입으로 반환객체를 만들어줄 수 있다.
Pydantic
모델과 달리 dict
모델은 클래스의 속성이름을 알 필요가 없다.
3.9 버전 이하에서는 from typing import Dict
을 입력해야한다고 한다.
1
2
3
4
5
6
7
8
9
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}
어려운 것은 없으므로 설명은 생략