विषय पर बढ़ें

एनोटेटेड सत्यापनकर्ता

??? एपीआई "एपीआई दस्तावेज़ीकरण" [pydantic.functional_validators.WrapValidator][pydantic.functional_validator.WrapValidator]
[pydantic.functional_validators.PlainValidator][pydantic.functional_validator.PlainValidator]
[pydantic.functional_validators.BeforeValidator][pydantic.functional_validator.BeforeValidator]
[pydantic.functional_validators.AfterValidator][pydantic.functional_validator.AfterValidator]

पाइडेंटिक Annotated के उपयोग के माध्यम से सत्यापनकर्ताओं को लागू करने का एक तरीका प्रदान करता है। जब भी आप सत्यापन को मॉडल या फ़ील्ड के बजाय किसी प्रकार से बांधना चाहते हैं तो आपको इसका उपयोग करना चाहिए।

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

इस उदाहरण में हमने कुछ प्रकार के उपनामों ( MyNumber = Annotated[...] ) का उपयोग किया है। हालांकि यह कोड की सुपाठ्यता में मदद कर सकता है, लेकिन इसकी आवश्यकता नहीं है, आप Annotated उपयोग सीधे मॉडल फ़ील्ड प्रकार संकेत में कर सकते हैं। ये प्रकार के उपनाम भी वास्तविक प्रकार नहीं हैं लेकिन आप वास्तविक प्रकार बनाने के लिए TypeAliasType के साथ एक समान दृष्टिकोण का उपयोग कर सकते हैं। कस्टम प्रकारों की अधिक विस्तृत व्याख्या के लिए कस्टम प्रकार देखें।

यह भी ध्यान देने योग्य है कि आप Annotated अन्य प्रकारों के अंदर नेस्ट कर सकते हैं। इस उदाहरण में हमने किसी सूची की आंतरिक वस्तुओं पर सत्यापन लागू करने के लिए इसका उपयोग किया। उसी दृष्टिकोण का उपयोग तानाशाही कुंजी आदि के लिए किया जा सकता है।

पहले, बाद, रैप और सादा सत्यापनकर्ता

पाइडेंटिक कई प्रकार के सत्यापनकर्ता कार्य प्रदान करता है:

  • सत्यापनकर्ता पाइडेंटिक के आंतरिक पार्सिंग के After चलते हैं। वे आम तौर पर अधिक सुरक्षित होते हैं और इसलिए उन्हें लागू करना आसान होता है।
  • पाइडेंटिक के आंतरिक पार्सिंग और सत्यापन से Before सत्यापनकर्ता चलते हैं (उदाहरण के लिए एक str को एक int पर मजबूर करना)। ये After वैलिडेटर की तुलना में अधिक लचीले हैं क्योंकि वे कच्चे इनपुट को संशोधित कर सकते हैं, लेकिन उन्हें कच्चे इनपुट से भी निपटना पड़ता है, जो सिद्धांत रूप में कोई भी मनमानी वस्तु हो सकती है।
  • Plain सत्यापनकर्ता एक mode='before' सत्यापनकर्ता की तरह होते हैं लेकिन वे सत्यापन को तुरंत समाप्त कर देते हैं, कोई और सत्यापनकर्ता नहीं बुलाया जाता है और Pydantic अपना कोई भी आंतरिक सत्यापन नहीं करता है।
  • Wrap सत्यापनकर्ता सभी में सबसे अधिक लचीले होते हैं। आप पाइडेंटिक और अन्य सत्यापनकर्ताओं के अपना काम करने से पहले या बाद में कोड चला सकते हैं या आप सफल मान या त्रुटि दोनों के साथ सत्यापन को तुरंत समाप्त कर सकते हैं।

आप एकाधिक पहले, बाद, या mode='wrap' सत्यापनकर्ताओं का उपयोग कर सकते हैं, लेकिन केवल एक PlainValidator उपयोग कर सकते हैं क्योंकि एक सादा सत्यापनकर्ता किसी भी आंतरिक सत्यापनकर्ता को कॉल नहीं करेगा।

