Zum Inhalt

Kommentierte Validatoren

??? API „API-Dokumentation“ [pydantic.functional_validators.WrapValidator][pydantic.Functional_validators.WrapValidator]
[pydantic.functional_validators.PlainValidator][pydantic.Functional_validators.PlainValidator]
[pydantic.functional_validators.BeforeValidator][pydantic.Functional_validators.BeforeValidator]
[pydantic.functional_validators.AfterValidator][pydantic.Functional_validators.AfterValidator]

Pydantic bietet eine Möglichkeit, Validatoren mithilfe von Annotated anzuwenden. Sie sollten dies immer dann verwenden, wenn Sie die Validierung an einen Typ statt an ein Modell oder ein Feld binden möchten.

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

In diesem Beispiel haben wir einige Typaliase verwendet ( MyNumber = Annotated[...] ). Dies kann zwar die Lesbarkeit des Codes verbessern, ist jedoch nicht erforderlich. Sie können Annotated direkt in einem Modellfeldtyphinweis verwenden. Bei diesen Typaliasen handelt es sich ebenfalls nicht um tatsächliche Typen, aber Sie können mit TypeAliasType einen ähnlichen Ansatz verwenden, um tatsächliche Typen zu erstellen. Eine ausführlichere Erläuterung der benutzerdefinierten Typen finden Sie unter Benutzerdefinierte Typen .

Es ist auch erwähnenswert, dass Sie Annotated in andere Typen verschachteln können. In diesem Beispiel haben wir dies verwendet, um die Validierung auf die inneren Elemente einer Liste anzuwenden. Der gleiche Ansatz kann für Diktiertasten usw. verwendet werden.

Vorher-, Nachher-, Wrap- und Plain-Validatoren

Pydantic bietet mehrere Arten von Validierungsfunktionen:

  • After Validatoren nach der internen Analyse von Pydantic ausgeführt wurden. Sie sind im Allgemeinen typsicherer und daher einfacher zu implementieren.
  • Before Validatoren vor der internen Analyse und Validierung von Pydantic ausgeführt werden (z. B. Umwandlung eines str in einen int ). Diese sind flexibler als After -Validatoren, da sie die Roheingabe ändern können, müssen sich aber auch mit der Roheingabe befassen, bei der es sich theoretisch um jedes beliebige Objekt handeln kann.
  • Plain Validatoren sind wie ein mode='before' Validator, aber sie beenden die Validierung sofort, es werden keine weiteren Validatoren aufgerufen und Pydantic führt keine seiner internen Validierungen durch.
  • Wrap Validatoren sind die flexibelsten von allen. Sie können Code ausführen, bevor oder nachdem Pydantic und andere Validatoren ihre Arbeit erledigt haben, oder Sie können die Validierung sofort beenden, sowohl mit einem erfolgreichen Wert als auch mit einem Fehler.

Sie können mehrere Vorher-, Nachher- oder mode='wrap' Validatoren verwenden, aber nur einen PlainValidator da ein einfacher Validator keine inneren Validatoren aufruft.

Hier ist ein Beispiel für einen mode='wrap' Validator:

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

Die gleichen „Modi“ gelten für @field_validator , was im nächsten Abschnitt besprochen wird.

Bestellung von Validatoren innerhalb von Annotated

Reihenfolge der Validierungsmetadaten innerhalb von Annotated Matters. Die Validierung erfolgt von rechts nach links und zurück. Das heißt, es werden von rechts nach links alle „Vorher“-Validatoren ausgeführt (oder „Wrap“-Validatoren aufgerufen) und dann von links nach rechts wieder alle „Nachher“-Validatoren aufgerufen.

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

Validierung von Standardwerten

Validatoren werden nicht ausgeführt, wenn der Standardwert verwendet wird. Dies gilt sowohl für @field_validator -Validatoren als auch für Annotated Validatoren. Sie können die Ausführung mit Field(validate_default=True) erzwingen. Wenn Sie validate_default auf True setzen, kommt das Verhalten der Verwendung von always=True im validator in Pydantic v1 am nächsten. Im Allgemeinen ist es jedoch besser, a zu verwenden @model_validator(mode='before') wobei die Funktion aufgerufen wird, bevor der innere Validator aufgerufen wird.

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'

Feldvalidatoren

??? API „API-Dokumentation“ [pydantic.functional_validators.field_validator][pydantic.Functional_validators.field_validator]

