コンテンツにスキップ

タイプ

可能な場合、Pydantic は標準ライブラリ タイプを使用してフィールドを定義するため、学習曲線がスムーズになります。ただし、多くの便利なアプリケーションには標準ライブラリ型が存在しないため、Pydantic は一般的に使用される多くの型を実装します。

Pydantic Extra Typesパッケージには、より複雑な型もあります。

目的に合った既存の型がない場合は、カスタム プロパティと検証を使用して独自の Pydantic 互換型を実装することもできます。

次のセクションでは、Pydantic でサポートされる型について説明します。

型変換

検証中に、Pydantic はデータを期待される型に強制できます。

強制には、厳格と緩いの 2 つのモードがあります。 Pydantic が strict モードと lax モードの両方でデータを変換する方法の詳細については、 「変換テーブル」を参照してください。

厳密な強制を有効にする方法の詳細については、 「厳密モード」「厳密なタイプ」を参照してください。

厳密な型

Pydantic は次の厳密な型を提供します。

これらの型は、検証された値がそれぞれの型であるか、その型のサブタイプである場合にのみ検証に合格します。

制約されたタイプ

この動作は、制約された型のstrictフィールドを通じても公開され、多数の複雑な検証ルールと組み合わせることができます。サポートされている引数については、個々の型シグネチャを参照してください。

次の注意事項が適用されます。

  • StrictBytes (およびconbytes()strictオプション) は、 bytesbytearray型の両方を受け入れます。
  • bool Python のintのサブクラスですが、 StrictInt (およびconint()strictオプション) はbool型を受け入れません。他のサブクラスも機能します。
  • StrictFloat (およびconfloat()strictオプション) はint受け入れません。

上記に加えて、有限値のみを受け入れる FiniteFloat 型も使用できます (つまり、 inf-infnanは受け入れません)。

カスタムタイプ

独自のカスタム データ型を定義することもできます。それを達成するにはいくつかの方法があります。

Annotated付きによるタイプの作成

PEP 593 では、型チェッカーの解釈方法を変更せずに実行時メタデータを型に添付する方法としてAnnotated導入されました。 Pydantic はこれを利用して、型チェッカーに関する限り、元の型と同一であるものの、検証を追加したり、異なる方法でシリアル化したりする型を作成できるようにします。

たとえば、正の int を表す型を作成するには、次のようにします。

# or `from typing import Annotated` for Python 3.9+
from typing_extensions import Annotated

from pydantic import Field, TypeAdapter, ValidationError

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

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1

try:
    ta.validate_python(-1)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for constrained-int
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

注釈付き型の制約を使用して、これを Pydantic に依存しないようにすることもできることに注意してください。

from annotated_types import Gt
from typing_extensions import Annotated

from pydantic import TypeAdapter, ValidationError

PositiveInt = Annotated[int, Gt(0)]

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1

try:
    ta.validate_python(-1)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for constrained-int
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

検証とシリアル化の追加

Pydantic がエクスポートするマーカーを使用して、検証、シリアル化、および JSON スキーマを任意の型に追加またはオーバーライドできます。

from typing_extensions import Annotated

from pydantic import (
    AfterValidator,
    PlainSerializer,
    TypeAdapter,
    WithJsonSchema,
)

TruncatedFloat = Annotated[
    float,
    AfterValidator(lambda x: round(x, 1)),
    PlainSerializer(lambda x: f'{x:.1e}', return_type=str),
    WithJsonSchema({'type': 'string'}, mode='serialization'),
]


ta = TypeAdapter(TruncatedFloat)

input = 1.02345
assert input != 1.0

assert ta.validate_python(input) == 1.0

assert ta.dump_json(input) == b'"1.0e+00"'

assert ta.json_schema(mode='validation') == {'type': 'number'}
assert ta.json_schema(mode='serialization') == {'type': 'string'}

ジェネリック

Annotated内で型変数を使用して、型に再利用可能な変更を加えることができます。

from typing import Any, List, Sequence, TypeVar

from annotated_types import Gt, Len
from typing_extensions import Annotated

from pydantic import ValidationError
from pydantic.type_adapter import TypeAdapter

