#0155（2025/05/30）Pydantic BaseSettings

# Pydantic BaseSettings **攻略ガイド**

> **Why BaseSettings?**
>
> * **環境依存値を型安全に管理**し、設定ミスをゼロに。
> * **ローカル → CI → ステージング → 本番** まで同一コードを使い回し。
> * **Secrets・Kubernetes・リモート設定サービス** との統合を最小コードで実現。

---

## 1. インストール & バージョンガイド

* **現行最新版 (v2.6 〜)**

  ```bash
  pip install "pydantic[dotenv]~=2.6"
  ```

  * `dotenv` extras を入れると `.env` 自動読込が可能。
  * 依存: Python 3.8+
* **レガシー (v1.x)**

  ```bash
  pip install "pydantic<2"
  ```

  * 新規プロジェクトなら v2 を強く推奨。

### バージョン判定ワンライナー

```python
import pydantic, sys
print(pydantic.version.VERSION, sys.version)
```

### v1 → v2 で得られる主なメリット

* **40〜50% 高速化**（新パースエンジン）
* **バリデータがデコレータ一発** `@field_validator`
* **型エラー時のメッセージがより詳細 & 多言語化**
* **settings\_customise\_sources** によるカスタムソース統合が簡潔

---

## 2. 基本の使い方 ― 5 分で “Hello, Settings”

```python
# settings.py
from pydantic import BaseSettings
class Settings(BaseSettings):
    app_name: str = "MyApp"
    port: int
    debug: bool = False

    model_config = {
        "env_prefix": "APP_",       # 全フィールド共通プレフィックス
        "env_file": ".env",         # カレントの .env を読込
        "env_file_encoding": "utf-8",
    }

settings = Settings()   # ⚡️ 一行で解決
```

**`.env` サンプル**

```
APP_PORT=8000
APP_DEBUG=true
```

### ここだけは覚えて：読み込み優先順位

1. 直接 `Settings(port=9999)` など **コード引数**
2. **環境変数**（例: `export APP_PORT=8888`）
3. **.env ファイル**
4. **デフォルト値**（クラス宣言内）

インフラ→CI→ローカル の上書き戦略がシンプル！

---

## 3. よく使うテクニック集

### 3‑1. シングルトン化で多重ロード防止

```python
from functools import lru_cache
@lru_cache
def get_settings():
    return Settings()
```

→ FastAPI では `Depends(get_settings)` で DI。

### 3‑2. プレフィックス & エイリアス

```python
from pydantic import Field
class API(BaseSettings):
    url: str = Field(alias="SERVICE_URL")
    timeout: int = Field(default=5, alias="SERVICE_TIMEOUT")
```

→ **外部サービス名変更時に Python コードは無改修**。

### 3‑3. Secret 値 & マスキング

```python
from pydantic import SecretStr
class Creds(BaseSettings):
    password: SecretStr
```

* `print(creds)` → `password=SecretStr('**********')`
* `creds.password.get_secret_value()` で生値取得。

### 3‑4. 型変換 Tips

* `bool` は `"true" "True" "1"` など **case-insensitive** で `True`。
* `list[int]` は `"1,2,3"` または JSON 文字列 `[1, 2, 3]` を受容。
* `Path` 型は OS パスセパレータを自動調整（Windows↔Unix）。

---

## 4. 応用：マルチ環境 & カスタムソース

### 4‑1. 環境別 .env を自動切替

```python
import os
class EnvSettings(BaseSettings):
    env: str = os.getenv("ENV", "local")
    model_config = {"env_file": f".env.{env}"}
```

* `ENV=prod python main.py` で `.env.prod` を読込。
* CI では `.env.ci` を用意するとセキュア。

### 4‑2. YAML + HTTP + env の 3 層構成例

```python
from pathlib import Path
import yaml, requests
from pydantic import BaseSettings

def yaml_source(_: BaseSettings):
    yml = Path("config.yaml")
    return yaml.safe_load(yml.read_text()) if yml.exists() else {}

def http_source(_: BaseSettings):
    r = requests.get("http://config-svc.local/config")
    return r.json() if r.ok else {}

class Cfg(BaseSettings):
    feature_flag: bool = False
    user_quota: int = 100

    model_config = {
        "settings_customise_sources": lambda cls, _: (
            http_source, yaml_source, *BaseSettings.settings_customise_sources()
        )
    }
```

