FastAPI 개념 이해
FastAPI + Pydantic
Background
- 백엔드 엔지니어 입장에서 API 개발보다 중요시 여겨지는 것이 API를 사용할 수 있도록 만든 문서 작업이다.
- 요구사항에 따른 기능 개발 및 테스트 코드를 주력으로 두다 보니 문서 작업에 소홀해지기 마련이다.
- 이렇게 되면 API를 사용하는 프론트 엔지니어들은 버그가 고쳐지기 이전이나 요구 사항의 변경으로 인해 바뀐 API 때문에 사용에 혼란을 겪게 된다.
-
Flask는 마이크로프레임워크로는 나은 선택이지만 요청/응답 등 데이터 검증 처리에 있어서 백엔드 엔지니어의 일을 가중시킨다.

FastAPI 채택
- 백엔드 엔지니어 입장에서 위와 같은 이슈를 해결하기 위해 문서의 자동화가 필요했음
- 코드의 변화에 따라 발생하는 요청/응답 스키마를 자동으로 문서화해 제공하고, 이를 프론트 엔지니어가 백엔드 애플리케이션의 코드 변화에도 즉각 대응할 수 있도록 한다면 문제 해소라고 판단함
-
FastAPI는 이러한 파이프라인을 제공해줌으로써 백엔드 엔지니어가 문서 작업에 할애하는 시간을 줄이고, 오직 코드에만 집중하도록 해 업무 효율을 증진시킬 수 있음

- 백엔드 엔지니어가 FastAPI를 이용해 애플리케이션을 개발하면 기본적으로 엔지니어가 만든 EndPoint(최종 목적지)를 제외하고 아래 3가지 EndPoint가 생성됨
- /docs : 대화형 API 문서
- /redoc : 대안 API 문서
- /openapi.json : 가공되지 않은 OpenAPI 스키마가 어떻게 생겼는지 궁금하다면, FastAPI는 자동으로 API의 설명과 함께 JSON(스키마)를 생성한다.
- 위 3가지 엔드포인트는 API에 대한 문서를 위한 것.
- 이를 기반으로 FastAPI에 내장되어 있는 Swagger, Redoc이 문서를 시각화 해줌으로써 백엔드 엔지니어가 문서 작업에 할애하는 시간을 대폭 줄일 수 있음
Pydantic
- 데이터를 검증하고 설정들을 관리하는 라이브러리
- pydantic은 런타임 환경에서 타입을 강제하고 타입이 유효하지 않을 때 에러를 발생시켜준다.
FastAPI가 요청과 응답을 파싱하는 원리
- 본래 FastAPI는 Python에서 기본적으로 제공하는 json 모듈의 loads를 이용해 JSON 모델로 반환한다.
-
이를 support해 주는 것이 pydantic, FastAPI 인스턴스에서 제공하는 데코레이터에 이와 관련된 로직 존재

Reference
실습
-
vscode project 생성 및 패키지 설치
D:\fastapi\
# fastapi 설치 # 프로덕션을 위해 uvicorn과 같은 ASGI 서버 설치 필요 pipenv install fastapi uvicorn[standard]
WSGI와 ASGI 차이
: python의 경우에도 어플리케이션 서버와의 통신을 위해 Gateway Interface가 존재하고 있고 초기에 나온 웹 어플리케이션과의 인터페이스가 WSGI(Web Service Gateway Interface)
ASGI(Asynchronous Server Gaterway Interface)는 비동기 가능 python 웹 서버, 프레임워크 및 애플리케이션 간의 표준 인터페이스를 제공하기 위한 것
- 쉽게 말해, WSGI가 동기 python 앱에 대한 표준을 제공했다면 ASGI는 WSGI 이전 버전과의 호환성 구현과 여러 서버 및 애플리케이션 프레임워크를 통해 비동기 및 동기 앱 모두에 대한 표준을 제공함
-
FastAPI로 애플리케이션 개발 전 테스트
# main.py from fastapi import FastAPI app = FastAPI() @app.get(path='/', description="FastAPI 테스트 용") def read_root(): return {"Hello": "World"}pipenv run uvicorn main:app --reload -
로컬 화면

-
/redoc 화면

