विषय पर बढ़ें

प्रकार

जहां संभव हो, पाइडेंटिक फ़ील्ड को परिभाषित करने के लिए मानक लाइब्रेरी प्रकारों का उपयोग करता है, इस प्रकार सीखने की अवस्था को सुचारू करता है। हालाँकि, कई उपयोगी अनुप्रयोगों के लिए, कोई मानक लाइब्रेरी प्रकार मौजूद नहीं है, इसलिए पाइडेंटिक कई सामान्य रूप से उपयोग किए जाने वाले प्रकारों को लागू करता है।

और भी जटिल प्रकार हैं जो पाइडेंटिक एक्स्ट्रा टाइप्स पैकेज में पाए जा सकते हैं।

यदि कोई मौजूदा प्रकार आपके उद्देश्य के अनुरूप नहीं है, तो आप कस्टम गुणों और सत्यापन के साथ अपने स्वयं के पाइडेंटिक-संगत प्रकारों को भी लागू कर सकते हैं।

निम्नलिखित अनुभाग पाइडेंटिक द्वारा समर्थित प्रकारों का वर्णन करते हैं।

रूपांतरण टाइप करें

सत्यापन के दौरान, पाइडेंटिक डेटा को अपेक्षित प्रकारों में परिवर्तित कर सकता है।

जबरदस्ती के दो तरीके हैं: सख्त और ढीला। पाइडेंटिक सख्त और ढीले दोनों मोड में डेटा को कैसे परिवर्तित करता है, इसके बारे में अधिक जानकारी के लिए रूपांतरण तालिका देखें।

सख्त ज़बरदस्ती को सक्षम करने के विवरण के लिए सख्त मोड और सख्त प्रकार देखें।

सख्त प्रकार

पाइडेंटिक निम्नलिखित सख्त प्रकार प्रदान करता है:

  • [StrictBool][पाइडेंटिक.टाइप्स.स्ट्रिक्टबूल]
  • [StrictBytes][पाइडेंटिक.टाइप्स.स्ट्रिक्टबाइट्स]
  • [StrictFloat][पाइडेंटिक.टाइप्स.स्ट्रिक्टफ्लोट]
  • [StrictInt][पाइडेंटिक.टाइप्स.स्ट्रिक्टइंट]
  • StrictStr

ये प्रकार केवल तभी सत्यापन पास करेंगे जब मान्य मान संबंधित प्रकार का हो या उस प्रकार का उपप्रकार हो।

विवश प्रकार

यह व्यवहार प्रतिबंधित प्रकारों के strict क्षेत्र के माध्यम से भी उजागर होता है और इसे कई जटिल सत्यापन नियमों के साथ जोड़ा जा सकता है। समर्थित तर्कों के लिए अलग-अलग प्रकार के हस्ताक्षर देखें।

निम्नलिखित चेतावनियाँ लागू होती हैं:

  • StrictBytes (और conbytes() का strict विकल्प) bytes और bytearray दोनों प्रकारों को स्वीकार करेगा।
  • StrictInt (और conint() का strict विकल्प) bool प्रकारों को स्वीकार नहीं करेगा, भले ही bool पायथन में int का एक उपवर्ग है। अन्य उपवर्ग काम करेंगे.
  • StrictFloat (और confloat() का strict विकल्प) int स्वीकार नहीं करेगा।

उपरोक्त के अलावा, आपके पास एक FiniteFloat प्रकार भी हो सकता है जो केवल परिमित मान स्वीकार करेगा (यानी inf , -inf या nan नहीं)।

कस्टम प्रकार

आप अपने स्वयं के कस्टम डेटा प्रकार भी परिभाषित कर सकते हैं। इसे हासिल करने के कई तरीके हैं।

Annotated के माध्यम से रचना प्रकार

पीईपी 593 ने Annotated रनटाइम मेटाडेटा को प्रकारों से जोड़ने के एक तरीके के रूप में पेश किया, बिना यह बदले कि टाइप चेकर्स उनकी व्याख्या कैसे करते हैं। पाइडेंटिक इसका लाभ उठाकर आपको ऐसे प्रकार बनाने की अनुमति देता है जो टाइप चेकर्स के संबंध में मूल प्रकार के समान हैं, लेकिन सत्यापन जोड़ते हैं, अलग-अलग क्रमबद्ध करते हैं, आदि।

