Aller au contenu

Types

Dans la mesure du possible, Pydantic utilise des types de bibliothèques standard pour définir les champs, lissant ainsi la courbe d'apprentissage. Cependant, pour de nombreuses applications utiles, il n'existe aucun type de bibliothèque standard, c'est pourquoi Pydantic implémente de nombreux types couramment utilisés.

Il existe également des types plus complexes qui peuvent être trouvés dans le package Pydantic Extra Types .

Si aucun type existant ne convient à votre objectif, vous pouvez également implémenter vos propres types compatibles Pydantic avec des propriétés et une validation personnalisées.

Les sections suivantes décrivent les types pris en charge par Pydantic.

Conversion de types

Lors de la validation, Pydantic peut contraindre les données aux types attendus.

Il existe deux modes de coercition : stricte et laxiste. Voir Tableau de conversion pour plus de détails sur la façon dont Pydantic convertit les données en modes strict et laxiste.

Voir Mode strict et Types stricts pour plus de détails sur l'activation de la coercition stricte.

Types stricts

Pydantic fournit les types stricts suivants:

Ces types ne réussiront la validation que lorsque la valeur validée est du type respectif ou est un sous-type de ce type.

Types contraints

Ce comportement est également exposé via le champ strict des types contraints et peut être combiné avec une multitude de règles de validation complexes. Consultez les signatures de type individuelles pour connaître les arguments pris en charge.

