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 einesstr
in einenint
). Diese sind flexibler alsAfter
-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 einmode='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, dieUserModel
Klasse und keine Instanz vonUserModel
. 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
oderAssertionError
auslösen (es könnenassert
-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')
本文总阅读量次