見出し画像

Pydantic V2: Essentials: モデル設定 (セクション3/13)

  • モデル全体における余分なフィールド、型変換、デフォルト値検証などを `model_config` で細かく制御できる。

  • 代入時のバリデーションや列挙型の扱い、文字列の正規化(空白除去・大文字変換)など、多様な設定オプションを備える。

  • モデルを不変化(frozen)にできるほか、厳密モード(strict)で型を厳格に扱うなど、幅広いカスタマイズが可能。

この記事では、Pydantic V2 におけるモデルのさまざまなデフォルト動作をカスタマイズする方法を紹介します。Pydantic の `BaseModel` には `model_config` という特別な属性があり、ここに通常 `ConfigDict` を割り当てることで、幅広い設定オプションを指定できます。これを理解することで、余分なフィールドの扱い方から、厳密・緩やかな型変換、文字列の加工、さらにはモデルの不変化まで、あらゆる動作を制御できます。


`model_config` を用いたデフォルト動作の変更

Pydantic では、モデル全体に影響する特定の動作があります。たとえば定義されていないフィールド(“extra”フィールド)が入力データに含まれている場合、それらは 無視 されるのが既定です。また、Pydantic は lax(非厳密)モードで入力データを宣言された型に自動的に変換しようとします。

こういった動作を上書きしたい場合、Pydantic が提供する設定オブジェクトを、クラス内部で下記のように定義します:

from pydantic import BaseModel, ConfigDict

class MyModel(BaseModel):
    model_config = ConfigDict(
        # ここに設定オプションを指定
    )
    ...

`ConfigDict` は名前付き引数を含む型付き辞書で、モデルが取るべきさまざまな動作を指定できます。以下では、よく使われる設定について説明します。


余分なフィールドの制御

デフォルトでは、Pydantic は未定義のフィールドを入力データから受け取った場合、それらを 無視 します。しかし、以下の 3 つのモードから選択することができます:

  1. `ignore` (デフォルト)
    余分なフィールドをサイレントに破棄します。モデルインスタンスには現れず、シリアライズ時にも消えています。

  2. `forbid`
    余分なフィールドが入力に含まれているとき、`ValidationError` を発生させます。REST API など厳密なデータ契約を要求する場面で便利です。

  3. `allow`
    モデルで定義されていないフィールドもインスタンスに含めて保持します。型チェックは行われませんが、モデルの `dict` や `model_dump()` に含まれるようになります。

例:

from pydantic import BaseModel, ConfigDict, ValidationError

class Example(BaseModel):
    model_config = ConfigDict(extra='forbid')
    field_1: int

try:
    obj = Example(field_1=10, unknown="extra!")
except ValidationError as e:
    print(e)
    # ValidationError: Extra inputs are not permitted

厳密(Strict)モード vs 緩やか(Lax)モードの型変換

Pydantic は基本的に lax モードで動作し、可能なら宣言された型に入力値を変換します。たとえば、`tuple` が `list` に変わったり、整数が浮動小数に変わったりすることがあります。もしこうした暗黙的な変換を禁止したいなら、モデルを 厳密モード に切り替えます:

class StrictExample(BaseModel):
    model_config = ConfigDict(strict=True)
    number: int
    coords: tuple

厳密モードでは、宣言された型と入力値の型が一致しなければバリデーションエラーになります。Pydantic のコンバージョン・テーブルを見ると、各型が lax/strict でどのように扱われるかを確認できます。たとえば `list` は通常 `tuple` に変換できません(厳密モードで)。


デフォルト値のバリデーション

フィールドのデフォルト値は、デフォルトではバリデーションの対象になりません。何も指定しないと、そのままモデルの一部になります。もしバリデーションや変換処理も適用したい場合は、次のようにします:

class DefaultsExample(BaseModel):
    model_config = ConfigDict(validate_default=True)
    # field_1 は int を期待するため、デフォルト値もバリデーションされる
    field_1: int = None 

`validate_default=True` を指定しない場合、`field_1: int = None` のように型と合わないデフォルト値を書いてもエラーにならず、そのまま代入されてしまいます。デフォルト値も型に適合させたいなら、上記の設定を入れましょう。


代入時のバリデーション

Pydantic はモデルを生成するときにデータをバリデーションしますが、デフォルト設定ではインスタンス生成後にフィールドを書き換えてもバリデーションは行われません:

class AssignExample(BaseModel):
    field: int

obj = AssignExample(field=10)
obj.field = "not an int"  # デフォルトではエラーにならない

既存のインスタンスの属性更新にも同様のチェックを加えたいときは、以下のようにします:

class AssignExample(BaseModel):
    model_config = ConfigDict(validate_assignment=True)
    field: int

obj = AssignExample(field=10)
obj.field = "not an int"  # ValidationError を発生させる

これにより、フィールドの整合性を常に保ちやすくなります。


モデルを不変化(Frozen)にする

Python の `@dataclass(frozen=True)` と同様、Pydantic モデルを丸ごと不変化でき、属性の再代入を禁止できます。以下のように指定します:

class FrozenExample(BaseModel):
    model_config = ConfigDict(frozen=True)
    value: int

example = FrozenExample(value=100)
example.value = 200  # ValidationError: Instance is frozen

凍結(frozen)されたモデルはハッシュ可能となり、ミュータブルなオブジェクトが使えない場所(たとえば辞書のキー)で使用できます。


