跳到主要内容

Types

There are also more complex types that can be found in the Pydantic Extra Types package

Custom Types

Composing types via Annotated

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

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1
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'),
]

Customizing validation with __get_pydantic_core_schema__

You can implement __get_pydantic_core_schema__ both on a custom type and on metadata intended to be put in Annotated. In both cases the API is middleware-like and similar to that of "wrap" validators: you get a source_type (which isn't necessarily the same as the class, in particular for generics) and a handler that you can call with a type to either call the next metadata in Annotated or call into Pydantic's internal schema generation

As a method on a custom type

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))

As an annotation

@dataclass(frozen=True)  
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)]

Handling third-party types

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
]

Summary

  • Pydantic provides high level hooks to customize types via Annotated like AfterValidator and Field. Use these when possible.
  • Under the hood these use pydantic-core to customize validation, and you can hook into that directly using GetPydanticSchema or a marker class with __get_pydantic_core_schema__.
  • If you really want a custom type you can implement __get_pydantic_core_schema__ on the type itself