15. Response Model
1. Response Model
- 다음과 같은 모든 path operations에서
response_model
매개변수를 사용하여 response에 사용되는 모델을 선언할 수 있다.
1] @app.get()
2] @app.post()
3] @app.put()
4] @app.delete()
5] 기타
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: List[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item
Note
- 여기서 주의해야 할 것은
response_model
은 "데코레이터" 메소드(get
,post
등)의 매개변수이며, 모든 매개변수 및 body와 같이 path operation function이 아니라는 것이다.
- 위의 예제에서는 Pydantic 모델 속성에 대해 선언하는 것과 동일한 유형을 수신하지만,
List[Item]
과 같은 Pydantic 모델의list
또한 수신할 수 있다. response_model
의 매개변수로 Pydantic 모델을 선언하는 것이 중요한 이유는 출력 데이터를 모델의 데이터로 제한하기 때문이다.
Note
- response 모델은 함수 반환 타입 어노테이션(여기서는
item: Item
) 대신 데코레이터 메소드의 매개변수(여기서는response_model=Item
)로 선언된다. - 왜냐하면 path function은 실제로 해당 reponse 모델을 바로 반환하지 않고
dict
, 데이터베이스 객체 또는 다른 모델을 반환한 후response_model
을 사용하여 필드 제한 및 직렬화를 수행할 수 있기 때문이다.
2. Return the same input data
- 다음의 예제는 UserIn 모델을 선언하고, 일반 텍스트 비밀번호를 포함하는 예제이다.
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
# Don't do this in production!
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
return user
- 다음과 같이 이 모델을 사용하여 입력을 선언하고, 동일한 모델을 사용하여 출력을 선언한다.
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
# Don't do this in production!
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
return user
- 위의 예제를 실행하면, 브라우저가 비밀번호를 포함하여 사용자를 생성할 때마다 API는 response에서 동일한 비밀번호를 반환하게 된다.
- 이와 같은 경우 결국 사용자 자신이 자신에게 비밀번호를 보내는 것이기 때문에 문제가 되지 않을 수 있다.
- 하지만 다른 path operation에 동일한 모델을 사용하는 경우 사용자의 비밀번호는 다른 모든 클라이언트에게 보내질 수 있다.
Note
- 절대 사용자의 비밀번호를 그대로 저장하거나, response로 보내서는 안 된다.
3. Add an output model
- 위의 예제 대신 일반 텍스트 비밀번호가 있는 입력 모델과 비밀번호가 없는 출력 모델을 만들 수 있다.
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
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
- 위의 예제를 다시 보면, 다음과 같이 path operation function의 반환값으로 함수 반환 타입 어노테이션이
UserIn
인user
를 지정했다.
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
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
- 하지만 다음과 같이
response_model
은UserOut
이 될 것이기 때문에, 비밀번호를 포함하지 않고 출력할 수 있게 되는 것이다.
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
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
4. See it in the docs
- 자동 문서를 보면 다음과 같이 입력과 출력 모두 자체적인 JSON 스키마를 가지게 된다는 것을 알 수 있다.
5. Response Model encoding parameters
- 다음의 예제는 response 모델에 기본값이 있는 예제이다.
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
- response 모델에는 다음과 같은 기본값이 지정되어 있다.
1] description: Optional[str] = None
- 기본값이
None
이다.
2] tax: float = 10.5
- 기본값이
10.5
이다.
3] tags: List[str] = []
- 기본값이 빈 리스트인
[]
이다.
- 위와 같이 기본값이 지정되어 있으나, 실제로 클라이언트로부터 저장되지 않은 경우 결과에서 생략할 수 있다.
1) Use the response_model_exclude_unset
parameter
- 결과에서 생략하려면 다음과 같이 path operation 데코레이터 매개변수
response_model_exclude_unset=True
를 설정하면 된다.
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
- 해당 기본값은 response에 포함되지 않고, 실제로 클라이언트로부터 설정된 값만 포함된다.
- 따라서 ID가
foo
인 항목에 대해 해당 path operation에 request를 보내는 경우 response는 다음과 같다.
response_model_exclude_unset=True
뿐만 아니라 다음의 매개변수도 사용할 수 있다.
1] response_model_exclude_defaults=True
2] response_model_exclude_none=True
(1) Data with values for fields with defaults
- 만약 ID가
bar
인 항목과 같이 기본값이 있는 모델이지만 필드에 대한 값이 존재하는 경우, response에 포함될 것이다.
(2) Data with the same values as the defaults
- 만약 ID가
baz
인 항목과 같이 데이터가 기본값과 동일한 값을 가지는 경우, 필드에 대한 값이 "명시적"으로 설정되었기 때문에 response에 포함될 것이다.
2) response_model_include
and response_model_exclude
- path operation 데코레이터 매개변수로
response_model_include
와response_model_exclude
를 사용할 수 있다. - 포함하거나 제외할 속성의 이름을
str
의set
로 받게 된다.
- 다음과 같이 작성할 수 있다.
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = 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]
{"name", "description"}
구문은 이 두 값으로 된set
를 의미한다.- 이는
set(["name", "description"])
과 동일하다.
Note
- 포함하거나 제외할 속성을 정할 때 더 좋은 방법은 위와 같은 매개변수를 사용하는 것 대신 여러 클래스를 사용하는 것이다.
(1) Using list
s instead of set
s
- 만약 다음과 같이
set
가 아닌list
또는tuple
을 사용해도 FastAPI가 자동으로 변환해 준다.
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = 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]