拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Pydantic 自定义类型全指南:从 Annotated 模式到 `__get_pydantic_core_schema__` 底层定制

Pydantic 自定义类型全指南从 Annotated 模式到__get_pydantic_core_schema__底层定制【免费下载链接】pydanticData validation using Python type hints项目地址: https://gitcode.com/GitHub_Trending/py/pydanticPydantic 以 Python 类型注解为核心定义了校验validation与序列化serialization的完整行为。本文以仓库文档 docs/concepts/types.md 为主体系统讲解在 Pydantic V2 中定义自定义类型的全套路径从零成本复用的Annotated约束模式、命名类型别名与递归类型到直接介入pydantic-coreschema 生成的高级钩子帮助读者掌握从改约束到接管校验管线的完整技能树。类型在 Pydantic 中的角色Pydantic 用类型来定义校验与序列化应如何执行。内置类型与标准库类型如int、str、date可以直接使用并通过严格模式strict mode与各种约束constraints加以控制。在这些基础之上Pydantic 还提供了一类额外类型它们要么直接内置于库中例如 SecretStr要么由pydantic-extra-types外部库提供。关键区别在于这类额外类型是使用文档后面描述的自定义类型模式实现的因此严格模式与约束无法直接施加在它们身上。内置与标准库类型的完整清单——允许的值、可用的校验约束、以及能否配置严格模式——见 built-in and standard library types 文档各类型允许值的速查表见转换表conversion table。本文余下部分将聚焦于如何定义你自己的自定义类型。使用 Annotated 模式定义可复用类型Annotated 模式可以将约束与类型本身打包成一个可跨代码库复用的新类型。例如定义一个正整数类型from typing import Annotated from pydantic import Field, TypeAdapter, ValidationError PositiveInt Annotated[int, Field(gt0)] # (1)! 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 [typegreater_than, input_value-1, input_typeint] 也可以使用annotated-types库中的约束元数据让该类型与 Pydantic 解耦即Pydantic 无关from annotated_types import Gt PositiveInt Annotated[int, Gt(0)]注意到Field(gt0)写的是约束本身而annotated-types的Gt(0)写法不依赖任何框架两者在 Pydantic 中等价。这里的TypeAdapter非常适合脱离模型、单独对某个类型做校验与 JSON Schema 生成的场景。为任意类型叠加校验、序列化与 JSON Schema通过 Pydantic 导出的三类标记marker你可以为任意类型添加或覆盖校验、序列化以及 JSON Schema 生成行为AfterValidator在类型校验通过之后追加后置校验PlainSerializer完全替换默认的序列化逻辑WithJsonSchema覆盖该类型在特定模式validation / serialization下的 JSON Schema。from typing 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_typestr), WithJsonSchema({type: string}, modeserialization), ] ta TypeAdapter(TruncatedFloat) input 1.02345 assert input ! 1.0 assert ta.validate_python(input) 1.0 assert ta.dump_json(input) b1.0e00 assert ta.json_schema(modevalidation) {type: number} assert ta.json_schema(modeserialization) {type: string}这个例子展示了两种模式的 JSON Schema 可以不同校验模式下仍是{type: number}而序列化模式下输出为字符串{type: string}因为序列化结果被改写成了科学计数法字符串。泛型在 Annotated 中使用类型变量TypeVar可以嵌入Annotated中得到参数化的可复用类型。文档给出两个互补的方向约束包在容器外层——限制列表长度from typing import Annotated, TypeVar from annotated_types import Gt, Len from pydantic import TypeAdapter, ValidationError T TypeVar(T) ShortList Annotated[list[T], Len(max_length4)] ta TypeAdapter(ShortList[int]) v ta.validate_python([1, 2, 3, 4]) assert v [1, 2, 3, 4] try: ta.validate_python([1, 2, 3, 4, 5]) except ValidationError as exc: print(exc) 1 validation error for list[int] List should have at most 4 items after validation, not 5 [typetoo_long, input_value[1, 2, 3, 4, 5], input_typelist] 约束作用在元素上——限制列表内每个元素PositiveList list[Annotated[T, Gt(0)]] ta TypeAdapter(PositiveList[float]) v ta.validate_python([1.0]) assert type(v[0]) is float try: ta.validate_python([-1.0]) except ValidationError as exc: print(exc) 1 validation error for list[constrained-float] 0 Input should be greater than 0 [typegreater_than, input_value-1.0, input_typefloat] 实例化时传入具体类型参数ShortList[int]、PositiveList[float]即可得到对应元素类型的约束版本。命名类型别名让别名可被 JSON Schema 引用上述例子都属于隐式implicit类型别名——只是把Annotated[...]赋值给一个变量。在运行时Pydantic 无法得知该变量叫什么名字这会带来两个问题别名的 JSON Schema 不会被收敛成$defs定义。当别名在一个模型里被多次使用时会产生大量重复定义大多数情况下递归类型别名无法工作。从 Pydantic v2.11 起命名类型别名被完整支持。做法是利用 Python 3.12 引入的type语句PEP 695或通过typing_extensions.TypeAliasType在更早版本上使用同一套 API Python 3.10 及以上TypeAliasTypepython from typing import Annotated from annotated_types import Gt from typing_extensions import TypeAliasType from pydantic import BaseModel PositiveIntList TypeAliasType(PositiveIntList, list[Annotated[int, Gt(0)]]) class Model(BaseModel): x: PositiveIntList y: PositiveIntList print(Model.model_json_schema()) # (1)! { $defs: { PositiveIntList: { items: {exclusiveMinimum: 0, type: integer}, type: array, } }, properties: { x: {$ref: #/$defs/PositiveIntList}, y: {$ref: #/$defs/PositiveIntList}, }, required: [x, y], title: Model, type: object, } 1. 若 PositiveIntList 以隐式别名定义其定义会在 x 和 y 中被重复展开。 Python 3.12 及以上新语法python from typing import Annotated from annotated_types import Gt from pydantic import BaseModel type PositiveIntList list[Annotated[int, Gt(0)]] class Model(BaseModel): x: PositiveIntList y: PositiveIntList print(Model.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: Model, type: object, } 可以看到模型 JSON Schema 中x、y两个字段都通过$ref指向$defs/PositiveIntList这一处定义实现了去重。仓库中对该行为的测试覆盖可见 tests/test_type_alias_type.py其中甚至包含JsonType这类递归别名的用例见该文件第 14 行。命名类型别名的使用边界重要警告尽管 PEP 695 命名别名与隐式别名对静态类型检查器而言等价但Pydantic 不会理解命名别名内部的字段级元数据。也就是说alias、default、deprecated这类字段专属元数据不能在命名别名中使用from typing import Annotated from typing_extensions import TypeAliasType from pydantic import BaseModel, Field MyAlias TypeAliasType(MyAlias, Annotated[int, Field(default1)]) class Model(BaseModel): x: MyAlias # This is not allowedPython 3.12 下的新语法等价写法为type MyAlias Annotated[int, Field(default1)]。只有能够直接作用于注解类型本身的元数据例如字段约束与 JSON 元数据才被允许。原因在于若要支持字段级元数据Pydantic 必须急切地检查别名的__value__从而无法把别名作为 JSON Schema 定义存储下来。与隐式别名一致命名别名同样支持泛型——在别名内部使用类型变量from typing import Annotated, TypeVar from annotated_types import Len from typing_extensions import TypeAliasType T TypeVar(T) ShortList TypeAliasType( ShortList, Annotated[list[T], Len(max_length4)], type_params(T,) )Python 3.12 新语法对应写法为type ShortList[T] Annotated[list[T], Len(max_length4)]。命名递归类型只要需要递归类型别名就应该使用命名别名。Pydantic 出于多种原因无法支持隐式递归别名——例如它无法跨模块解析前向引用forward annotations。下面是经典的 JSON 类型定义注意PEP 695 别名值是惰性求值的因此不需要前向引用而TypeAliasType版本的值会被急切求值必须用引号包住尚未定义的Json Python 3.10 及以上TypeAliasTypepython from typing_extensions import TypeAliasType from pydantic import TypeAdapter Json TypeAliasType( Json, dict[str, Json] | list[Json] | str | int | float | bool | None, # (1)! ) ta TypeAdapter(Json) print(ta.json_schema()) { $defs: { Json: { anyOf: [ { additionalProperties: {$ref: #/$defs/Json}, type: object, }, {items: {$ref: #/$defs/Json}, type: array}, {type: string}, {type: integer}, {type: number}, {type: boolean}, {type: null}, ] } }, $ref: #/$defs/Json, } 1. 该注解会被急切求值而 Json 此时还未定义因此必须用引号包裹成字符串。 Python 3.12 及以上新语法python from pydantic import TypeAdapter type Json dict[str, Json] | list[Json] | str | int | float | bool | None # (1)! ta TypeAdapter(Json) print(ta.json_schema()) { $defs: { Json: { anyOf: [ { additionalProperties: {$ref: #/$defs/Json}, type: object, }, {items: {$ref: #/$defs/Json}, type: array}, {type: string}, {type: integer}, {type: number}, {type: boolean}, {type: null}, ] } }, $ref: #/$defs/Json, } 1. 命名类型别名的值是惰性求值的因此无需使用前向引用。生成的 schema 递归地通过$ref引用自身恰好表达了 JSON 数据的自相似结构。小贴士Pydantic 在 pydantic/types.py 中内置了开箱即用的JsonValue类型其定义就是一个带标签判别联合discriminated union的递归别名校验失败时报错为invalid-json-value可作为此类场景的直接替代品。用__get_pydantic_core_schema__深度定制校验当需要对自定义类做更彻底的控制时——尤其是当你拥有这个类、或可以继承它时——可以实现一个特殊的__get_pydantic_core_schema__方法直接告诉 Pydantic 如何生成pydantic-core的 schema。需要说明的是pydantic内部用pydantic-core完成校验与序列化这是 Pydantic V2 的新 API也是最可能在后续版本中被调整的部分。建议优先使用内置构造如annotated-types、pydantic.Field、BeforeValidator等。__get_pydantic_core_schema__既可以实现为自定义类型上的方法也可以实现为打算放进Annotated的元数据。两种情况下 API 都类似wrap校验器呈现中间件风格你拿到一个source_type不一定是类本身对泛型尤其如此以及一个handler可以用类型调用它要么调用Annotated中下一层元数据要么直接进入 Pydantic 内部的 schema 生成机制。最简单的空操作实现就是把拿到的类型交给 handler 后原样返回。你还可以选择在调用 handler 前修改类型、修改 handler 返回的 core schema、或完全不调用 handler。仓库中 pydantic/annotated_handlers.py 定义了GetCoreSchemaHandler的完整接口__call__调用下一层生成逻辑、generate_schema生成与当前上下文无关的 schema、resolve_ref_schema解析definition-ref以及field_name属性下文详述。作为自定义类型的方法下面的例子定义了一个Username类型它继承了str并在校验通过后额外执行一次cls转换从而保证返回的是Username实例。这在功能上等价于 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核心逻辑是handler(str)先让 Pydantic 按内建str生成校验 schema再用no_info_after_validator_function(cls, ...)把结果包进Username。关于自定义类型的 JSON Schema 定制细节可参考 JSON Schema 概念文档。作为 Annotated 元数据marker 类很多时候你想让自定义类型支持更多参数化维度不止泛型参数或者你并不想真正产生一个子类实例而是保留原类型、只追加额外校验。例如若要亲手实现一个AfterValidator对应前文Adding validation and serialization一节可以这样做from collections.abc import Callable from dataclasses import dataclass from typing import Annotated, Any from pydantic_core import CoreSchema, core_schema from pydantic import BaseModel, GetCoreSchemaHandler dataclass(frozenTrue) # (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(nameABC).name abc # (2)!frozenTrue使MyAfterValidator可哈希。没有它像Username | None这样的联合类型会报错。注意类型检查器不会像上一个例子那样抱怨把ABC赋给Username因为从类型系统角度看Username与str并非不同类型。Annotated中多个元数据与目标类型的调用顺序在仓库测试 tests/test_annotated.py 中有精确验证外层元数据先进入、后退出before 在外层after 由内向外整体呈洋葱式中间件结构。处理第三方类型上一个模式的另一典型应用场景是接入未针对 Pydantic 设计的第三方类型。下面用一个假想第三方类型ThirdPartyType演示完整流程用Annotated包一层带__get_pydantic_core_schema__和__get_pydantic_json_schema__的 marker 类实现int 解析为实例、实例原样通过、其余输入报错、序列化永远输出 intfrom typing import Annotated, Any from pydantic_core import core_schema 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 wasnt designed with Pydantic integration in mind, and so doesnt 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_schemafrom_int_schema, python_schemacore_schema.union_schema( [ # check if its an instance first before doing any further work core_schema.is_instance_schema(ThirdPartyType), from_int_schema, ] ), serializationcore_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 well use as the annotation for fields on BaseModels, 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_type1) 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_typeinstance) 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_typea) except ValidationError as e: print(e) 2 validation errors for Model third_party_type.is-instance[ThirdPartyType] Input should be an instance of ThirdPartyType [typeis_instance_of, input_valuea, input_typestr] third_party_type.chain[int,function-plain[validate_from_int()]] Input should be a valid integer, unable to parse string as an integer [typeint_parsing, input_valuea, input_typestr] assert Model.model_json_schema() { properties: { third_party_type: {title: Third Party Type, type: integer} }, required: [third_party_type], title: Model, type: object, }要点拆解chain_schema串起先按 int 校验再执行转换函数两步json_or_python_schema分别定义了 JSON 输入与 Python 输入两条路径Python 路径用union_schema先做is_instance_schema实例直接放行再做 int 转换__get_pydantic_json_schema__委托handler复用int的 JSON Schema因此最终 schema 中该字段显示为integer。这套模式完全适用于 Pandas、NumPy 等库的自定义类型接入。整个示例在仓库中的对应实现与测试模式可参见 pydantic/annotated_handlers.py 与 tests/test_annotated.py。用GetPydanticSchema减少样板代码上面的 marker 类写法需要不少样板。对许多简单场景可以用pydantic.GetPydanticSchema大幅精简——它接收两个可选回调分别对应__get_pydantic_core_schema__与__get_pydantic_json_schema__其源码见 pydantic/types.pyfrom typing import Annotated from pydantic_core import core_schema 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(yab).y ababGetPydanticSchema(lambda tp, handler: ...)内部通过__getattr__把回调映射为对应的钩子方法源码见 pydantic/types.py省去了手写 marker 类的步骤。小结三层定制阶梯高层钩子优先用Annotated组合AfterValidator、Field等现成标记满足绝大多数约束与转换需求中间层介入上述标记底层都经由pydantic-core定制校验需要更细控制时用GetPydanticSchema或带__get_pydantic_core_schema__的 marker 类直接接入类型自身接管如果你确实要定义全新类型就在类型上实现__get_pydantic_core_schema__。处理自定义泛型类这是一个进阶技巧——大多数场景下标准 Pydantic 模型已足够。但当你需要把 Generic Classes 作为字段类型并根据泛型参数子类型做定制校验时__get_pydantic_core_schema__是入口。关键前提与原则如果该泛型类拥有__get_pydantic_core_schema__类方法就无需配置arbitrary_types_allowedsource_type参数与cls不同因此要用typing.get_args或typing_extensions.get_args提取泛型参数提取参数后用handler.generate_schema为它们生成 schema。不要写handler(get_args(source_type)[0])——那会把当前Annotated元数据等上下文影响带进参数 schema 的生成generate_schema生成的是与当前上下文无关的 schema。这一点对自定义类型影响较小但对会修改 schema 构建的 annotated 元数据至关重要。下面的Owner是一个任意泛型类并非 Pydantic 模型它要求item字段必须是泛型参数ItemType类型的实例from dataclasses import dataclass from typing import Any, Generic, TypeVar, get_args, get_origin from pydantic_core import CoreSchema, core_schema from pydantic import ( BaseModel, GetCoreSchemaHandler, ValidationError, ValidatorFunctionWrapHandler, ) ItemType TypeVar(ItemType) # This is not a pydantic model, its 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_schemacore_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( namedata[name], itemdata[item] ), # note that we reuse the same schema here as below python_schema, ), ] ), python_schemapython_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_ownerOwner(nameJohn, itemCar(colorblack)), home_ownerOwner(nameJames, itemHouse(rooms3)), ) print(model) car_ownerOwner(nameJohn, itemCar(colorblack)) home_ownerOwner(nameJames, itemHouse(rooms3)) try: # If the values of the sub-types are invalid, we get an error Model( car_ownerOwner(nameJohn, itemHouse(rooms3)), home_ownerOwner(nameJames, itemCar(colorblack)), ) 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 [typefloat_parsing, input_valueKinda good, input_typestr] cheese Input should be a valid boolean, unable to interpret input [typebool_parsing, input_valueyeah, input_typestr] # 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_ownerOwner(nameJohn, itemCar(colorblack)) home_ownerOwner(nameJames, itemHouse(rooms3)) 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 [typemissing, input_value{rooms: 3}, input_typedict] home_owner.item.rooms Field required [typemissing, input_value{color: black}, input_typedict] 这个例子同时展示了 Python 输入与 JSON 输入两条路径Python 路径要求传入真实的Owner实例并校验其itemJSON 路径则按{name, item}的 typed-dict 结构解析后重建Owner。泛型参数ItemType不同Car还是Houseitem的校验 schema 就不同这正是按子类型定制校验的核心价值。泛型容器同一思想可以直接套用到自定义容器类型上。下面是一个自定义Sequence的实现它既能接受已构造的MySequence实例也能接受普通列表并自动包装from collections.abc import Sequence from typing import Any, TypeVar, get_args from pydantic_core import ValidationError, core_schema 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_defaultTrue) 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 [typeis_instance_of, input_value[a], input_typelist] s1.function-after[MySequence(), json-or-python[jsonlist[int],pythonchain[is-instance[Sequence],function-wrap[sequence_validator()]]]].0 Input should be a valid integer, unable to parse string as an integer [typeint_parsing, input_valuea, input_typestr] 实现要点handler.generate_schema(Sequence[args[0]])借用 Pydantic 内建对Sequence的 schema 生成能力再用no_info_after_validator_function(MySequence, ...)把结果包装成MySequence最后用union_schema同时接受已是实例与需要转换两条路径。仓库 tests/test_generics.py 中还有针对泛型 schema 生成与TypeAliasType参数化的大量回归用例。在自定义类型中访问字段名从 Pydantic V2.4 起V2.0 至 V2.3 曾不可用可以在__get_pydantic_core_schema__中通过handler.field_name访问当前字段名并在校验函数里通过info.field_name取得它。handler.field_name是GetCoreSchemaHandler的属性定义见 pydantic/annotated_handlers.py。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 fCustomType{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) ) class MyModel(BaseModel): my_field: CustomType m MyModel(my_field1) print(m.my_field) # CustomType1 my_field由于field_name来自handler它同样适用于Annotated中的标记——例如AfterValidatorfrom typing 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_field1) print(m.my_field) # 1 my_field这在实现根据字段名改变校验行为例如日志字段名、动态错误信息时非常实用。全文要点回顾Pydantic 以类型为唯一事实来源驱动校验与序列化内置/标准库类型开箱即用严格模式与约束可直接叠加额外类型如 SecretStr源码见 pydantic/types.py则由自定义类型模式实现不受严格模式与约束影响。复用约束的首选是Annotated模式Field、annotated-types配合AfterValidator/PlainSerializer/WithJsonSchema可叠加校验、序列化与 JSON Schema且支持泛型。需要 JSON Schema 去重或定义递归类型时使用命名类型别名TypeAliasType或 Python 3.12 的type语句但注意字段级元数据alias、default、deprecated不可放入命名别名。更深层的定制走__get_pydantic_core_schema__可在自定义类型上实现、也可做成Annotated元数据GetPydanticSchema能显著减少 marker 样板代码。泛型类与泛型容器可通过get_args提取类型参数配合handler.generate_schema按子类型定制校验handler.field_name让自定义类型能感知所在字段。按高层标记 → 中间层钩子 → 类型自身实现的顺序选型既能覆盖绝大多数业务需求又能在必要时拥有 pydantic-core 级别的完全控制力。【免费下载链接】pydanticData validation using Python type hints项目地址: https://gitcode.com/GitHub_Trending/py/pydantic创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门