SequenceType = TypeVar('SequenceType', bound=Sequence[Any])


ShortSequence = Annotated[SequenceType, Len(max_length=10)]


ta = TypeAdapter(ShortSequence[List[int]])

v = ta.validate_python([1, 2, 3, 4, 5])
assert v == [1, 2, 3, 4, 5]

try:
    ta.validate_python([1] * 100)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[int]
      List should have at most 10 items after validation, not 100 [type=too_long, input_value=[1, 1, 1, 1, 1, 1, 1, 1, ... 1, 1, 1, 1, 1, 1, 1, 1], input_type=list]
    """


T = TypeVar('T')  # or a bound=SupportGt

PositiveList = List[Annotated[T, Gt(0)]]

ta = TypeAdapter(PositiveList[float])

v = ta.validate_python([1])
assert type(v[0]) is float


try:
    ta.validate_python([-1])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[constrained-float]
    0
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

名前付き型の別名

上記の例では、暗黙的な型エイリアスを使用しています。これは、JSON スキーマにtitleを持つことができず、スキーマがフィールド間でコピーされることを意味します。 PEP 695TypeAliasTypeその型付け拡張機能バックポート経由で使用して名前付きエイリアスを作成し、サブクラスを作成せずに新しい型を定義できるようにします。この新しい型は、名前のように単純なものにすることも、複雑な検証ロジックを付加することもできます。

from typing import List

from annotated_types import Gt
from typing_extensions import Annotated, TypeAliasType

from pydantic import BaseModel

ImplicitAliasPositiveIntList = List[Annotated[int, Gt(0)]]


class Model1(BaseModel):
    x: ImplicitAliasPositiveIntList
    y: ImplicitAliasPositiveIntList


print(Model1.model_json_schema())
"""
{
    'properties': {
        'x': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'title': 'X',
            'type': 'array',
        },
        'y': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'title': 'Y',
            'type': 'array',
        },
    },
    'required': ['x', 'y'],
    'title': 'Model1',
    'type': 'object',
}
"""

PositiveIntList = TypeAliasType('PositiveIntList', List[Annotated[int, Gt(0)]])


class Model2(BaseModel):
    x: PositiveIntList
    y: PositiveIntList


print(Model2.model_json_schema())
"""
{
    '$defs': {
        'PositiveIntList': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'type': 'array',
        }
    },
    'properties': {
        'x': {'$ref': '#/$defs/PositiveIntList'},
        'y': {'$ref': '#/$defs/PositiveIntList'},
    },
    'required': ['x', 'y'],
    'title': 'Model2',
    'type': 'object',
}
"""

これらの名前付き型エイリアスはジェネリックにすることもできます。

from typing import Generic, List, TypeVar

from annotated_types import Gt
from typing_extensions import Annotated, TypeAliasType

from pydantic import BaseModel, ValidationError

T = TypeVar('T')  # or a `bound=SupportGt`

PositiveList = TypeAliasType(
    'PositiveList', List[Annotated[T, Gt(0)]], type_params=(T,)
)


class Model(BaseModel, Generic[T]):
    x: PositiveList[T]


assert Model[int].model_validate_json('{"x": ["1"]}').x == [1]

try:
    Model[int](x=[-1])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model[int]
    x.0
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

名前付き再帰型

TypeAliasType使用して再帰型を作成することもできます。

from typing import Any, Dict, List, Union

from pydantic_core import PydanticCustomError
from typing_extensions import Annotated, TypeAliasType

from pydantic import (
    TypeAdapter,
    ValidationError,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)


def json_custom_error_validator(
    value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
) -> Any:
    """Simplify the error message to avoid a gross error stemming
    from exhaustive checking of all union options.
    """
    try:
        return handler(value)
    except ValidationError:
        raise PydanticCustomError(
            'invalid_json',
            'Input is not valid json',
        )


Json = TypeAliasType(
    'Json',
    Annotated[
        Union[Dict[str, 'Json'], List['Json'], str, int, float, bool, None],
        WrapValidator(json_custom_error_validator),
    ],
)


ta = TypeAdapter(Json)

v = ta.validate_python({'x': [1], 'y': {'z': True}})
assert v == {'x': [1], 'y': {'z': True}}

try:
    ta.validate_python({'x': object()})
except ValidationError as exc:
    print(exc)
    """
    1 validation error for function-wrap[json_custom_error_validator()]
      Input is not valid json [type=invalid_json, input_value={'x': <object object at 0x0123456789ab>}, input_type=dict]
    """

__get_pydantic_core_schema__を使用した検証のカスタマイズ

Pydantic がカスタム クラスを処理する方法をより広範囲にカスタマイズするには、特にクラスにアクセスできる場合、またはクラスをサブクラス化できる場合、特別な__get_pydantic_core_schema__を実装して、Pydantic にpydantic-coreスキーマの生成方法を指示できます。

pydantic内部でpydantic-core使用して検証とシリアル化を処理しますが、これは Pydantic V2 の新しい API であるため、将来的に調整される可能性が最も高い領域の 1 つであり、次のような組み込み構造に固執するように努める必要があります。 annotated-typespydantic.Field 、またはBeforeValidatorなどによって提供されるもの。

__get_pydantic_core_schema__は、カスタム型とAnnotatedに入れることを目的としたメタデータの両方に実装できます。どちらの場合も、API はsource_typeのようなもので、「ラップ」バリデーターの API に似ています。source_type (特にジェネリックスの場合、必ずしもクラスと同じであるとは限りません) と、型を使用して呼び出すことができるhandler取得します。 Annotatedの次のメタデータを呼び出すか、Pydantic の内部スキーマ生成を呼び出します。

最も単純な no-op 実装は、指定された型でハンドラーを呼び出し、それを結果として返します。ハンドラーを呼び出す前に型を変更すること、ハンドラーによって返されるコア スキーマを変更すること、またはハンドラーをまったく呼び出さないことも選択できます。

カスタムタイプのメソッドとして

以下は、 __get_pydantic_core_schema__使用して検証方法をカスタマイズする型の例です。これは、Pydantic V1 で__get_validators__実装するのと同等です。

from typing import Any

from pydantic_core import CoreSchema, core_schema

from pydantic import GetCoreSchemaHandler, TypeAdapter


class Username(str):
    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        return core_schema.no_info_after_validator_function(cls, handler(str))


ta = TypeAdapter(Username)
res = ta.validate_python('abc')
assert isinstance(res, Username)
assert res == 'abc'

カスタム タイプの JSON スキーマをカスタマイズする方法の詳細については、 「JSON スキーマ」を参照してください。

注釈として

多くの場合、ジェネリック型パラメーターだけではなくカスタム型をパラメーター化したい場合があります (これは型システムを介して行うことができ、後で説明します)。あるいは、実際にはサブクラスのインスタンスを作成する必要がない (または作成したくない) かもしれません。実際には、追加の検証が行われただけで、元の型が必要になります。

たとえば、 pydantic.AfterValidator ( 「検証とシリアル化の追加」を参照) を自分で実装する場合は、次のようなことを行うことになります。

from dataclasses import dataclass
from typing import Any, Callable

from pydantic_core import CoreSchema, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


@dataclass(frozen=True)  # (1)!
class MyAfterValidator:
    func: Callable[[Any], Any]

    def __get_pydantic_core_schema__(
        self, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        return core_schema.no_info_after_validator_function(
            self.func, handler(source_type)
        )


Username = Annotated[str, MyAfterValidator(str.lower)]


class Model(BaseModel):
    name: Username


assert Model(name='ABC').name == 'abc'  # (2)!
  1. frozen=True指定により、 MyAfterValidatorハッシュ可能になります。これを使用しないと、 Username | Noneなどの共用体が使用されます。 Username | Noneエラーは発生しません。
  2. 型チェッカーは、 Username strとは異なる型とはみなさないため、前の例のように'ABC' Usernameに割り当てることについて文句を言わないことに注意してください。

サードパーティタイプの処理

前のセクションのパターンの別の使用例は、サードパーティのタイプを処理することです。

from typing import Any

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import (
    BaseModel,
    GetCoreSchemaHandler,
    GetJsonSchemaHandler,
    ValidationError,
)
from pydantic.json_schema import JsonSchemaValue


class ThirdPartyType:
    """
    This is meant to represent a type from a third-party library that wasn't designed with Pydantic
    integration in mind, and so doesn't have a `pydantic_core.CoreSchema` or anything.
    """

    x: int

    def __init__(self):
        self.x = 0


class _ThirdPartyTypePydanticAnnotation:
    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        """
        We return a pydantic_core.CoreSchema that behaves in the following ways:

        * ints will be parsed as `ThirdPartyType` instances with the int as the x attribute
        * `ThirdPartyType` instances will be parsed as `ThirdPartyType` instances without any changes
        * Nothing else will pass validation
        * Serialization will always return just an int
        """

        def validate_from_int(value: int) -> ThirdPartyType:
            result = ThirdPartyType()
            result.x = value
            return result

        from_int_schema = core_schema.chain_schema(
            [
                core_schema.int_schema(),
                core_schema.no_info_plain_validator_function(validate_from_int),
            ]
        )

        return core_schema.json_or_python_schema(
            json_schema=from_int_schema,
            python_schema=core_schema.union_schema(
                [
                    # check if it's an instance first before doing any further work
                    core_schema.is_instance_schema(ThirdPartyType),
                    from_int_schema,
                ]
            ),
            serialization=core_schema.plain_serializer_function_ser_schema(
                lambda instance: instance.x
            ),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        # Use the same schema that would be used for `int`
        return handler(core_schema.int_schema())


# We now create an `Annotated` wrapper that we'll use as the annotation for fields on `BaseModel`s, etc.
PydanticThirdPartyType = Annotated[
    ThirdPartyType, _ThirdPartyTypePydanticAnnotation
]


# Create a model class that uses this annotation as a field
class Model(BaseModel):
    third_party_type: PydanticThirdPartyType


# Demonstrate that this field is handled correctly, that ints are parsed into `ThirdPartyType`, and that
# these instances are also "dumped" directly into ints as expected.
m_int = Model(third_party_type=1)
assert isinstance(m_int.third_party_type, ThirdPartyType)
assert m_int.third_party_type.x == 1
assert m_int.model_dump() == {'third_party_type': 1}

# Do the same thing where an instance of ThirdPartyType is passed in
instance = ThirdPartyType()
assert instance.x == 0
instance.x = 10

m_instance = Model(third_party_type=instance)
assert isinstance(m_instance.third_party_type, ThirdPartyType)
assert m_instance.third_party_type.x == 10
assert m_instance.model_dump() == {'third_party_type': 10}

# Demonstrate that validation errors are raised as expected for invalid inputs
try:
    Model(third_party_type='a')
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    third_party_type.is-instance[ThirdPartyType]
      Input should be an instance of ThirdPartyType [type=is_instance_of, input_value='a', input_type=str]
    third_party_type.chain[int,function-plain[validate_from_int()]]
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """


assert Model.model_json_schema() == {
    'properties': {
        'third_party_type': {'title': 'Third Party Type', 'type': 'integer'}
    },
    'required': ['third_party_type'],
    'title': 'Model',
    'type': 'object',
}

このアプローチを使用して、たとえば Pandas または Numpy 型の動作を定義できます。

GetPydanticSchema使用して定型文を削減する

??? API「APIドキュメント」 pydantic.types.GetPydanticSchema

マーカー クラスを作成する上記の例では、かなりの量の定型文が必要であることに気づくかもしれません。多くの単純なケースでは、 pydantic.GetPydanticSchema使用することでこれを大幅に最小限に抑えることができます。

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetPydanticSchema


class Model(BaseModel):
    y: Annotated[
        str,
        GetPydanticSchema(
            lambda tp, handler: core_schema.no_info_after_validator_function(
                lambda x: x * 2, handler(tp)
            )
        ),
    ]


assert Model(y='ab').y == 'abab'

まとめ

要約しましょう:

  1. Pydantic は、 AfterValidatorFieldなどのAnnotatedを介して型をカスタマイズするための高レベルのフックを提供します。可能な場合はこれらを使用してください。
  2. これらは内部でpydantic-core使用して検証をカスタマイズしており、 GetPydanticSchemaまたは__get_pydantic_core_schema__のマーカー クラスを使用してそれに直接接続できます。
  3. 本当にカスタム型が必要な場合は、型自体に__get_pydantic_core_schema__を実装できます。