यहां 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]
    """

वही "मोड" @field_validator पर लागू होते हैं, जिसकी चर्चा अगले भाग में की गई है।

Annotated के भीतर सत्यापनकर्ताओं का आदेश देना

Annotated मामलों के भीतर सत्यापन मेटाडेटा का क्रम। सत्यापन दाएँ से बाएँ और पीछे जाता है। अर्थात्, यह दाएं से बाएं सभी "पहले" सत्यापनकर्ताओं को चलाता है (या "रैप" सत्यापनकर्ताओं को कॉल करता है), फिर बाएं से दाएं सभी "बाद" सत्यापनकर्ताओं को कॉल करता है।

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

डिफ़ॉल्ट मानों का सत्यापन

जब डिफ़ॉल्ट मान का उपयोग किया जाता है तो सत्यापनकर्ता नहीं चलेंगे। यह @field_validator सत्यापनकर्ताओं और Annotated सत्यापनकर्ताओं दोनों पर लागू होता है। आप उन्हें Field(validate_default=True) के साथ चलने के लिए बाध्य कर सकते हैं। validate_default True पर सेट करना Pydantic v1 में validator में always=True उपयोग करने के सबसे निकटतम व्यवहार है। हालाँकि, आमतौर पर आप इसका उपयोग करना बेहतर समझते हैं @model_validator(mode='before') जहां आंतरिक सत्यापनकर्ता को कॉल करने से पहले फ़ंक्शन को कॉल किया जाता है।

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'

फ़ील्ड सत्यापनकर्ता

??? एपीआई "एपीआई दस्तावेज़ीकरण" [pydantic.functional_validators.field_validator][pydantic.functional_validator.field_validator]

यदि आप किसी मॉडल के विशिष्ट फ़ील्ड में एक सत्यापनकर्ता संलग्न करना चाहते हैं तो आप @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]
    """

सत्यापनकर्ताओं पर ध्यान देने योग्य कुछ बातें:

  • @field_validator s "वर्ग विधियाँ" हैं, इसलिए उन्हें प्राप्त होने वाला पहला तर्क मान UserModel वर्ग है, UserModel का उदाहरण नहीं। हम अनुशंसा करते हैं कि आप उचित प्रकार की जाँच प्राप्त करने के लिए @field_validator डेकोरेटर के नीचे @classmethod डेकोरेटर का उपयोग करें।
  • दूसरा तर्क सत्यापित करने के लिए फ़ील्ड मान है; इसे आपकी इच्छानुसार नाम दिया जा सकता है
  • तीसरा तर्क, यदि मौजूद है, तो pydantic.ValidationInfo का एक उदाहरण है
  • सत्यापनकर्ताओं को या तो पार्स किए गए मान को वापस करना चाहिए या ValueError या AssertionError बढ़ाना चाहिए ( assert स्टेटमेंट का उपयोग किया जा सकता है)।
  • एक एकल सत्यापनकर्ता को कई फ़ील्ड नामों को पास करके कई फ़ील्ड पर लागू किया जा सकता है।
  • विशेष मान '*' पास करके सभी फ़ील्ड पर एक एकल सत्यापनकर्ता को भी बुलाया जा सकता है।

!!! चेतावनी यदि आप assert स्टेटमेंट का उपयोग करते हैं, तो ध्यान रखें कि -O ऑप्टिमाइज़ेशन फ़्लैग के साथ पायथन को चलाने से assert स्टेटमेंट अक्षम हो जाएगा, और सत्यापनकर्ता काम करना बंद कर देंगे

!!! ध्यान दें FieldValidationInfo 2.4 में अप्रचलित है, इसके बजाय ValidationInfo उपयोग करें।

यदि आप @field_validator के अंदर किसी अन्य फ़ील्ड से मानों तक पहुंचना चाहते हैं, तो यह ValidationInfo.data का उपयोग करके संभव हो सकता है, जो कि फ़ील्ड नाम से फ़ील्ड मान का एक निर्देश है। सत्यापन फ़ील्ड को परिभाषित किए गए क्रम में किया जाता है, इसलिए ValidationInfo.data उपयोग करते समय आपको सावधान रहना होगा कि आप उस फ़ील्ड तक न पहुंचें जो अभी तक मान्य/पॉप्युलेट नहीं किया गया है - उदाहरण के लिए, उपरोक्त कोड में, आप एक्सेस नहीं कर पाएंगे info.data['id'] name_must_contain_space के भीतर से। हालाँकि, ज्यादातर मामलों में जहां आप एकाधिक फ़ील्ड मानों का उपयोग करके सत्यापन करना चाहते हैं, @model_validator उपयोग करना बेहतर है जिसकी चर्चा नीचे अनुभाग में की गई है।

