# 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/)