カスタムジェネリッククラスの処理

!!!警告 これは高度なテクニックであり、最初は必要ないかもしれません。ほとんどの場合、おそらく標準の Pydantic モデルで問題ありません。

ジェネリック クラスをフィールド タイプとして使用し、 __get_pydantic_core_schema__を使用して「タイプ パラメーター」 (またはサブタイプ) に基づいてカスタム検証を実行できます。

サブタイプとして使用しているジェネリック クラスにクラスメソッド__get_pydantic_core_schema__がある場合、それが機能するために arbitrary_types_allowed を使用する必要はありません。

source_typeパラメーターはclsパラメーターと同じではないため、 typing.get_args (またはtyping_extensions.get_args ) を使用して汎用パラメーターを抽出できます。次に、 handler.generate_schemaを呼び出すことで、 handler使用してそれらのスキーマを生成できます。次のようなことはしないことに注意してください handler(get_args(source_type)[0]) Annotatedメタデータなどの現在のコンテキストに影響されるスキーマではなく、その汎用パラメーターに対して無関係なスキーマを生成したいためです。これはカスタム型ではそれほど重要ではありませんが、スキーマの構築を変更する注釈付きメタデータにとっては重要です。

from dataclasses import dataclass
from typing import Any, Generic, TypeVar

from pydantic_core import CoreSchema, core_schema
from typing_extensions import get_args, get_origin