उदाहरण के लिए, एक सकारात्मक पूर्णांक का प्रतिनिधित्व करने वाला एक प्रकार बनाने के लिए:

# or `from typing import Annotated` for Python 3.9+
from typing_extensions import Annotated

from pydantic import Field, TypeAdapter, ValidationError

PositiveInt = Annotated[int, Field(gt=0)]

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1

try:
    ta.validate_python(-1)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for constrained-int
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

ध्यान दें कि आप इसे पाइडेंटिक-अज्ञेयवादी बनाने के लिए एनोटेटेड-प्रकारों से बाधाओं का भी उपयोग कर सकते हैं:

from annotated_types import Gt
from typing_extensions import Annotated

from pydantic import TypeAdapter, ValidationError

PositiveInt = Annotated[int, Gt(0)]

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1

try:
    ta.validate_python(-1)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for constrained-int
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

सत्यापन और क्रमांकन जोड़ना

आप पाइडेंटिक द्वारा निर्यात किए जाने वाले मार्करों का उपयोग करके मनमाने ढंग से प्रकार में सत्यापन, क्रमबद्धता और JSON स्कीमा को जोड़ या ओवरराइड कर सकते हैं:

from typing_extensions import Annotated

from pydantic import (
    AfterValidator,
    PlainSerializer,
    TypeAdapter,
    WithJsonSchema,
)

TruncatedFloat = Annotated[
    float,
    AfterValidator(lambda x: round(x, 1)),
    PlainSerializer(lambda x: f'{x:.1e}', return_type=str),
    WithJsonSchema({'type': 'string'}, mode='serialization'),
]


ta = TypeAdapter(TruncatedFloat)

input = 1.02345
assert input != 1.0

assert ta.validate_python(input) == 1.0

assert ta.dump_json(input) == b'"1.0e+00"'

assert ta.json_schema(mode='validation') == {'type': 'number'}
assert ta.json_schema(mode='serialization') == {'type': 'string'}

जेनेरिक्स

आप प्रकारों में पुन: प्रयोज्य संशोधन करने के लिए Annotated के भीतर प्रकार चर का उपयोग कर सकते हैं:

from typing import Any, List, Sequence, TypeVar

from annotated_types import Gt, Len
from typing_extensions import Annotated

from pydantic import ValidationError
from pydantic.type_adapter import TypeAdapter

SequenceType = TypeVar('SequenceType', bound=Sequence[Any])


ShortSequence = Annotated[SequenceType, Len(max_length=10)]


ta = TypeAdapter(ShortSequence[List[int]])

v = ta.validate_python([1, 2, 3, 4, 5])
assert v == [1, 2, 3, 4, 5]