- description이 문서에 들어가 있음을 확인할 수 있다. 기본적으로 status_code를 지정해주지 않으면 문서에서는 200 Success 코드를 나타내며 관련된 사항은 FastAPI 인스턴스의 데코레이터에서 설정할 수 있다.
- Text response가 application/json으로 타입이 나온걸 확인할 수 있음 이를 수정하려면, response_class를 통해 해당 API가 json 응답이 아닌 text 응답이라는 것을 문서에 명시할 수 있다.
# main.py import uvicorn from fastapi import FastAPI, status from fastapi. responses import PlainTextResponse app = FastAPI() @app.get( path='/', description="FastAPI 테스트 용", status_code=status.HTTP_200_OK, response_class=PlainTextResponse, responses = {200 : {"description" : "FastAPI 응답"}} ) def hnpark_test(): return "OK" if __name__=="__main__": uvicorn.run(app)
- response 문구 변경 및 response schema 변경 확인
- Request / Response(JSON) : 사용자가 서비스에 가입하는 요청 API
-
요청과 응답을 JSON으로 받는 API를 정의하고 문서화 진행
FastAPI에서는 pydantic을 사용해 Request, Response Model을 정의할 수 있다.
-
pipenv install pydantic[email]을 먼저 진행해야한다.
from fastapi import FastAPI from fastapi. responses import JSONResponse app = FastAPI() # 기본적으로 pydantic에서 주어지는 멤버 변수는 required이며 필수가 아닌 변수로 정하고 싶다면 # typing의 Optional을 이용하면 된다. from datetime import datetime from pydantic import BaseModel, EmailStr, Field from typing import Optional # BaseModel # : 입력 데이터를 보장해주는게 아니라 입력을 받아 데이터 형식과 제약조건을 보장해주는 모델 class RequestUserDto(BaseModel): nickname: str = Field(title="사용자 닉네임") email: EmailStr = Field(title="사용자 이메일 주소") description: Optional[str] = Field(title="자기소개") @app.post(path = '/register', description="회원 가입 API", status_code=201, response_class=JSONResponse, responses={ 201: { "description": "가입 사용자 정보", "content": { "application/json": { "example": { "nickname": "박하늘", "email": "hnpark@t3q.com", "description": "자기소개..." } } } } } ) def register_user(req: RequestUserDto): return req.dict() -
화면

-
422 Validation Error는 코드에서 정의하지 않았는데 나온다. 기본적으로 pydantic은 데이터 검증 기능을 제공한다. 클라이언트 요청 값이나 DTO 변환 시 데이터가 누락되는 경우 자동으로 잡아주는 기능을 제공한다.
Response Model
- 반환 모델을 문서화 할 때 example을 넣었다. 간단한 모델이라면 상관이 없지만 많은 정보를 줘야하는 모델인 경우 데코레이터 내에 모든 변수를 쓰고 그에 따른 예시를 작성해줘야한다. 그렇게 되면 API 부분에 코드가 방대하게 쌓이게 되고 코드 리딩이 어려워진다.
- Request 모델과 마찬가지로 Response 모델도 DTO로 정의해 사용할 수 있다.
DTO/DAO
-
DAO : Data Access Object는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트
-
DTO : Data Transfer Object는 VO(Value Object)로 바꿔 말할 수 있는데 계층간 데이터 교환을 위한 자바빈즈를 말한다. 컨트롤러, 뷰, 비즈니스 계층, 퍼시스턴스 계층을 말하며 각 계층간 데이터 교환을 위한 객체를 DTO 또는 VO라고 부른다.
from fastapi import FastAPI, Request
app = FastAPI()
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
# response 모델도 DTO 정의
class ResponseUserDto(BaseModel):
nickname: str = Field(title="사용자 닉네임")
email: EmailStr = Field(title="사용자 이메일 주소")
description: Optional[str] = Field(title="자기소개")
@app.post(
path = '/register', description="회원 가입 API",
status_code=201,
response_model=ResponseUserDto,
responses={201: {"description" : "가입 사용자 정보"}}
)
def register_user(req: Request):
return req.dict()
-
이때는 response_class가 아닌 response_model을 사용해야 한다. 둘의 차이는 response_class는 REST API에서 제공하는 content-type 기반한 인스턴스를 넣어야하고, response-model은 pydantic으로 정의한 클래스가 들어간다.

-
response model로 pydantic을 사용하면 샘플 코드가 부정확하다. 이럴 때는 config 클래스를 이용해서 샘플 모델을 정의할 수 있다.
# response 모델도 DTO 정의 class ResponseUserDto(BaseModel): # nickname: str = Field(title="사용자 닉네임") # email: EmailStr = Field(title="사용자 이메일 주소") # description: Optional[str] = Field(title="자기소개") class Config: schema_extra = { "example": { "nickname" : "박하늘", "email": "hnpark@t3q.com", "description": "자기소개" } } @app.post( path = '/register', description="회원 가입 API", status_code=201, response_model=ResponseUserDto, responses={201: {"description" : "가입 사용자 정보"}} ) def register_user(req: Request): return req.dict() - 원하는 샘플을 넣기 위해서는 schema_extra를 사용해 example을 정의하면 된다.
-
response samples에 정의한 example 내용이 나오는걸 확인할 수 있다.

requirements
[packages]
fastapi = "==0.79.0"
uvicorn = {extras = ["standard"], version = "==0.18.2"}
pydantic = {extras = ["email"], version = "==1.9.1"}
HTTP 특정 메소드
- POST : 데이터 생성
- GET : 데이터 읽기
- PUT : 데이터 업데이트
- DELETE : 데이터 삭제