Study/fastapi

pydantic의 orm_model=true, 가상필드, validator 정리

bluebamus 2025. 1. 31.

1. orm_mode=True 설정과 사용법

   1) 기본 개념

      - orm_mode=True를 설정하면, SQLAlchemy ORM 객체를 Pydantic 모델로 변환할 수 있다.
      - 일반적으로 BaseModel은 딕셔너리 데이터를 기대하지만, orm_mode를 사용하면 ORM 객체에서도 속성을 추출할 수 있다.

 

   2) 예제 코드 (SQLAlchemy ORM + Pydantic)

from typing import Optional
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from pydantic import BaseModel

DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

# ✅ SQLAlchemy 모델 정의
class UserORM(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    email = Column(String, unique=True, index=True)

# ✅ Pydantic 모델 정의
class UserSchema(BaseModel):
    id: int
    name: str
    email: str

    class Config:
        from_attributes = True  # orm_mode=True (FastAPI 최신 버전에서는 from_attributes 사용)

# ✅ ORM 객체를 Pydantic 모델로 변환하는 예제
db = SessionLocal()
user_orm = UserORM(id=1, name="John Doe", email="john@example.com")
user_pydantic = UserSchema.model_validate(user_orm)  # ORM 객체를 Pydantic 모델로 변환

print(user_pydantic)  # UserSchema(id=1, name='John Doe', email='john@example.com')

 

2. 가상 필드 (@computed_property) 사용 방법

   1) 기본 개념

      - Pydantic에서 데이터베이스에 존재하지 않는 "가상 필드"를 추가하려면 computed_property를 사용한다.

      - Pydantic 모델의 @property는 동작하지 않음.
      - Pydantic v2 → @computed_field 사용
      - Pydantic v1 → @root_validator 사용

 

   2) @computed_property 사용 (Pydantic v2)

      - Pydantic 2.x에서는 @computed_property를 사용해야 한다.

from pydantic import BaseModel, computed_field

class OrderSchema(BaseModel):
    id: int
    product_name: str
    quantity: int
    price: float
    is_paid: bool

    @computed_field  # ✅ Pydantic 2.x에서 사용
    def total_price(self) -> float:
        return self.quantity * self.price

    @computed_field
    def payment_status(self) -> str:
        return "Paid" if self.is_paid else "Pending"

    class Config:
        from_attributes = True  # ORM 변환 가능

 

   3)  @property 대신 @root_validator 사용 (Pydantic v1)

      - Pydantic 1.x에서는 @computed_field이 없으므로, @root_validator를 사용해야 한다.

from pydantic import BaseModel, root_validator

class OrderSchema(BaseModel):
    id: int
    product_name: str
    quantity: int
    price: float
    is_paid: bool
    total_price: float
    payment_status: str

    @root_validator(pre=True)
    def calculate_fields(cls, values):
        values["total_price"] = values["quantity"] * values["price"]
        values["payment_status"] = "Paid" if values["is_paid"] else "Pending"
        return values

    class Config:
        from_attributes = True  # ORM 변환 가능

 

3. 다양한 Validator 사용 방법

   - @field_validator를 사용하여 입력 값을 검증합니다.
   - @model_validator를 사용하여 여러 필드 간의 관계를 검증할 수 있습니다.

 

   1) 단일 필드 검증 (@field_validator)

from pydantic import field_validator, EmailStr

class UserSchemaValidated(BaseModel):
    name: str
    email: EmailStr  # 기본 이메일 검증 적용

    @field_validator("name")
    @classmethod
    def validate_name(cls, value: str) -> str:
        if len(value) < 3:
            raise ValueError("이름은 최소 3자 이상이어야 합니다.")
        return value

user = UserSchemaValidated(name="John", email="john@example.com")
print(user)  # 정상 작동

# 오류 발생 예시
try:
    UserSchemaValidated(name="Jo", email="john@example.com")  # 이름이 너무 짧음
except ValueError as e:
    print(e)  # "이름은 최소 3자 이상이어야 합니다."

 

   2) 여러 필드 간 관계 검증 (@model_validator)

from pydantic import model_validator

class UserWithPassword(BaseModel):
    password: str
    password_confirm: str

    @model_validator(mode="before")
    @classmethod
    def check_passwords(cls, values):
        if values["password"] != values["password_confirm"]:
            raise ValueError("비밀번호가 일치하지 않습니다.")
        return values

# 정상 동작
user = UserWithPassword(password="securepass", password_confirm="securepass")

# 오류 발생 예시
try:
    UserWithPassword(password="securepass", password_confirm="wrongpass")
except ValueError as e:
    print(e)  # "비밀번호가 일치하지 않습니다."

 

댓글