[FastAPI] 3일차 - Pydantic과 Type Hints로 데이터 모델링 마스터하기

빛나김·2026년 1월 15일

Fast API

목록 보기
3/7
post-thumbnail

들어가며

지난 시간에는 Path Parameter와 Query Parameter를 통해 URL에서 데이터를 받는 방법을 배웠습니다. 하지만 실제 API를 개발할 때는 더 복잡한 데이터 구조를 다뤄야 합니다. 예를 들어, 사용자 정보를 생성하거나 상품 정보를 수정할 때는 여러 필드를 포함한 복잡한 데이터를 주고받아야 하죠.

오늘은 FastAPI에서 데이터 모델링의 핵심인 Python Type HintsPydantic에 대해 알아보겠습니다. 이 두 기술을 마스터하면 타입 안전성을 보장하면서도 강력한 데이터 검증 기능을 갖춘 API를 만들 수 있습니다.


1. Python과 JSON - 데이터 직렬화의 이해

왜 직렬화가 필요한가?

FastAPI는 Python 객체(dict, list 등)를 HTTP 응답으로 보낼 때 자동으로 JSON으로 변환합니다. 이 과정을 직렬화(Serialization)라고 하며, 반대로 JSON을 Python 객체로 변환하는 과정을 역직렬화(Deserialization)라고 합니다.

Python과 JSON의 타입 매핑:

  • Python dict → JSON Object {}
  • Python list, tuple, set → JSON Array []
  • Python str → JSON String
  • Python int, float → JSON Number
  • Python True/False → JSON true/false
  • Python None → JSON null

FastAPI의 자동 직렬화

from fastapi import FastAPI

app = FastAPI()

@app.get("/items")
def get_items():
    return {
        "items": ["apple", "banana"],
        "count": 2,
        "available": True
    }

FastAPI가 자동으로 이 Python dict를 JSON으로 변환하여 클라이언트에게 전송합니다.


2. Python Type Hints - 타입 명시로 안전성 확보

Type Hints란?

Python 3.5부터 도입된 Type Hints는 변수와 함수의 타입을 명시적으로 선언할 수 있게 해줍니다.

기본 타입 지정

def greet(name: str) -> str:
    return f"Hello, {name}!"

age: int = 25
price: float = 19.99
is_active: bool = True

Type Hints의 장점:
1. 가독성 향상: 코드를 읽는 사람이 변수의 타입을 즉시 이해할 수 있습니다
2. IDE 지원: 자동 완성과 타입 체크 기능 활용 가능
3. FastAPI 통합: FastAPI가 타입 정보를 활용해 자동으로 검증합니다

FastAPI에서의 타입 검증

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}
  • /users/123 → 정상 작동 ✅
  • /users/abc → 422 에러 발생 ❌

FastAPI가 자동으로 타입을 검증하고 잘못된 타입이 들어오면 422 Unprocessable Entity 에러를 반환합니다.

복합 타입 - List와 Dict

from typing import List, Dict

scores: List[int] = [80, 90, 100]
user: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
users: List[Dict[str, str]] = [
    {"name": "Bob", "email": "bob@example.com"},
    {"name": "Charlie", "email": "charlie@example.com"}
]

Optional과 None

값이 있을 수도 있고 없을 수도 있는 경우 Optional을 사용합니다.

from typing import Optional

def find_user(user_id: int) -> Optional[str]:
    # 사용자를 찾으면 이름을 반환, 없으면 None 반환
    if user_id == 1:
        return "Alice"
    return None

# Query Parameter에서 Optional 사용
@app.get("/search")
def search(q: Optional[str] = None):
    if q:
        return {"query": q}
    return {"message": "No query provided"}

3. Pydantic - 강력한 데이터 모델링

Pydantic이란?

Pydantic은 Python 타입 힌트를 사용하여 데이터 검증과 설정 관리를 수행하는 라이브러리입니다. FastAPI의 핵심 기능 중 하나죠.

BaseModel로 데이터 모델 정의하기

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = False  # 기본값 설정

이렇게 정의한 모델은:

  • name: 필수 문자열 필드
  • price: 필수 실수 필드
  • is_offer: 선택적 불리언 필드 (기본값 False)

Pydantic의 자동 검증

