383 lines
11 KiB
Markdown
383 lines
11 KiB
Markdown
# Pydantic ConfigDict 사용 매뉴얼
|
|
|
|
## 개요
|
|
|
|
Pydantic v2에서 `ConfigDict`는 모델의 유효성 검사, 직렬화, JSON 스키마 생성 등의 동작을 제어하는 설정을 정의하는 TypedDict입니다.
|
|
|
|
> Pydantic v1의 `class Config`는 더 이상 권장되지 않으며, `ConfigDict`를 사용해야 합니다.
|
|
|
|
## 기본 사용법
|
|
|
|
```python
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
class MyModel(BaseModel):
|
|
model_config = ConfigDict(
|
|
str_strip_whitespace=True,
|
|
strict=True
|
|
)
|
|
|
|
name: str
|
|
age: int
|
|
```
|
|
|
|
## 설정 옵션 전체 목록
|
|
|
|
### 문자열 처리
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `str_to_lower` | `bool` | `False` | 문자열을 소문자로 변환 |
|
|
| `str_to_upper` | `bool` | `False` | 문자열을 대문자로 변환 |
|
|
| `str_strip_whitespace` | `bool` | `False` | 문자열 앞뒤 공백 제거 |
|
|
| `str_min_length` | `int \| None` | `None` | 문자열 최소 길이 |
|
|
| `str_max_length` | `int \| None` | `None` | 문자열 최대 길이 |
|
|
|
|
**예시:**
|
|
```python
|
|
class UserInput(BaseModel):
|
|
model_config = ConfigDict(
|
|
str_strip_whitespace=True,
|
|
str_to_lower=True,
|
|
str_min_length=1,
|
|
str_max_length=100
|
|
)
|
|
|
|
username: str
|
|
|
|
user = UserInput(username=" HELLO ")
|
|
print(user.username) # "hello"
|
|
```
|
|
|
|
### 유효성 검사
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `strict` | `bool` | `False` | 엄격한 타입 검사 활성화 (타입 강제 변환 비활성화) |
|
|
| `validate_assignment` | `bool` | `False` | 속성 할당 시 유효성 검사 수행 |
|
|
| `validate_default` | `bool` | `False` | 기본값도 유효성 검사 수행 |
|
|
| `validate_return` | `bool` | `False` | 반환값 유효성 검사 |
|
|
| `revalidate_instances` | `Literal['always', 'never', 'subclass-instances']` | `'never'` | 모델 인스턴스 재검증 시점 |
|
|
| `arbitrary_types_allowed` | `bool` | `False` | Pydantic이 지원하지 않는 타입 허용 |
|
|
|
|
**예시 - strict 모드:**
|
|
```python
|
|
class StrictModel(BaseModel):
|
|
model_config = ConfigDict(strict=True)
|
|
|
|
count: int
|
|
|
|
# strict=False (기본값): "123" -> 123 자동 변환
|
|
# strict=True: "123" 입력 시 ValidationError 발생
|
|
```
|
|
|
|
**예시 - validate_assignment:**
|
|
```python
|
|
class User(BaseModel):
|
|
model_config = ConfigDict(validate_assignment=True)
|
|
|
|
age: int
|
|
|
|
user = User(age=25)
|
|
user.age = "invalid" # ValidationError 발생
|
|
```
|
|
|
|
### Extra 필드 처리
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `extra` | `'allow' \| 'ignore' \| 'forbid'` | `'ignore'` | 추가 필드 처리 방식 |
|
|
|
|
**값 설명:**
|
|
- `'ignore'`: 추가 필드 무시 (기본값)
|
|
- `'allow'`: 추가 필드 허용, `__pydantic_extra__`에 저장
|
|
- `'forbid'`: 추가 필드 입력 시 에러 발생
|
|
|
|
**예시:**
|
|
```python
|
|
class AllowExtra(BaseModel):
|
|
model_config = ConfigDict(extra='allow')
|
|
|
|
name: str
|
|
|
|
data = AllowExtra(name="John", unknown_field="value")
|
|
print(data.__pydantic_extra__) # {'unknown_field': 'value'}
|
|
|
|
class ForbidExtra(BaseModel):
|
|
model_config = ConfigDict(extra='forbid')
|
|
|
|
name: str
|
|
|
|
ForbidExtra(name="John", unknown="value") # ValidationError 발생
|
|
```
|
|
|
|
### 불변성 (Immutability)
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `frozen` | `bool` | `False` | 모델을 불변(immutable)으로 만듦, `__hash__()` 구현 |
|
|
|
|
**예시:**
|
|
```python
|
|
class ImmutableUser(BaseModel):
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
name: str
|
|
age: int
|
|
|
|
user = ImmutableUser(name="John", age=30)
|
|
user.age = 31 # 에러 발생: Instance is frozen
|
|
|
|
# frozen=True이면 해시 가능
|
|
users_set = {user} # 정상 작동
|
|
```
|
|
|
|
### 별칭 (Alias) 설정
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `populate_by_name` | `bool` | `False` | 필드명과 별칭 모두로 값 설정 허용 (deprecated) |
|
|
| `validate_by_alias` | `bool` | `True` | 별칭으로 필드 값 설정 허용 |
|
|
| `validate_by_name` | `bool` | `False` | 별칭이 있어도 필드명으로 값 설정 허용 |
|
|
| `serialize_by_alias` | `bool` | `False` | 직렬화 시 별칭 사용 |
|
|
| `alias_generator` | `Callable[[str], str] \| None` | `None` | 별칭 자동 생성 함수 |
|
|
| `loc_by_alias` | `bool` | `True` | 에러 위치에 별칭 사용 |
|
|
|
|
**예시:**
|
|
```python
|
|
from pydantic import Field
|
|
|
|
class APIResponse(BaseModel):
|
|
model_config = ConfigDict(
|
|
validate_by_alias=True,
|
|
validate_by_name=True,
|
|
serialize_by_alias=True
|
|
)
|
|
|
|
user_name: str = Field(alias="userName")
|
|
|
|
# 둘 다 가능
|
|
response1 = APIResponse(userName="John")
|
|
response2 = APIResponse(user_name="John")
|
|
|
|
print(response1.model_dump(by_alias=True)) # {"userName": "John"}
|
|
```
|
|
|
|
**예시 - alias_generator:**
|
|
```python
|
|
def to_camel(name: str) -> str:
|
|
parts = name.split('_')
|
|
return parts[0] + ''.join(word.capitalize() for word in parts[1:])
|
|
|
|
class CamelModel(BaseModel):
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
serialize_by_alias=True
|
|
)
|
|
|
|
first_name: str
|
|
last_name: str
|
|
|
|
data = CamelModel(firstName="John", lastName="Doe")
|
|
print(data.model_dump(by_alias=True))
|
|
# {"firstName": "John", "lastName": "Doe"}
|
|
```
|
|
|
|
### JSON 스키마
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `title` | `str \| None` | `None` | JSON 스키마 타이틀 |
|
|
| `json_schema_extra` | `dict \| Callable \| None` | `None` | JSON 스키마에 추가할 정보 |
|
|
| `json_schema_serialization_defaults_required` | `bool` | `False` | 직렬화 스키마에서 기본값이 있는 필드도 required로 표시 |
|
|
| `json_schema_mode_override` | `Literal['validation', 'serialization', None]` | `None` | JSON 스키마 모드 강제 지정 |
|
|
|
|
**예시 - json_schema_extra:**
|
|
```python
|
|
class Product(BaseModel):
|
|
model_config = ConfigDict(
|
|
title="상품 정보",
|
|
json_schema_extra={
|
|
"example": {
|
|
"name": "노트북",
|
|
"price": 1500000
|
|
},
|
|
"description": "상품 데이터를 나타내는 모델"
|
|
}
|
|
)
|
|
|
|
name: str
|
|
price: int
|
|
|
|
# OpenAPI/Swagger 문서에 예시가 표시됨
|
|
```
|
|
|
|
**예시 - Callable json_schema_extra:**
|
|
```python
|
|
def add_examples(schema: dict) -> dict:
|
|
schema["examples"] = [
|
|
{"name": "예시1", "value": 100},
|
|
{"name": "예시2", "value": 200}
|
|
]
|
|
return schema
|
|
|
|
class DynamicSchema(BaseModel):
|
|
model_config = ConfigDict(json_schema_extra=add_examples)
|
|
|
|
name: str
|
|
value: int
|
|
```
|
|
|
|
### ORM/속성 모드
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `from_attributes` | `bool` | `False` | 객체 속성에서 모델 생성 허용 (SQLAlchemy 등) |
|
|
|
|
**예시:**
|
|
```python
|
|
class UserORM:
|
|
def __init__(self, name: str, age: int):
|
|
self.name = name
|
|
self.age = age
|
|
|
|
class UserModel(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
name: str
|
|
age: int
|
|
|
|
orm_user = UserORM(name="John", age=30)
|
|
pydantic_user = UserModel.model_validate(orm_user)
|
|
print(pydantic_user) # name='John' age=30
|
|
```
|
|
|
|
### Enum 처리
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `use_enum_values` | `bool` | `False` | Enum 대신 값(value)으로 저장 |
|
|
|
|
**예시:**
|
|
```python
|
|
from enum import Enum
|
|
|
|
class Status(Enum):
|
|
ACTIVE = "active"
|
|
INACTIVE = "inactive"
|
|
|
|
class User(BaseModel):
|
|
model_config = ConfigDict(use_enum_values=True)
|
|
|
|
status: Status
|
|
|
|
user = User(status=Status.ACTIVE)
|
|
print(user.status) # "active" (문자열)
|
|
print(type(user.status)) # <class 'str'>
|
|
|
|
# use_enum_values=False (기본값)이면
|
|
# user.status는 Status.ACTIVE (Enum 객체)
|
|
```
|
|
|
|
### 직렬화 설정
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `ser_json_timedelta` | `'iso8601' \| 'float'` | `'iso8601'` | timedelta JSON 직렬화 형식 |
|
|
| `ser_json_bytes` | `'utf8' \| 'base64' \| 'hex'` | `'utf8'` | bytes JSON 직렬화 인코딩 |
|
|
| `ser_json_inf_nan` | `'null' \| 'constants' \| 'strings'` | `'null'` | 무한대/NaN JSON 직렬화 형식 |
|
|
|
|
### 숫자/Float 설정
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `allow_inf_nan` | `bool` | `True` | float에서 무한대/NaN 허용 |
|
|
| `coerce_numbers_to_str` | `bool` | `False` | 숫자를 문자열로 강제 변환 허용 |
|
|
|
|
### 기타 설정
|
|
|
|
| 옵션 | 타입 | 기본값 | 설명 |
|
|
|------|------|--------|------|
|
|
| `protected_namespaces` | `tuple[str, ...]` | `('model_',)` | 보호할 필드명 접두사 |
|
|
| `hide_input_in_errors` | `bool` | `False` | 에러 메시지에서 입력값 숨김 |
|
|
| `defer_build` | `bool` | `False` | validator/serializer 빌드 지연 |
|
|
| `use_attribute_docstrings` | `bool` | `False` | 속성 docstring을 필드 설명으로 사용 |
|
|
| `regex_engine` | `'rust-regex' \| 'python-re'` | `'rust-regex'` | 정규식 엔진 선택 |
|
|
| `validation_error_cause` | `bool` | `False` | Python 예외를 에러 원인에 포함 |
|
|
|
|
## 설정 상속
|
|
|
|
자식 모델은 부모 모델의 `model_config`를 상속받습니다.
|
|
|
|
```python
|
|
class ParentModel(BaseModel):
|
|
model_config = ConfigDict(
|
|
str_strip_whitespace=True,
|
|
extra='allow'
|
|
)
|
|
|
|
name: str
|
|
|
|
class ChildModel(ParentModel):
|
|
model_config = ConfigDict(
|
|
frozen=True # 부모 설정 + frozen=True
|
|
)
|
|
|
|
age: int
|
|
|
|
# ChildModel은 str_strip_whitespace=True, extra='allow', frozen=True
|
|
```
|
|
|
|
## FastAPI와 함께 사용
|
|
|
|
FastAPI에서 요청/응답 스키마로 사용할 때 특히 유용합니다.
|
|
|
|
```python
|
|
from fastapi import FastAPI
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
app = FastAPI()
|
|
|
|
class CreateUserRequest(BaseModel):
|
|
model_config = ConfigDict(
|
|
str_strip_whitespace=True,
|
|
json_schema_extra={
|
|
"example": {
|
|
"username": "johndoe",
|
|
"email": "john@example.com"
|
|
}
|
|
}
|
|
)
|
|
|
|
username: str = Field(..., min_length=3, max_length=50)
|
|
email: str
|
|
|
|
class UserResponse(BaseModel):
|
|
model_config = ConfigDict(
|
|
from_attributes=True, # ORM 객체에서 변환 가능
|
|
serialize_by_alias=True
|
|
)
|
|
|
|
id: int
|
|
user_name: str = Field(alias="userName")
|
|
|
|
@app.post("/users", response_model=UserResponse)
|
|
async def create_user(user: CreateUserRequest):
|
|
# user.username은 자동으로 공백이 제거됨
|
|
...
|
|
```
|
|
|
|
## 주의사항
|
|
|
|
1. **v1에서 마이그레이션**: `class Config`는 deprecated입니다. `model_config = ConfigDict(...)`를 사용하세요.
|
|
|
|
2. **populate_by_name은 deprecated**: `validate_by_alias`와 `validate_by_name`을 함께 사용하세요.
|
|
|
|
3. **json_encoders는 deprecated**: 커스텀 직렬화가 필요하면 `@field_serializer` 데코레이터를 사용하세요.
|
|
|
|
## 참고 자료
|
|
|
|
- [Pydantic Configuration API 공식 문서](https://docs.pydantic.dev/latest/api/config/)
|
|
- [Pydantic Models 개념](https://docs.pydantic.dev/latest/concepts/models/)
|
|
- [Pydantic Migration Guide](https://docs.pydantic.dev/latest/migration/)
|