타입
가능한 경우 Pydantic은 표준 라이브러리 유형을 사용하여 필드를 정의하므로 학습 곡선이 원활해집니다. 그러나 많은 유용한 애플리케이션의 경우 표준 라이브러리 유형이 존재하지 않으므로 Pydantic은 일반적으로 사용되는 많은 유형을 구현합니다.
Pydantic Extra Types 패키지에는 더 복잡한 유형도 있습니다.
기존 유형이 목적에 적합하지 않은 경우 사용자 정의 속성 및 유효성 검사를 통해 자체 Pydantic 호환 유형을 구현할 수도 있습니다.
다음 섹션에서는 Pydantic에서 지원하는 유형을 설명합니다.
- 표준 라이브러리 유형 — Python 표준 라이브러리의 유형입니다.
- 엄격한 유형 - 호환 유형의 강제를 방지할 수 있는 유형입니다.
- 사용자 정의 데이터 유형 — 자신만의 사용자 정의 데이터 유형을 생성합니다.
- 필드 유형 변환 — 다양한 필드 유형 간의 엄격하고 느슨한 변환입니다.
유형 변환¶
검증 중에 Pydantic은 데이터를 예상 유형으로 강제할 수 있습니다.
강제에는 엄격함과 느슨함의 두 가지 모드가 있습니다. Pydantic이 엄격 모드와 완화 모드 모두에서 데이터를 변환하는 방법에 대한 자세한 내용은 변환 표를 참조하세요.
엄격한 강제 활성화에 대한 자세한 내용은 엄격한 모드 및 엄격한 유형을 참조하세요.
엄격한 유형¶
Pydantic은 다음과 같은 엄격한 유형을 제공합니다:
이러한 유형은 검증된 값이 해당 유형이거나 해당 유형의 하위 유형인 경우에만 검증을 통과합니다.
제한된 유형¶
이 동작은 제한된 유형의 strict
필드를 통해서도 노출되며 다수의 복잡한 유효성 검사 규칙과 결합될 수 있습니다. 지원되는 인수는 개별 유형 서명을 참조하세요.
다음과 같은 주의 사항이 적용됩니다.
StrictBytes
(및conbytes()
의strict
옵션)은bytes
및bytearray
유형을 모두 허용합니다.StrictInt
(및conint()
의strict
옵션)은bool
Python에서int
의 하위 클래스임에도 불구하고bool
유형을 허용하지 않습니다. 다른 하위 클래스도 작동합니다.StrictFloat
(및confloat()
의strict
옵션)은int
허용하지 않습니다.
위의 것 외에도 유한한 값만 허용하는 FiniteFloat
유형도 있을 수 있습니다(예: inf
, -inf
또는 nan
아님).
사용자 정의 유형¶
사용자 정의 데이터 유형을 정의할 수도 있습니다. 이를 달성하는 방법에는 여러 가지가 있습니다.
Annotated
를 통해 유형 작성¶
PEP 593에서는 유형 검사기가 해석하는 방식을 변경하지 않고 런타임 메타데이터를 유형에 첨부하는 방법으로 Annotated
도입했습니다. Pydantic은 이를 활용하여 유형 검사기에 관한 한 원래 유형과 동일한 유형을 생성할 수 있지만 유효성 검사를 추가하고 다르게 직렬화하는 등의 작업을 수행할 수 있습니다.
예를 들어, 양의 int를 나타내는 유형을 생성하려면 다음을 수행하십시오.
# 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]
"""
주석이 달린 유형 의 제약 조건을 사용하여 Pydantic에 구애받지 않게 만들 수도 있습니다.
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]
"""
유효성 검사 및 직렬화 추가¶
Pydantic이 내보내는 마커를 사용하여 임의 유형에 유효성 검사, 직렬화 및 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
가질 수 없으며 해당 스키마가 필드 간에 복사된다는 것을 의미합니다. 입력 확장 백포트를 통해 PEP 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이 사용자 정의 클래스를 처리하는 방법을 보다 광범위하게 사용자 정의하려면, 특히 클래스에 액세스하거나 클래스를 하위 클래스로 분류할 수 있는 경우 특별한 __get_pydantic_core_schema__
를 구현하여 Pydantic에 pydantic-core
스키마 생성 방법을 알려줄 수 있습니다.
pydantic
검증 및 직렬화를 처리하기 위해 내부적으로 pydantic-core
사용하지만 이는 Pydantic V2의 새로운 API이므로 향후 조정될 가능성이 가장 높은 영역 중 하나이며 다음과 같은 내장 구성을 고수해야 합니다. annotated-types
, pydantic.Field
또는 BeforeValidator
등에 의해 제공되는 것입니다.
사용자 정의 유형과 Annotated
에 넣을 메타데이터 모두에서 __get_pydantic_core_schema__
구현할 수 있습니다. 두 경우 모두 API는 미들웨어와 유사하며 "래핑" 유효성 검사기의 API와 유사합니다. source_type
(특히 제네릭의 경우 클래스와 반드시 동일할 필요는 없음)과 유형으로 호출할 수 있는 handler
를 얻습니다. Annotated
에서 다음 메타데이터를 호출하거나 Pydantic의 내부 스키마 생성을 호출합니다.
가장 간단한 무작동 구현은 제공된 유형으로 핸들러를 호출한 다음 이를 결과로 반환합니다. 핸들러를 호출하기 전에 유형을 수정하거나, 핸들러에서 반환된 핵심 스키마를 수정하거나, 핸들러를 전혀 호출하지 않도록 선택할 수도 있습니다.
사용자 정의 유형에 대한 메소드¶
다음은 검증 방법을 사용자 정의하기 위해 __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)!
frozen=True
사양은MyAfterValidator
해시 가능하게 만듭니다. 이것이 없으면Username | None
오류를 발생시키지 않습니다.- 유형 검사기는
Username
str
과 별개의 유형으로 간주하지 않기 때문에 이전 예제에서 했던 것처럼Username
에'ABC'
할당에 대해 불평하지 않습니다.
타사 유형 처리¶
이전 섹션의 패턴에 대한 또 다른 사용 사례는 타사 유형을 처리하는 것입니다.
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',
}
예를 들어 Pandas 또는 Numpy 유형의 동작을 정의하는 데 이 접근 방식을 사용할 수 있습니다.
GetPydanticSchema
사용하여 상용구 줄이기¶
??? api "API 문서" 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'
요약¶
요약하자면:
- Pydantic은
AfterValidator
및Field
와 같은Annotated
를 통해 유형을 사용자 정의할 수 있는 높은 수준의 후크를 제공합니다. 가능하면 이것을 사용하십시오. - 내부적으로는
pydantic-core
사용하여 유효성 검사를 사용자 정의하고GetPydanticSchema
또는__get_pydantic_core_schema__
가 있는 마커 클래스를 사용하여 직접 연결할 수 있습니다. - 사용자 정의 유형을 정말로 원한다면 유형 자체에
__get_pydantic_core_schema__
구현할 수 있습니다.
사용자 정의 일반 클래스 처리¶
!!! 경고 이는 처음에는 필요하지 않을 수도 있는 고급 기술입니다. 대부분의 경우 표준 Pydantic 모델을 사용하면 문제가 없을 것입니다.
일반 클래스를 필드 유형으로 사용하고 __get_pydantic_core_schema__
를 사용하여 "유형 매개변수"(또는 하위 유형)를 기반으로 사용자 정의 유효성 검사를 수행할 수 있습니다.
하위 유형으로 사용 중인 일반 클래스에 클래스 메소드 __get_pydantic_core_schema__
가 있는 경우 해당 클래스가 작동하기 위해 arbitrary_types_allowed
를 사용할 필요가 없습니다.
source_type
매개변수는 cls
매개변수와 동일하지 않기 때문에 typing.get_args
(또는 typing_extensions.get_args
)를 사용하여 일반 매개변수를 추출할 수 있습니다. 그런 다음 handler
사용하여 handler.generate_schema
호출하여 스키마를 생성할 수 있습니다. 우리는 다음과 같은 작업을 수행하지 않습니다. 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'>
[AfterValidator
][pydantic.function_validators.AfterValidator]와 같이 Annotated
와 함께 사용되는 마커에서 field_name
에 액세스할 수도 있습니다.
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'>
本文总阅读量次