मॉडल सत्यापनकर्ता

??? एपीआई "एपीआई दस्तावेज़ीकरण" [pydantic.functional_validators.model_validator][pydantic.functional_validator.model_validator]

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

!!! ध्यान दें "रिटर्न टाइप चेकिंग पर" @model_validator से सजाए गए तरीकों को विधि के अंत में सेल्फ इंस्टेंस वापस करना चाहिए। टाइप चेकिंग उद्देश्यों के लिए, आप सजाए गए तरीके के रिटर्न प्रकार के रूप में typing या typing_extensions बैकपोर्ट से Self उपयोग कर सकते हैं। उपरोक्त उदाहरण के संदर्भ में, आप इसका भी उपयोग कर सकते हैं def check_passwords_match(self: 'UserModel') -> 'UserModel' यह इंगित करने के लिए कि विधि मॉडल का एक उदाहरण लौटाती है।

!!! नोट "विरासत पर" बेस क्लास में परिभाषित एक @model_validator उपवर्ग उदाहरण के सत्यापन के दौरान बुलाया जाएगा।

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.

मॉडल सत्यापनकर्ता mode='before' , mode='after' या mode='wrap' हो सकते हैं।

मॉडल सत्यापनकर्ताओं को कच्चे इनपुट को पारित करने से पहले जो अक्सर एक dict[str, Any] है लेकिन मॉडल का एक उदाहरण भी हो सकता है (उदाहरण के लिए यदि UserModel.model_validate(UserModel.construct(...)) कहा जाता है) या कुछ और क्योंकि आप मनमानी वस्तुओं को model_validate में पास कर सकते हैं। इस mode='before' के कारण सत्यापनकर्ता बेहद लचीले और शक्तिशाली होते हैं लेकिन इन्हें लागू करना बोझिल और त्रुटि प्रवण हो सकता है। मॉडल सत्यापनकर्ताओं से पहले क्लास विधियां होनी चाहिए। पहला तर्क cls होना चाहिए (और हम यह भी अनुशंसा करते हैं कि आप उचित प्रकार की जाँच के लिए @model_validator के नीचे @classmethod उपयोग करें), दूसरा तर्क इनपुट होगा (आपको इसे आम तौर पर Any के रूप में टाइप करना चाहिए और प्रकार को सीमित करने के लिए isinstance उपयोग करना चाहिए) और तीसरा तर्क (यदि मौजूद है) एक pydantic.ValidationInfo होगा।

mode='after' सत्यापनकर्ता उदाहरण विधियां हैं और हमेशा पहले तर्क के रूप में मॉडल का एक उदाहरण प्राप्त करते हैं। अपने सत्यापनकर्ता के अंत में उदाहरण वापस करना सुनिश्चित करें। आपको हस्ताक्षर के रूप में (cls, ModelType) उपयोग नहीं करना चाहिए, इसके बजाय केवल (self) उपयोग करें और टाइप चेकर्स को आपके लिए self के प्रकार का अनुमान लगाने दें। चूंकि ये पूरी तरह से सुरक्षित हैं इसलिए इन्हें mode='before' सत्यापनकर्ताओं की तुलना में लागू करना अक्सर आसान होता है। यदि कोई फ़ील्ड सत्यापित करने में विफल रहता है, तो उस फ़ील्ड के लिए mode='after' सत्यापनकर्ताओं को नहीं बुलाया जाएगा।

सत्यापनकर्ताओं में त्रुटियों को संभालना

