Fastapi

[FastAPI] Pydantic 사용법 (5)

Suda_777 2024. 8. 31. 21:28
반응형

1. Pydantic

1.1. Pydantic 이란?

Pydantic은 파이썬 데이터 클래스와 유사한 구조로 데이터 모델을 정의하고, 이를 통해 데이터 검증과 변환을 수행하는 라이브러리입니다. 특히, 데이터의 유효성을 보장하고 자동으로 타입을 변환해주는 기능을 제공함으로써, 신뢰성 있는 애플리케이션 개발에 도움을 줍니다.

 

Pydantic 사용의 장점

 코드의 간결성: 데이터 검증, 변환 로직을 수동으로 작성할 필요 없이, Pydantic 모델을 정의함으로써 자동으로 처리할 수 있어 코드가 간결해집니다.

안정성: FastAPI와 같은 웹 프레임워크에서는 외부에서 들어오는 데이터가 많기 때문에, 신뢰할 수 있는 데이터 검증이 필수적입니다. Pydantic은 이러한 역할을 수행함으로써 애플리케이션의 안정성을 높입니다.

타입 힌트와의 연계: Pydantic은 파이썬의 타입 힌트를 적극적으로 활용하여, 개발자가 원하는 데이터 구조를 명확하게 표현할 수 있습니다. 이는 IDE의 자동 완성 및 타입 검사를 통해 개발 생산성을 높여줍니다.

확장성: Pydantic의 모델은 매우 유연하고 확장 가능하여, 다양한 형태의 데이터 구조를 지원하고, 필요에 따라 유효성 검사를 커스터마이즈할 수 있습니다.

1.2. Pydantic의 기능

자동 데이터 변환: 잘못된 타입의 데이터를 올바른 타입으로 자동으로 변환해 주며, 예외가 발생하지 않도록 합니다.

 자동 데이터 검증: 클라이언트가 전송한 요청 데이터가 정의된 스키마에 맞는지 자동으로 검증합니다.

 유효성 검사: 특정 필드가 필수인지, 값이 올바른 범위 내에 있는지 등 세부적인 유효성 검사를 간편하게 수행할 수 있습니다.

중첩 모델: Pydantic 모델은 다른 Pydantic 모델을 필드로 포함할 수 있어, 복잡한 데이터 구조를 쉽게 관리할 수 있습니다.

환경 설정 관리: Pydantic의 BaseSettings 클래스를 사용하여 환경 변수나 설정 파일에서 설정 값을 가져와 관리할 수 있습니다.

 


2. Pydantic 모델 생성

2.1. BaseModel 클래스

  • BaseModel 클래스
    • Pydantic의 BaseModel 클래스는 데이터 모델을 정의하는 기본 클래스
    • 이 클래스를 상속받아 새로운 모델을 정의
    • 모델이 정의된 데이터에 대해 자동으로 유효성 검사를 수행
    • 필요시 데이터를 변환하는 기능을 제공

2.2. BaseModel 클래스 사용 예제

  • 클래스의 속성으로 필드 정의
    • 필드에는 기본값을 설정할 수도 있으며, 기본값이 설정되지 않은 필드는 필수 입력 값으로 간주
    • 기본값이 없는 필드는 해당 필드에 값이 제공되지 않으면 검증 시 오류
  • 타입 힌트를 기반으로 데이터 검증을 수행
    • 필드의 이름과 함께 타입을 지정하면, Pydantic은 해당 필드에 대한 데이터 검증을 수행
from pydantic import BaseModel

class Product(BaseModel):
    name: str
    price: float = 0.0  # 기본값 0.0
    in_stock: bool = True  # 기본값 True

 

객체 생성

user = Product(name='test', price=100.0, in_stock=True)
user

 

2.3. Field 사용하기

Field()를 사용하여 기본값과 추가적인 설명을 명시적으로 설정하는 방법은 데이터 검증 및 문서화에 유리합니다. 아래는 주어진 코드를 Field()를 사용하여 변경한 예시입니다

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., description="제품 이름")  # 필수 필드이므로 '...' 사용
    price: float = Field(0.0, description="제품 가격, 기본값은 0.0")  # 기본값 0.0
    in_stock: bool = Field(True, description="재고 여부, 기본값은 True")  # 기본값 True

3. 데이터 검증

3.1. 기본 데이터 검증

만약 타입 힘트에 맞지 않는 데이터가 들어올 시, ValidationError 발생

from pydantic import ValidationError

try:
    user = User(id=123, name="Alice", price=100, in_stock=True)
except ValidationError as e:
    print(e)

 

3.2. 유효성 검사 도구

3.2.1 정규 표현식을 사용한 문자열 검증

정규표현식 형식을 검증

Field에서 pattern을 사용

아래 예시코드는 이메일 형식이 올바른지 자동으로 검증

from pydantic import BaseModel, Field

class User(BaseModel):
    email: str = Field(..., pattern=r'^\S+@\S+\.\S+$')

 

3.2.2. 문자열 검증