from pydantic import (
    BaseModel,
    GetCoreSchemaHandler,
    ValidationError,
    ValidatorFunctionWrapHandler,
)

ItemType = TypeVar('ItemType')


# This is not a pydantic model, it's an arbitrary generic class
@dataclass
class Owner(Generic[ItemType]):
    name: str
    item: ItemType

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        origin = get_origin(source_type)
        if origin is None:  # used as `x: Owner` without params
            origin = source_type
            item_tp = Any
        else:
            item_tp = get_args(source_type)[0]
        # both calling handler(...) and handler.generate_schema(...)
        # would work, but prefer the latter for conceptual and consistency reasons
        item_schema = handler.generate_schema(item_tp)

        def val_item(
            v: Owner[Any], handler: ValidatorFunctionWrapHandler
        ) -> Owner[Any]:
            v.item = handler(v.item)
            return v

        python_schema = core_schema.chain_schema(
            # `chain_schema` means do the following steps in order:
            [
                # Ensure the value is an instance of Owner
                core_schema.is_instance_schema(cls),
                # Use the item_schema to validate `items`
                core_schema.no_info_wrap_validator_function(
                    val_item, item_schema
                ),
            ]
        )

        return core_schema.json_or_python_schema(
            # for JSON accept an object with name and item keys
            json_schema=core_schema.chain_schema(
                [
                    core_schema.typed_dict_schema(
                        {
                            'name': core_schema.typed_dict_field(
                                core_schema.str_schema()
                            ),
                            'item': core_schema.typed_dict_field(item_schema),
                        }
                    ),
                    # after validating the json data convert it to python
                    core_schema.no_info_before_validator_function(
                        lambda data: Owner(
                            name=data['name'], item=data['item']
                        ),
                        # note that we re-use the same schema here as below
                        python_schema,
                    ),
                ]
            ),
            python_schema=python_schema,
        )


class Car(BaseModel):
    color: str


class House(BaseModel):
    rooms: int


class Model(BaseModel):
    car_owner: Owner[Car]
    home_owner: Owner[House]


model = Model(
    car_owner=Owner(name='John', item=Car(color='black')),
    home_owner=Owner(name='James', item=House(rooms=3)),
)
print(model)
"""
car_owner=Owner(name='John', item=Car(color='black')) home_owner=Owner(name='James', item=House(rooms=3))
"""