try:
    ta.validate_python([1] * 100)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[int]
      List should have at most 10 items after validation, not 100 [type=too_long, input_value=[1, 1, 1, 1, 1, 1, 1, 1, ... 1, 1, 1, 1, 1, 1, 1, 1], input_type=list]
    """


T = TypeVar('T')  # or a bound=SupportGt

PositiveList = List[Annotated[T, Gt(0)]]

ta = TypeAdapter(PositiveList[float])

v = ta.validate_python([1])
assert type(v[0]) is float


try:
    ta.validate_python([-1])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[constrained-float]
    0
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

नामित प्रकार के उपनाम

उपरोक्त उदाहरण अंतर्निहित प्रकार के उपनामों का उपयोग करते हैं। इसका मतलब यह है कि वे JSON स्कीमा में title नहीं रख पाएंगे और उनका स्कीमा फ़ील्ड के बीच कॉपी किया जाएगा। आप नामित उपनाम बनाने के लिए इसके टाइपिंग-एक्सटेंशन बैकपोर्ट के माध्यम से पीईपी 695 के TypeAliasType उपयोग कर सकते हैं, जिससे आप उपवर्ग बनाए बिना एक नए प्रकार को परिभाषित कर सकते हैं। यह नया प्रकार एक नाम जितना सरल हो सकता है या इसके साथ जटिल सत्यापन तर्क जुड़ा हो सकता है:

from typing import List

from annotated_types import Gt
from typing_extensions import Annotated, TypeAliasType

from pydantic import BaseModel

ImplicitAliasPositiveIntList = List[Annotated[int, Gt(0)]]


class Model1(BaseModel):
    x: ImplicitAliasPositiveIntList
    y: ImplicitAliasPositiveIntList


print(Model1.model_json_schema())
"""
{
    'properties': {
        'x': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'title': 'X',
            'type': 'array',
        },
        'y': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'title': 'Y',
            'type': 'array',
        },
    },
    'required': ['x', 'y'],
    'title': 'Model1',
    'type': 'object',
}
"""

PositiveIntList = TypeAliasType('PositiveIntList', List[Annotated[int, Gt(0)]])


class Model2(BaseModel):
    x: PositiveIntList
    y: PositiveIntList


print(Model2.model_json_schema())
"""
{
    '$defs': {
        'PositiveIntList': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'type': 'array',
        }
    },
    'properties': {
        'x': {'$ref': '#/$defs/PositiveIntList'},
        'y': {'$ref': '#/$defs/PositiveIntList'},
    },
    'required': ['x', 'y'],
    'title': 'Model2',
    'type': 'object',
}
"""

ये नामित प्रकार उपनाम सामान्य भी हो सकते हैं:

from typing import Generic, List, TypeVar

from annotated_types import Gt
from typing_extensions import Annotated, TypeAliasType

from pydantic import BaseModel, ValidationError

T = TypeVar('T')  # or a `bound=SupportGt`

PositiveList = TypeAliasType(
    'PositiveList', List[Annotated[T, Gt(0)]], type_params=(T,)
)


class Model(BaseModel, Generic[T]):
    x: PositiveList[T]


assert Model[int].model_validate_json('{"x": ["1"]}').x == [1]

try:
    Model[int](x=[-1])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model[int]
    x.0
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

नामांकित पुनरावर्ती प्रकार

आप पुनरावर्ती प्रकार बनाने के लिए TypeAliasType भी उपयोग कर सकते हैं:

from typing import Any, Dict, List, Union

from pydantic_core import PydanticCustomError
from typing_extensions import Annotated, TypeAliasType

from pydantic import (
    TypeAdapter,
    ValidationError,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)


def json_custom_error_validator(
    value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
) -> Any:
    """Simplify the error message to avoid a gross error stemming
    from exhaustive checking of all union options.
    """
    try:
        return handler(value)
    except ValidationError:
        raise PydanticCustomError(
            'invalid_json',
            'Input is not valid json',
        )


Json = TypeAliasType(
    'Json',
    Annotated[
        Union[Dict[str, 'Json'], List['Json'], str, int, float, bool, None],
        WrapValidator(json_custom_error_validator),
    ],
)


ta = TypeAdapter(Json)

v = ta.validate_python({'x': [1], 'y': {'z': True}})
assert v == {'x': [1], 'y': {'z': True}}

try:
    ta.validate_python({'x': object()})
except ValidationError as exc:
    print(exc)
    """
    1 validation error for function-wrap[json_custom_error_validator()]
      Input is not valid json [type=invalid_json, input_value={'x': <object object at 0x0123456789ab>}, input_type=dict]
    """

__get_pydantic_core_schema__ के साथ सत्यापन को अनुकूलित करना

पाइडेंटिक कस्टम कक्षाओं को कैसे संभालता है, इसका अधिक व्यापक अनुकूलन करने के लिए, और विशेष रूप से जब आपके पास क्लास तक पहुंच है या इसे उपवर्गित कर सकते हैं, तो आप पाइडेंटिक को pydantic-core स्कीमा उत्पन्न करने का तरीका बताने के लिए एक विशेष __get_pydantic_core_schema__ लागू कर सकते हैं।

जबकि pydantic सत्यापन और क्रमांकन को संभालने के लिए आंतरिक रूप से pydantic-core उपयोग करता है, यह पाइडेंटिक V2 के लिए एक नया एपीआई है, इस प्रकार यह भविष्य में बदलाव किए जाने वाले क्षेत्रों में से एक है और आपको अंतर्निहित निर्माणों से चिपके रहने का प्रयास करना चाहिए जो annotated-types , pydantic.Field , या BeforeValidator इत्यादि द्वारा प्रदान किए गए हैं।

आप __get_pydantic_core_schema__ कस्टम प्रकार और Annotated में रखे जाने वाले मेटाडेटा दोनों पर लागू कर सकते हैं। दोनों ही मामलों में एपीआई मिडलवेयर जैसा है और "रैप" सत्यापनकर्ताओं के समान है: आपको एक source_type मिलता है (जो जरूरी नहीं कि क्लास के समान हो, विशेष रूप से जेनरिक के लिए) और एक handler जिसे आप एक प्रकार से कॉल कर सकते हैं या तो Annotated में अगले मेटाडेटा को कॉल करने के लिए या पाइडेंटिक की आंतरिक स्कीमा पीढ़ी में कॉल करने के लिए।

सबसे सरल नो-ऑप कार्यान्वयन आपके द्वारा दिए गए प्रकार के साथ हैंडलर को कॉल करता है, फिर उसे परिणाम के रूप में लौटाता है। आप हैंडलर को कॉल करने से पहले प्रकार को संशोधित करना, हैंडलर द्वारा लौटाए गए कोर स्कीमा को संशोधित करना या हैंडलर को बिल्कुल भी कॉल न करना चुन सकते हैं।

एक कस्टम प्रकार पर एक विधि के रूप में

निम्नलिखित एक प्रकार का उदाहरण है जो इसे मान्य करने के तरीके को अनुकूलित करने के लिए __get_pydantic_core_schema__ उपयोग करता है। यह Pydantic V1 में __get_validators__ लागू करने के बराबर है।

from typing import Any

from pydantic_core import CoreSchema, core_schema

from pydantic import GetCoreSchemaHandler, TypeAdapter


class Username(str):
    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        return core_schema.no_info_after_validator_function(cls, handler(str))


ta = TypeAdapter(Username)
res = ta.validate_python('abc')
assert isinstance(res, Username)
assert res == 'abc'

कस्टम प्रकारों के लिए JSON स्कीमा को अनुकूलित करने के तरीके के बारे में अधिक जानकारी के लिए JSON स्कीमा देखें।

एक टिप्पणी के रूप में

अक्सर आप अपने कस्टम प्रकार को केवल सामान्य प्रकार के पैरामीटरों से अधिक द्वारा पैरामीट्रिज़ करना चाहेंगे (जो आप टाइप सिस्टम के माध्यम से कर सकते हैं और बाद में चर्चा की जाएगी)। या फिर आप वास्तव में अपने उपवर्ग का उदाहरण बनाने की परवाह नहीं करते (या करना चाहते हैं); आप वास्तव में मूल प्रकार चाहते हैं, बस कुछ अतिरिक्त सत्यापन के साथ।

उदाहरण के लिए, यदि आपको pydantic.AfterValidator ( सत्यापन और क्रमबद्धता जोड़ना देखें) स्वयं लागू करना था, तो आप निम्न के समान कुछ करेंगे:

from dataclasses import dataclass
from typing import Any, Callable

from pydantic_core import CoreSchema, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


@dataclass(frozen=True)  # (1)!
class MyAfterValidator:
    func: Callable[[Any], Any]

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


Username = Annotated[str, MyAfterValidator(str.lower)]


class Model(BaseModel):
    name: Username


assert Model(name='ABC').name == 'abc'  # (2)!
  1. frozen=True विनिर्देशन MyAfterValidator धोने योग्य बनाता है। इसके बिना, Username | None जैसे एक संघ Username | None त्रुटि नहीं उठाएगा.
  2. ध्यान दें कि टाइप चेकर्स Username को 'ABC' निर्दिष्ट करने के बारे में शिकायत नहीं करेंगे जैसा कि उन्होंने पिछले उदाहरण में किया था क्योंकि वे Username str से अलग प्रकार नहीं मानते हैं।

तृतीय-पक्ष प्रकारों को संभालना

पिछले अनुभाग में पैटर्न के लिए एक अन्य उपयोग का मामला तीसरे पक्ष के प्रकारों को संभालना है।

from typing import Any

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import (
    BaseModel,
    GetCoreSchemaHandler,
    GetJsonSchemaHandler,
    ValidationError,
)
from pydantic.json_schema import JsonSchemaValue


class ThirdPartyType:
    """
    This is meant to represent a type from a third-party library that wasn't designed with Pydantic
    integration in mind, and so doesn't have a `pydantic_core.CoreSchema` or anything.
    """

    x: int

    def __init__(self):
        self.x = 0


class _ThirdPartyTypePydanticAnnotation:
    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        """
        We return a pydantic_core.CoreSchema that behaves in the following ways:

        * ints will be parsed as `ThirdPartyType` instances with the int as the x attribute
        * `ThirdPartyType` instances will be parsed as `ThirdPartyType` instances without any changes
        * Nothing else will pass validation
        * Serialization will always return just an int
        """

        def validate_from_int(value: int) -> ThirdPartyType:
            result = ThirdPartyType()
            result.x = value
            return result

        from_int_schema = core_schema.chain_schema(
            [
                core_schema.int_schema(),
                core_schema.no_info_plain_validator_function(validate_from_int),
            ]
        )

        return core_schema.json_or_python_schema(
            json_schema=from_int_schema,
            python_schema=core_schema.union_schema(
                [
                    # check if it's an instance first before doing any further work
                    core_schema.is_instance_schema(ThirdPartyType),
                    from_int_schema,
                ]
            ),
            serialization=core_schema.plain_serializer_function_ser_schema(
                lambda instance: instance.x
            ),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        # Use the same schema that would be used for `int`
        return handler(core_schema.int_schema())


# We now create an `Annotated` wrapper that we'll use as the annotation for fields on `BaseModel`s, etc.
PydanticThirdPartyType = Annotated[
    ThirdPartyType, _ThirdPartyTypePydanticAnnotation
]


# Create a model class that uses this annotation as a field
class Model(BaseModel):
    third_party_type: PydanticThirdPartyType


# Demonstrate that this field is handled correctly, that ints are parsed into `ThirdPartyType`, and that
# these instances are also "dumped" directly into ints as expected.
m_int = Model(third_party_type=1)
assert isinstance(m_int.third_party_type, ThirdPartyType)
assert m_int.third_party_type.x == 1
assert m_int.model_dump() == {'third_party_type': 1}

# Do the same thing where an instance of ThirdPartyType is passed in
instance = ThirdPartyType()
assert instance.x == 0
instance.x = 10

m_instance = Model(third_party_type=instance)
assert isinstance(m_instance.third_party_type, ThirdPartyType)
assert m_instance.third_party_type.x == 10
assert m_instance.model_dump() == {'third_party_type': 10}

# Demonstrate that validation errors are raised as expected for invalid inputs
try:
    Model(third_party_type='a')
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    third_party_type.is-instance[ThirdPartyType]
      Input should be an instance of ThirdPartyType [type=is_instance_of, input_value='a', input_type=str]
    third_party_type.chain[int,function-plain[validate_from_int()]]
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """


