विषय पर बढ़ें

सत्यापनकर्ता

!!! चेतावनी "🚧 कार्य प्रगति पर है" यह पृष्ठ कार्य प्रगति पर है।

यह पृष्ठ पाइडेंटिक में अधिक जटिल, कस्टम सत्यापनकर्ता बनाने के लिए उदाहरण स्निपेट प्रदान करता है।

[Annotated][टाइपिंग.एनोटेटेड] मेटाडेटा के साथ कस्टम वैलिडेटर का उपयोग करना

इस उदाहरण में, हम एक कस्टम सत्यापनकर्ता का निर्माण करेंगे, जो एक [Annotated][टाइपिंग.एनोटेटेड] प्रकार से जुड़ा होगा, जो यह सुनिश्चित करता है कि एक [datetime][डेटटाइम.डेटटाइम] ऑब्जेक्ट किसी दिए गए टाइमज़ोन बाधा का पालन करता है।

कस्टम सत्यापनकर्ता समयक्षेत्र के स्ट्रिंग विनिर्देश का समर्थन करता है, और यदि datetime ऑब्जेक्ट में सही समयक्षेत्र नहीं है तो एक त्रुटि उत्पन्न होगी।

हम एनोटेटेड प्रकार की स्कीमा को अनुकूलित करने के लिए सत्यापनकर्ता में __get_pydantic_core_schema__ उपयोग करते हैं (इस मामले में, datetime), जो हमें कस्टम सत्यापन तर्क जोड़ने की अनुमति देता है। विशेष रूप से, हम एक wrap वैलिडेटर फ़ंक्शन का उपयोग करते हैं ताकि हम [datetime][डेटटाइम.डेटटाइम] के डिफ़ॉल्ट pydantic सत्यापन से पहले और बाद में दोनों ऑपरेशन कर सकें।

import datetime as dt
from dataclasses import dataclass
from pprint import pprint
from typing import Any, Callable, Optional

import pytz
from pydantic_core import CoreSchema, core_schema
from typing_extensions import Annotated

from pydantic import (
    GetCoreSchemaHandler,
    PydanticUserError,
    TypeAdapter,
    ValidationError,
)


@dataclass(frozen=True)
class MyDatetimeValidator:
    tz_constraint: Optional[str] = None

    def tz_constraint_validator(
        self,
        value: dt.datetime,
        handler: Callable,  # (1)!
    ):
        """Validate tz_constraint and tz_info."""
        # handle naive datetimes
        if self.tz_constraint is None:
            assert (
                value.tzinfo is None
            ), 'tz_constraint is None, but provided value is tz-aware.'
            return handler(value)

        # validate tz_constraint and tz-aware tzinfo
        if self.tz_constraint not in pytz.all_timezones:
            raise PydanticUserError(
                f'Invalid tz_constraint: {self.tz_constraint}',
                code='unevaluable-type-annotation',
            )
        result = handler(value)  # (2)!
        assert self.tz_constraint == str(
            result.tzinfo
        ), f'Invalid tzinfo: {str(result.tzinfo)}, expected: {self.tz_constraint}'

        return result

    def __get_pydantic_core_schema__(
        self,
        source_type: Any,
        handler: GetCoreSchemaHandler,
    ) -> CoreSchema:
        return core_schema.no_info_wrap_validator_function(
            self.tz_constraint_validator,
            handler(source_type),
        )


LA = 'America/Los_Angeles'
ta = TypeAdapter(Annotated[dt.datetime, MyDatetimeValidator(LA)])
print(
    ta.validate_python(dt.datetime(2023, 1, 1, 0, 0, tzinfo=pytz.timezone(LA)))
)
#> 2023-01-01 00:00:00-07:53

LONDON = 'Europe/London'
try:
    ta.validate_python(
        dt.datetime(2023, 1, 1, 0, 0, tzinfo=pytz.timezone(LONDON))
    )
except ValidationError as ve:
    pprint(ve.errors(), width=100)
    """
    [{'ctx': {'error': AssertionError('Invalid tzinfo: Europe/London, expected: America/Los_Angeles')},
    'input': datetime.datetime(2023, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>),
    'loc': (),
    'msg': 'Assertion failed, Invalid tzinfo: Europe/London, expected: America/Los_Angeles',
    'type': 'assertion_error',
    'url': 'https://errors.pydantic.dev/2.8/v/assertion_error'}]
    """
  1. handler फ़ंक्शन वह है जिसे हम मानक pydantic सत्यापन के साथ इनपुट को सत्यापित करने के लिए कहते हैं
  2. हम इस रैप वैलिडेटर में मानक pydantic सत्यापन के साथ इनपुट को सत्यापित करने के लिए handler फ़ंक्शन को कॉल करते हैं

हम यूटीसी ऑफसेट बाधाओं को भी इसी तरह लागू कर सकते हैं। यह मानते हुए कि हमारे पास एक lower_bound और एक upper_bound है, हम यह सुनिश्चित करने के लिए एक कस्टम सत्यापनकर्ता बना सकते हैं कि हमारे datetime में एक यूटीसी ऑफसेट है जो हमारे द्वारा परिभाषित सीमा के भीतर समावेशी है:

import datetime as dt
from dataclasses import dataclass
from pprint import pprint
from typing import Any, Callable

import pytz
from pydantic_core import CoreSchema, core_schema
from typing_extensions import Annotated

from pydantic import GetCoreSchemaHandler, TypeAdapter, ValidationError


