Aller au contenu

Validateurs annotés

??? API "Documentation API" [pydantic.functional_validators.WrapValidator][pydantic.function_validators.WrapValidator]
[pydantic.functional_validators.PlainValidator][pydantic.function_validators.PlainValidator]
[pydantic.functional_validators.BeforeValidator][pydantic.function_validators.BeforeValidator]
[pydantic.functional_validators.AfterValidator][pydantic.function_validators.AfterValidator]

Pydantic fournit un moyen d'appliquer des validateurs via l'utilisation d' Annotated . Vous devez l'utiliser chaque fois que vous souhaitez lier la validation à un type plutôt qu'à un modèle ou un champ.

from typing import Any, List

from typing_extensions import Annotated

from pydantic import BaseModel, ValidationError
from pydantic.functional_validators import AfterValidator


def check_squares(v: int) -> int:
    assert v**0.5 % 1 == 0, f'{v} is not a square number'
    return v


def double(v: Any) -> Any:
    return v * 2


MyNumber = Annotated[int, AfterValidator(double), AfterValidator(check_squares)]


class DemoModel(BaseModel):
    number: List[MyNumber]


print(DemoModel(number=[2, 8]))
#> number=[4, 16]
try:
    DemoModel(number=[2, 4])
except ValidationError as e:
    print(e)
    """
    1 validation error for DemoModel
    number.1
      Assertion failed, 8 is not a square number
    assert ((8 ** 0.5) % 1) == 0 [type=assertion_error, input_value=4, input_type=int]
    """

Dans cet exemple, nous avons utilisé des alias de type ( MyNumber = Annotated[...] ). Bien que cela puisse aider à la lisibilité du code, ce n'est pas obligatoire, vous pouvez utiliser Annotated directement dans un indice de type de champ de modèle. Ces alias de type ne sont pas non plus des types réels, mais vous pouvez utiliser une approche similaire avec TypeAliasType pour créer des types réels. Voir Types personnalisés pour une explication plus détaillée des types personnalisés.

Il convient également de noter que vous pouvez imbriquer Annotated dans d’autres types. Dans cet exemple, nous l'avons utilisé pour appliquer la validation aux éléments internes d'une liste. La même approche peut être utilisée pour les clés de dict, etc.

Validateurs Avant, Après, Wrap et Plain