* **HTTP > YAML > env > .env > default** の優先度が実現。

### 4‑3. 完全ネストモデル

```python
class PG(BaseSettings):
    host: str
    port: int = 5432
    db: str
class Redis(BaseSettings):
    url: str
class Global(BaseSettings):
    pg: PG
    redis: Redis
```

* 必要な環境変数は `PG_HOST`, `PG_PORT`, `REDIS_URL` 等。
* **.env** が肥大化したらファイル分割 or Prefix で整理。

---

## 5. 運用：テスト・リロード・フレームワーク統合

### 5‑1. テストでの環境変数上書き

```python
import pytest
from settings import Settings

def test_debug(monkeypatch):
    monkeypatch.setenv("APP_DEBUG", "true")
    assert Settings().debug is True
```

* `monkeypatch` はテスト終了後に自動リセット。

### 5‑2. 設定ホットリロード（watchdog 使用例）

```python
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

class ReloadHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.src_path.endswith(".env"):
            Settings.model_reset()  # v2 のみ
            print("🔄 Settings reloaded")
```

### 5‑3. フレームワーク接続例

* **FastAPI**: `settings = Settings(); app = FastAPI()` → 依存注入でシングルトン共有。
* **Celery**: Worker 起動スクリプト内で `os.environ.update(Settings().model_dump())`。
* **Django**: `settings.py` 内で `from .settings_obj import Settings`。DjangoConf を置換可能。

---

## 6. 移行 & バージョン差分チェックリスト

* **Config → model\_config** に置換。
* **@validator → @field\_validator** へリネーム。
* **json() / dict() → model\_dump\_json() / model\_dump()**。
* \*\*allow\_mutation=False → model\_config = {"frozen": True}\`。
* **parse\_obj\_as → pydantic.TypeAdapter().validate\_python(...)**。
* **settings\_customise\_sources** は新 API 名。旧 `customise_sources` は削除。

> 🚩 まずはテストを用意 → `pip install "pydantic[dotenv]>=2" --upgrade` → エラーを上から潰す。

---

## 7. よくある落とし穴と対策

* **インスタンスを複数生成** → 毎回ファイル I/O → 遅延。
  ⇒ `@lru_cache` を付けて **シングルトン** 化。
* **bool 変換の罠** → `"false" "0" "no"` 以外のスペルは全部 `True` 扱い。
  ⇒ `.env` では **小文字 true/false** を統一。
* **巨大ネスト** → 環境変数名が長大化。
  ⇒ `.env` を複数ファイルに分割 or YAML/HTTP を併用。
* **Secret の誤露出** → `print(settings)` がパスワードを表示。
  ⇒ `SecretStr` を必ず使う & 発行先ログを確認。
* **文字エンコーディング** → Windows の CP932 で `.env` が文字化け。
  ⇒ `model_config["env_file_encoding"] = "utf-8"` を明示。

---

## 8. スニペット集 & リンク

### 8‑1. AWS 認証キーの最小定義

```python
class AWS(BaseSettings):
    access_key: str
    secret_key: SecretStr
    region: str = "ap-northeast-1"
    model_config = {"env_prefix": "AWS_"}
```

### 8‑2. 実行時 CPU コア数でデフォルト設定

```python
import os, multiprocessing
class Perf(BaseSettings):
    workers: int = multiprocessing.cpu_count() * 2 + 1
```

### 8‑3. Slack Webhook + Proxy 設定

```python
class Slack(BaseSettings):
    webhook_url: str = Field(..., alias="SLACK_WEBHOOK")
    proxy: str | None = Field(None, alias="HTTPS_PROXY")
```

### 8‑4. 公式 & 実例ドキュメント

* **Pydantic Settings**: [https://docs.pydantic.dev/latest/usage/pydantic\_settings/](https://docs.pydantic.dev/latest/usage/pydantic_settings/)
* **FastAPI Settings Example**: [https://fastapi.tiangolo.com/advanced/settings/](https://fastapi.tiangolo.com/advanced/settings/)
* **Pydantic GitHub Discussions**: 最新パターンをキャッチアップ。

---

### 🏁 まとめ

> **“設定はコードの一部”** — BaseSettings で “人間が触る領域” を統制し、アプリ本体は “型保証された設定オブジェクト” に依存するだけ。これが **運用・開発・セキュリティ** をラクにする最短ルート！