Lewati ke isi

Validator Beranotasi

??? api "Dokumentasi API" [pydantic.functional_validators.WrapValidator][pydantic.fungsional_validators.WrapValidator]
[pydantic.functional_validators.PlainValidator][pydantic.fungsional_validator.PlainValidator]
[pydantic.functional_validators.BeforeValidator][pydantic.fungsional_validators.BeforeValidator]
[pydantic.functional_validators.AfterValidator][pydantic.fungsional_validator.AfterValidator]

Pydantic menyediakan cara untuk menerapkan validator melalui penggunaan Annotated . Anda harus menggunakan ini kapan pun Anda ingin mengikat validasi ke suatu tipe, bukan model atau bidang.

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

Dalam contoh ini kami menggunakan beberapa tipe alias ( MyNumber = Annotated[...] ). Meskipun hal ini dapat membantu keterbacaan kode, hal ini tidak diperlukan, Anda dapat menggunakan Annotated secara langsung dalam petunjuk jenis bidang model. Alias tipe ini juga bukan tipe sebenarnya tetapi Anda dapat menggunakan pendekatan serupa dengan TypeAliasType untuk membuat tipe sebenarnya. Lihat Tipe Kustom untuk penjelasan lebih detail mengenai tipe kustom.

Perlu juga dicatat bahwa Anda dapat membuat sarang Annotated di dalam tipe lain. Dalam contoh ini kami menggunakannya untuk menerapkan validasi ke item dalam daftar. Pendekatan yang sama dapat digunakan untuk kunci dict, dll.

Validator Sebelum, Setelah, Bungkus, dan Biasa

Pydantic menyediakan beberapa jenis fungsi validator:

  • After validator dijalankan setelah parsing internal Pydantic. Mereka umumnya lebih aman dan lebih mudah diterapkan.
  • Before validator dijalankan sebelum penguraian dan validasi internal Pydantic (misalnya pemaksaan str ke int ). Ini lebih fleksibel daripada validator After karena mereka dapat memodifikasi masukan mentah, namun mereka juga harus menangani masukan mentah, yang secara teori dapat berupa objek apa pun.
  • Validator Plain seperti validator mode='before' tetapi mereka segera menghentikan validasi, tidak ada validator lebih lanjut yang dipanggil dan Pydantic tidak melakukan validasi internal apa pun.
  • Validator Wrap adalah yang paling fleksibel dari semuanya. Anda dapat menjalankan kode sebelum atau setelah Pydantic dan validator lain melakukan tugasnya atau Anda dapat segera menghentikan validasi, baik dengan nilai berhasil atau kesalahan.

Anda dapat menggunakan beberapa validator sebelum, sesudah, atau mode='wrap' , namun hanya satu PlainValidator karena validator biasa tidak akan memanggil validator dalam apa pun.

Berikut ini contoh validator 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]
    """

"Mode" yang sama berlaku untuk @field_validator , yang akan dibahas di bagian selanjutnya.

Pengurutan validator dalam Annotated

Urutan metadata validasi dalam hal-hal Annotated . Validasi berjalan dari kanan ke kiri dan belakang. Yaitu, dari kanan ke kiri menjalankan semua validator "sebelum" (atau memanggil validator "bungkus"), lalu dari kiri ke kanan kembali memanggil semua validator "setelah".

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

Validasi nilai default

Validator tidak akan berjalan ketika nilai default digunakan. Hal ini berlaku untuk validator @field_validator dan validator Annotated . Anda dapat memaksanya untuk dijalankan dengan Field(validate_default=True) . Menyetel validate_default ke True memiliki perilaku yang paling mirip dengan penggunaan always=True di validator di Pydantic v1. Namun, Anda biasanya lebih baik menggunakan a @model_validator(mode='before') di mana fungsinya dipanggil sebelum validator dalam dipanggil.

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'

Validator lapangan

??? api "Dokumentasi API" [pydantic.functional_validators.field_validator][pydantic.fungsional_validator.field_validator]

Jika Anda ingin melampirkan validator ke bidang model tertentu, Anda dapat menggunakan dekorator @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]
    """

