FastAPI 공식문서 따라하기[16] - Response Model
https://fastapi.tiangolo.com/tutorial/response-model/ 공식문서 따라하는 글
☑️ Response Model
Response
에 model
을 담아 리턴할 수 있다 어떤 요청에 대해서도 가능하다.(@app.post()
, @app.get()
, @app.put()
, @app.delete()
…)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item
코드를 보면, Item
이라는 객체를 보내면 이를 받아 그대로 리턴해버린다.
response_model
에 리턴할 자료형을 입력해주었다.
SwaggerUI
에서도 확인이 가능하지만, 처음인만큼 뭔가 Postman
으로 시험해보았다.
그대로 리턴해준다.
공식문서에서 주의해야한다고 지적한 점은 위치이다.
response_model
은 인자에서 선언한 것이 아닌,decorator
에서 선언했다.
- 리스트로도 받을 수 있다고 한다. ex)
List[Item]
response_model
을 사용함으로써 다음과 같은 이점을 얻을 수 있다고 한다.
- 출력 데이터를 선언한 타입으로 변환할 수 있다.
- 그로인해서 데이터 검증을 하니까 데이터 검증은 덤이다.
OpenAPI
같은 문서도구에JSON Schema
를 만들어 등록한다.
가장 중요한 점은
출력 데이터 모델을 지정해줄 수 있다는 것이다.
☑️ Return the same input data
여기서 예제로 UserIn
이라는 모델을 선언했다. 이 모델은 password
라는 속성값을 str
형식으로 받는다.
1
2
3
4
5
6
7
8
9
10
11
12
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
그리고 UserIn
모델을 받고, 그대로 리턴해버리는 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
# Don't do this in production!
# 추가
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
return user
유저가 직접 password
를 입력하는 경우에도 이 때는 문제가 생기지 않을 수 있다.
하지만 만약 이 모델이 다른 요청들의 필요에 의해 그대로 넘겨버리면 항상 password
를 전송하는 위험부담이 생겨버린다.
공식문서에서는 절대로
password
값을 그대로 저장하지 말라고 써있다. 암호화는 필수다.
☑️ Add an output model
password
를 제외하고 데이터를 보내버리게 모델을 새로 만든다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
UserOut
은 password
값을 포함하지 않고 response_model=UserOut
을 통해 userOut
모델을 보내버린다.
어떻게 이런 일이? 요청에는 password
를 보냈는데, 돌아온 값은 password
가 포함되지 않았다.
FastAPI
는Pydantic
을 이용하여 내보낼 데이터에 포함되지 않은 속성값들을 필터하는 기능을 가진다고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str| None = None
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"kms": {"name": "bag","price" : 350},
"bjh": {"name": "watch","price": 60, "description": "test des", "tax" : 25.3},
"jyb": {"name": "neck", "price":30, "description": None,"tags":["test tag","test2 tag2"]}
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
요청을 쿼리파라미터로 받기에
1
2
3
http://localhost:8000/items/bjh
http://localhost:8000/items/kms
http://localhost:8000/items/jyb
셋 중 하나로 보내면 그에 맞는 데이터값을 보내게 된다.
위의 구문중 크게 어려운 문법은 없지만 response_model_exclude_unset
은 다소 생소하다.
☑️ Use the response_model_exclude_unset
parameter
뜻은 간단하다. 반환 모델에서 default value
가 설정되지 않은 속성값들은 반환할 때 포함시키지 않는다. 즉, 설정된 값만 리턴한다.
만약 kms
로 요청을 보낸다면 이렇게 default value
값이 설정된 것들(description
,tags
,tax
)은 응답에 포함시키지 않는다. 다른 속성값들은 필수로 채워줘야한다.
결과
1
2
3
4
{
"name": "bag",
"price": 350
}
또한
response_model_exclude_defaults=True
response_model_exclude_none=True
도 있다.
여기서 헷갈리는 점이 있다.
items와 Item
과 어떤 연관관계가 있을까?
없다. 그저 이름이 비슷할뿐이다. items
를 test
로 바꾸고 리턴값에 test[item_id]
로 바꿔도 돌아간다.
해당 옵션을 빼고 jyb
요청을 보내면 default value
를 설정한 tax
가 포함되어있다.
None
으로 설정하지 않는다면 그 어떤값도 default value
라고 판단하지 않고 보낸다.
☑️ response_model_include
and response_model_exclude
이름에서 보면 유추할 수 있듯이, 어떤 값을 포함할지 안할지를 정할 수 있다.
공식문서에서는 이러한 방식을 추천한다기 보다는 새로운 클래스를 만들어서 반환하는 것을 추천한다. 그 이유는 결국 이렇게 데코레이션을 단 모델도,
JSON Shcema
를 생성할때 포함되기 때문이다. 또한respone_model_by_alias
도 마찬가지다.
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
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
return items[item_id]
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
return items[item_id]
요청
1
http://localhost:8000/items/baz/name
결과
1
2
3
4
{
"name": "Baz",
"description": "There goes my baz"
}
즉, response_model_include
는, 지정된 값이 존재할 시 리턴한다. 없으면 null
을 리턴한다.
반대로 response_model_exclude
는 위에서 지정한 tax
만 제외하고 값이 있다면 전부 리턴한다.
http://localhost:8000/items/foo/public로 요청을 보내면
foo
에는 없는description
이null
로 입력되는데, 이는respone_model=Item
으로 리턴 모델을 지정해놔서 그렇다.