기능 | FastAPI (Pydantic ) | Django (Serializer ) |
---|---|---|
데이터 검증 | ✅ 가능 (BaseModel 사용) | ✅ 가능 (serializers.Serializer 사용) |
JSON 직렬화 | ✅ 가능 (Python 객체 → JSON 변환) | ✅ 가능 (.data 사용) |
DB 모델 연동 | ❌ X (Pydantic 은 DB 모델과 분리됨) | ✅ 가능 (serializers.ModelSerializer 사용 가능) |
데이터 역직렬화 | ✅ 가능 (JSON → Pydantic 객체) | ✅ 가능 (.is_valid() 사용) |
요청 데이터 처리 | ✅ request.body 자동 변환 | ✅ request.data (Django REST Framework 필요) |
🔹 결론:
Pydantic
은 DB 모델과 완전히 독립적이며, 요청 데이터를 검증하는 데 사용됨. Serializer
는 Django 모델과 연동 가능 (ModelSerializer
지원) @field_validator("<필드 이름>"), @classmethod
데코레이터를 사용하여 특정 필드의 값을 검증from pydantic import BaseModel, field_validator
class ExampleModel(BaseModel):
number: int
@field_validator("number")
@classmethod
def validator_number(cls, value):
if value < 1 :
raise ValueError("Number must be at least 1")
if value > 100 :
raise ValueError("Number must not exceed 100")
return Value
example = ExampleModel(number = 50)
print(example) # Output : number 50
- 단일 필드 검증을 넘어 필드 간 관계를 고려한 검증이 필요
- 실제 서비스에서 발생할 수 있는 논리적 오류를 방지
- 보안, 데이터 정합성을 보장하기 위해 더 정교한 검증이 필요
@field_validator
from pydantic import BaseModel, field_validator, ValidationError
special_word = "!@#$%^&*()-_+="
class UserRegister(BaseModel):
username : str
password : str
@field_validator("password")
@classmethod
def validate_password(cls, value):
if len(value) < 8:
raise ValueError("password must be at least 8 characters")
flag = False
for char in value:
if char.isupper():
flag = True
break
if count == 0 :
raise ValueError("password must contain at least one uppercase letter")
if not any(char in special_word for char in value):
raise ValueError("password must contain at least one special character")
return value
class UserRegister(BaseModel):
username: str
password: str
@field_validator("password")
@classmethod
def validate_password(cls, value):
if len(value) < 8:
raise ValueError("Password must be at least 8 characters long")
if not re.search(r"[A-Z]", value):
raise ValueError("Password must contain at least one uppercase letter")
if not re.search(r"\d", value):
raise ValueError("Password must contain at least one number")
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", value):
raise ValueError("Password must contain at least one special character")
return value
@model_validator
poetry add email-validator
before
after
class UserRegister(BaseModel):
username : str
password : str
confirm_password : str
@model_validator(mode="after")
def check_password_match(self):
if self.password != self.confirm_password:
raise ValueError("Passwords do not match")
return self
from pydantic import BaseModel, model_validator, Field, EmailStr
class ContactInfo(BaseModel):
email : EmailStr | None = None
phone_number : str | None = None # Optional , 선택적이란 의미
@model_validator(mode="after")
def exist_email_phone(self):
if self.email is None and self.phone_number is None:
raise ValueError("Must have email or phone_number")
return self
# no_email = ContactInfo(phone_number="phone_number")
no_phone_number = ContactInfo(email="email@email.com")
# no = ContactInfo()
print(no_phone_number.email)
@model_validator(mode="before")
@classmethod
def preprocess_username(cls, data):
# 객체가 특정 클래스(또는 데이터 타입)의 인스턴스인지 확인하는 함수
if isinstance(data, dict) and "username" in data:
data["username"] = data["username"].lower()
return data
@computed_field
- 자동계산 필드
- 입렵값 기반으로 자동 계산되는 필드
- 나이대신 생년원일을 입력받고 자동으로 계산
- 정가와 할인율을 입력하면 할인가를 자동으로 계산
from pydantic import BaseModel, computed_field
from datetime import datetime
class User(BaseModel):
name : str
birth_year : int
@computed_field
@property
def age(self) -> int:
return datetime.now().yaer - self.birth_year
user = User(name="Alice", brith_year = 2000)
print(user.age) # output : 25
default_factory
- 초기값을 동적으로 설정할 떄 사용
- 자동증가 ID
- created_at을 현재시간으로 설정
- verification code를 위해 6개의 랜덤숫자 설정
- default_factory는 해당 필드에 값이 제공되지 않았을 때, 기본적으로 어떤 값을 넣을지를 지정하는 기능
- 기본값을 제공하지 않으면 실행되는 기능
from pydantic import BaseModel, Field
from datetime import datetime
class LogEntry(BaseModel):
message: str
created_at: datetime = Field(default_factory=datetime.utcnow)
log1 = LogEntry(message="System started")
print(log1.created_at) # 자동생성된 UTC 시간
#
import uuid
from datetime import datetime
from pydantic import BaseModel, Field
class User(BaseModel):
# uuid.uuid4() : 랜덤한 UUID 생성
user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str
role: str = Field(default="user")
created_at : str = Field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
user1 = User(name="alice")
print(user1)
#
import random
from datetime import datetime, timedelta
from pydantic import BaseModel, Field, field_serializer
class Otp(BaseModel):
phone_number : str
otp: int=Field(default_factory=lambda: random.randint(100000,999999))
otp_expiry: str=Field(default_factory=lambda : (datetime.now()+ timedelta(minutes=5)).isoformat())
# @field_serializer("otp_expiry")
# def serialize_datetime(self, value: datetime) -> str:
# return value.isoformat()
otp = Otp(phone_number="010")
print(otp)
- 특정 필드에 여러 타입 지원
- product_id가 int또는 str일 경우
- score가 int또는 float일 경우
Union
사용
from pydantic import
from typing import Union
class Product(BaseModel):
product_id: Union[int, str]
name: str
p1 = Product(product_id=123, name="Laptop")
p2 = Product(product_id="XYZ-456", name="Phone")
print(p1.product_id) # 123
print(p2.product_id) # "XYZ-456"
json_encoders
, @field_serializer
- 직렬화 Serialization: 다양한 종류의 데이터를 기계가 쓰고 읽기 편리하게 나타낸 방식
- 기본적으로 datetime, Decimal 등은 JSON으로 직렬화할 수 없음
- API응답에서 날짜 형식을 통일하거나 소수점 자리수 제한할 때 필요
- json_encoders는 Pydantic에서 특정 데이터 타입을 JSON으로 변환할 때 사용하는 커스텀 인코더
- datetime을 직접 str 형식으로 변환해서 JSON으로 출력하고 싶을 때 사용
- 기본 FastAPI 서버내에서는 기본값 유지
from pydantic import BaseModel
from datetime import datetime
class Order(BaseModel):
order_id: int
total_price: float
created_at: datetime
# class Config:
# json_encoders = {
# datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")
# }
@field_serializer("created_at")
def serialize_datetime(self, value: datetime) -> str:
return value.strftime("%Y-%m-%d %H:%M:%S")
order = Order(order_id=1, total_price=100.5, created_at = datetime.now())
print(order.model_dump_json())
response_model_exclude
- 응답에서 민감한정보(비밀번호, API Key 등)을 숨기고 싶을 떄
- 유저 권한에 따라 보여줄 데이터 조절
- admin은 모든 필드
- 일반 유저는 일부 피륻
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
username: str
email: str
password: str
@app.get("/users/me", response_model=User, response_model_exclude={"password"})
async def get_user():
user_data = {"username":"testuser","email":"test@test.com","password":"mypassword"}
return User(**user_data)
# username, email 만 출력됨
Response
, xml.etree.ElementTree
- API가 JSON 뿐만 아니라 XML도 지원해야할 때
- 사용자가 원하는 응답 포맷을 동적으로 변경하도록 설정
from fastapi import FastAPI
from fastapi.responses import JSONResponse, Response
import xml.etree.ElementTree as ET
app = FastAPI()
@app.get("/data/")
async def get_data(format: str="json"):
data = {"message": "Hello, FastAPI"}
if format == "xml":
root = ET.Element("response")
message = ET.SubElement(root, "message")
message.text = data["message"]
xml_str = ET.tostring(root, encoding="utf-8", method="xml")
return Response(content=xml_str, media_type="application/xml")
return JSONResponse(content=data)
# GET /data/?fromat=xml
# <response>
# <message>Hello, FastAPI</message>
# </response>
- 글로벌 API에서는 사용자의 언어에 따라 다른 메세지 반환 필요
- 브라우저 요청 헤더
Accept-Lanugage
를 사용하여 자동 감지- 지원하는 언어가 없으면 기본값을 설정해야 함
from fastapi import FastAPI, Header
app = FastAPI()
responses = {
"en": {"message": "Hello, welcome!"},
"ko": {"message": "안녕하세요, 환영합니다!"},
"fr": {"message": "Bonjour, bienvenue!"}
}
@app.get("/greet/")
async def greet(accept_language: str = Header("en")):
return responses.get(accept_language, responses["en"]) # 기본값 en