assert Model.model_json_schema() == {
    'properties': {
        'third_party_type': {'title': 'Third Party Type', 'type': 'integer'}
    },
    'required': ['third_party_type'],
    'title': 'Model',
    'type': 'object',
}

आप इस दृष्टिकोण का उपयोग उदाहरण के लिए पांडा या नम्पी प्रकारों के लिए व्यवहार को परिभाषित करने के लिए कर सकते हैं।

बॉयलरप्लेट को कम करने के लिए GetPydanticSchema उपयोग करना

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

आप देख सकते हैं कि उपरोक्त उदाहरण जहां हम एक मार्कर क्लास बनाते हैं, उसके लिए अच्छी मात्रा में बॉयलरप्लेट की आवश्यकता होती है। कई साधारण मामलों के लिए आप pydantic.GetPydanticSchema उपयोग करके इसे बहुत कम कर सकते हैं:

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetPydanticSchema


class Model(BaseModel):
    y: Annotated[
        str,
        GetPydanticSchema(
            lambda tp, handler: core_schema.no_info_after_validator_function(
                lambda x: x * 2, handler(tp)
            )
        ),
    ]


assert Model(y='ab').y == 'abab'

सारांश

आओ पूर्वावलोकन कर लें:

  1. Pydantic AfterValidator और Field जैसे Annotated के माध्यम से प्रकारों को अनुकूलित करने के लिए उच्च स्तरीय हुक प्रदान करता है। जब संभव हो तो इनका उपयोग करें.
  2. हुड के तहत ये सत्यापन को अनुकूलित करने के लिए pydantic-core उपयोग करते हैं, और आप सीधे GetPydanticSchema या __get_pydantic_core_schema__ के साथ एक मार्कर क्लास का उपयोग करके इसमें शामिल हो सकते हैं।
  3. यदि आप वास्तव में एक कस्टम प्रकार चाहते हैं तो आप प्रकार पर ही __get_pydantic_core_schema__ लागू कर सकते हैं।

