목차
개요
FastAPI의 공식문서에는 pydantic을 이용한 예제들이 많이 등장한다. 하지만 pydantic보다 dataclass를 선호해서 FastAPI+dataclass 조합을 이용중인데 이대로 사용하다 보니 Swagger 문서에서 Response 되는 Data를 명시하는 부분에 제외(exclude)할 데이터를 설정하는 부분에서 문제가 있었다.
이 문제를 해결하기위해 python dataclass를 pydantic의 BaseModel로 변환시켜 보자는 아이디어를 떠올렸고 이번 포스팅은 그 방법을 기록한 내용이다.
1. FastAPI의 response_model_exclude ?
FastAPI의 APIRouter 클래스에는 다음과 같은 옵션들이 존재한다.
이 중에서도 response_model_exclude라는 옵션이 존재하는데 이는 Response Data에서 특정 필드를 제외하는 옵션이다. pydantic의 BaseModel을 이용한다면 해당 옵션이 동작하지만 필자의 경우 dataclass를 이용하고 있기 때문에 해당 옵션을 사용하지 못한다는 문제가 있었다.
2. pydantic의 create_model을 이용해 dataclass를 BaseModel로 변환하기
결국 response_model_exclude를 사용하려면 pydantic의 BaseModel을 써야 하기에 dataclass를 pydantic의 BaseModel로 변환하는 방법이 없을까를 고민하게 됐다. 조사해 보니 pydantic의 create_model이라는 함수가 있는 걸 확인했다.
pydantic의 공식문서에는 해당 기능이 Dynamic 한 BaseModel을 만들 수 있다고 설명하고 있다. 조금의 삽질 끝에 다음과 같이 간단한 helper 함수를 만드는 방식으로 create_model을 이용하여 dataclass를 BaseModel로 변환할 수 있었다.
from pydantic import Bcreate_model
from typing import get_type_hints
def dataclass_to_basemodel(entity):
hints = get_type_hints(entity)
new_model = create_model("DynamicBaseModel", **{k: (v, ...) for k, v in hints.items()})
return new_model
그러나 위와 같이 사용할 경우 APIRouter의 response_model_exclude 옵션이 제대로 동작하지 않았다.
3. pydantic의 create_model을 만들 때 get_type_hints와 Field 사용하기
앞서 간단히 만들었던 helper 함수의 문제를 개선해 보자. 앞서 기재한 함수의 문제는 동적으로 생성한 BaseModel에 대해 APIRouter의 response_model_exclude 옵션이 동작하지 않는다는 점이다. 그렇다면 BaseModel을 생성할 때 특정 필드를 exclude 시킬 수 있는 방법도 있을 것 같다.
이를 가능하게 하는 방법은 pydantic의 Field 객체를 이용하는 것이었다.
from pydantic import Field
...
hints = get_type_hints(entity)
attributes = {}
for k, v in hints.items():
if k in exclude_fields:
attributes[k] = (v, Field(..., exclude=True))
else:
attributes[k] = (v, Field(..., include=True))
new_model = create_model("DynamicBaseModel", **attributes)
typing 모듈의 get_type_hints에 dataclass를 넣어주면 해당 dataclass의 field가 어떤 타입을 가지고 있는지 dictionary 형태로 추출해준다. 그러고 난 다음 pydantic의 Field를 이용해 Swagger 문서에서 제외할 데이터에 exclude옵션을 설정해주자.
그렇게 만들어낸 필드를 다시 create_model을 사용하여 Dynamic한 BaseModel을 만들 수 있다.
4. 마치며
dataclass를 이용하여 ResponseModel을 표현하는 방법에 대해서는 오랜 시간 고민한 문제이다.
이 조사를 하기 이전에는 typing과 Generic을 적절히 이용해 클래스를 만들고 데이터 구조를 추상화하여 type hinting을 통해 해결했었지만 이렇게 한 경우 데이터 구조대로 클래스를 만들어야 했기에 번거로움이 수반됐다.
이번에 사용한 방법은 이미 만들어둔 모델을 통해 제어하는 방법이기에 비교적 수월했지만 FastAPI는 pydantic과 연계하여 구조를 잡아놨기에 그 틀을 벗어나 Framework를 사용하는 것에 어떤 의미가 있을지는 고민해봐야겠다.
5. Reference
FastAPI Response Model
Pydantic Dynamic Create Base Model - create_model function
- https://docs.pydantic.dev/latest/api/base_model/#pydantic.create_model
StackOverFlow - pydantic create_model usage
Blog - dataclass to pydantic basemodel conversion
- https://www.zachbellay.com/posts/dataclass-to-pydantic-basemodel-conversion/
'Frame Work > FastAPI' 카테고리의 다른 글
[FastAPI] Depands와 Dependency-injector DI 비교해보기 (1) | 2024.02.18 |
---|---|
[FastAPI] Too Many Open files Error가 일어난다면 (0) | 2024.02.05 |
[FastAPI] Request 단위의 Transaction 잡기 (1) | 2023.12.01 |
[FastAPI] Header Authenticate와 Swagger Authorize (1) | 2023.11.27 |
[FastAPI] Post API의 Form Body 테스트 (0) | 2023.10.11 |