जैसा कि पिछले अनुभागों में उल्लेख किया गया है, सत्यापन विफल होने का संकेत देने के लिए आप एक सत्यापनकर्ता के भीतर या तो एक ValueError या AssertionError ( assert ... स्टेटमेंट सहित) बढ़ा सकते हैं। आप एक PydanticCustomError भी बढ़ा सकते हैं जो थोड़ा अधिक क्रियात्मक है लेकिन आपको अतिरिक्त लचीलापन देता है। किसी भी अन्य त्रुटि ( TypeError सहित) को बबल किया गया है और 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]
    """

विशेष प्रकार

पाइडेंटिक कुछ विशेष प्रकार प्रदान करता है जिनका उपयोग सत्यापन को अनुकूलित करने के लिए किया जा सकता है।

  • [InstanceOf][pydantic.functional_validator.InstanceOf] एक प्रकार है जिसका उपयोग यह सत्यापित करने के लिए किया जा सकता है कि कोई मान किसी दिए गए वर्ग का एक उदाहरण है।

    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_validator.SkipValidation] एक प्रकार है जिसका उपयोग किसी फ़ील्ड पर सत्यापन को छोड़ने के लिए किया जा सकता है।

    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]

  • ध्यान दें कि दूसरे आइटम का सत्यापन छोड़ दिया गया है। यदि इसका प्रकार गलत है तो यह क्रमबद्धता के दौरान एक चेतावनी जारी करेगा।

फ़ील्ड जांच

वर्ग निर्माण के दौरान, सत्यापनकर्ताओं की जाँच यह पुष्टि करने के लिए की जाती है कि उनके द्वारा निर्दिष्ट फ़ील्ड वास्तव में मॉडल पर मौजूद हैं।

यह अवांछनीय हो सकता है यदि, उदाहरण के लिए, आप फ़ील्ड को मान्य करने के लिए एक सत्यापनकर्ता को परिभाषित करना चाहते हैं जो केवल उस मॉडल के उपवर्गों पर मौजूद होगा जहां सत्यापनकर्ता को परिभाषित किया गया है।

यदि आप कक्षा निर्माण के दौरान इन चेकों को अक्षम करना चाहते हैं, तो आप सत्यापनकर्ता को कीवर्ड तर्क के रूप में check_fields=False पास कर सकते हैं।

डेटाक्लास सत्यापनकर्ता

सत्यापनकर्ता पाइडेंटिक डेटाक्लास के साथ भी काम करते हैं।

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

मान्यता प्रसंग

आप एक संदर्भ ऑब्जेक्ट को सत्यापन विधियों में पास कर सकते हैं जिसे info तर्क से सजाए गए सत्यापनकर्ता कार्यों तक पहुंचा जा सकता है:

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'

यह तब उपयोगी होता है जब आपको रनटाइम के दौरान सत्यापन व्यवहार को गतिशील रूप से अपडेट करने की आवश्यकता होती है। उदाहरण के लिए, यदि आप चाहते हैं कि किसी फ़ील्ड में अनुमत मानों का गतिशील रूप से नियंत्रणीय सेट हो, तो यह अनुमत मानों को संदर्भ के आधार पर पारित करके और जो अनुमत है उसे अपडेट करने के लिए एक अलग तंत्र रखकर किया जा सकता है:

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

इसी तरह, आप क्रमबद्धता के लिए एक संदर्भ का उपयोग कर सकते हैं।

BaseModel आरंभीकरण के साथ सत्यापन संदर्भ का उपयोग करना

हालाँकि मानक BaseModel इनिशियलाइज़र में संदर्भ निर्दिष्ट करने का कोई तरीका नहीं है, आप contextvars.ContextVar और एक कस्टम __init__ विधि के उपयोग के माध्यम से इस पर काम कर सकते हैं:

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

सत्यापनकर्ताओं का पुन: उपयोग करना

कभी-कभी, आप एकाधिक फ़ील्ड/मॉडल पर एक ही सत्यापनकर्ता का उपयोग करना चाहेंगे (उदाहरण के लिए कुछ इनपुट डेटा को सामान्य करने के लिए)। "बेवकूफ" दृष्टिकोण एक अलग फ़ंक्शन लिखना होगा, फिर इसे कई डेकोरेटर्स से कॉल करना होगा। जाहिर है, इसमें बहुत अधिक दोहराव और बॉयलर प्लेट कोड शामिल है। निम्नलिखित दृष्टिकोण दर्शाता है कि आप एक सत्यापनकर्ता का पुन: उपयोग कैसे कर सकते हैं ताकि अतिरेक कम से कम हो और मॉडल फिर से लगभग घोषणात्मक बन जाएं।

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

本文总阅读量