Les mises en garde suivantes s'appliquent:

  • StrictBytes (et l'option strict de conbytes() ) acceptera à la fois les types bytes et bytearray .
  • StrictInt (et l'option strict de conint() ) n'acceptera pas les types bool , même si bool est une sous-classe de int en Python. D'autres sous-classes fonctionneront.
  • StrictFloat (et l'option strict de confloat() ) n'acceptera pas int .

Outre ce qui précède, vous pouvez également avoir un type FiniteFloat qui n'acceptera que des valeurs finies (c'est-à-dire pas inf , -inf ou nan ).

Types personnalisés

Vous pouvez également définir vos propres types de données personnalisés. Il existe plusieurs façons d’y parvenir.

Composition de types via Annotated

PEP 593 a introduit Annotated comme moyen d'attacher des métadonnées d'exécution aux types sans changer la façon dont les vérificateurs de types les interprètent. Pydantic en profite pour vous permettre de créer des types identiques au type d'origine en ce qui concerne les vérificateurs de types, mais en ajoutant une validation, en sérialisant différemment, etc.

Par exemple, pour créer un type représentant un entier positif:

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

Notez que vous pouvez également utiliser des contraintes de types annotés pour rendre ce Pydantic-agnostique:

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

Ajout de la validation et de la sérialisation

Vous pouvez ajouter ou remplacer des schémas de validation, de sérialisation et JSON à un type arbitraire à l'aide des marqueurs exportés par 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'}

Génériques

Vous pouvez utiliser des variables de type dans Annotated pour apporter des modifications réutilisables aux types:

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

Alias de type nommé

Les exemples ci-dessus utilisent des alias de type implicites. Cela signifie qu'ils ne pourront pas avoir de title dans les schémas JSON et que leur schéma sera copié entre les champs. Vous pouvez utiliser TypeAliasType de PEP 695 via son backport d'extensions de typage pour créer des alias nommés, vous permettant de définir un nouveau type sans créer de sous-classes. Ce nouveau type peut être aussi simple qu'un nom ou être associé à une logique de validation complexe:

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',
}
"""

Ces alias de type nommés peuvent également être génériques:

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

Types récursifs nommés

Vous pouvez également utiliser TypeAliasType pour créer des types récursifs:

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

Personnalisation de la validation avec __get_pydantic_core_schema__

Pour effectuer une personnalisation plus approfondie de la façon dont Pydantic gère les classes personnalisées, et en particulier lorsque vous avez accès à la classe ou pouvez la sous-classer, vous pouvez implémenter un __get_pydantic_core_schema__ spécial pour indiquer à Pydantic comment générer le schéma pydantic-core .

Bien que pydantic utilise pydantic-core en interne pour gérer la validation et la sérialisation, il s'agit d'une nouvelle API pour Pydantic V2, c'est donc l'un des domaines les plus susceptibles d'être modifiés à l'avenir et vous devriez essayer de vous en tenir aux constructions intégrées comme ceux fournis par annotated-types , pydantic.Field ou BeforeValidator et ainsi de suite.

Vous pouvez implémenter __get_pydantic_core_schema__ à la fois sur un type personnalisé et sur des métadonnées destinées à être placées dans Annotated . Dans les deux cas l'API est de type middleware et similaire à celle des validateurs "wrap" : vous obtenez un source_type (qui n'est pas forcément le même que la classe, notamment pour les génériques) et un handler que vous pouvez appeler avec un type soit pour appeler les métadonnées suivantes dans Annotated , soit pour appeler la génération de schéma interne de Pydantic.

L'implémentation sans opération la plus simple appelle le gestionnaire avec le type qui vous est donné, puis le renvoie comme résultat. Vous pouvez également choisir de modifier le type avant d'appeler le gestionnaire, de modifier le schéma principal renvoyé par le gestionnaire ou de ne pas appeler le gestionnaire du tout.

En tant que méthode sur un type personnalisé

Ce qui suit est un exemple d'un type qui utilise __get_pydantic_core_schema__ pour personnaliser la façon dont il est validé. Cela équivaut à implémenter __get_validators__ dans 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'

Voir Schéma JSON pour plus de détails sur la façon de personnaliser les schémas JSON pour les types personnalisés.

En guise d'annotation

Souvent, vous souhaiterez paramétrer votre type personnalisé avec plus que de simples paramètres de type génériques (ce que vous pouvez faire via le système de types et qui sera discuté plus tard). Ou vous ne vous souciez peut-être pas (ou ne voulez pas) créer une instance de votre sous-classe; vous voulez en fait le type d'origine, juste avec une validation supplémentaire effectuée.

Par exemple, si vous deviez implémenter vous-même pydantic.AfterValidator (voir Ajout d'une validation et d'une sérialisation ), vous feriez quelque chose de similaire à ce qui suit:

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. La spécification frozen=True rend MyAfterValidator hachable. Sans cela, un syndicat tel que Username | None générera une erreur.
  2. Notez que les vérificateurs de type ne se plaindront pas de l'attribution 'ABC' à Username comme ils l'ont fait dans l'exemple précédent, car ils ne considèrent pas Username comme un type distinct de str .

Gestion des types tiers

Un autre cas d'utilisation du modèle de la section précédente consiste à gérer des types tiers.

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

Vous pouvez utiliser cette approche pour, par exemple, définir le comportement des types Pandas ou Numpy.

Utiliser GetPydanticSchema pour réduire le passe-partout

??? API "Documentation API" pydantic.types.GetPydanticSchema

Vous remarquerez peut-être que les exemples ci-dessus dans lesquels nous créons une classe de marqueurs nécessitent une bonne quantité de passe-partout. Pour de nombreux cas simples, vous pouvez grandement minimiser cela en utilisant 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'

Résumé

Récapitulons :

  1. Pydantic fournit des hooks de haut niveau pour personnaliser les types via Annotated comme AfterValidator et Field . Utilisez-les lorsque cela est possible.
  2. Sous le capot, ceux-ci utilisent pydantic-core pour personnaliser la validation, et vous pouvez vous y connecter directement en utilisant GetPydanticSchema ou une classe de marqueur avec __get_pydantic_core_schema__ .
  3. Si vous voulez vraiment un type personnalisé, vous pouvez implémenter __get_pydantic_core_schema__ sur le type lui-même.

Gestion des classes génériques personnalisées

!!! avertissement Il s'agit d'une technique avancée dont vous n'aurez peut-être pas besoin au début. Dans la plupart des cas, les modèles Pydantic standard vous conviendront probablement.

Vous pouvez utiliser des classes génériques comme types de champs et effectuer une validation personnalisée basée sur les « paramètres de type » (ou sous-types) avec __get_pydantic_core_schema__ .

Si la classe générique que vous utilisez comme sous-type a une méthode de classe __get_pydantic_core_schema__ , vous n'avez pas besoin d'utiliser arbitrary_types_allowed pour que cela fonctionne.

Étant donné que le paramètre source_type n'est pas identique au paramètre cls , vous pouvez utiliser typing.get_args (ou typing_extensions.get_args ) pour extraire les paramètres génériques. Ensuite, vous pouvez utiliser le handler pour générer un schéma pour eux en appelant handler.generate_schema . Notez que nous ne faisons pas quelque chose comme handler(get_args(source_type)[0]) parce que nous voulons générer un schéma sans rapport pour ce paramètre générique, et non un schéma influencé par le contexte actuel des métadonnées Annotated et autres. Ceci est moins important pour les types personnalisés, mais crucial pour les métadonnées annotées qui modifient la construction du schéma.

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

Conteneurs génériques

La même idée peut être appliquée pour créer des types de conteneurs génériques, comme un type Sequence personnalisé:

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

Accès au nom du champ

!!!note Cela n'était pas possible avec Pydantic V2 à V2.3, cela a été ré-ajouté dans Pydantic V2.4.

À partir de Pydantic V2.4, vous pouvez accéder au nom du champ via handler.field_name dans __get_pydantic_core_schema__ et ainsi définir le nom du champ qui sera disponible à partir de 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'>

Vous pouvez également accéder à field_name à partir des marqueurs utilisés avec Annotated , comme [AfterValidator][pydantic.function_validators.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'>

本文总阅读量