कस्टम जेनेरिक कक्षाओं को संभालना

!!! चेतावनी यह एक उन्नत तकनीक है जिसकी आपको शुरुआत में आवश्यकता नहीं होगी। अधिकांश मामलों में आप संभवतः मानक पाइडेंटिक मॉडल के साथ ठीक रहेंगे।

आप जेनेरिक कक्षाओं को फ़ील्ड प्रकारों के रूप में उपयोग कर सकते हैं और __get_pydantic_core_schema__ के साथ "प्रकार पैरामीटर" (या उप-प्रकार) के आधार पर कस्टम सत्यापन कर सकते हैं।

यदि जेनेरिक क्लास जिसे आप उप-प्रकार के रूप में उपयोग कर रहे हैं, में क्लासमेथोड __get_pydantic_core_schema__ है, तो आपको इसे काम करने के लिए arbitrary_types_allowed का उपयोग करने की आवश्यकता नहीं है।

क्योंकि source_type पैरामीटर cls पैरामीटर के समान नहीं है, आप सामान्य पैरामीटर निकालने के लिए typing.get_args (या typing_extensions.get_args ) का उपयोग कर सकते हैं। फिर आप handler.generate_schema पर कॉल करके उनके लिए स्कीमा तैयार करने के लिए handler उपयोग कर सकते हैं। ध्यान दें कि हम ऐसा कुछ नहीं करते हैं handler(get_args(source_type)[0]) क्योंकि हम उस सामान्य पैरामीटर के लिए एक असंबंधित स्कीमा उत्पन्न करना चाहते हैं, न कि वह जो Annotated मेटाडेटा आदि के वर्तमान संदर्भ से प्रभावित हो। यह कस्टम प्रकारों के लिए कम महत्वपूर्ण है, लेकिन एनोटेटेड मेटाडेटा के लिए महत्वपूर्ण है जो स्कीमा बिल्डिंग को संशोधित करता है।

