以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/07/13/160648より取得しました。


Pydanticで扱える型、フィールドの使い方を確認する

これは、なにをしたくて書いたもの?

前にPydanticを少し見てみました。

Pythonのデータバリデーションライブラリー、Pydanticを試す - CLOVER🍀

この時はGet Startedにほぼ習った感じでしたが、今回は扱える型について見ていこうと思います。

Types

Pydanticで扱える型については、こちらのページに書かれています。

Types - Pydantic

方針としては、Pythonの標準ライブラリーの型を使ってフィールド定義するのが基本です。ただ、それだけでは足りないのでPydanticが
追加で型を実装しています。

Where possible Pydantic uses standard library types to define fields, thus smoothing the learning curve. For many useful applications, however, no standard library type exists, so Pydantic implements many commonly used types.

Python標準ライブラリーの型についてはこちらですね。

Standard Library Types - Pydantic

厳密な型もあり、独自の型も定義できるようです。

このページには見当たらないのですが、Pydanticが提供している型がこちらのようですね。

Pydantic Types - Pydantic

たとえば、こちらの例で書かれているPositiveIntですが

PositiveInt = Annotated[int, Field(gt=0)]

Types / Custom Types / Using the annotated pattern

まさしくPydanticのPositiveIntの定義になっています。

Pydantic Types / types / PositiveInt

こんな感じで独自の型を作るんですね。

これにはFieldというものが基盤になり、ここで様々な制約を定義できるようです。

Fields - Pydantic

Fields - Pydantic

さらに複雑な型を扱う場合として、Pydantic Extra Typesが紹介されているのですが。

GitHub - pydantic/pydantic-extra-types: Extra Pydantic types.

この他にPydanticもNetwork Typesというものを提供しているようですね。

Network Types - Pydantic

データ型の変換規則…フィールドの型に対して、どのようなデータ型を(変換して)受け付けることができるのかは
こちらのページに表としてまとめられているので見ておくとよいでしょう。

Conversion Table - Pydantic

Strictモードだと、ほとんど変換されなくなることがわかります。

Fields

こう見ていくと、Fieldについても見ておいた方がよさそうな気がしてきますね。

Fields - Pydantic

こういう感じでフィールドに代入して使うようです。

class Model(BaseModel):
    name: str = Field(frozen=True)

Fieldを使うことで、こんなことができるみたいです。

  • デフォルト値を設定する
  • フィールドのエイリアスを設定する
  • 制約を設定する
    • 数値制約(大小など)
    • 文字列制約(文字列長、正規表現
    • 小数制約(最大桁数、最大小数点桁数)
  • 対象のフィールドをモデルの文字列表現に含めるかどうか
  • union型の識別
  • Strictモードの設定
  • 不変性
  • モデルをダンプする時に除外対象とするかどうか
  • フィールドを非推奨に設定する
  • JSON Schemaのカスタマイズ

APIドキュメントはこちら。

Fields - Pydantic

こう見ると、制約の定義の方法もだいたいわかる感じですね。

では、少し試してみましょう。

環境

今回の環境はこちら。

$ python3 --version
Python 3.12.3


$ uv --version
uv 0.7.20

プロジェクトを作成する

最初にuvでプロジェクトを作成します。

$ uv init --vcs none pydantic-types-fields
$ cd pydantic-types-fields
$ rm main.py

不要なファイルは削除。

ライブラリーのインストール。

$ uv add pydantic

テストなどに必要なライブラリーをインストール。

$ uv add --dev pytest mypy ruff

確認はテストコードで行います。

pyproject.toml

[project]
name = "pydantic-types-fields"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "pydantic>=2.11.7",
]

[dependency-groups]
dev = [
    "mypy>=1.16.1",
    "pytest>=8.4.1",
    "ruff>=0.12.3",
]

[tool.mypy]
strict = true
disallow_any_unimported = true
disallow_any_expr = true
disallow_any_explicit = true
warn_unreachable = true
pretty = true
plugins = ["pydantic.mypy"]

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true

PydanticのMypyプラグインも追加しています。