Wenn Sie einem bestimmten Feld eines Modells einen Validator hinzufügen möchten, können Sie den @field_validator Dekorator verwenden.

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

Ein paar Dinge, die Sie bei Validatoren beachten sollten:

  • @field_validator s sind „Klassenmethoden“, daher ist der erste Argumentwert, den sie erhalten, die UserModel Klasse und keine Instanz von UserModel . Wir empfehlen Ihnen, den @classmethod Dekorator unterhalb des @field_validator -Dekorators zu verwenden, um eine ordnungsgemäße Typprüfung zu erhalten.
  • das zweite Argument ist der zu validierende Feldwert; Es kann beliebig benannt werden
  • Das dritte Argument, falls vorhanden, ist eine Instanz von pydantic.ValidationInfo
  • Validatoren sollten entweder den analysierten Wert zurückgeben oder einen ValueError oder AssertionError auslösen (es können assert -Anweisungen verwendet werden).
  • Ein einzelner Validator kann auf mehrere Felder angewendet werden, indem ihm mehrere Feldnamen übergeben werden.
  • Ein einzelner Validator kann auch für alle Felder aufgerufen werden, indem der Sonderwert '*' übergeben wird.

!!! Warnung Wenn Sie assert -Anweisungen verwenden, denken Sie daran, dass die Ausführung von Python mit dem Optimierungsflag -O assert -Anweisungen deaktiviert und Validatoren nicht mehr funktionieren .

!!! Hinweis FieldValidationInfo ist in 2.4 veraltet . Verwenden Sie stattdessen ValidationInfo .

Wenn Sie auf Werte aus einem anderen Feld innerhalb eines @field_validator zugreifen möchten, ist dies möglicherweise mithilfe von ValidationInfo.data möglich, einem Diktat von Feldname zu Feldwert. Die Validierung erfolgt in den definierten Bestellfeldern. Daher müssen Sie bei der Verwendung ValidationInfo.data vorsichtig sein, um nicht auf ein Feld zuzugreifen, das noch nicht validiert/ausgefüllt wurde – im obigen Code hätten Sie beispielsweise keinen Zugriff darauf info.data['id'] aus name_must_contain_space . In den meisten Fällen, in denen Sie eine Validierung mit mehreren Feldwerten durchführen möchten, ist es jedoch besser, @model_validator zu verwenden, was im folgenden Abschnitt erläutert wird.

Modellvalidatoren

??? API „API-Dokumentation“ [pydantic.functional_validators.model_validator][pydantic.Functional_validators.model_validator]

Mit @model_validator kann auch eine Validierung der gesamten Modelldaten durchgeführt werden.

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

!!! Hinweis „Überprüfung des Rückgabetyps“ Mit @model_validator dekorierte Methoden sollten die self-Instanz am Ende der Methode zurückgeben. Zur Typüberprüfung können Sie Self von typing oder den typing_extensions -Backport als Rückgabetyp der dekorierten Methode verwenden. Im Kontext des obigen Beispiels könnten Sie auch verwenden def check_passwords_match(self: 'UserModel') -> 'UserModel' um anzuzeigen, dass die Methode eine Instanz des Modells zurückgibt.

!!! Hinweis „Bei Vererbung“ Ein in einer Basisklasse definierter @model_validator wird während der Validierung einer Unterklasseninstanz aufgerufen.

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.

Modellvalidatoren können mode='before' , mode='after' oder mode='wrap' sein.

Vor Modellvalidatoren wird die Roheingabe übergeben, die häufig ein dict[str, Any] ist, aber auch eine Instanz des Modells selbst sein kann (z. B. if UserModel.model_validate(UserModel.construct(...)) aufgerufen wird) oder irgendetwas anderes, da Sie beliebige Objekte an model_validate übergeben können. Aus diesem mode='before' sind Validatoren äußerst flexibel und leistungsstark, ihre Implementierung kann jedoch umständlich und fehleranfällig sein. Vor Modellvalidatoren sollten Klassenmethoden stehen. Das erste Argument sollte cls sein (und wir empfehlen Ihnen außerdem, @classmethod unter @model_validator für eine ordnungsgemäße Typprüfung zu verwenden), das zweite Argument ist die Eingabe (Sie sollten es im Allgemeinen als Any eingeben und isinstance verwenden, um den Typ einzugrenzen) und das dritte Das Argument (falls vorhanden) ist ein pydantic.ValidationInfo .