# 올바른 데이터 - 성공 ✅
item_success = Item(name="Apple", price=100)

# 잘못된 타입 - ValidationError 발생 ❌
try:
    item_fail = Item(name="Banana", price="hello")  # price는 float이어야 함
except Exception as e:
    print(e)

Pydantic은 타입이 맞지 않으면 ValidationError를 발생시키고, FastAPI는 이를 422 HTTP 에러로 변환합니다.


4. Request Body - POST 요청으로 데이터 받기

Request Body란?

GET 요청은 URL에 데이터를 포함하지만, POST/PUT 요청은 Request Body에 데이터를 담아 보냅니다. 주로 복잡한 데이터를 생성하거나 수정할 때 사용합니다.

Pydantic 모델을 Request Body로 사용하기

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ItemCreate(BaseModel):
    name: str
    price: float
    description: Optional[str] = None

@app.post("/items")
def create_item(item: ItemCreate):
    return {
        "message": "Item created",
        "name": item.name,
        "price": item.price
    }

Swagger UI에서 테스트:
1. http://127.0.0.1:8000/docs 접속
2. POST /items 엔드포인트 선택
3. "Try it out" 클릭
4. Request Body 입력:

{
  "name": "Laptop",
  "price": 1200.50,
  "description": "High performance laptop"
}

FastAPI의 자동 구분

FastAPI는 매개변수의 타입을 보고 자동으로 구분합니다:

  • Path Parameter: URL 경로에 포함된 변수 ({user_id})
  • Query Parameter: 기본 타입 (str, int, float, bool)
  • Request Body: Pydantic BaseModel
@app.put("/users/{user_id}/items/{item_id}")
def update_item(
    user_id: int,           # Path Parameter
    item_id: int,           # Path Parameter
    item: ItemCreate,       # Request Body
    q: Optional[str] = None # Query Parameter
):
    return {
        "user_id": user_id,
        "item_id": item_id,
        "item": item,
        "q": q
    }

선택적 필드와 기본값

class Item(BaseModel):
    name: str
    price: float
    description: Optional[str] = None  # 선택적 필드
    tax: float = 0.1  # 기본값 0.1
  • description은 제공하지 않아도 됨 (None)
  • tax는 제공하지 않으면 0.1로 설정됨

5. Response Model - 응답 데이터 제어하기

왜 Response Model이 필요한가?

API 응답에서 특정 필드를 제외하고 싶을 때가 있습니다. 예를 들어, 사용자 정보를 반환할 때 비밀번호는 제외해야 하죠.

response_model 사용하기

class UserCreate(BaseModel):
    email: str
    password: str

class UserResponse(BaseModel):
    id: int
    email: str
    # password 필드 없음!

@app.post("/signup", response_model=UserResponse)
def signup(user: UserCreate):
    # DB에 저장하는 로직...
    return {
        "id": 1,
        "email": user.email,
        "password": user.password  # 이 값은 응답에서 제외됨!
    }

response_model=UserResponse를 지정하면:

  • FastAPI가 자동으로 UserResponse 스키마에 맞춰 응답을 필터링합니다
  • password 필드는 응답 JSON에서 제외됩니다
  • API 문서에도 UserResponse 스키마가 표시됩니다

Response Model의 장점

  1. 보안: 민감한 정보를 자동으로 제외
  2. 문서화: Swagger UI에 응답 형식이 자동으로 표시
  3. 타입 안전성: 잘못된 응답 형식이면 500 에러 발생

마무리

오늘 배운 내용을 정리하면:

직렬화/역직렬화: Python 객체 ↔ JSON 변환 자동화
Type Hints: 타입 명시로 코드 안전성 향상
List, Dict, Optional: 복합 타입 활용
Pydantic BaseModel: 강력한 데이터 검증과 모델링
Request Body: POST/PUT 요청으로 복잡한 데이터 받기
Response Model: 응답 데이터 제어 및 보안

Pydantic과 Type Hints는 FastAPI의 핵심 기능입니다. 이를 제대로 활용하면 타입 안전성을 보장하면서도 자동 검증, 자동 문서화를 모두 얻을 수 있습니다. 다음 시간에는 더 고급 Pydantic 기능과 데이터베이스 연동에 대해 알아보겠습니다!


참고 자료:

profile
함께 성장

0개의 댓글