インストールされたライブラリーの一覧。

$ uv pip list
Package           Version
----------------- -------
annotated-types   0.7.0
iniconfig         2.1.0
mypy              1.16.1
mypy-extensions   1.1.0
packaging         25.0
pathspec          0.12.1
pluggy            1.6.0
pydantic          2.11.7
pydantic-core     2.33.2
pygments          2.19.2
pytest            8.4.1
ruff              0.12.3
typing-extensions 4.14.1
typing-inspection 0.4.1

雛形を作成する

モデルとテストコードの雛形を作成します。

models.py

from pydantic import BaseModel, Field, HttpUrl, PositiveInt


## ここにモデルを書く!

テストコードはこちら。

test_models.py

from pydantic import HttpUrl, ValidationError
from models import User, User2


## ここにテストを書く!

こちらをもとに作成していきます。

Typesを試す

まずはTypesを使ってみます。

class User(BaseModel):
    first_name: str
    last_name: str
    age: PositiveInt
    web: HttpUrl

Pythonの標準ライブラリーの型(str)、Pydanticの型(PositiveInt)、PydanticのNetworkの型(HttpUrl)からそれぞれ
選んでいます。

テストコードで確認します。

def test_valid_user() -> None:
    user = User.model_validate(
        {
            "first_name": "カツオ",
            "last_name": "磯野",
            "age": 11,
            "web": "https://katsuo.isono.example.com",
        }
    )

    assert user.first_name == "カツオ"
    assert user.last_name == "磯野"
    assert user.age == 11
    assert user.web == HttpUrl("https://katsuo.isono.example.com")

strからHttpUrlに変換してくれるようですね。

自分で書いてもOKですが。

def test_valid_user_2() -> None:
    user = User.model_validate(
        {
            "first_name": "カツオ",
            "last_name": "磯野",
            "age": 11,
            "web": HttpUrl("https://katsuo.isono.example.com"),
        }
    )

    assert user.first_name == "カツオ"
    assert user.last_name == "磯野"
    assert user.age == 11
    assert user.web == HttpUrl("https://katsuo.isono.example.com")

バリデーションエラーとなる場合。

def test_invalid_user() -> None:
    try:
        User.model_validate({"first_name": 1, "age": -5, "web": "ftp://example.com"})
    except ValidationError as e:
        assert e.errors() == [
            {
                "type": "string_type",
                "loc": ("first_name",),
                "msg": "Input should be a valid string",
                "input": 1,
                "url": "https://errors.pydantic.dev/2.11/v/string_type",
            },
            {
                "type": "missing",
                "loc": ("last_name",),
                "msg": "Field required",
                "input": {"first_name": 1, "age": -5, "web": "ftp://example.com"},
                "url": "https://errors.pydantic.dev/2.11/v/missing",
            },
            {
                "type": "greater_than",
                "loc": ("age",),
                "msg": "Input should be greater than 0",
                "input": -5,
                "ctx": {"gt": 0},
                "url": "https://errors.pydantic.dev/2.11/v/greater_than",
            },
            {
                "type": "url_scheme",
                "loc": ("web",),
                "msg": "URL scheme should be 'http' or 'https'",
                "input": "ftp://example.com",
                "ctx": {"expected_schemes": "'http' or 'https'"},
                "url": "https://errors.pydantic.dev/2.11/v/url_scheme",
            },
        ]

型だけでもある程度バリデーションが可能ですね。

Fieldsを試す

次はFieldsを使ってみます。

先ほどのクラスを少しカスタマイズしたものを用意。最大長(最大値)や正規表現を使っています。

class User2(BaseModel):
    first_name: str = Field(min_length=1, max_length=10)
    last_name: str = Field(default="磯野", min_length=1, max_length=10)
    age: PositiveInt = Field(lt=100)
    web: str = Field(pattern=r"^https?://.+", max_length=35)

HttpUrlは文字列の最大長を制御できないようだったので、今回はFieldを直接扱うことにしました。

バリデーションOKとなる場合。

