Types
可能な場合、Pydanticは標準ライブラリ型を使用してフィールドを定義し、学習曲線をなめらかにします。ただし、多くの便利なアプリケーションには標準ライブラリ型が存在しないため、Pydanticは一般的に使用される多くの型を実装しています。
Pydantic Extra Typesパッケージには、より複雑な型もあります。
既存の型が目的に合わない場合は、カスタムプロパティと検証を使用して、独自のPydantic互換型を実装することもできます。
以下のセクションでは、Pydanticでサポートされている型について説明します。
- Standard Library Types — Pythonの標準ライブラリのタイプ。
- Strict Types — 互換性のあるタイプからの強制を防ぐことができるタイプ。
- Custom Data Types — 独自のカスタムデータ型を作成します。
- Field Type Conversions — 異なるフィールドタイプ間の厳密および緩やかな変換を行います。
Type conversion¶
検証中、Pydanticはデータを期待される型に強制的に変換することができます。
強制にはstrictとlaxという2つのモードがあります。strictモードとlaxモードの両方でPydanticがデータを変換する方法の詳細については、Conversion Tableを参照してください。
厳密な強制を有効にする方法の詳細については、Strict modeおよびStrict Typesを参照してください。
Strict Types¶
Pydanticには次の厳密な型があります。
これらの型は、検証された値がそれぞれの型であるか、またはその型のサブタイプである場合にのみ検証に合格します。
Constrained types¶
この動作は、制約された型の"strict"フィールドによっても公開され、多数の複雑な検証規則と組み合わせることができます。サポートされている引数については、個々の型のシグネチャを参照してください。
次の注意事項が適用されます。
StrictBytes
(およびconbytes()
のstrict
オプション)はbytes
型とbytearray
型の両方を受け付けます。StrictInt
(およびconint()
のstrict
オプション)は、bool
がPythonのint
のサブクラスであっても、bool
型を受け入れません。他のサブクラスは動作します。StrictFloat
(およびconfloat()
のstrict
オプション)はint
を受け入れません。
上記の他に、FiniteFloat
型を指定することもできます。これは有限の値のみを受け入れます(つまりinf
、-inf
、nan
)。
Custom Types¶
また、独自のカスタム・データ型を定義することもできます。これを実現する方法はいくつかあります。
Composing types via 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]
"""
annotated-typesの制約を使用して、これを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]
"""
Adding validation and serialization¶
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'}
Generics¶
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]
"""
Named type aliases¶
上記の例では、暗黙的な型エイリアスを使用しています。
これは、JSONスキーマにtitle
を持つことができず、スキーマがフィールド間でコピーされることを意味します。
PEP 695のtyping-extensionsバックポート経由でTypeAliasType
を使用して、名前付きエイリアスを作成できます。これにより、サブクラスを作成せずに新しい型を定義できます。
この新しいタイプは、名前のように単純にすることも、複雑な検証ロジックを付加することもできます。
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',
}
"""
These named type aliases can also be generic:
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]
"""
Named recursive types¶
You can also use TypeAliasType
to create recursive types:
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]
"""
Customizing validation with __get_pydantic_core_schema__
¶
Pydanticがカスタムクラスを処理する方法をより広範にカスタマイズするために、特にクラスにアクセスできる場合やサブクラスを作成できる場合には、特別な__get_pydantic_core_schema__
を実装して、pydantic-core
スキーマの生成方法をPydanticに指示することができます。
pydantic
は内部でpydantic-core
を使用して検証とシリアライゼーションを処理しますが、これはPydantic V2の新しいAPIであるため、将来微調整される可能性が最も高い領域の1つであり、annotated-types
、pydantic.Field
、BeforeValidator
などで提供されているような組み込み構造に固執するようにしてください。
__get_pydantic_core_schema__
は、カスタム型とAnnotated
に入れることを意図したメタデータの両方に実装できます。
どちらの場合も、APIはミドルウェアに似ており、"ラップ"バリデータのAPIに似ています。
source_type
(特にジェネリックスの場合、クラスと同じである必要はありません)とhandler
を取得します。これらを型で呼び出すことで、Annotated
内の次のメタデータを呼び出すか、Pydanticの内部スキーマ生成に呼び出すことができます。
最も単純なno-op実装は、指定された型でハンドラを呼び出し、それを結果として返します。また、ハンドラを呼び出す前に型を変更したり、ハンドラによって返されるコアスキーマを変更したり、ハンドラをまったく呼び出さないようにすることもできます。
As a method on a custom type¶
以下は、検証方法をカスタマイズするために__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 Schemaを参照してください。
As an annotation¶
一般的な型パラメーター(型システムを介して行うことができ、後で説明します)だけではなく、カスタム型をパラメーター化することがよくあります。
あるいは、実際にはサブクラスのインスタンスを作成する必要がない(または作成したい)場合もあります。実際には、追加の検証を行うだけで、元の型が必要になります。
たとえば、pydantic.AfterValidator
(Adding validation and serializationを参照)を自分で実装する場合は、次のようなことを行います。
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)!
frozen=True
の指定はMyAfterValidator
をハッシュ可能にします。これがないと、Username None
のようなUnionでエラーが発生します。
- 型チェッカーは、前の例のように
Username
に'ABC'
を割り当てることについて文句を言わないことに注意してください。なぜなら、彼らはUsername
をstr
とは別の型と見なしていないからです。
Handling third-party types¶
前のセクションのパターンのもう1つの使用例は、サード・パーティーのタイプを処理することです。
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タイプの動作を定義できます。
Using GetPydanticSchema
to reduce boilerplate¶
API Documentation
マーカー・クラスを作成する上記の例では、かなりの量の定型文が必要であることにお気付きかもしれません。
多くの単純なケースでは、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'
Summary¶
まとめましょう。
- Pydanticは、
AfterValidator
やField
のようなAnnotated
を介して型をカスタマイズするための高レベルのフックを提供しています。可能であればこれらを使用してください。
- 内部では
pydantic-core
を使用して検証をカスタマイズし、GetPydanticSchema
または__get_pydantic_core_schema__
を使用してマーカークラスを直接フックすることができます。
- 本当にカスタム型が必要な場合は、型自体に
__get_pydantic_core_schema__
を実装することができます。
Handling custom generic classes¶
Warning
これは、最初は必要のない高度なテクニックです。ほとんどの場合、標準のPydanticモデルで問題ありません。
Generic Classesをフィールド型として使用し、__get_pydantic_core_schema__
で"型パラメータ"(またはサブ型)に基づいてカスタム検証を実行できます。
サブタイプとして使用しているGenericクラスに__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])
のようなことはしないことに注意してください。なぜなら、"注釈付き"メタデータなどの現在のコンテキストに影響されるものではなく、その汎用パラメータに対して無関係なスキーマを生成したいからです。
これは、カスタム・タイプではそれほど重要ではありませんが、スキーマ構築を変更する注釈付きメタデータでは重要です。
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]
"""
Generic containers¶
同じ考え方を適用して、カスタムの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]
"""
Access to field name¶
Note
これはPydantic V2からV2.3では不可能でしたが、Pydantic V2.4で[再追加]されました。(https://github.com/pydantic/pydantic/pull/7542)
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
のように、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'>