mode='after' Validatoren sind Instanzmethoden und erhalten immer eine Instanz des Modells als erstes Argument. Stellen Sie sicher, dass Sie die Instanz am Ende Ihres Validators zurückgeben. Sie sollten nicht (cls, ModelType) als Signatur verwenden, sondern einfach (self) verwenden und Typprüfer den Typ von self für Sie ableiten lassen. Da diese vollständig typsicher sind, sind sie oft einfacher zu implementieren als mode='before' Validatoren. Wenn die Validierung eines Feldes fehlschlägt, werden keine mode='after' -Validatoren für dieses Feld aufgerufen.

Umgang mit Fehlern in Validatoren

Wie in den vorherigen Abschnitten erwähnt, können Sie in einem Validator entweder einen ValueError oder AssertionError (einschließlich solcher, die durch assert ... -Anweisungen generiert werden) auslösen, um anzuzeigen, dass die Validierung fehlgeschlagen ist. Sie können auch einen PydanticCustomError auslösen, der etwas ausführlicher ist, Ihnen aber zusätzliche Flexibilität bietet. Alle anderen Fehler (einschließlich TypeError ) werden aufgeschlüsselt und nicht in einen ValidationError eingeschlossen.

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

Sondertypen

Pydantic bietet einige spezielle Typen, mit denen die Validierung angepasst werden kann.

  • [InstanceOf][pydantic.Functional_validators.InstanceOf] ist ein Typ, der verwendet werden kann, um zu überprüfen, ob ein Wert eine Instanz einer bestimmten Klasse ist.

    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.Functional_validators.SkipValidation] ist ein Typ, der verwendet werden kann, um die Validierung eines Feldes zu überspringen.

    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]

  • Beachten Sie, dass die Validierung des zweiten Elements übersprungen wird. Wenn es den falschen Typ hat, wird während der Serialisierung eine Warnung ausgegeben.

Feldkontrollen

Während der Klassenerstellung werden Validatoren überprüft, um zu bestätigen, dass die von ihnen angegebenen Felder tatsächlich im Modell vorhanden sind.

Dies kann unerwünscht sein, wenn Sie beispielsweise einen Validator definieren möchten, um Felder zu validieren, die nur in Unterklassen des Modells vorhanden sind, in denen der Validator definiert ist.

Wenn Sie diese Prüfungen während der Klassenerstellung deaktivieren möchten, können Sie check_fields=False als Schlüsselwortargument an den Validator übergeben.

Datenklassenvalidatoren

Validatoren funktionieren auch mit Pydantic-Datenklassen.

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

Validierungskontext

Sie können ein Kontextobjekt an die Validierungsmethoden übergeben, auf das über das info -Argument an dekorierte Validatorfunktionen zugegriffen werden kann:

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'

Dies ist nützlich, wenn Sie das Validierungsverhalten während der Laufzeit dynamisch aktualisieren müssen. Wenn Sie beispielsweise möchten, dass ein Feld über einen dynamisch steuerbaren Satz zulässiger Werte verfügt, können Sie dies erreichen, indem Sie die zulässigen Werte kontextabhängig übergeben und über einen separaten Mechanismus zum Aktualisieren der zulässigen Werte verfügen:

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

Ebenso können Sie einen Kontext für die Serialisierung verwenden .

Verwendung des Validierungskontexts mit BaseModel -Initialisierung

Obwohl es im Standard BaseModel Initialisierer keine Möglichkeit gibt, einen Kontext anzugeben, können Sie dies durch die Verwendung von contextvars.ContextVar und einer benutzerdefinierten __init__ Methode umgehen:

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

Wiederverwendung von Validatoren

Gelegentlich möchten Sie denselben Validator für mehrere Felder/Modelle verwenden (z. B. um einige Eingabedaten zu normalisieren). Der „naive“ Ansatz wäre, eine separate Funktion zu schreiben und sie dann von mehreren Dekoratoren aufzurufen. Dies bringt natürlich eine Menge Wiederholungen und Standardcode mit sich. Der folgende Ansatz zeigt, wie Sie einen Validator wiederverwenden können, sodass die Redundanz minimiert wird und die Modelle wieder nahezu deklarativ werden.

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

本文总阅读量