try:
    # If the values of the sub-types are invalid, we get an error
    Model(
        car_owner=Owner(name='John', item=House(rooms=3)),
        home_owner=Owner(name='James', item=Car(color='black')),
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    wine
      Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='Kinda good', input_type=str]
    cheese
      Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='yeah', input_type=str]
    """

# Similarly with JSON
model = Model.model_validate_json(
    '{"car_owner":{"name":"John","item":{"color":"black"}},"home_owner":{"name":"James","item":{"rooms":3}}}'
)
print(model)
"""
car_owner=Owner(name='John', item=Car(color='black')) home_owner=Owner(name='James', item=House(rooms=3))
"""

try:
    Model.model_validate_json(
        '{"car_owner":{"name":"John","item":{"rooms":3}},"home_owner":{"name":"James","item":{"color":"black"}}}'
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    car_owner.item.color
      Field required [type=missing, input_value={'rooms': 3}, input_type=dict]
    home_owner.item.rooms
      Field required [type=missing, input_value={'color': 'black'}, input_type=dict]
    """

汎用コンテナ

同じ考え方を適用して、カスタムSequenceタイプのような汎用コンテナ タイプを作成できます。

from typing import Any, Sequence, TypeVar

from pydantic_core import ValidationError, core_schema
from typing_extensions import get_args

from pydantic import BaseModel, GetCoreSchemaHandler

T = TypeVar('T')


class MySequence(Sequence[T]):
    def __init__(self, v: Sequence[T]):
        self.v = v

    def __getitem__(self, i):
        return self.v[i]

    def __len__(self):
        return len(self.v)

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        instance_schema = core_schema.is_instance_schema(cls)

        args = get_args(source)
        if args:
            # replace the type and rely on Pydantic to generate the right schema
            # for `Sequence`
            sequence_t_schema = handler.generate_schema(Sequence[args[0]])
        else:
            sequence_t_schema = handler.generate_schema(Sequence)

        non_instance_schema = core_schema.no_info_after_validator_function(
            MySequence, sequence_t_schema
        )
        return core_schema.union_schema([instance_schema, non_instance_schema])


class M(BaseModel):
    model_config = dict(validate_default=True)

    s1: MySequence = [3]


m = M()
print(m)
#> s1=<__main__.MySequence object at 0x0123456789ab>
print(m.s1.v)
#> [3]


class M(BaseModel):
    s1: MySequence[int]


M(s1=[1])
try:
    M(s1=['a'])
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for M
    s1.is-instance[MySequence]
      Input should be an instance of MySequence [type=is_instance_of, input_value=['a'], input_type=list]
    s1.function-after[MySequence(), json-or-python[json=list[int],python=chain[is-instance[Sequence],function-wrap[sequence_validator()]]]].0
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

フィールド名へのアクセス

!!!note これは Pydantic V2 ~ V2.3 では不可能でした。Pydantic V2.4 で再度追加されました。

Pydantic V2.4 以降では、 __get_pydantic_core_schema__内のhandler.field_name介してフィールド名にアクセスでき、それによってinfo.field_nameから利用できるフィールド名を設定できます。

from typing import Any

from pydantic_core import core_schema

from pydantic import BaseModel, GetCoreSchemaHandler, ValidationInfo


class CustomType:
    """Custom type that stores the field it was used in."""

    def __init__(self, value: int, field_name: str):
        self.value = value
        self.field_name = field_name

    def __repr__(self):
        return f'CustomType<{self.value} {self.field_name!r}>'

    @classmethod
    def validate(cls, value: int, info: ValidationInfo):
        return cls(value, info.field_name)

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.with_info_after_validator_function(
            cls.validate, handler(int), field_name=handler.field_name
        )


class MyModel(BaseModel):
    my_field: CustomType


m = MyModel(my_field=1)
print(m.my_field)
#> CustomType<1 'my_field'>

[AfterValidator][pydantic.function_validators.AfterValidator] など、 Annotatedで使用されるマーカーからfield_nameにアクセスすることもできます。

from typing_extensions import Annotated

from pydantic import AfterValidator, BaseModel, ValidationInfo


def my_validators(value: int, info: ValidationInfo):
    return f'<{value} {info.field_name!r}>'


class MyModel(BaseModel):
    my_field: Annotated[int, AfterValidator(my_validators)]


m = MyModel(my_field=1)
print(m.my_field)
#> <1 'my_field'>

本文总阅读量