연기된 주석
연기된 주석( 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'
내부적으로 Pydantic은 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)
순환 참조¶
자체 참조 재귀 모델로 작업할 때 검증 입력에서 순환 참조가 발생할 수 있습니다. 예를 들어, 속성의 역참조를 사용하여 ORM 인스턴스의 유효성을 검사할 때 이런 일이 발생할 수 있습니다.
순환 참조로 데이터의 유효성을 검사하려고 시도하는 동안 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=[])])]
마찬가지로 Pydantic이 직렬화 중에 재귀 참조를 발견하면 최대 재귀 깊이가 초과될 때까지 기다리지 않고 즉시 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}]}]}],
}
"""
本文总阅读量次