विषय पर बढ़ें

स्थगित एनोटेशन

स्थगित एनोटेशन (जैसा कि PEP563 में वर्णित है) "बस काम करें"।

from __future__ import annotations

from typing import Any

from pydantic import BaseModel


class Model(BaseModel):
    a: list[int]
    b: Any


print(Model(a=('1', 2, 3), b='ok'))
#> a=[1, 2, 3] b='ok'

आंतरिक रूप से, पाइडेंटिक एनोटेशन को हल करने के लिए typing.get_type_hints के समान एक विधि को कॉल करेगा।

बिना उपयोग किये भी from __future__ import annotations , ऐसे मामलों में जहां संदर्भित प्रकार अभी तक परिभाषित नहीं किया गया है, एक ForwardRef या स्ट्रिंग का उपयोग किया जा सकता है:

from typing import ForwardRef

from pydantic import BaseModel

Foo = ForwardRef('Foo')


class Foo(BaseModel):
    a: int = 123
    b: Foo = None


print(Foo())
#> a=123 b=None
print(Foo(b={'a': '321'}))
#> a=123 b=Foo(a=321, b=None)

स्व-संदर्भित (या "पुनरावर्ती") मॉडल

स्व-संदर्भित फ़ील्ड वाले मॉडल भी समर्थित हैं। मॉडल निर्माण के बाद स्व-संदर्भित फ़ील्ड स्वचालित रूप से हल हो जाएंगी।

मॉडल के भीतर, आप एक स्ट्रिंग का उपयोग करके अभी तक नहीं बने मॉडल का उल्लेख कर सकते हैं:

from pydantic import BaseModel


class Foo(BaseModel):
    a: int = 123
    #: The sibling of `Foo` is referenced by string
    sibling: 'Foo' = None


print(Foo())
#> a=123 sibling=None
print(Foo(sibling={'a': '321'}))
#> a=123 sibling=Foo(a=321, sibling=None)

यदि तुम प्रयोग करते हो from __future__ import annotations , आप मॉडल को उसके प्रकार के नाम से भी संदर्भित कर सकते हैं:

from __future__ import annotations

from pydantic import BaseModel


class Foo(BaseModel):
    a: int = 123
    #: The sibling of `Foo` is referenced directly by type
    sibling: Foo = None


print(Foo())
#> a=123 sibling=None
print(Foo(sibling={'a': '321'}))
#> a=123 sibling=Foo(a=321, sibling=None)

चक्रीय संदर्भ

स्व-संदर्भित पुनरावर्ती मॉडल के साथ काम करते समय, यह संभव है कि आपको सत्यापन इनपुट में चक्रीय संदर्भ का सामना करना पड़ सकता है। उदाहरण के लिए, यह तब हो सकता है जब विशेषताओं से बैक-रेफरेंस के साथ ओआरएम उदाहरणों को मान्य किया जाता है।

चक्रीय संदर्भों के साथ डेटा को मान्य करने का प्रयास करते समय Python RecursionError बढ़ाने के बजाय, Pydantic चक्रीय संदर्भ का पता लगाने और एक उचित ValidationError बढ़ाने में सक्षम है:

from typing import Optional

from pydantic import BaseModel, ValidationError


class ModelA(BaseModel):
    b: 'Optional[ModelB]' = None


class ModelB(BaseModel):
    a: Optional[ModelA] = None


cyclic_data = {}
cyclic_data['a'] = {'b': cyclic_data}
print(cyclic_data)
#> {'a': {'b': {...}}}