from dataclasses import dataclass
from typing import Any, Generic, TypeVar

from pydantic_core import CoreSchema, core_schema
from typing_extensions import get_args, get_origin

from pydantic import (
    BaseModel,
    GetCoreSchemaHandler,
    ValidationError,
    ValidatorFunctionWrapHandler,
)

ItemType = TypeVar('ItemType')


# This is not a pydantic model, it's an arbitrary generic class
@dataclass
class Owner(Generic[ItemType]):
    name: str
    item: ItemType

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        origin = get_origin(source_type)
        if origin is None:  # used as `x: Owner` without params
            origin = source_type
            item_tp = Any
        else:
            item_tp = get_args(source_type)[0]
        # both calling handler(...) and handler.generate_schema(...)
        # would work, but prefer the latter for conceptual and consistency reasons
        item_schema = handler.generate_schema(item_tp)

        def val_item(
            v: Owner[Any], handler: ValidatorFunctionWrapHandler
        ) -> Owner[Any]:
            v.item = handler(v.item)
            return v

        python_schema = core_schema.chain_schema(
            # `chain_schema` means do the following steps in order:
            [
                # Ensure the value is an instance of Owner
                core_schema.is_instance_schema(cls),
                # Use the item_schema to validate `items`
                core_schema.no_info_wrap_validator_function(
                    val_item, item_schema
                ),
            ]
        )

        return core_schema.json_or_python_schema(
            # for JSON accept an object with name and item keys
            json_schema=core_schema.chain_schema(
                [
                    core_schema.typed_dict_schema(
                        {
                            'name': core_schema.typed_dict_field(
                                core_schema.str_schema()
                            ),
                            'item': core_schema.typed_dict_field(item_schema),
                        }
                    ),
                    # after validating the json data convert it to python
                    core_schema.no_info_before_validator_function(
                        lambda data: Owner(
                            name=data['name'], item=data['item']
                        ),
                        # note that we re-use the same schema here as below
                        python_schema,
                    ),
                ]
            ),
            python_schema=python_schema,
        )


class Car(BaseModel):
    color: str


class House(BaseModel):
    rooms: int


class Model(BaseModel):
    car_owner: Owner[Car]
    home_owner: Owner[House]


model = Model(
    car_owner=Owner(name='John', item=Car(color='black')),
    home_owner=Owner(name='James', item=House(rooms=3)),
)
print(model)
"""
car_owner=Owner(name='John', item=Car(color='black')) home_owner=Owner(name='James', item=House(rooms=3))
"""