@dataclass(frozen=True)
class MyDatetimeValidator:
    lower_bound: int
    upper_bound: int

    def validate_tz_bounds(self, value: dt.datetime, handler: Callable):
        """Validate and test bounds"""
        assert value.utcoffset() is not None, 'UTC offset must exist'
        assert self.lower_bound <= self.upper_bound, 'Invalid bounds'

        result = handler(value)

        hours_offset = value.utcoffset().total_seconds() / 3600
        assert (
            self.lower_bound <= hours_offset <= self.upper_bound
        ), 'Value out of bounds'

        return result

    def __get_pydantic_core_schema__(
        self,
        source_type: Any,
        handler: GetCoreSchemaHandler,
    ) -> CoreSchema:
        return core_schema.no_info_wrap_validator_function(
            self.validate_tz_bounds,
            handler(source_type),
        )


LA = 'America/Los_Angeles'  # UTC-7 or UTC-8
ta = TypeAdapter(Annotated[dt.datetime, MyDatetimeValidator(-10, -5)])
print(
    ta.validate_python(dt.datetime(2023, 1, 1, 0, 0, tzinfo=pytz.timezone(LA)))
)
#> 2023-01-01 00:00:00-07:53

LONDON = 'Europe/London'
try:
    print(
        ta.validate_python(
            dt.datetime(2023, 1, 1, 0, 0, tzinfo=pytz.timezone(LONDON))
        )
    )
except ValidationError as e:
    pprint(e.errors(), width=100)
    """
    [{'ctx': {'error': AssertionError('Value out of bounds')},
    'input': datetime.datetime(2023, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>),
    'loc': (),
    'msg': 'Assertion failed, Value out of bounds',
    'type': 'assertion_error',
    'url': 'https://errors.pydantic.dev/2.8/v/assertion_error'}]
    """

नेस्टेड मॉडल फ़ील्ड्स को मान्य करना

यहां, हम नेस्टेड मॉडल के फ़ील्ड को सत्यापित करने के दो तरीके प्रदर्शित करते हैं, जहां सत्यापनकर्ता मूल मॉडल से डेटा का उपयोग करता है।

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

ऐसा करने का एक तरीका बाहरी मॉडल पर एक कस्टम सत्यापनकर्ता रखना है:

from typing import List

from typing_extensions import Self

from pydantic import BaseModel, ValidationError, model_validator


class User(BaseModel):
    username: str
    password: str


class Organization(BaseModel):
    forbidden_passwords: List[str]
    users: List[User]

    @model_validator(mode='after')
    def validate_user_passwords(self) -> Self:
        """Check that user password is not in forbidden list. Raise a validation error if a forbidden password is encountered."""
        for user in self.users:
            current_pw = user.password
            if current_pw in self.forbidden_passwords:
                raise ValueError(
                    f'Password {current_pw} is forbidden. Please choose another password for user {user.username}.'
                )
        return self


data = {
    'forbidden_passwords': ['123'],
    'users': [
        {'username': 'Spartacat', 'password': '123'},
        {'username': 'Iceburgh', 'password': '87'},
    ],
}
try:
    org = Organization(**data)
except ValidationError as e:
    print(e)
    """
    1 validation error for Organization
      Value error, Password 123 is forbidden. Please choose another password for user Spartacat. [type=value_error, input_value={'forbidden_passwords': [...gh', 'password': '87'}]}, input_type=dict]
    """

वैकल्पिक रूप से, एक कस्टम सत्यापनकर्ता का उपयोग नेस्टेड मॉडल वर्ग ( User ) में किया जा सकता है, जिसमें मूल मॉडल से निषिद्ध पासवर्ड डेटा को सत्यापन संदर्भ के माध्यम से पारित किया जा सकता है।

!!! चेतावनी एक सत्यापनकर्ता के भीतर संदर्भ को बदलने की क्षमता नेस्टेड सत्यापन में बहुत अधिक शक्ति जोड़ती है, लेकिन इससे भ्रमित करने वाला या डिबग करने में कठिन कोड भी हो सकता है। इस दृष्टिकोण का प्रयोग अपने जोखिम पर करें!

from typing import List

from pydantic import BaseModel, ValidationError, ValidationInfo, field_validator


class User(BaseModel):
    username: str
    password: str

    @field_validator('password', mode='after')
    @classmethod
    def validate_user_passwords(
        cls, password: str, info: ValidationInfo
    ) -> str:
        """Check that user password is not in forbidden list."""
        forbidden_passwords = (
            info.context.get('forbidden_passwords', []) if info.context else []
        )
        if password in forbidden_passwords:
            raise ValueError(f'Password {password} is forbidden.')
        return password


class Organization(BaseModel):
    forbidden_passwords: List[str]
    users: List[User]

    @field_validator('forbidden_passwords', mode='after')
    @classmethod
    def add_context(cls, v: List[str], info: ValidationInfo) -> List[str]:
        if info.context is not None:
            info.context.update({'forbidden_passwords': v})
        return v


data = {
    'forbidden_passwords': ['123'],
    'users': [
        {'username': 'Spartacat', 'password': '123'},
        {'username': 'Iceburgh', 'password': '87'},
    ],
}

try:
    org = Organization.model_validate(data, context={})
except ValidationError as e:
    print(e)
    """
    1 validation error for Organization
    users.0.password
      Value error, Password 123 is forbidden. [type=value_error, input_value='123', input_type=str]
    """

ध्यान दें कि यदि संदर्भ संपत्ति model_validate में शामिल नहीं है, तो info.context None होगा और निषिद्ध पासवर्ड सूची उपरोक्त कार्यान्वयन में संदर्भ में नहीं जोड़ी जाएगी। इस प्रकार, validate_user_passwords वांछित पासवर्ड सत्यापन नहीं करेगा।

सत्यापन संदर्भ के बारे में अधिक विवरण यहां पाया जा सकता है।


本文总阅读量