EmailStr : 이메일 주소 형식을 검증

from pydantic import BaseModel, EmailStr

class User(BaseModel):
    email: EmailStr

 

AnyUrl: http, https, ftp 등의 다양한 URL 형식을 허용하는 타입

HttpUrl: http와 https로 시작하는 URL을 검증하는 타입

from pydantic import BaseModel, AnyUrl, HttpUrl

class Website(BaseModel):
    url: AnyUrl

class SecureWebsite(BaseModel):
    secure_url: HttpUrl

# 올바른 URL
site = Website(url="ftp://example.com/resource")
secure_site = SecureWebsite(secure_url="https://example.com")

# 잘못된 URL
try:
    site = Website(url="not_a_url")
except ValidationError as e:
    print(e)

 

3.2.3. strict 검증

StrictStr, StrictInt, StrictBool, StrictFloat: 이 타입들은 해당 필드가 정확히 지정된 타입인지 엄격하게 검증,

타입 변환을 시도하지 않으며, 입력된 데이터가 정확히 지정된 타입과 일치하지 않으면 오류를 발생

from pydantic import BaseModel, StrictStr, StrictInt

class Product(BaseModel):
    name: StrictStr
    quantity: StrictInt

 

3.2.4. 범위 검증

StrictStr, StrictInt, StrictBool, StrictFloat

필드에 대해 특정 범위 내에 있는지 검증

from pydantic import BaseModel, constr, conint

class User(BaseModel):
    username: constr(min_length=3, max_length=20)
    age: conint(gt=0, le=120)

 

3.2.5. 필수 필드와 선택 필드

모든 필드가 필수는 아님

Optional 타입 힌트를 사용해 선택적으로 필드를 사용할 수 있도록 함.

from typing import Optional
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    age: Optional[int] = None  # 선택적 필드
    email: str

 

데이터 실행

User(id=1, name="test", email="abc@google.com")

3.3. 커스텀 데이터 검증

사용자 정의 검증 로직 추가

  • @validator 데코레이터를 사용

나이가 양수인지 확인하는 검증 예시코드

from pydantic import BaseModel, field_validator, ValidationInfo

class User(BaseModel):
    age: int

    @field_validator('age')
    def validate_age(cls, v: int) -> int:  # cls 없이 사용 가능
        if v < 0:
            raise ValueError("나이는 0 이상이어야 합니다.")
        return v

 

여러 필드를 동시에 검증

  • @model_validator는 전체 모델의 데이터에 접근할 수 있음
from pydantic import BaseModel, model_validator

class Model(BaseModel):
    x: str
    y: int

    @model_validator(mode='before')
    def validate_model(cls, values):
        if values['x'] == 'error':
            raise ValueError("x에 잘못된 값이 들어갔습니다.")
        return values

Model(x="valid", y=123)

 


 

4. Pydantic 모델의 활용

4.1. Get의 쿼리 매개변수

`Depends()`를 사용하여 FastAPI는 해당 쿼리 매개변수를 모델에 맞춰 자동으로 매핑하고, 모델의 검증 기능을 활용

from fastapi import FastAPI, Depends
from pydantic import BaseModel

app = FastAPI()

# 쿼리 매개변수를 위한 Pydantic 모델 정의
class ItemQueryParams(BaseModel):
    name: str
    price_min: float = 0
    price_max: float = 1000

# 쿼리 매개변수를 Pydantic 모델로 처리
@app.get("/items/")
async def read_items(params: ItemQueryParams = Depends()):
    return {"name": params.name, "price_min": params.price_min, "price_max": params.price_max}

 

4.2. Post에서 Request Body 부분

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    id: int
    name: str
    price: float

# Item 모델을 사용하여 요청 바디로 데이터를 받는 엔드포인트
@app.post("/items/")
async def create_item(item: Item):
    return item

 


5. 모델 중첩

필드의 타입으로 다른 모델을 이용할 수 있음

from pydantic import BaseModel

# Address 모델 정의
class Address(BaseModel):
    street: str
    city: str
    zipcode: str

# User 모델 정의 (Address 모델을 포함)
class User(BaseModel):
    name: str
    age: int
    address: Address  # Address 모델을 필드로 포함

 

아래와 같이 모델에 데이터를 넣음

# 중첩된 데이터 구조를 사용하여 User 모델 인스턴스 생성
user_data = {
    "name": "Alice",
    "age": 30,
    "address": {
        "street": "123 Main St",
        "city": "Wonderland",
        "zipcode": "12345"
    }
}

user = User(**user_data)
print(user)

 


6. ORM 통합

6.1. Pydantic 모델과 ORM 모델의 차이점

Pydantic 모델: 주로 데이터 검증, 변환, API 요청/응답 처리에 사용됩니다. Pydantic은 Python의 데이터 클래스와 유사하게 동작하며, JSON과 같은 데이터를 쉽게 처리할 수 있습니다.

ORM 모델: 데이터베이스와 상호 작용하기 위해 사용됩니다. ORM 모델은 데이터베이스 테이블과 직접 매핑되며, 데이터를 저장, 조회, 업데이트하는 기능을 제공합니다.

 

