목차
개요
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
Response Model - Return Type - FastAPI
FastAPI framework, high performance, easy to learn, fast to code, ready for production
fastapi.tiangolo.com
Pydantic Dynamic Create Base Model - create_model function
- https://docs.pydantic.dev/latest/api/base_model/#pydantic.create_model
BaseModel - Pydantic
Dynamically creates and returns a new Pydantic model, in other words, create_model dynamically creates a subclass of BaseModel. Parameters: Name Type Description Default __model_name str The name of the newly created model. required __config__ ConfigDict |
docs.pydantic.dev
StackOverFlow - pydantic create_model usage
Creating a Pydantic model dynamically from a Python dataclass
I'd like to dynamically create a Pydantic model from a dataclass, similar to how you can dynamically create a Marshmallow schema from a dataclass as in marshmallow-dataclass or https://stevenloria....
stackoverflow.com
Blog - dataclass to pydantic basemodel conversion
- https://www.zachbellay.com/posts/dataclass-to-pydantic-basemodel-conversion/
Zach Bellay | Dataclass to Pydantic BaseModel Conversion
3 min, 532 words Note: This post is a dramaticized development experience I had at work that stemmed largely from miscommunication and bad assumptions. Nonetheless, if you're in a situation like this, here's what I did. The Problem Let's say an existing co
www.zachbellay.com
'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 |