数値を文字列に変換する

フィールドが `str` の場合、デフォルトでは数値は文字列に変換されません。整数を渡すとバリデーションエラーが発生します:

class NumToStr(BaseModel):
    value: str

NumToStr(value=123)
# エラー: Input should be a valid string

これを明示的に許可したい場合は、以下の設定を入れます:

class NumToStr(BaseModel):
    model_config = ConfigDict(coerce_numbers_to_str=True)
    value: str

converted = NumToStr(value=123)
print(converted.value)  # "123"

ただしこれは lax モードでのみ有効です。`strict=True` にしていると同じ変換は許されません。


文字列の正規化

入力文字列の取り扱いを楽にするために、大文字/小文字統一や先頭末尾の空白削除などが必要になる場合があります。Pydantic では以下のオプションを使って簡単に実現可能です:

  • `str_strip_whitespace=True`: 全文字列フィールドから先頭と末尾の空白文字を除去

  • `str_to_lower=True` または `str_to_upper=True`: 全文字列フィールドを小文字や大文字に統一

たとえば:

class StringModel(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        str_to_lower=True
    )
    text: str

obj = StringModel(text="  HELLO  ")
print(obj.text)
# "hello" (空白除去+小文字化)

Python Enums の扱い

文字列を特定の値のみに限定したいとき、Python の `Enum` が便利です。Pydantic で列挙型を使う場合、フィールドに Enum クラスを指定するだけでOKです:

from enum import Enum

class Color(Enum):
    red = "Red"
    green = "Green"
    blue = "Blue"

class Paint(BaseModel):
    color: Color

p = Paint(color="Red")
# => Paint(color=<Color.red: 'Red'>)

`"Purple"` のような未定義値を入れると `ValidationError` になります。

`use_enum_values` の利用

列挙型そのものではなく、列挙型が持つ値(ここでは `"Red"`)を直接フィールドに保存したい場合は、以下のように設定します:

class Paint(BaseModel):
    model_config = ConfigDict(use_enum_values=True)
    color: Color

これにより、`Paint(color="Red")` の `color` は単なる `"Red"` という文字列となります(内部バリデーションは相変わらず Enum メンバーを参照して行われます)。

注意点として、デフォルト値 に列挙型メンバーを置いたとき、`use_enum_values=True` かつ `validate_default=False`(デフォルト設定)の場合だと、enum メンバーがそのまま残る可能性があります。バリデーションが実行されれば変換も走るので、必要に応じて `validate_default=True` を指定する、もしくはデフォルト値を直接 `EnumMember.value` にしておくのが安心です。


まとめ: 小さなプロジェクトでの例

さまざまな設定を組み合わせる例として、自動車モデルを考えます。要件は以下のとおりです:

  • 余分なフィールドを禁止 (`extra='forbid'`)

  • 文字列フィールドから空白を除去

  • デフォルトと代入にもバリデーションを適用

  • 一部属性には列挙型を使用

from datetime import date
from enum import Enum
from pydantic import BaseModel, ConfigDict

class AutomobileType(Enum):
    sedan = "Sedan"
    coupe = "Coupe"
    convertible = "Convertible"
    suv = "SUV"
    truck = "Truck"

class Automobile(BaseModel):
    model_config = ConfigDict(
        extra='forbid',             # 未定義のフィールドを許可しない
        str_strip_whitespace=True,  # 文字列の前後の空白を取り除く
        validate_default=True,      # デフォルト値もバリデーション対象に
        validate_assignment=True,   # インスタンス生成後の代入にもバリデーション
    )
    manufacturer: str
    series_name: str
    type_: AutomobileType
    is_electric: bool = False
    manufactured_date: date
    base_msrp_usd: float
    vin: str
    number_of_doors: int = 4
    registration_country: str | None = None
    license_plate: str | None = None

# 使用例
json_data = '''
{
    "manufacturer": " BMW ",
    "series_name": " M4 ",
    "type_": "Convertible",
    "manufactured_date": "2023-01-01",
    "base_msrp_usd": 93300,
    "vin": " 1234567890 "
}
'''

auto = Automobile.model_validate_json(json_data)
print(auto)
# "BMW", "M4" などと正規化される。余分なフィールドはエラーに。enum もバリデートされる。

結果として:

  • `" BMW "` と `" M4 "` はそれぞれ `"BMW"`, `"M4"` に空白が取り除かれる。

  • 定義外のキーが JSON に含まれていれば例外が発生。

  • `is_electric` のようなデフォルト値を含むフィールドもバリデーションされる。

  • インスタンス化後に不正な代入をしても `validate_assignment=True` でエラーになる。


おわりに

Pydantic のモデル設定機能は多岐にわたりますが、使いこなせるようになると非常にパワフルです。余分なフィールドや型チェックを厳密に管理する場合、文字列フィールドの大文字小文字・空白トリミング、列挙型の値取り扱いなど、一つひとつ `model_config` で設定できます。型アノテーションと組み合わせれば、アプリケーションのデータ構造を堅牢かつ柔軟に保つことが可能です。Pydantic が提供するこれらのオプションをうまく活用し、定義済みのルールを常に正しく維持できるデータモデルを構築してください。

「超温和なパイソン」へ

いいなと思ったら応援しよう!