6.2.  Pydantic과 SQLAlchemy 모델의 통합

Pydantic 모델과 SQLAlchemy 모델을 함께 사용하는 주요 목적은 다음과 같습니다.

  • 데이터베이스의 데이터를 검증된 API 응답으로 변환: 데이터베이스에서 가져온 데이터를 Pydantic 모델을 통해 API 응답으로 제공할 때, 데이터 검증과 직렬화를 자동으로 처리할 수 있습니다.
  • API 요청 데이터를 데이터베이스로 저장: 클라이언트로부터 받은 데이터를 Pydantic 모델로 검증한 후, 이를 SQLAlchemy 모델로 변환하여 데이터베이스에 저장할 수 있습니다.

6.3. 예시 코드

1. SQLAlchemy 모델 정의

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

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)

# 데이터베이스 연결 설정
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

 

2. Pydantic 모델 정의

  • UserBase: 공통 필드를 정의한 기본 Pydantic 모델입니다.
  • UserCreate: 데이터를 생성할 때 사용할 Pydantic 모델입니다.
  • User: 데이터베이스에서 조회한 데이터를 응답할 때 사용할 Pydantic 모델입니다.
from pydantic import BaseModel

class UserBase(BaseModel):
    name: str
    email: str

class UserCreate(UserBase):
    pass  # 추가로 필드를 정의하거나 기능을 확장할 수 있음

class User(UserBase):
    id: int

    class Config:
        orm_mode = True  # ORM 통합 모드 활성화

 

 

3. ORM 통합을 위한 orm_mode

  • Pydantic 모델에 Config 클래스를 정의하고 orm_mode = True로 설정
  • 이를 통해 SQLAlchemy 모델의 데이터를 Pydantic 모델로 변환할 수 있음

pydantic 모델 -> orm 모델 (직접 넣어주어야 함)

def pydantic_to_orm(pydantic_model: UserCreate) -> UserORM:
    return UserORM(name=pydantic_model.name, email=pydantic_model.email)

 

orm 모델 -> pydantic 모델  (from_orm 메소드 사용)

user_orm = UserORM(id=1, name="Alice", email="alice@example.com")
user_pydantic = User.from_orm(user_orm)
print(user_pydantic.json())

 

 

4. Post 에서 사용

client 요청 -> pydantic 모델 -> orm 모델

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/users/", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    db_user = UserORM(name=user.name, email=user.email)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return User.from_orm(db_user)

 

5. Get 에서 사용

데이터베이스 -> ORM 모델 -> Pydantic 모델

@app.get("/users/{user_id}", response_model=User)
def read_user(user_id: int, db: Session = Depends(get_db)):
    db_user = db.query(UserORM).filter(UserORM.id == user_id).first()
    return User.from_orm(db_user)

 

7. Pydantic 설정 관리

pydantic은 애플리케이션 설정을 관리하기 위한 강력한 도구를 제공함. 특히, 환경 변수나 설정 파일을 사용하여 애플리케이션의 설정을 중앙에서 관리하고, 이를 코드와 분리하는 것이 중요. Pydantic의 BaseSettings 클래스를 사용하면 이러한 설정을 간편하게 관리할 수 있다.

 

7.1. settings 관리

  • 보안: 중요한 설정(예: 데이터베이스 URL, API 키)을 코드에서 분리
  • 유지보수성: 설정이 중앙화되어 있어 관리가 쉬워짐
  • 유연성: 환경에 따라 설정을 변경하기 쉽습니다(예: 개발 환경과 배포 환경에서 다른 설정 사용).

env_file 설정을 통해 .env 파일에서 설정 값을 로드할 수 있음

from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str = "My FastAPI Application"
    debug: bool = False
    database_url: str

    class Config:
        case_sensitive = False  # 대소문자 구분을 하지 않음
		env_file = ".env"  # 설정 파일 경로를 지정


# 설정 인스턴스를 생성
settings = Settings()

print(settings.app_name)  # 출력: My FastAPI Application
print(settings.debug)      # 출력: False
print(settings.database_url)  # 환경 변수나 .env 파일에 지정된 데이터베이스 URL

 

BaseSettings 클래스에서 설정 필드의 값은 다음과 같은 우선순위로 결정

1. 환경 변수: 시스템 환경 변수에 설정된 값이 가장 높은 우선순위를 가집니다.

2. 설정 파일: .env 파일이나 다른 설정 파일에서 로드된 값이 그다음 우선순위를 가집니다.

3. 클래스의 기본값: 클래스에서 정의한 기본값이 우선순위의 마지막을 차지합니다.

 

7.2. 설정 값의 동적 로드

class Settings(BaseSettings):
    app_name: str
    debug: bool

    class Config:
        env_file = ".env.dev" if __debug__ else ".env.prod"

settings = Settings()

print(settings.debug)  # 개발 또는 프로덕션 환경에 따라 다른 값이 로드됨

 

반응형