def test_valid_user2() -> None:
    user = User2.model_validate(
        {
            "first_name": "カツオ",
            "age": 11,
            "web": "https://katsuo.isono.example.com",
        }
    )

    assert user.first_name == "カツオ"
    assert user.last_name == "磯野"
    assert user.age == 11
    assert user.web == "https://katsuo.isono.example.com"

バリデーションNGとなる場合。

def test_invalid_user2() -> None:
    try:
        User2.model_validate(
            {
                "first_name": "あいうえおかきくけこさ",
                "last_name": "",
                "age": 100,
                "web": "ftp://katsuo.isono.example.com/foobarfuga",
            }
        )
    except ValidationError as e:
        assert e.errors() == [
            {
                "type": "string_too_long",
                "loc": ("first_name",),
                "msg": "String should have at most 10 characters",
                "input": "あいうえおかきくけこさ",
                "ctx": {"max_length": 10},
                "url": "https://errors.pydantic.dev/2.11/v/string_too_long",
            },
            {
                "type": "string_too_short",
                "loc": ("last_name",),
                "msg": "String should have at least 1 character",
                "input": "",
                "ctx": {"min_length": 1},
                "url": "https://errors.pydantic.dev/2.11/v/string_too_short",
            },
            {
                "ctx": {
                    "lt": 100,
                },
                "input": 100,
                "loc": ("age",),
                "msg": "Input should be less than 100",
                "type": "less_than",
                "url": "https://errors.pydantic.dev/2.11/v/less_than",
            },
            {
                "ctx": {
                    "max_length": 35,
                },
                "input": "ftp://katsuo.isono.example.com/foobarfuga",
                "loc": ("web",),
                "msg": "String should have at most 35 characters",
                "type": "string_too_long",
                "url": "https://errors.pydantic.dev/2.11/v/string_too_long",
            },
        ]

パッと見では期待通り動作しているように見えるのですが、web(URL)に関しては定義した正規表現と文字列長の両方に違反して
いるのですが、文字列長のみが検出されていますね。

もうひとつテストを用意。

def test_invalid_user2_2() -> None:
    try:
        User2.model_validate(
            {
                "first_name": "あいうえおかきくけこさ",
                "last_name": "",
                "age": 100,
                "web": "ftp://katsuo.isono.example.com",
            }
        )
    except ValidationError as e:
        assert e.errors() == [
            {
                "type": "string_too_long",
                "loc": ("first_name",),
                "msg": "String should have at most 10 characters",
                "input": "あいうえおかきくけこさ",
                "ctx": {"max_length": 10},
                "url": "https://errors.pydantic.dev/2.11/v/string_too_long",
            },
            {
                "type": "string_too_short",
                "loc": ("last_name",),
                "msg": "String should have at least 1 character",
                "input": "",
                "ctx": {"min_length": 1},
                "url": "https://errors.pydantic.dev/2.11/v/string_too_short",
            },
            {
                "ctx": {
                    "lt": 100,
                },
                "input": 100,
                "loc": ("age",),
                "msg": "Input should be less than 100",
                "type": "less_than",
                "url": "https://errors.pydantic.dev/2.11/v/less_than",
            },
            {
                "ctx": {
                    "pattern": "^https?://.+",
                },
                "input": "ftp://katsuo.isono.example.com",
                "loc": ("web",),
                "msg": "String should match pattern '^https?://.+'",
                "type": "string_pattern_mismatch",
                "url": "https://errors.pydantic.dev/2.11/v/string_pattern_mismatch",
            },
        ]

こちらはURLの文字列長をオーバーしていないので、正規表現でNGになったことが検出されました。

ということは、定義されているバリデーションルールのどれかひとつを満たさなかった時点で処理が打ち切られるということ
ですね。

おわりに

Pydanticで扱える型、フィールドの使い方を確認してみました。

使い方自体は特に難しくない気がするのですが、ちゃんとドキュメントを読まないと機能に気づかないですね…。

雰囲気で使うのではなく、やっぱりドキュメントを読むことが大事だなと思いました。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/07/13/160648より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14