Pydantic fournit plusieurs types de fonctions de validation:

  • After les validateurs s'exécutent après l'analyse interne de Pydantic. Ils sont généralement plus sûrs et donc plus faciles à mettre en œuvre.
  • Before les validateurs ne s'exécutent avant l'analyse et la validation internes de Pydantic (par exemple, la coercition d'un str vers un int ). Ceux-ci sont plus flexibles que les validateurs After puisqu'ils peuvent modifier l'entrée brute, mais ils doivent également gérer l'entrée brute, qui en théorie pourrait être n'importe quel objet arbitraire.
  • Les validateurs Plain sont comme un validateur mode='before' mais ils terminent la validation immédiatement, aucun autre validateur n'est appelé et Pydantic n'effectue aucune de ses validations internes.
  • Les validateurs Wrap sont les plus flexibles de tous. Vous pouvez exécuter du code avant ou après que Pydantic et d'autres validateurs aient fait leur travail ou vous pouvez mettre fin à la validation immédiatement, à la fois avec une valeur réussie ou une erreur.

Vous pouvez utiliser plusieurs validateurs avant, après ou mode='wrap' , mais un seul PlainValidator puisqu'un validateur simple n'appellera aucun validateur interne.

Voici un exemple de validateur mode='wrap':

import json
from typing import Any, List

from typing_extensions import Annotated

from pydantic import (
    BaseModel,
    ValidationError,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
)
from pydantic.functional_validators import WrapValidator


def maybe_strip_whitespace(
    v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> int:
    if info.mode == 'json':
        assert isinstance(v, str), 'In JSON mode the input must be a string!'
        # you can call the handler multiple times
        try:
            return handler(v)
        except ValidationError:
            return handler(v.strip())
    assert info.mode == 'python'
    assert isinstance(v, int), 'In Python mode the input must be an int!'
    # do no further validation
    return v


MyNumber = Annotated[int, WrapValidator(maybe_strip_whitespace)]


class DemoModel(BaseModel):
    number: List[MyNumber]


print(DemoModel(number=[2, 8]))
#> number=[2, 8]
print(DemoModel.model_validate_json(json.dumps({'number': [' 2 ', '8']})))
#> number=[2, 8]
try:
    DemoModel(number=['2'])
except ValidationError as e:
    print(e)
    """
    1 validation error for DemoModel
    number.0
      Assertion failed, In Python mode the input must be an int!
    assert False
     +  where False = isinstance('2', int) [type=assertion_error, input_value='2', input_type=str]
    """

Les mêmes «modes» s’appliquent à @field_validator , qui est abordé dans la section suivante.

Ordre des validateurs dans Annotated

Ordre des métadonnées de validation dans les sujets Annotated . La validation se fait de droite à gauche et inversement. Autrement dit, il va de droite à gauche en exécutant tous les validateurs "avant" (ou en appelant les validateurs "wrap"), puis de gauche à droite en appelant tous les validateurs "après".

from typing import Any, Callable, List, cast

from typing_extensions import Annotated, TypedDict

from pydantic import (
    AfterValidator,
    BaseModel,
    BeforeValidator,
    PlainValidator,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)
from pydantic.functional_validators import field_validator


class Context(TypedDict):
    logs: List[str]


def make_validator(label: str) -> Callable[[Any, ValidationInfo], Any]:
    def validator(v: Any, info: ValidationInfo) -> Any:
        context = cast(Context, info.context)
        context['logs'].append(label)
        return v

    return validator


def make_wrap_validator(
    label: str,
) -> Callable[[Any, ValidatorFunctionWrapHandler, ValidationInfo], Any]:
    def validator(
        v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
    ) -> Any:
        context = cast(Context, info.context)
        context['logs'].append(f'{label}: pre')
        result = handler(v)
        context['logs'].append(f'{label}: post')
        return result

    return validator


class A(BaseModel):
    x: Annotated[
        str,
        BeforeValidator(make_validator('before-1')),
        AfterValidator(make_validator('after-1')),
        WrapValidator(make_wrap_validator('wrap-1')),
        BeforeValidator(make_validator('before-2')),
        AfterValidator(make_validator('after-2')),
        WrapValidator(make_wrap_validator('wrap-2')),
        BeforeValidator(make_validator('before-3')),
        AfterValidator(make_validator('after-3')),
        WrapValidator(make_wrap_validator('wrap-3')),
        BeforeValidator(make_validator('before-4')),
        AfterValidator(make_validator('after-4')),
        WrapValidator(make_wrap_validator('wrap-4')),
    ]
    y: Annotated[
        str,
        BeforeValidator(make_validator('before-1')),
        AfterValidator(make_validator('after-1')),
        WrapValidator(make_wrap_validator('wrap-1')),
        BeforeValidator(make_validator('before-2')),
        AfterValidator(make_validator('after-2')),
        WrapValidator(make_wrap_validator('wrap-2')),
        PlainValidator(make_validator('plain')),
        BeforeValidator(make_validator('before-3')),
        AfterValidator(make_validator('after-3')),
        WrapValidator(make_wrap_validator('wrap-3')),
        BeforeValidator(make_validator('before-4')),
        AfterValidator(make_validator('after-4')),
        WrapValidator(make_wrap_validator('wrap-4')),
    ]

    val_x_before = field_validator('x', mode='before')(
        make_validator('val_x before')
    )
    val_x_after = field_validator('x', mode='after')(
        make_validator('val_x after')
    )
    val_y_wrap = field_validator('y', mode='wrap')(
        make_wrap_validator('val_y wrap')
    )


context = Context(logs=[])

A.model_validate({'x': 'abc', 'y': 'def'}, context=context)
print(context['logs'])
"""
[
    'val_x before',
    'wrap-4: pre',
    'before-4',
    'wrap-3: pre',
    'before-3',
    'wrap-2: pre',
    'before-2',
    'wrap-1: pre',
    'before-1',
    'after-1',
    'wrap-1: post',
    'after-2',
    'wrap-2: post',
    'after-3',
    'wrap-3: post',
    'after-4',
    'wrap-4: post',
    'val_x after',
    'val_y wrap: pre',
    'wrap-4: pre',
    'before-4',
    'wrap-3: pre',
    'before-3',
    'plain',
    'after-3',
    'wrap-3: post',
    'after-4',
    'wrap-4: post',
    'val_y wrap: post',
]
"""

Validation des valeurs par défaut

Les validateurs ne s'exécuteront pas lorsque la valeur par défaut est utilisée. Cela s'applique à la fois aux validateurs @field_validator et aux validateurs Annotated . Vous pouvez les forcer à s'exécuter avec Field(validate_default=True) . Définir validate_default sur True a le comportement le plus proche de l'utilisation always=True dans validator dans Pydantic v1. Cependant, il est généralement préférable d'utiliser un @model_validator(mode='before') où la fonction est appelée avant l'appel du validateur interne.

from typing_extensions import Annotated

from pydantic import BaseModel, Field, field_validator


class Model(BaseModel):
    x: str = 'abc'
    y: Annotated[str, Field(validate_default=True)] = 'xyz'

    @field_validator('x', 'y')
    @classmethod
    def double(cls, v: str) -> str:
        return v * 2


print(Model())
#> x='abc' y='xyzxyz'
print(Model(x='foo'))
#> x='foofoo' y='xyzxyz'
print(Model(x='abc'))
#> x='abcabc' y='xyzxyz'
print(Model(x='foo', y='bar'))
#> x='foofoo' y='barbar'

Validateurs de terrain

??? API "Documentation API" [pydantic.functional_validators.field_validator][pydantic.function_validators.field_validator]

Si vous souhaitez attacher un validateur à un champ spécifique d'un modèle, vous pouvez utiliser le décorateur @field_validator .

from pydantic import (
    BaseModel,
    ValidationError,
    ValidationInfo,
    field_validator,
)


class UserModel(BaseModel):
    name: str
    id: int

    @field_validator('name')
    @classmethod
    def name_must_contain_space(cls, v: str) -> str:
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

    # you can select multiple fields, or use '*' to select all fields
    @field_validator('id', 'name')
    @classmethod
    def check_alphanumeric(cls, v: str, info: ValidationInfo) -> str:
        if isinstance(v, str):
            # info.field_name is the name of the field being validated
            is_alphanumeric = v.replace(' ', '').isalnum()
            assert is_alphanumeric, f'{info.field_name} must be alphanumeric'
        return v


print(UserModel(name='John Doe', id=1))
#> name='John Doe' id=1

try:
    UserModel(name='samuel', id=1)
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    name
      Value error, must contain a space [type=value_error, input_value='samuel', input_type=str]
    """

try:
    UserModel(name='John Doe', id='abc')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    id
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
    """

try:
    UserModel(name='John Doe!', id=1)
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    name
      Assertion failed, name must be alphanumeric
    assert False [type=assertion_error, input_value='John Doe!', input_type=str]
    """

Quelques points à noter sur les validateurs:

  • Les @field_validator sont des "méthodes de classe", donc la première valeur d'argument qu'ils reçoivent est la classe UserModel , pas une instance de UserModel . Nous vous recommandons d'utiliser le décorateur @classmethod sous le décorateur @field_validator pour obtenir une vérification de type appropriée.
  • le deuxième argument est la valeur du champ à valider ; il peut être nommé comme bon vous semble
  • le troisième argument, s'il est présent, est une instance de pydantic.ValidationInfo
  • les validateurs doivent soit renvoyer la valeur analysée, soit déclencher une ValueError ou AssertionError (des instructions assert peuvent être utilisées).
  • Un seul validateur peut être appliqué à plusieurs champs en lui transmettant plusieurs noms de champs.
  • Un seul validateur peut également être appelé sur tous les champs en passant la valeur spéciale '*' .

!!! avertissement Si vous utilisez des instructions assert , gardez à l'esprit que l'exécution de Python avec l' indicateur d'optimisation -O désactive les instructions assert et les validateurs cesseront de fonctionner .

!!! Remarque FieldValidationInfo est obsolète dans la version 2.4, utilisez plutôt ValidationInfo .

Si vous souhaitez accéder aux valeurs d'un autre champ à l'intérieur d'un @field_validator , cela peut être possible en utilisant ValidationInfo.data , qui est un dict du nom du champ à la valeur du champ. La validation se fait dans la commande les champs sont définis, vous devez donc faire attention lorsque vous utilisez ValidationInfo.data à ne pas accéder à un champ qui n'a pas encore été validé/rempli — dans le code ci-dessus, par exemple, vous ne pourrez pas accéder info.data['id'] depuis name_must_contain_space . Cependant, dans la plupart des cas où vous souhaitez effectuer une validation en utilisant plusieurs valeurs de champ, il est préférable d'utiliser @model_validator qui est abordé dans la section ci-dessous.

Validateurs de modèles

??? API "Documentation API" [pydantic.functional_validators.model_validator][pydantic.function_validators.model_validator]

La validation peut également être effectuée sur l'ensemble des données du modèle à l'aide de @model_validator .

from typing import Any

from typing_extensions import Self

from pydantic import BaseModel, ValidationError, model_validator


class UserModel(BaseModel):
    username: str
    password1: str
    password2: str

    @model_validator(mode='before')
    @classmethod
    def check_card_number_omitted(cls, data: Any) -> Any:
        if isinstance(data, dict):
            assert (
                'card_number' not in data
            ), 'card_number should not be included'
        return data

    @model_validator(mode='after')
    def check_passwords_match(self) -> Self:
        pw1 = self.password1
        pw2 = self.password2
        if pw1 is not None and pw2 is not None and pw1 != pw2:
            raise ValueError('passwords do not match')
        return self


print(UserModel(username='scolvin', password1='zxcvbn', password2='zxcvbn'))
#> username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
    UserModel(username='scolvin', password1='zxcvbn', password2='zxcvbn2')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
      Value error, passwords do not match [type=value_error, input_value={'username': 'scolvin', '... 'password2': 'zxcvbn2'}, input_type=dict]
    """

try:
    UserModel(
        username='scolvin',
        password1='zxcvbn',
        password2='zxcvbn',
        card_number='1234',
    )
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
      Assertion failed, card_number should not be included
    assert 'card_number' not in {'card_number': '1234', 'password1': 'zxcvbn', 'password2': 'zxcvbn', 'username': 'scolvin'} [type=assertion_error, input_value={'username': 'scolvin', '..., 'card_number': '1234'}, input_type=dict]
    """

!!! note "Lors de la vérification du type de retour" Les méthodes décorées avec @model_validator doivent renvoyer l'instance self à la fin de la méthode. À des fins de vérification de type, vous pouvez utiliser Self à partir de typing ou du backport typing_extensions comme type de retour de la méthode décorée. Dans le contexte de l'exemple ci-dessus, vous pouvez également utiliser def check_passwords_match(self: 'UserModel') -> 'UserModel' pour indiquer que la méthode renvoie une instance du modèle.

!!! note "Sur l'héritage" Un @model_validator défini dans une classe de base sera appelé lors de la validation d'une instance de sous-classe.

Overriding a `@model_validator` in a subclass will override the base class' `@model_validator`, and thus only the subclass' version of said `@model_validator` will be called.

Les validateurs de modèles peuvent être mode='before' , mode='after' ou mode='wrap' .

Avant que les validateurs de modèles ne reçoivent l'entrée brute qui est souvent un dict[str, Any] mais peut également être une instance du modèle lui-même (par exemple si UserModel.model_validate(UserModel.construct(...)) est appelé) ou toute autre chose puisque vous pouvez transmettre des objets arbitraires dans model_validate . Pour cette raison, les validateurs mode='before' sont extrêmement flexibles et puissants, mais peuvent être lourds et sujets aux erreurs à mettre en œuvre. Avant que les validateurs de modèles ne soient des méthodes de classe. Le premier argument doit être cls (et nous vous recommandons également d'utiliser @classmethod sous @model_validator pour une vérification de type appropriée), le deuxième argument sera l'entrée (vous devez généralement le saisir comme Any et utiliser isinstance pour affiner le type) et le troisième L'argument (s'il est présent) sera un pydantic.ValidationInfo .

Les validateurs mode='after' sont des méthodes d'instance et reçoivent toujours une instance du modèle comme premier argument. Assurez-vous de renvoyer l'instance à la fin de votre validateur. Vous ne devez pas utiliser (cls, ModelType) comme signature, mais simplement utiliser (self) et laisser les vérificateurs de type déduire le type de self pour vous. Comme ils sont entièrement sécurisés, ils sont souvent plus faciles à implémenter que les validateurs mode='before' . Si un champ ne parvient pas à être validé, les validateurs mode='after' pour ce champ ne seront pas appelés.

Gestion des erreurs dans les validateurs

Comme mentionné dans les sections précédentes, vous pouvez déclencher une ValueError ou AssertionError (y compris celles générées par les instructions assert ... ) au sein d'un validateur pour indiquer l'échec de la validation. Vous pouvez également générer une PydanticCustomError qui est un peu plus verbeuse mais vous offre une flexibilité supplémentaire. Toutes les autres erreurs (y compris TypeError ) sont bouillonnées et non enveloppées dans un ValidationError .

from pydantic_core import PydanticCustomError

from pydantic import BaseModel, ValidationError, field_validator


class Model(BaseModel):
    x: int

    @field_validator('x')
    @classmethod
    def validate_x(cls, v: int) -> int:
        if v % 42 == 0:
            raise PydanticCustomError(
                'the_answer_error',
                '{number} is the answer!',
                {'number': v},
            )
        return v


try:
    Model(x=42 * 2)
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    x
      84 is the answer! [type=the_answer_error, input_value=84, input_type=int]
    """

Types spéciaux

Pydantic propose quelques types spéciaux qui peuvent être utilisés pour personnaliser la validation.

  • [InstanceOf][pydantic.function_validators.InstanceOf] est un type qui peut être utilisé pour valider qu'une valeur est une instance d'une classe donnée.

    from typing import List

    from pydantic import BaseModel, InstanceOf, ValidationError

    class Fruit: def repr(self): return self.class.name

    class Banana(Fruit): ...

    class Apple(Fruit): ...

    class Basket(BaseModel): fruits: List[InstanceOf[Fruit]]

    print(Basket(fruits=[Banana(), Apple()]))

    > fruits=[Banana, Apple]

    try: Basket(fruits=[Banana(), 'Apple']) except ValidationError as e: print(e) """ 1 validation error for Basket fruits.1 Input should be an instance of Fruit [type=is_instance_of, input_value='Apple', input_type=str] """

  • [SkipValidation][pydantic.function_validators.SkipValidation] est un type qui peut être utilisé pour ignorer la validation sur un champ.

    from typing import List

    from pydantic import BaseModel, SkipValidation

    class Model(BaseModel): names: List[SkipValidation[str]]

    m = Model(names=['foo', 'bar']) print(m)

    > names=['foo', 'bar']

    m = Model(names=['foo', 123]) # (1)! print(m)

    > names=['foo', 123]

  • Notez que la validation du deuxième élément est ignorée. S'il est du mauvais type, il émettra un avertissement lors de la sérialisation.

Vérifications sur le terrain

Lors de la création de la classe, les validateurs sont vérifiés pour confirmer que les champs qu'ils spécifient existent réellement sur le modèle.

Cela peut s'avérer indésirable si, par exemple, vous souhaitez définir un validateur pour valider des champs qui ne seront présents que sur les sous-classes du modèle où le validateur est défini.

Si vous souhaitez désactiver ces vérifications lors de la création de la classe, vous pouvez transmettre check_fields=False comme argument mot-clé au validateur.

Validateurs de classes de données

Les validateurs fonctionnent également avec les classes de données Pydantic.

from pydantic import field_validator
from pydantic.dataclasses import dataclass


@dataclass
class DemoDataclass:
    product_id: str  # should be a five-digit string, may have leading zeros

    @field_validator('product_id', mode='before')
    @classmethod
    def convert_int_serial(cls, v):
        if isinstance(v, int):
            v = str(v).zfill(5)
        return v


print(DemoDataclass(product_id='01234'))
#> DemoDataclass(product_id='01234')
print(DemoDataclass(product_id=2468))
#> DemoDataclass(product_id='02468')

Contexte de validation

Vous pouvez transmettre un objet contextuel aux méthodes de validation accessibles depuis l'argument info aux fonctions de validation décorées:

from pydantic import BaseModel, ValidationInfo, field_validator


class Model(BaseModel):
    text: str

    @field_validator('text')
    @classmethod
    def remove_stopwords(cls, v: str, info: ValidationInfo):
        context = info.context
        if context:
            stopwords = context.get('stopwords', set())
            v = ' '.join(w for w in v.split() if w.lower() not in stopwords)
        return v


data = {'text': 'This is an example document'}
print(Model.model_validate(data))  # no context
#> text='This is an example document'
print(Model.model_validate(data, context={'stopwords': ['this', 'is', 'an']}))
#> text='example document'
print(Model.model_validate(data, context={'stopwords': ['document']}))
#> text='This is an example'

Ceci est utile lorsque vous devez mettre à jour dynamiquement le comportement de validation pendant l'exécution. Par exemple, si vous souhaitez qu'un champ ait un ensemble de valeurs autorisées contrôlables dynamiquement, cela peut être fait en transmettant les valeurs autorisées par contexte et en disposant d'un mécanisme distinct pour mettre à jour ce qui est autorisé:

from typing import Any, Dict, List

from pydantic import (
    BaseModel,
    ValidationError,
    ValidationInfo,
    field_validator,
)

_allowed_choices = ['a', 'b', 'c']


def set_allowed_choices(allowed_choices: List[str]) -> None:
    global _allowed_choices
    _allowed_choices = allowed_choices


def get_context() -> Dict[str, Any]:
    return {'allowed_choices': _allowed_choices}


class Model(BaseModel):
    choice: str

    @field_validator('choice')
    @classmethod
    def validate_choice(cls, v: str, info: ValidationInfo):
        allowed_choices = info.context.get('allowed_choices')
        if allowed_choices and v not in allowed_choices:
            raise ValueError(f'choice must be one of {allowed_choices}')
        return v


print(Model.model_validate({'choice': 'a'}, context=get_context()))
#> choice='a'

try:
    print(Model.model_validate({'choice': 'd'}, context=get_context()))
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    choice
      Value error, choice must be one of ['a', 'b', 'c'] [type=value_error, input_value='d', input_type=str]
    """

set_allowed_choices(['b', 'c'])

try:
    print(Model.model_validate({'choice': 'a'}, context=get_context()))
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    choice
      Value error, choice must be one of ['b', 'c'] [type=value_error, input_value='a', input_type=str]
    """

De même, vous pouvez utiliser un contexte pour la sérialisation .

Utilisation du contexte de validation avec l'initialisation BaseModel

Bien qu'il n'existe aucun moyen de spécifier un contexte dans l'initialiseur BaseModel standard, vous pouvez contourner ce problème en utilisant contextvars.ContextVar et une méthode __init__ personnalisée:

from contextlib import contextmanager
from contextvars import ContextVar
from typing import Any, Dict, Iterator

from pydantic import BaseModel, ValidationInfo, field_validator

_init_context_var = ContextVar('_init_context_var', default=None)


@contextmanager
def init_context(value: Dict[str, Any]) -> Iterator[None]:
    token = _init_context_var.set(value)
    try:
        yield
    finally:
        _init_context_var.reset(token)


class Model(BaseModel):
    my_number: int

    def __init__(self, /, **data: Any) -> None:
        self.__pydantic_validator__.validate_python(
            data,
            self_instance=self,
            context=_init_context_var.get(),
        )

    @field_validator('my_number')
    @classmethod
    def multiply_with_context(cls, value: int, info: ValidationInfo) -> int:
        if info.context:
            multiplier = info.context.get('multiplier', 1)
            value = value * multiplier
        return value


print(Model(my_number=2))
#> my_number=2

with init_context({'multiplier': 3}):
    print(Model(my_number=2))
    #> my_number=6

print(Model(my_number=2))
#> my_number=2

Réutiliser les validateurs

Parfois, vous souhaiterez utiliser le même validateur sur plusieurs champs/modèles (par exemple pour normaliser certaines données d'entrée). L'approche "naïve" consisterait à écrire une fonction distincte, puis à l'appeler à partir de plusieurs décorateurs. Évidemment, cela implique beaucoup de répétitions et de codes passe-partout. L'approche suivante montre comment réutiliser un validateur afin que la redondance soit minimisée et que les modèles redeviennent presque déclaratifs.

from pydantic import BaseModel, field_validator


def normalize(name: str) -> str:
    return ' '.join((word.capitalize()) for word in name.split(' '))


class Producer(BaseModel):
    name: str

    _normalize_name = field_validator('name')(normalize)


class Consumer(BaseModel):
    name: str

    _normalize_name = field_validator('name')(normalize)


jane_doe = Producer(name='JaNe DOE')
print(repr(jane_doe))
#> Producer(name='Jane Doe')
john_doe = Consumer(name='joHN dOe')
print(repr(john_doe))
#> Consumer(name='John Doe')

本文总阅读量