try:
    ModelB.model_validate(cyclic_data)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for ModelB
    a.b
      Recursion error - cyclic reference detected [type=recursion_loop, input_value={'a': {'b': {...}}}, input_type=dict]
    """

क्योंकि यह त्रुटि वास्तव में अधिकतम रिकर्सन गहराई को पार किए बिना उठाई गई है, आप सीमित शेष रिकर्सन गहराई के बारे में चिंता किए बिना उठाए गए ValidationError पकड़ और संभाल सकते हैं:

from contextlib import contextmanager
from dataclasses import field
from typing import Iterator, List

from pydantic import BaseModel, ValidationError, field_validator


def is_recursion_validation_error(exc: ValidationError) -> bool:
    errors = exc.errors()
    return len(errors) == 1 and errors[0]['type'] == 'recursion_loop'


@contextmanager
def suppress_recursion_validation_error() -> Iterator[None]:
    try:
        yield
    except ValidationError as exc:
        if not is_recursion_validation_error(exc):
            raise exc


class Node(BaseModel):
    id: int
    children: List['Node'] = field(default_factory=list)

    @field_validator('children', mode='wrap')
    @classmethod
    def drop_cyclic_references(cls, children, h):
        try:
            return h(children)
        except ValidationError as exc:
            if not (
                is_recursion_validation_error(exc)
                and isinstance(children, list)
            ):
                raise exc

            value_without_cyclic_refs = []
            for child in children:
                with suppress_recursion_validation_error():
                    value_without_cyclic_refs.extend(h([child]))
            return h(value_without_cyclic_refs)


# Create data with cyclic references representing the graph 1 -> 2 -> 3 -> 1
node_data = {'id': 1, 'children': [{'id': 2, 'children': [{'id': 3}]}]}
node_data['children'][0]['children'][0]['children'] = [node_data]

print(Node.model_validate(node_data))
#> id=1 children=[Node(id=2, children=[Node(id=3, children=[])])]

इसी तरह, यदि पाइडेंटिक को क्रमबद्धता के दौरान एक पुनरावर्ती संदर्भ का सामना करना पड़ता है, तो अधिकतम पुनरावर्तन गहराई के पार होने की प्रतीक्षा करने के बजाय, एक ValueError तुरंत उठाया जाता है:

from pydantic import TypeAdapter

# Create data with cyclic references representing the graph 1 -> 2 -> 3 -> 1
node_data = {'id': 1, 'children': [{'id': 2, 'children': [{'id': 3}]}]}
node_data['children'][0]['children'][0]['children'] = [node_data]

try:
    # Try serializing the circular reference as JSON
    TypeAdapter(dict).dump_json(node_data)
except ValueError as exc:
    print(exc)
    """
    Error serializing to JSON: ValueError: Circular reference detected (id repeated)
    """

यदि चाहें तो इसे भी संभाला जा सकता है:

from dataclasses import field
from typing import Any, List

from pydantic import (
    SerializerFunctionWrapHandler,
    TypeAdapter,
    field_serializer,
)
from pydantic.dataclasses import dataclass


@dataclass
class NodeReference:
    id: int


@dataclass
class Node(NodeReference):
    children: List['Node'] = field(default_factory=list)

    @field_serializer('children', mode='wrap')
    def serialize(
        self, children: List['Node'], handler: SerializerFunctionWrapHandler
    ) -> Any:
        """
        Serialize a list of nodes, handling circular references by excluding the children.
        """
        try:
            return handler(children)
        except ValueError as exc:
            if not str(exc).startswith('Circular reference'):
                raise exc

            result = []
            for node in children:
                try:
                    serialized = handler([node])
                except ValueError as exc:
                    if not str(exc).startswith('Circular reference'):
                        raise exc
                    result.append({'id': node.id})
                else:
                    result.append(serialized)
            return result


# Create a cyclic graph:
nodes = [Node(id=1), Node(id=2), Node(id=3)]
nodes[0].children.append(nodes[1])
nodes[1].children.append(nodes[2])
nodes[2].children.append(nodes[0])

print(nodes[0])
#> Node(id=1, children=[Node(id=2, children=[Node(id=3, children=[...])])])

# Serialize the cyclic graph:
print(TypeAdapter(Node).dump_python(nodes[0]))
"""
{
    'id': 1,
    'children': [{'id': 2, 'children': [{'id': 3, 'children': [{'id': 1}]}]}],
}
"""

本文总阅读量