Beberapa hal yang perlu diperhatikan pada validator:

  • @field_validator adalah "metode kelas", jadi nilai argumen pertama yang mereka terima adalah kelas UserModel , bukan turunan dari UserModel . Kami menyarankan Anda menggunakan dekorator @classmethod di bawah dekorator @field_validator untuk mendapatkan pemeriksaan tipe yang tepat.
  • argumen kedua adalah nilai bidang yang akan divalidasi; bisa diberi nama sesukamu
  • argumen ketiga, jika ada, adalah turunan dari pydantic.ValidationInfo
  • validator harus mengembalikan nilai yang diurai atau menaikkan ValueError atau AssertionError (pernyataan assert dapat digunakan).
  • Validator tunggal dapat diterapkan ke beberapa bidang dengan meneruskan beberapa nama bidang ke dalamnya.
  • Validator tunggal juga dapat dipanggil di semua bidang dengan meneruskan nilai khusus '*' .

!!! peringatan Jika Anda menggunakan pernyataan assert , perlu diingat bahwa menjalankan Python dengan tanda optimasi -O akan menonaktifkan pernyataan assert , dan validator akan berhenti bekerja .

!!! catatan FieldValidationInfo tidak digunakan lagi di 2.4, gunakan ValidationInfo sebagai gantinya.

Jika Anda ingin mengakses nilai dari bidang lain di dalam @field_validator , hal ini dapat dilakukan menggunakan ValidationInfo.data , yang merupakan dict nama bidang ke nilai bidang. Validasi dilakukan pada kolom urutan yang ditentukan, jadi Anda harus berhati-hati saat menggunakan ValidationInfo.data agar tidak mengakses kolom yang belum divalidasi/diisi — pada kode di atas, misalnya, Anda tidak akan dapat mengakses info.data['id'] dari dalam name_must_contain_space . Namun, dalam kebanyakan kasus ketika Anda ingin melakukan validasi menggunakan beberapa nilai bidang, lebih baik menggunakan @model_validator yang dibahas pada bagian di bawah ini.

Validator model

??? api "Dokumentasi API" [pydantic.functional_validators.model_validator][pydantic.fungsional_validator.model_validator]

Validasi juga dapat dilakukan pada seluruh data model menggunakan @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]
    """

!!! catatan "Saat pengecekan tipe kembali" Metode yang dihiasi dengan @model_validator harus mengembalikan instance mandiri di akhir metode. Untuk tujuan pemeriksaan tipe, Anda dapat menggunakan Self dari typing atau backport typing_extensions sebagai tipe kembalian dari metode yang didekorasi. Dalam konteks contoh di atas, Anda juga bisa menggunakan def check_passwords_match(self: 'UserModel') -> 'UserModel' untuk menunjukkan bahwa metode mengembalikan sebuah instance dari model.

!!! catatan "Pada Warisan" @model_validator yang didefinisikan dalam kelas dasar akan dipanggil selama validasi instance subkelas.

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.

Validator model dapat berupa mode='before' , mode='after' atau mode='wrap' .

Sebelum validator model diberikan masukan mentah yang seringkali berupa dict[str, Any] namun bisa juga berupa turunan dari model itu sendiri (misalnya jika UserModel.model_validate(UserModel.construct(...)) dipanggil) atau apa pun karena Anda dapat meneruskan objek arbitrer ke model_validate . Karena itu validator mode='before' ini sangat fleksibel dan kuat, namun penerapannya rumit dan rawan kesalahan. Sebelum validator model harus menjadi metode kelas. Argumen pertama harus cls (dan kami juga menyarankan Anda menggunakan @classmethod di bawah @model_validator untuk pemeriksaan tipe yang tepat), argumen kedua akan menjadi input (biasanya Anda harus mengetikkannya sebagai Any dan menggunakan isinstance untuk mempersempit tipe) dan yang ketiga argumen (jika ada) akan menjadi pydantic.ValidationInfo .

validator mode='after' adalah metode instan dan selalu menerima instance model sebagai argumen pertama. Pastikan untuk mengembalikan instance di akhir validator Anda. Anda tidak boleh menggunakan (cls, ModelType) sebagai tanda tangan, cukup gunakan (self) dan biarkan pemeriksa tipe menyimpulkan tipe self untuk Anda. Karena ini sepenuhnya aman untuk diketik, seringkali lebih mudah diterapkan daripada validator mode='before' . Jika ada bidang yang gagal divalidasi, validator mode='after' untuk bidang tersebut tidak akan dipanggil.

Menangani kesalahan pada validator

Seperti disebutkan di bagian sebelumnya, Anda dapat memunculkan ValueError atau AssertionError (termasuk pernyataan yang dihasilkan oleh assert ... ) dalam validator untuk menunjukkan validasi gagal. Anda juga dapat meningkatkan PydanticCustomError yang sedikit lebih bertele-tele namun memberi Anda fleksibilitas ekstra. Kesalahan lainnya (termasuk TypeError ) akan dimunculkan dan tidak dimasukkan ke dalam 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]
    """

