विषय पर बढ़ें

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

??? एपीआई "एपीआई दस्तावेज़ीकरण" pydantic.validate_call_decorator.validate_call

@validate_call डेकोरेटर किसी फ़ंक्शन में दिए गए तर्कों को फ़ंक्शन को कॉल करने से पहले फ़ंक्शन के एनोटेशन का उपयोग करके पार्स और मान्य करने की अनुमति देता है।

जबकि हुड के तहत यह मॉडल निर्माण और आरंभीकरण के समान दृष्टिकोण का उपयोग करता है (अधिक विवरण के लिए सत्यापनकर्ता देखें), यह न्यूनतम बॉयलरप्लेट के साथ आपके कोड पर सत्यापन लागू करने का एक बेहद आसान तरीका प्रदान करता है।

उपयोग का उदाहरण:

from pydantic import ValidationError, validate_call


@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
    b = s.encode()
    return separator.join(b for _ in range(count))


a = repeat('hello', 3)
print(a)
#> b'hellohellohello'

b = repeat('x', '4', separator=b' ')
print(b)
#> b'x x x x'

try:
    c = repeat('hello', 'wrong')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for repeat
    1
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='wrong', input_type=str]
    """

तर्क प्रकार

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

# TODO replace find_file with something that isn't affected the filesystem
import os
from pathlib import Path
from typing import Optional, Pattern

from pydantic import DirectoryPath, validate_call


@validate_call
def find_file(path: DirectoryPath, regex: Pattern, max=None) -> Optional[Path]:
    for i, f in enumerate(path.glob('**/*')):
        if max and i > max:
            return
        if f.is_file() and regex.fullmatch(str(f.relative_to(path))):
            return f


# note: this_dir is a string here
this_dir = os.path.dirname(__file__)

print(find_file(this_dir, '^validation.*'))
print(find_file(this_dir, '^foobar.*', max=3))

कुछ नोट्स:

  • हालाँकि उन्हें स्ट्रिंग्स के रूप में पारित किया जाता है, डेकोरेटर द्वारा path और regex क्रमशः Path ऑब्जेक्ट और रेगेक्स में बदल दिया जाता है।
  • max कोई प्रकार का एनोटेशन नहीं है, इसलिए डेकोरेटर द्वारा इसे Any माना जाएगा।

इस तरह की ज़बरदस्ती बेहद मददगार हो सकती है, लेकिन भ्रमित करने वाली या अवांछित भी हो सकती है। इस संबंध में @validate_call की सीमाओं की चर्चा के लिए ज़बरदस्ती और सख्ती देखें।

फ़ंक्शन हस्ताक्षर

@validate_call डेकोरेटर को सभी संभावित पैरामीटर कॉन्फ़िगरेशन और इनके सभी संभावित संयोजनों का उपयोग करके फ़ंक्शन के साथ काम करने के लिए डिज़ाइन किया गया है:

  • डिफ़ॉल्ट के साथ या उसके बिना स्थितीय या कीवर्ड तर्क।
  • परिवर्तनीय स्थितीय तर्क * (अक्सर *args ) के माध्यम से परिभाषित होते हैं।
  • परिवर्तनीय कीवर्ड तर्क ** (अक्सर **kwargs ) के माध्यम से परिभाषित होते हैं।
  • कीवर्ड-केवल तर्क: *, के बाद तर्क।
  • केवल स्थितीय तर्क: , / से पहले तर्क (पायथन 3.8 में नया)।

उपरोक्त सभी पैरामीटर प्रकार प्रदर्शित करने के लिए:

from pydantic import validate_call


@validate_call
def pos_or_kw(a: int, b: int = 2) -> str:
    return f'a={a} b={b}'


print(pos_or_kw(1))
#> a=1 b=2
print(pos_or_kw(a=1))
#> a=1 b=2
print(pos_or_kw(1, 3))
#> a=1 b=3
print(pos_or_kw(a=1, b=3))
#> a=1 b=3


@validate_call
def kw_only(*, a: int, b: int = 2) -> str:
    return f'a={a} b={b}'


print(kw_only(a=1))
#> a=1 b=2
print(kw_only(a=1, b=3))
#> a=1 b=3


@validate_call
def pos_only(a: int, b: int = 2, /) -> str:  # python 3.8 only
    return f'a={a} b={b}'


print(pos_only(1))
#> a=1 b=2
print(pos_only(1, 2))
#> a=1 b=2


@validate_call
def var_args(*args: int) -> str:
    return str(args)


print(var_args(1))
#> (1,)
print(var_args(1, 2))
#> (1, 2)
print(var_args(1, 2, 3))
#> (1, 2, 3)


@validate_call
def var_kwargs(**kwargs: int) -> str:
    return str(kwargs)


print(var_kwargs(a=1))
#> {'a': 1}
print(var_kwargs(a=1, b=2))
#> {'a': 1, 'b': 2}


@validate_call
def armageddon(
    a: int,
    /,  # python 3.8 only
    b: int,
    *c: int,
    d: int,
    e: int = None,
    **f: int,
) -> str:
    return f'a={a} b={b} c={c} d={d} e={e} f={f}'


print(armageddon(1, 2, d=3))
#> a=1 b=2 c=() d=3 e=None f={}
print(armageddon(1, 2, 3, 4, 5, 6, d=8, e=9, f=10, spam=11))
#> a=1 b=2 c=(3, 4, 5, 6) d=8 e=9 f={'f': 10, 'spam': 11}

फ़ंक्शन तर्कों का वर्णन करने के लिए फ़ील्ड का उपयोग करना

फ़ील्ड और सत्यापन के बारे में अतिरिक्त जानकारी प्रदान करने के लिए फ़ील्ड का उपयोग @validate_call के साथ भी किया जा सकता है। सामान्य तौर पर इसका उपयोग एनोटेटेड के साथ एक प्रकार के संकेत में किया जाना चाहिए, जब तक कि default_factory निर्दिष्ट न हो, उस स्थिति में इसे फ़ील्ड के डिफ़ॉल्ट मान के रूप में उपयोग किया जाना चाहिए:

from datetime import datetime

from typing_extensions import Annotated

from pydantic import Field, ValidationError, validate_call


@validate_call
def how_many(num: Annotated[int, Field(gt=10)]):
    return num


try:
    how_many(1)
except ValidationError as e:
    print(e)
    """
    1 validation error for how_many
    0
      Input should be greater than 10 [type=greater_than, input_value=1, input_type=int]
    """


@validate_call
def when(dt: datetime = Field(default_factory=datetime.now)):
    return dt


print(type(when()))
#> <class 'datetime.datetime'>

alias उपयोग डेकोरेटर के साथ सामान्य रूप से किया जा सकता है।

from typing_extensions import Annotated

from pydantic import Field, validate_call


@validate_call
def how_many(num: Annotated[int, Field(gt=10, alias='number')]):
    return num


how_many(number=42)

Mypy के साथ प्रयोग

validate_call डेकोरेटर को mypy के साथ "आउट ऑफ द बॉक्स" काम करना चाहिए क्योंकि इसे किसी फ़ंक्शन को उसी हस्ताक्षर के साथ वापस करने के लिए परिभाषित किया गया है जिस फ़ंक्शन को वह सजाता है। एकमात्र सीमा यह है कि चूँकि हम mypy को यह सोचने के लिए प्रेरित करते हैं कि डेकोरेटर द्वारा लौटाया गया फ़ंक्शन, सजाए जा रहे फ़ंक्शन के समान है; कच्चे फ़ंक्शन या अन्य विशेषताओं तक पहुंच के लिए type: ignore

कच्चा कार्य

जो कच्चा फ़ंक्शन सजाया गया था वह पहुंच योग्य है, यह उपयोगी है यदि कुछ परिदृश्यों में आप अपने इनपुट तर्कों पर भरोसा करते हैं और फ़ंक्शन को सबसे अधिक प्रदर्शन वाले तरीके से कॉल करना चाहते हैं (नीचे प्रदर्शन पर नोट्स देखें):

from pydantic import validate_call


@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
    b = s.encode()
    return separator.join(b for _ in range(count))


a = repeat('hello', 3)
print(a)
#> b'hellohellohello'

b = repeat.raw_function('good bye', 2, separator=b', ')
print(b)
#> b'good bye, good bye'

एसिंक फ़ंक्शन

@validate_call उपयोग async फ़ंक्शंस पर भी किया जा सकता है:

class Connection:
    async def execute(self, sql, *args):
        return 'testing@example.com'


conn = Connection()
# ignore-above
import asyncio

from pydantic import PositiveInt, ValidationError, validate_call


@validate_call
async def get_user_email(user_id: PositiveInt):
    # `conn` is some fictional connection to a database
    email = await conn.execute('select email from users where id=$1', user_id)
    if email is None:
        raise RuntimeError('user not found')
    else:
        return email


async def main():
    email = await get_user_email(123)
    print(email)
    #> testing@example.com
    try:
        await get_user_email(-4)
    except ValidationError as exc:
        print(exc.errors())
        """
        [
            {
                'type': 'greater_than',
                'loc': (0,),
                'msg': 'Input should be greater than 0',
                'input': -4,
                'ctx': {'gt': 0},
                'url': 'https://errors.pydantic.dev/2/v/greater_than',
            }
        ]
        """


asyncio.run(main())
# requires: `conn.execute()` that will return `'testing@example.com'`

कस्टम कॉन्फिग

@validate_call के पीछे के मॉडल को config सेटिंग का उपयोग करके अनुकूलित किया जा सकता है, जो सामान्य मॉडल में ConfigDict उप-वर्ग को सेट करने के बराबर है।

कॉन्फिगरेशन को डेकोरेटर के लिए config कीवर्ड तर्क का उपयोग करके सेट किया जाता है, यह या तो एक कॉन्फिग क्लास या गुणों का एक निर्देश हो सकता है जिसे बाद में एक क्लास में बदल दिया जाता है।

from pydantic import ValidationError, validate_call


class Foobar:
    def __init__(self, v: str):
        self.v = v

    def __add__(self, other: 'Foobar') -> str:
        return f'{self} + {other}'

    def __str__(self) -> str:
        return f'Foobar({self.v})'


@validate_call(config=dict(arbitrary_types_allowed=True))
def add_foobars(a: Foobar, b: Foobar):
    return a + b


c = add_foobars(Foobar('a'), Foobar('b'))
print(c)
#> Foobar(a) + Foobar(b)

try:
    add_foobars(1, 2)
except ValidationError as e:
    print(e)
    """
    2 validation errors for add_foobars
    0
      Input should be an instance of Foobar [type=is_instance_of, input_value=1, input_type=int]
    1
      Input should be an instance of Foobar [type=is_instance_of, input_value=2, input_type=int]
    """

एक्सटेंशन - किसी फ़ंक्शन को कॉल करने से पहले तर्कों को मान्य करना

कुछ मामलों में, किसी फ़ंक्शन के तर्कों के सत्यापन को फ़ंक्शन कॉल से अलग करना सहायक हो सकता है। यह तब उपयोगी हो सकता है जब कोई विशेष कार्य महंगा/समय लेने वाला हो।

यहां उस समाधान का एक उदाहरण दिया गया है जिसका उपयोग आप उस पैटर्न के लिए कर सकते हैं:

from pydantic import validate_call


@validate_call
def validate_foo(a: int, b: int):
    def foo():
        return a + b

    return foo


foo = validate_foo(a=1, b=2)
print(foo())
#> 3

सीमाएँ

सत्यापन अपवाद

वर्तमान में सत्यापन विफलता पर, एक मानक Pydantic ValidationError उठाया जाता है। विवरण के लिए मॉडल त्रुटि प्रबंधन देखें।

यह सहायक है क्योंकि इसकी str() विधि उत्पन्न हुई त्रुटि का उपयोगी विवरण प्रदान करती है और .errors() और .json() जैसी विधियाँ अंतिम उपयोगकर्ताओं के लिए त्रुटियों को उजागर करते समय उपयोगी हो सकती हैं। हालाँकि, ValidationError ValueError से विरासत में मिला है न कि TypeError , जो अप्रत्याशित हो सकता है क्योंकि पायथन अमान्य या गुम तर्कों पर TypeError बढ़ाएगा। इसे भविष्य में या तो कस्टम त्रुटि की अनुमति देकर या डिफ़ॉल्ट रूप से एक अलग अपवाद उठाकर, या दोनों द्वारा संबोधित किया जा सकता है।

जबरदस्ती और सख्ती

पाइडेंटिक वर्तमान में किसी प्रकार के गलत होने पर त्रुटि उत्पन्न करने के बजाय प्रकारों को ज़बरदस्ती करने की कोशिश करने के पक्ष में है, मॉडल डेटा रूपांतरण देखें और @validate_call अलग नहीं है।

प्रदर्शन

हमने पाइडेंटिक को यथासंभव निष्पादक बनाने के लिए एक बड़ा प्रयास किया है और फ़ंक्शन परिभाषित होने पर तर्क निरीक्षण और मॉडल निर्माण केवल एक बार किया जाता है, हालांकि कच्चे फ़ंक्शन को कॉल करने की तुलना में @validate_call डेकोरेटर का उपयोग करने पर अभी भी प्रदर्शन प्रभाव पड़ेगा .

कई स्थितियों में इसका बहुत कम या कोई ध्यान देने योग्य प्रभाव नहीं होगा, हालाँकि ध्यान रखें कि @validate_call दृढ़ता से टाइप की गई भाषाओं में फ़ंक्शन परिभाषाओं के समकक्ष या विकल्प नहीं है; यह कभी नहीं होगा.

वापसी मूल्य

फ़ंक्शन का रिटर्न मान वैकल्पिक रूप से इसके रिटर्न प्रकार एनोटेशन के विरुद्ध मान्य किया जा सकता है।


本文总阅读量