공식문서를 번역한 내용입니다.
https://fastapi.tiangolo.com/ko/tutorial/response-model/
Response Model - Return Type - FastAPI
FastAPI framework, high performance, easy to learn, fast to code, ready for production
fastapi.tiangolo.com
경로 연산 함수 return type에 주석을 달아 응답에 사용되는 유형을 선언할 수 있습니다.
함수 매개변수의 입력 데이터와 동일한 방식으로 유형 어노테이션을 사용할 수 있으며, Pydantic 모델, list, dict, int, bool 등의 스칼라 값 등을 사용할 수 있습니다.
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/")
async def create_item(item: Item) -> Item:
return item
@app.get("/items/")
async def read_items() -> list[Item]:
return [
Item(name="Portal Gun", price=42.0),
Item(name="Plumbus", price=32.0),
]
FastAPI는 이 반환 유형을 사용합니다:
- 반환된 데이터의 유효성을 검사합니다.
- 데이터가 유효하지 않은 경우(예: 필드가 누락된 경우) 앱 코드가 손상되어 정상적으로 반환되어야 하는 데이터를 반환하지 못하고 잘못된 데이터를 반환하는 대신 서버 오류를 반환한다는 의미입니다. 이렇게 하면 여러분과 여러분의 클라이언트가 예상한 데이터와 데이터 형태를 확실히 받을 수 있습니다.
- OpenAPI 경로 작업에서 응답에 대한 JSON 스키마를 추가합니다.
- 이 스키마는 자동 문서에서 사용됩니다.
- 자동 클라이언트 코드 생성 도구에서도 사용됩니다.
- 하지만 가장 중요한 것은:
- 출력 데이터를 반환 유형에 정의된 것으로 제한하고 필터링한다는 것입니다.
- 이는 보안을 위해 특히 중요하며, 이에 대한 자세한 내용은 아래에서 살펴보겠습니다.
- 출력 데이터를 반환 유형에 정의된 것으로 제한하고 필터링한다는 것입니다.
응답 모델 매개변수
유형이 선언한 것과 정확히 일치하지 않는 일부 데이터를 반환해야 하거나 반환하고 싶은 경우가 있습니다.
예를 들어, 사전이나 데이터베이스 객체를 반환하고 싶지만 이를 Pydantic 모델로 선언할 수 있습니다. 이렇게 하면 Pydantic 모델이 반환한 객체(예: 사전 또는 데이터베이스 객체)에 대한 모든 데이터 문서화, 유효성 검사 등을 수행합니다.
반환 유형 주석을 추가하면 도구와 편집기에서 함수가 선언한 유형(예: Pydantic 모델)과 다른 유형(예: 딕셔너리)을 반환하고 있다는 (올바른) 오류를 표시합니다.
이러한 경우 반환 유형 대신 경로 연산 데코레이터 매개변수 response_model을 사용할 수 있습니다.
응답 모델 매개변수는 모든 경로 연산에 사용할 수 있습니다:
- @app.get()
- @app.post()
- @app.put()
- @app.delete()
- etc.
from typing import Any
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) -> Any:
return item
@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
🍳참고 응답_모델은 "데코레이터" 메서드(get, post 등)의 매개변수라는 점에 유의하세요. 모든 매개변수나 본문처럼 경로 연산 함수의 매개변수가 아닙니다.
response_model은 Pydantic 모델 필드에 대해 선언하는 것과 동일한 유형을 수신하므로 Pydantic 모델일 수도 있지만, 예를 들어 List[Item]과 같은 Pydantic 모델 목록일 수도 있습니다.
FastAPI는 이 response_model을 사용하여 모든 데이터 문서화, 유효성 검사 등을 수행하고 출력 데이터를 유형 선언에 맞게 변환 및 필터링합니다.
응답_모델 우선순위
반환 유형과 응답 모델을 모두 선언하면 응답 모델이 우선권을 가지며 FastAPI에서 사용됩니다.
이렇게 하면 응답 모델과 다른 유형을 반환하는 경우에도 함수에 올바른 유형 어노테이션을 추가하여 편집기 및 mypy와 같은 도구에서 사용할 수 있습니다. 또한 응답 모델을 사용하여 데이터 유효성 검사, 문서화 등을 FastAPI가 수행하도록 할 수 있습니다.
응답 모델=None을 사용하여 해당 경로 작업에 대한 응답 모델 생성을 비활성화할 수도 있습니다. 유효한 Pydantic 필드가 아닌 항목에 대한 유형 주석을 추가하는 경우 이 작업을 수행해야 할 수 있으며, 아래 섹션 중 하나에서 그 예제를 볼 수 있습니다.
동일한 입력 데이터 반환
여기서는 UserIn 모델을 선언하고 있으며, 여기에는 일반 텍스트 비밀번호가 포함됩니다:
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/")
async def create_user(user: UserIn) -> UserIn:
return user
❗Info
EmailStr을 사용하려면 먼저 email_validator를 설치하세요.
예: pip install email-validator 또는 pip install pydantic[email].
그리고 이 모델을 사용하여 입력을 선언하고 동일한 모델을 사용하여 출력을 선언합니다:
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/")
async def create_user(user: UserIn) -> UserIn:
return user
이제 브라우저에서 비밀번호를 사용하여 사용자를 생성할 때마다 API는 동일한 비밀번호를 응답으로 반환합니다.
이 경우 동일한 사용자가 비밀번호를 전송하기 때문에 문제가 되지 않을 수 있습니다.
하지만 다른 경로 연산에 동일한 모델을 사용하면 모든 클라이언트에 사용자의 비밀번호를 전송할 수 있습니다.
❗ 위험 모든 주의 사항을 숙지하고 자신이 무엇을 하고 있는지 잘 알고 있는 경우가 아니라면 사용자의 일반 비밀번호를 저장하거나 이와 같은 응답으로 보내지 마세요.
결과 모델 추가
대신 일반 텍스트 비밀번호가 포함된 입력 모델과 비밀번호가 없는 출력 모델을 만들 수 있습니다:
from typing import Any
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) -> Any:
return user
여기서는 경로 연산 함수가 비밀번호가 포함된 동일한 입력 사용자를 반환하고 있지만:
from typing import Any
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) -> Any:
return user
...응답 모델을 비밀번호를 포함하지 않는 UserOut 모델로 선언했습니다:
from typing import Any
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) -> Any:
return user
따라서 FastAPI는 출력 모델에 선언되지 않은 모든 데이터를 필터링하는 작업을 처리합니다(Pydantic 사용).
응답 모델 또는 반환 유형
이 경우 두 모델이 서로 다르기 때문에 함수 반환 유형을 UserOut으로 주석 처리하면 편집기와 도구에서 서로 다른 클래스이므로 잘못된 유형을 반환한다고 불평할 것입니다.
그렇기 때문에 이 예제에서는 response_model 매개변수에서 선언해야 합니다.
...하지만 이를 극복하는 방법을 아래에서 계속 읽어보세요.
UserOut이라는 response_model을 따로 만들어 적용한 결과, password를 보냈는데, 돌아온 값엔 password가 포함되지 않았다.
반환 유형 및 데이터 필터링
이전 예제에서 계속해 보겠습니다. 함수에 하나의 유형으로 주석을 달되 더 많은 데이터를 포함하는 것을 반환하고 싶었습니다.
응답 모델을 사용하여 데이터를 계속 필터링하는 FastAPI를 원합니다.
이전 예제에서는 클래스가 다르기 때문에 response_model 매개변수를 사용해야 했습니다. 하지만 이는 또한 함수 반환 유형을 확인하는 편집기 및 도구의 지원을 받지 못한다는 것을 의미합니다.
하지만 이와 같은 작업을 수행해야 하는 대부분의 경우, 이 예제에서처럼 모델에서 일부 데이터를 필터링/제거하기만 하면 됩니다.
이러한 경우 클래스와 상속을 사용하여 함수 유형 주석을 활용하면 편집기와 도구에서 더 나은 지원을 받으면서도 FastAPI 데이터 필터링 기능을 이용할 수 있습니다.
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class BaseUser(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserIn(BaseUser):
password: str
@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser:
return user
이 코드가 유형 측면에서 정확하기 때문에 에디터와 mypy로부터 툴링 지원을 받을 수 있을 뿐만 아니라 FastAPI로부터 데이터 필터링도 받을 수 있습니다.
어떻게 작동할까요? 확인해 봅시다. 🤓
유형 주석 및 툴링
먼저 편집자, 마이피 및 기타 도구에서 이를 어떻게 볼 수 있는지 살펴봅시다.
BaseUser에는 기본 필드가 있습니다. 그런 다음 UserIn은 BaseUser를 상속하고 비밀번호 필드를 추가하므로 두 모델의 모든 필드를 포함하게 됩니다.
함수 반환 유형에 BaseUser로 주석을 달았지만 실제로는 UserIn 인스턴스를 반환합니다.
에디터, mypy 및 기타 도구는 이에 대해 불평하지 않을 것입니다. 유형 측면에서 UserIn은 BaseUser의 서브클래스로, 예상되는 것이 BaseUser인 경우 유효한 유형이기 때문입니다.
FastAPI 데이터 필터링
이제 FastAPI의 경우 반환 유형이 표시되고 반환하는 항목이 유형에 선언된 필드만 포함하는지 확인합니다.
그렇지 않으면 예상보다 훨씬 더 많은 데이터가 반환될 수 있으므로 반환된 데이터 필터링에 동일한 클래스 상속 규칙이 사용되지 않도록 하기 위해 FastAPI는 내부적으로 Pydantic에서 몇 가지 작업을 수행합니다.
이렇게 하면 툴링 지원이 포함된 유형 주석과 데이터 필터링이라는 두 가지 장점을 모두 얻을 수 있습니다.
문서에서 보기
자동 문서를 보면 입력 모델과 출력 모델에 모두 고유한 JSON 스키마가 있는 것을 확인할 수 있습니다:
그리고 두 모델 모두 대화형 API 문서에 사용될 것입니다:
기타 반환 유형 주석
유효한 Pydantic 필드가 아닌 것을 반환하고 함수에서 주석을 달았는데 도구(편집기, 마이피 등)에서 제공하는 지원을 받기 위해 주석을 달아야 하는 경우가 있을 수 있습니다.
직접 응답 반환하기
가장 일반적인 경우는 고급 문서의 뒷부분에 설명된 대로 응답을 직접 반환하는 경우입니다.
from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse, RedirectResponse
app = FastAPI()
@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response:
if teleport:
return RedirectResponse(url="<https://www.youtube.com/watch?v=dQw4w9WgXcQ>")
return JSONResponse(content={"message": "Here's your interdimensional portal."})
이 간단한 경우는 반환 유형 어노테이션이 Response의 클래스(또는 하위 클래스)이기 때문에 FastAPI에서 자동으로 처리됩니다.
또한 RedirectResponse와 JSONResponse는 모두 Response의 하위 클래스이므로 유형 어노테이션이 올바르므로 도구도 만족할 것입니다.
응답 하위 클래스 주석 달기
유형 어노테이션에서 응답의 하위 클래스를 사용할 수도 있습니다:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/teleport")
async def get_teleport() -> RedirectResponse:
return RedirectResponse(url="<https://www.youtube.com/watch?v=dQw4w9WgXcQ>")
RedirectResponse는 Response의 서브클래스이고, FastAPI는 이 간단한 경우를 자동으로 처리하기 때문에 이 방법도 작동합니다.
잘못된 반환 유형 어노테이션
그러나 유효한 Pydantic 유형이 아닌 다른 임의의 객체(예: 데이터베이스 객체)를 반환하고 함수에 이와 같은 주석을 달면 FastAPI는 해당 유형 주석에서 Pydantic 응답 모델을 생성하려고 시도하고 실패합니다.
예를 들어, 하나 이상의 유형이 유효한 Pydantic 유형이 아닌 서로 다른 유형 간에 유니온과 같은 것이 있는 경우에도 마찬가지입니다(예: 실패):
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response | dict:
if teleport:
return RedirectResponse(url="<https://www.youtube.com/watch?v=dQw4w9WgXcQ>")
return {"message": "Here's your interdimensional portal."}
...유형 어노테이션이 피단틱 유형이 아니며 단일 응답 클래스나 하위 클래스가 아니라 응답과 딕셔너리 사이의 유니온(둘 중 하나)이기 때문에 실패합니다.
응답 모델 비활성화
위의 예에서 계속하여, FastAPI에서 수행하는 기본 데이터 유효성 검사, 문서화, 필터링 등을 원하지 않을 수 있습니다.
그러나 편집기 및 유형 검사기(예: mypy)와 같은 도구의 지원을 받으려면 함수에 반환 유형 어노테이션을 계속 유지해야 할 수도 있습니다.
이 경우 응답 모델 생성을 비활성화하려면 response_model=None으로 설정하면 됩니다:
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Response | dict:
if teleport:
return RedirectResponse(url="<https://www.youtube.com/watch?v=dQw4w9WgXcQ>")
return {"message": "Here's your interdimensional portal."}
이렇게 하면 FastAPI가 응답 모델 생성을 건너뛰게 되므로 FastAPI 애플리케이션에 영향을 주지 않고 필요한 모든 반환 유형 어노테이션을 사용할 수 있습니다. 🤓
응답 모델 인코딩 매개변수
응답 모델에 다음과 같은 기본값이 있을 수 있습니다:
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 = {
"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]
- 설명: Union[str, None] = None(또는 파이썬 3.10에서 str | None = None)의 기본값은 None입니다.
- tax: float = 10.5의 기본값은 10.5입니다.
- 태그: List[str] = []는 빈 목록의 기본값입니다: [].
그러나 실제로 저장되지 않은 경우 결과에서 생략하고 싶을 수 있습니다.
예를 들어, NoSQL 데이터베이스에 선택적 속성이 많은 모델이 있지만 기본값으로 가득 찬 매우 긴 JSON 응답을 전송하고 싶지 않은 경우입니다.
응답_모델_제외_언셋 매개변수 사용
경로 연산 데코레이터 매개변수 응답_모델_제외_언셋=True를 설정할 수 있습니다:
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 = {
"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]
를 입력하면 이러한 기본값은 응답에 포함되지 않고 실제로 설정된 값만 포함됩니다.
따라서 ID가 foo인 항목에 대해 해당 경로 작업으로 요청을 보내면 기본값이 포함되지 않은 응답이 표시됩니다:
{
"name": "Foo",
"price": 50.2
}
❗INFO 이를 위해 FastAPI는 Pydantic 모델의 .dict()를 exclude_unset 매개변수와 함께 사용합니다.
❗INFO 이것 또한 사용할 수 있습니다: 응답_모델_제외_기본값=True response_model_exclude_none=True 를 사용할 수도 있습니다. exclude_defaults 및 exclude_none에 대한 Pydantic 문서에 설명된 대로.
기본값이 있는 필드에 대한 값이 있는 데이터
그러나 데이터에 ID 막대가 있는 항목과 같이 기본값이 있는 모델의 필드 값이 있는 경우:
{
"name": "Bar",
"description": "The bartenders",
"price": 62,
"tax": 20.2
}
를 입력하면 응답에 포함됩니다.
기본값과 동일한 값을 가진 데이터
데이터에 기본값과 동일한 값이 있는 경우(예: ID가 baz인 항목):
{
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": []
}
설명, 세금 및 태그가 기본값과 동일한 값을 갖더라도 기본값에서 가져온 것이 아니라 명시적으로 설정되었다는 것을 알아차릴 수 있을 만큼(실제로 Pydantic은 충분히 똑똑합니다) FastAPI는 똑똑합니다.
따라서 JSON 응답에 포함됩니다.
🔎TIP 기본값은 없음뿐만 아니라 무엇이든 될 수 있습니다. 목록([]), 10.5의 실수 등이 될 수 있습니다.
응답_모델_포함 및 응답_모델_제외
경로 연산 데코레이터 매개변수 응답_모델_포함 및 응답_모델_제외를 사용할 수도 있습니다.
이 매개변수는 포함(나머지는 생략) 또는 제외(나머지는 포함)할 속성의 이름과 함께 문자열 집합을 받습니다.
Pydantic 모델이 하나뿐이고 출력에서 일부 데이터를 제거하려는 경우 빠른 바로 가기로 사용할 수 있습니다.
🔎TIP 그러나 이러한 매개 변수 대신 여러 클래스를 사용하여 위의 아이디어를 사용하는 것이 좋습니다. 응답 모델 포함 또는 응답 모델 제외를 사용하여 일부 속성을 생략하더라도 앱의 OpenAPI(및 문서)에서 생성된 JSON 스키마가 여전히 전체 모델에 대한 스키마가 되기 때문입니다. 이는 유사하게 작동하는 response_model_by_alias에도 적용됩니다.
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]
🔎TIP 구문 {"name", "description"}은 이 두 값으로 집합을 만듭니다. 이는 set(["name", "description"])와 동일합니다.
세트 대신 목록 사용
집합을 사용하는 것을 잊고 목록이나 튜플을 대신 사용하는 경우에도 FastAPI는 이를 집합으로 변환하여 올바르게 작동합니다:
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]
요약
경로 연산 데코레이터의 매개변수 response_model을 사용하여 응답 모델을 정의하고 특히 비공개 데이터를 필터링할 수 있습니다.
명시적으로 설정된 값만 반환하려면 response_model_exclude_unset을 사용하세요.
'코딩 > FastAPI' 카테고리의 다른 글
FastAPI 배우기 - Response Status Code (0) | 2023.08.15 |
---|---|
FastAPI 배우기 - Extra Models (0) | 2023.08.01 |
FastAPI 배우기 - Header Parameters (0) | 2023.07.11 |
FastAPI 배우기 - Cookie Parameters (0) | 2023.07.11 |
FastAPI 배우기 - Extra Data Types (0) | 2023.07.03 |