Tipe Khusus

Pydantic menyediakan beberapa tipe khusus yang dapat digunakan untuk menyesuaikan validasi.

  • [InstanceOf][pydantic.function_validators.InstanceOf] adalah tipe yang dapat digunakan untuk memvalidasi bahwa suatu nilai adalah turunan dari kelas tertentu.

    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] adalah tipe yang dapat digunakan untuk melewati validasi pada suatu bidang.

    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]

  • Perhatikan bahwa validasi item kedua dilewati. Jika tipenya salah maka akan mengeluarkan peringatan selama serialisasi.

Pemeriksaan lapangan

Selama pembuatan kelas, validator diperiksa untuk mengonfirmasi bahwa bidang yang mereka tentukan benar-benar ada pada model.

Hal ini mungkin tidak diinginkan jika, misalnya, Anda ingin menentukan validator untuk memvalidasi bidang yang hanya akan ada pada subkelas model tempat validator ditentukan.

Jika Anda ingin menonaktifkan pemeriksaan ini selama pembuatan kelas, Anda dapat meneruskan check_fields=False sebagai argumen kata kunci ke validator.

Validator kelas data

Validator juga bekerja dengan kelas data 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')

Konteks Validasi

Anda dapat meneruskan objek konteks ke metode validasi yang dapat diakses dari argumen info ke fungsi validator yang dihias:

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'

Ini berguna ketika Anda perlu memperbarui perilaku validasi secara dinamis selama runtime. Misalnya, jika Anda ingin sebuah bidang memiliki kumpulan nilai yang diizinkan yang dapat dikontrol secara dinamis, hal ini dapat dilakukan dengan meneruskan nilai yang diizinkan berdasarkan konteks, dan memiliki mekanisme terpisah untuk memperbarui nilai yang diizinkan:

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

Demikian pula, Anda dapat menggunakan konteks untuk serialisasi .

Menggunakan konteks validasi dengan inisialisasi BaseModel

Meskipun tidak ada cara untuk menentukan konteks dalam penginisialisasi BaseModel standar, Anda dapat menyiasatinya melalui penggunaan contextvars.ContextVar dan metode __init__ khusus:

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

Menggunakan kembali Validator

Kadang-kadang, Anda ingin menggunakan validator yang sama pada beberapa bidang/model (misalnya untuk menormalkan beberapa data masukan). Pendekatan "naif" adalah dengan menulis fungsi terpisah, lalu memanggilnya dari beberapa dekorator. Jelas, ini memerlukan banyak pengulangan dan kode pelat ketel. Pendekatan berikut menunjukkan bagaimana Anda dapat menggunakan kembali validator sehingga redundansi diminimalkan dan model menjadi hampir deklaratif.

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

本文总阅读量