Перейти к содержанию

Типы

Там, где это возможно, Pydantic использует стандартные библиотечные типы для определения полей, тем самым сглаживая кривую обучения. Однако для многих полезных приложений не существует стандартного типа библиотеки, поэтому Pydantic реализует многие часто используемые типы.

Существуют также более сложные типы, которые можно найти в пакете дополнительных типов Pydantic .

Если ни один из существующих типов не соответствует вашим целям, вы также можете реализовать свои собственные типы, совместимые с Pydantic, с настраиваемыми свойствами и проверкой.

В следующих разделах описаны типы, поддерживаемые Pydantic.

Преобразование типов

Во время проверки Pydantic может привести данные к ожидаемым типам.

Существует два способа принуждения: строгий и нестрогий. См. Таблицу преобразования для получения более подробной информации о том, как Pydantic преобразует данные как в строгом, так и в нестрогом режимах.

Подробные сведения о включении строгого приведения см. в разделах «Строгий режим» и «Строгие типы» .

Строгие типы

Pydantic предоставляет следующие строгие типы:

Эти типы пройдут проверку только в том случае, если проверенное значение относится к соответствующему типу или является подтипом этого типа.

Ограниченные типы

Это поведение также проявляется через strict поле ограниченных типов и может сочетаться с множеством сложных правил проверки. См. отдельные сигнатуры типов поддерживаемых аргументов.

Применяются следующие предостережения:

  • StrictBytesstrict опция conbytes() ) будут принимать как типы bytes , так и bytearray .
  • StrictIntstrict параметр conint() ) не принимают типы bool , хотя bool является подклассом int в Python. Другие подклассы будут работать.
  • StrictFloatstrict вариант confloat() ) не принимают int .

Помимо вышесказанного, вы также можете иметь тип FiniteFloat, который будет принимать только конечные значения (т.е. не inf , -inf или nan ).

Пользовательские типы

Вы также можете определить свои собственные типы данных. Есть несколько способов добиться этого.

Составление типов через Annotated

В PEP 593 Annotated был представлен как способ прикрепления метаданных времени выполнения к типам без изменения того, как их интерпретируют средства проверки типов. Pydantic использует это преимущество, чтобы позволить вам создавать типы, идентичные исходному типу с точки зрения средств проверки типов, но с добавлением проверки, другой сериализацией и т. д.

Например, чтобы создать тип, представляющий положительное целое число:

# 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]
    """

Добавление проверки и сериализации

Вы можете добавить или переопределить схемы проверки, сериализации и JSON для произвольного типа, используя маркеры, экспортируемые Pydantic:

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]
    """

Псевдонимы именованных типов

В приведенных выше примерах используются неявные псевдонимы типов. Это означает, что они не смогут иметь title в схемах JSON, и их схема будет копироваться между полями. Вы можете использовать TypeAliasType PEP 695 через его бэкпорт расширений ввода для создания именованных псевдонимов, что позволяет вам определять новый тип без создания подклассов. Этот новый тип может быть таким же простым, как имя, или иметь сложную логику проверки:

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 для проверки и сериализации, это новый API для Pydantic V2, поэтому это одна из областей, которая, скорее всего, будет изменена в будущем, и вам следует стараться придерживаться встроенных конструкций, таких как те, которые предоставляются annotated-types , pydantic.Field или BeforeValidator и т. д.

Вы можете реализовать __get_pydantic_core_schema__ как для пользовательского типа, так и для метаданных, предназначенных для размещения в Annotated . В обоих случаях API похож на промежуточное программное обеспечение и аналогичен API валидаторов «обертывания»: вы получаете source_type (который не обязательно совпадает с классом, в частности для дженериков) и handler , который вы можете вызвать с типом либо вызвать следующие метаданные в Annotated , либо вызвать генерацию внутренней схемы Pydantic.

Самая простая реализация без операций вызывает обработчик заданного типа, а затем возвращает его в качестве результата. Вы также можете изменить тип перед вызовом обработчика, изменить базовую схему, возвращаемую обработчиком, или вообще не вызывать обработчик.

Как метод пользовательского типа

Ниже приведен пример типа, который использует __get_pydantic_core_schema__ для настройки способа проверки. Это эквивалентно реализации __get_validators__ в Pydantic V1.

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 вызовет ошибку.
  2. Обратите внимание, что средства проверки типов не будут жаловаться на присвоение 'ABC' Username , как это было в предыдущем примере, поскольку они не считают Username отдельным типом от str .

Обработка сторонних типов

Другой вариант использования шаблона из предыдущего раздела — обработка сторонних типов.

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 предоставляет перехватчики высокого уровня для настройки типов с помощью Annotated таких как AfterValidator и Field . Используйте их, когда это возможно.
  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][pydantic.config.ConfigDict.dictary_types_allowed], чтобы он работал.

Поскольку параметр source_type отличается от параметра cls , вы можете использовать typing.get_args (или typing_extensions.get_args ) для извлечения общих параметров. Затем вы можете использовать handler для создания для них схемы, вызвав handler.generate_schema . Обратите внимание, что мы не делаем что-то вроде 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, вы можете получить доступ к имени поля через handler.field_name в __get_pydantic_core_schema__ и тем самым установить имя поля, которое будет доступно из 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'>

Вы также можете получить доступ к field_name из маркеров, используемых с Annotated , например AfterValidator.

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'>

本文总阅读量