try:
    # If the values of the sub-types are invalid, we get an error
    Model(
        car_owner=Owner(name='John', item=House(rooms=3)),
        home_owner=Owner(name='James', item=Car(color='black')),
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    wine
      Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='Kinda good', input_type=str]
    cheese
      Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='yeah', input_type=str]
    """

# Similarly with JSON
model = Model.model_validate_json(
    '{"car_owner":{"name":"John","item":{"color":"black"}},"home_owner":{"name":"James","item":{"rooms":3}}}'
)
print(model)
"""
car_owner=Owner(name='John', item=Car(color='black')) home_owner=Owner(name='James', item=House(rooms=3))
"""

try:
    Model.model_validate_json(
        '{"car_owner":{"name":"John","item":{"rooms":3}},"home_owner":{"name":"James","item":{"color":"black"}}}'
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    car_owner.item.color
      Field required [type=missing, input_value={'rooms': 3}, input_type=dict]
    home_owner.item.rooms
      Field required [type=missing, input_value={'color': 'black'}, input_type=dict]
    """

सामान्य कंटेनर

कस्टम Sequence प्रकार की तरह, सामान्य कंटेनर प्रकार बनाने के लिए भी यही विचार लागू किया जा सकता है:

from typing import Any, Sequence, TypeVar

from pydantic_core import ValidationError, core_schema
from typing_extensions import get_args

from pydantic import BaseModel, GetCoreSchemaHandler

T = TypeVar('T')


class MySequence(Sequence[T]):
    def __init__(self, v: Sequence[T]):
        self.v = v

    def __getitem__(self, i):
        return self.v[i]

    def __len__(self):
        return len(self.v)

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        instance_schema = core_schema.is_instance_schema(cls)

        args = get_args(source)
        if args:
            # replace the type and rely on Pydantic to generate the right schema
            # for `Sequence`
            sequence_t_schema = handler.generate_schema(Sequence[args[0]])
        else:
            sequence_t_schema = handler.generate_schema(Sequence)

        non_instance_schema = core_schema.no_info_after_validator_function(
            MySequence, sequence_t_schema
        )
        return core_schema.union_schema([instance_schema, non_instance_schema])


class M(BaseModel):
    model_config = dict(validate_default=True)

    s1: MySequence = [3]


m = M()
print(m)
#> s1=<__main__.MySequence object at 0x0123456789ab>
print(m.s1.v)
#> [3]


class M(BaseModel):
    s1: MySequence[int]


M(s1=[1])
try:
    M(s1=['a'])
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for M
    s1.is-instance[MySequence]
      Input should be an instance of MySequence [type=is_instance_of, input_value=['a'], input_type=list]
    s1.function-after[MySequence(), json-or-python[json=list[int],python=chain[is-instance[Sequence],function-wrap[sequence_validator()]]]].0
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

फ़ील्ड नाम तक पहुंच

!!!ध्यान दें यह Pydantic V2 से V2.3 के साथ संभव नहीं था, इसे Pydantic V2.4 में पुनः जोड़ा गया था।

Pydantic V2.4 के अनुसार, आप __get_pydantic_core_schema__ के भीतर handler.field_name के माध्यम से फ़ील्ड नाम तक पहुंच सकते हैं और इस प्रकार फ़ील्ड नाम सेट कर सकते हैं जो info.field_name से उपलब्ध होगा।

from typing import Any

from pydantic_core import core_schema

from pydantic import BaseModel, GetCoreSchemaHandler, ValidationInfo


class CustomType:
    """Custom type that stores the field it was used in."""

    def __init__(self, value: int, field_name: str):
        self.value = value
        self.field_name = field_name

    def __repr__(self):
        return f'CustomType<{self.value} {self.field_name!r}>'

    @classmethod
    def validate(cls, value: int, info: ValidationInfo):
        return cls(value, info.field_name)

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.with_info_after_validator_function(
            cls.validate, handler(int), field_name=handler.field_name
        )


class MyModel(BaseModel):
    my_field: CustomType


m = MyModel(my_field=1)
print(m.my_field)
#> CustomType<1 'my_field'>

आप Annotated के साथ उपयोग किए गए मार्करों से भी field_name तक पहुंच सकते हैं, जैसे [AfterValidator][pydantic.functional_validator.AfterValidator]।

from typing_extensions import Annotated

from pydantic import AfterValidator, BaseModel, ValidationInfo


def my_validators(value: int, info: ValidationInfo):
    return f'<{value} {info.field_name!r}>'


class MyModel(BaseModel):
    my_field: Annotated[int, AfterValidator(my_validators)]


m = MyModel(my_field=1)
print(m.my_field)
#> <1 'my_field'>

本文总阅读量