날짜: 2025-06-15
from pydantic import BaseModel, Field, field_validator, model_validator, field_serializer, computed_field
from enum import Enum
from datetime import datetime, date
from typing import Any
class TypeEnum(str, Enum):
def __new__(cls, value: str, label: str):
obj = str.__new__(cls)
obj._value_ = value
obj.label = label
return obj
class UserType(TypeEnum):
NORMAL = "NORMAL", "일반 회원"
ADMIN = "ADMIN", "관리자"
GUEST = "GUEST", "비회원"
class Role(str, Enum):
USER = "user"
ADMIN = "admin"
class User(BaseModel):
id: int = Field(..., title="사용자 ID", description="정수형 고유 ID")
name: str = Field(..., title="이름", description="사용자 이름")
age: int = Field(..., title="나이", description="0 이상", ge=0)
role: Role = Field(default=Role.USER, title="역할", description="사용자 역할 (Enum)")
email: str | None = Field(
default=None,
alias="email_address",
title="이메일",
description="nullable 이메일 주소"
)
bio: str | None = Field(default=None, title="소개", description="nullable 자기소개")
score: float = Field(default=1.0, title="점수", description="0보다 커야 함", gt=0)
created_at: datetime = Field(default_factory=datetime.utcnow)
birthday: date | None = Field(default=None, title="생일")
misc: Any | None = Field(default=None, title="기타 정보")
user_type: UserType = Field(default=UserType.NORMAL)
# -------------------------
# ✅ 필드 유효성 검사기
@field_validator("name")
def strip_and_capitalize_name(cls, v: str) -> str:
return v.strip().title()
@field_validator("score")
def round_score(cls, v: float) -> float:
return round(v, 2)
# -------------------------
# ✅ 모델 전처리
@model_validator(mode="before")
@classmethod
def preprocess_input(cls, data: dict) -> dict:
data["name"] = data.get("name", "").replace(" ", " ")
return data
# -------------------------
# ✅ 모델 후처리
@model_validator(mode="after")
def validate_logic(self) -> "User":
if self.role == Role.ADMIN and self.age < 18:
raise ValueError("관리자는 18세 이상이어야 합니다")
return self
# -------------------------
# ✅ 응답 직렬화
@field_serializer("created_at")
def format_created_at(self, value: datetime, _info) -> str:
return value.strftime("%Y-%m-%d %H:%M")
# -------------------------
# ✅ 계산 필드 (출력 전용)
@computed_field
@property
def is_adult(self) -> bool:
return self.age >= 18
@computed_field
@property
def user_type_label(self) -> str:
return self.user_type.label
# -------------------------
# ✅ 모델 전역 설정
model_config = {
"use_enum_values": True,
"populate_by_name": True,
"str_strip_whitespace": True,
"coerce_numbers_to_str": False, # 예시: string 필드에 123 들어오면 에러 발생
"extra": "forbid", # 모델 정의 외 필드는 허용 안 함
"frozen": False, # True로 하면 인스턴스 수정 불가
"json_schema_extra": {
"examples": [
{
"id": 1,
"name": "홍길동",
"age": 30,
"role": "user",
"email_address": "hong@example.com",
"score": 3.14,
"birthday": "1990-01-01"
}
]
}
}
from sqlmodel import SQLModel, Field
from datetime import datetime, date
from typing import Any
from enum import Enum
class TypeEnum(str, Enum):
def __new__(cls, value: str, label: str):
obj = str.__new__(cls)
obj._value_ = value
obj.label = label
return obj
class UserType(TypeEnum):
NORMAL = "NORMAL", "일반 회원"
ADMIN = "ADMIN", "관리자"
GUEST = "GUEST", "비회원"
class Role(str, Enum):
USER = "user"
ADMIN = "admin"
class User(SQLModel, table=True):
__tablename__ = "user"
id: int | None = Field(default=None, primary_key=True, title="사용자 ID")
name: str = Field(title="이름", max_length=100)
age: int = Field(ge=0, title="나이")
role: Role = Field(default=Role.USER, title="역할")
email: str | None = Field(default=None, title="이메일", max_length=255)
bio: str | None = Field(default=None, title="소개")
score: float = Field(default=1.0, gt=0, title="점수")
created_at: datetime = Field(default_factory=datetime.utcnow)
birthday: date | None = Field(default=None)
misc: Any | None = Field(default=None)
user_type: UserType = Field(default=UserType.NORMAL, title="회원 유형")
# ✅ Enum label 프로퍼티
@property
def user_type_label(self) -> str:
return self.user_type.label