关于 pydantic-v2
面向 Python 开发者的 Pydantic v2 参考技能,帮助编写和验证数据模型。内容聚焦 v2 API、序列化及 v1 迁移时的常见问题。
主要功能
- 01类型化模型与字段约束
- 02字段及模型验证器
- 03序列化与计算字段
- 04从环境变量和 .env 加载设置
- 05TypeAdapter 与联合类型验证
- 06v1 到 v2 迁移指导
适用场景
- 01定义经过验证的 DTO 和配置模型
- 02添加字段或跨字段自定义验证
- 03序列化模型以供 API 或数据交换使用
- 04从环境变量加载应用设置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bm629/ agent-skills --skill pydantic-v2安装后在 Claude Code 中输入/pydantic-v2确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/migration-v1-to-v2.mdserialization.mdsettings.mdsources.mdvalidators.md详细介绍
Pydantic v2
概述
Pydantic v2 根据 Python 类型提示验证并序列化数据。BaseModel
的子类通过注解声明字段;实例化时会验证并转换输入,并可通过 model_dump* 将实例
往返转换为字典/JSON。本技能介绍编写当前 v2 代码所需的知识增量——即 v1 之后的
API(model_config = ConfigDict(...)、model_dump()、@field_validator),以及 v1
编码习惯遗留后容易踩到的陷阱。它涵盖独立使用 Pydantic 的场景:模型、字段、验证器、
序列化、配置、pydantic-settings、TypeAdapter,以及结构化类型和联合类型。关于框架层面的请求/响应
建模和 response_model,请参阅 fastapi 技能——本技能不重复介绍
框架机制。
何时启用
- ✅ 使用带类型的字段和约束定义数据模型、配置对象或 DTO。
- ✅ 编写字段级或模型级验证、自定义序列化,或计算字段。
- ✅ 使用
pydantic-settings从环境变量 /.env加载应用配置。 - ✅ 使用
TypeAdapter批量验证非模型形状(list[Model]、字典、基本类型)。 - ✅ 将 v1 时代的代码(
class Config、.dict()、@validator)现代化为 v2。
以下情况请勿启用:
- 任务涉及 FastAPI 请求/响应接线或
response_model——请使用fastapi技能。 - 主题是 Pydantic-AI Agent 或 Logfire 插桩——它们属于不同的产品,不在范围内。
工作流
步骤 1:使用类型提示定义模型
字段的写法是 name: Type。没有默认值时,字段为必填;设置默认值后,字段才是
可选的。特别注意,在 v2 中,Optional[int](即 int | None)只表示字段可为
空——它不会提供默认值;省略默认值时,该字段仍为必填。对可变默认值使用
default_factory(不要使用字面量 []/{},否则多个实例会共享同一个值)。
from pydantic import BaseModel, Field
class User(BaseModel):
id: int # required
name: str = "anon" # optional, has default
nickname: str | None = None # nullable AND optional (explicit default)
tags: list[str] = Field(default_factory=list) # safe mutable default
使用可信的关键字参数构造(User(id=1)),或者使用
类方法 User.model_validate(obj)(字典 / 对象)和
User.model_validate_json(data)(JSON 字符串/字节)验证不可信输入。
model_construct(**data) 会跳过验证——仅用于可信数据。
步骤 2:使用 Annotated[..., Field(...)] 约束字段
优先使用 Annotated 风格;它能保持类型准确,并便于组合。
Field 提供 gt/ge/lt/le/multiple_of(数值)、
min_length/max_length/pattern(字符串),以及 default、default_factory、
description、alias 和 exclude。
from typing import Annotated
from pydantic import BaseModel, Field
class Account(BaseModel):
balance: Annotated[int, Field(ge=0)]
handle: Annotated[str, Field(min_length=3, max_length=32, pattern=r"^[a-z0-9_]+$")]
score: Annotated[float, Field(default=0.0, le=100.0)]
步骤 3:使用 @field_validator / @model_validator 验证
字段验证器按字段运行;模型验证器则跨字段运行。关于
before/after/
plain/wrap 模式、ValidationInfo 和 @classmethod 规则,请参阅
references/validators.md。
from typing_extensions import Self
from pydantic import BaseModel, field_validator, model_validator
class Signup(BaseModel):
email: str
password: str
password_repeat: str
@field_validator("email", mode="after")
@classmethod
def lowercase(cls, v: str) -> str:
return v.lower()
@model_validator(mode="after")
def passwords_match(self) -> Self:
if self.password != self.password_repeat:
raise ValueError("passwords do not match")
return self
步骤 4:使用 model_dump* 序列化
model_dump() → 字典,model_dump_json() → JSON 字符串。使用
include / exclude / exclude_unset / exclude_defaults / exclude_none /
by_alias / mode 控制输出结构。使用 @field_serializer 自定义序列化,并通过
@computed_field 添加派生输出。请参阅 references/serialization.md。
user.model_dump() # dict, python types
user.model_dump(exclude={"password"}) # drop a field
user.model_dump(exclude_unset=True) # only explicitly-set fields (PATCH-style)
user.model_dump_json(by_alias=True, exclude_none=True, indent=2)
步骤 5:使用 model_config = ConfigDict(...) 配置
类属性 model_config(而非嵌套的 class Config)用于存放
ConfigDict。常用选项:strict、from_attributes(读取 ORM 对象)、
validate_assignment、use_enum_values、extra("ignore"/"allow"/
"forbid")、frozen、str_strip_whitespace。对于按别名验证的配置,当前 2.11+
更推荐使用 validate_by_name=True(并同时设置 validate_by_alias=True)——请参阅“易踩陷阱”。
from pydantic import BaseModel, ConfigDict
class Row(BaseModel):
model_config = ConfigDict(from_attributes=True, extra="forbid", frozen=True)
id: int
label: str
obj = Row.model_validate(orm_row) # from_attributes reads attributes off an ORM object
步骤 6:使用 pydantic-settings 加载配置
BaseSettings 位于独立的 pydantic-settings 包中(不属于核心
pydantic)。它会从环境变量和 .env 中读取字段。通过
SettingsConfigDict 配置。有关嵌套设置和来源优先级,请参阅 references/settings.md。
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="APP_", env_file=".env",
env_file_encoding="utf-8")
api_key: str # required → from APP_API_KEY
host: str = "127.0.0.1"
port: int = 8000 # coerced from the string env value
settings = Settings() # reads env / .env at construction
步骤 7:结构——枚举、Literal、联合类型、TypeAdapter
将一个模型用作另一个模型的字段类型,以嵌套模型。对于固定选项,使用 Enum(继承
str, Enum,以便生成适合 JSON 的值)或 Literal。
判别式联合类型会通过 Field(discriminator=...) 根据标签字段进行分派。
使用 TypeAdapter 验证非模型形状(列表、字典、基本类型)——对于批量或独立验证,
这是比 RootModel 更符合 v2 惯用方式的选择。
from typing import Annotated, Literal
from pydantic import BaseModel, Field, TypeAdapter
class Cat(BaseModel):
kind: Literal["cat"]
lives: int
class Dog(BaseModel):
kind: Literal["dog"]
good_boy: bool = True
class Owner(BaseModel):
pet: Annotated[Cat | Dog, Field(discriminator="kind")]
cats = TypeAdapter(list[Cat]).validate_python([{"kind": "cat", "lives": 9}])
步骤 8:处理 ValidationError
输入无效时,model_validate* 和模型构造会引发 ValidationError。
.errors() 返回一个字典列表(type、loc、msg、input、url);
.error_count() 返回数量;.json() 会将其序列化。
from pydantic import ValidationError
try:
User.model_validate({"id": "not-an-int"})
except ValidationError as e:
for err in e.errors():
print(".".join(map(str, err["loc"])), err["type"], err["msg"])
规则
硬性规则(绝不违反):
- 仅使用 v2 API。
model_config = ConfigDict(...)、model_dump()/model_dump_json()、model_validate()/model_validate_json()、@field_validator/@model_validator、model_json_schema()。 绝不使用class Config、.dict()/.json()、@validator/@root_validator、.parse_obj()/.parse_raw()或.schema()。 @field_validator下方必须紧接@classmethod。@model_validator(mode="after")对self操作,不需要@classmethod。Optional[x]不会提供默认值。 如果字段应为可选字段,请显式提供默认值(x | None = None)。- 可变默认值必须使用
default_factory, 绝不能直接使用[]/{}/dict()。 BaseSettings从pydantic_settings导入, 而不是pydantic。- 不要在
BaseModel中将TypeAdapter用作字段注解; 应单独使用它来验证非模型类型。
偏好(可覆盖):
- 优先使用
Annotated[T, Field(...)],而不是将Field(...)赋值为默认值。 - 将请求模型和响应模型分开,不要用同一个模型处理两者。
- 对高频路径复用同一个
TypeAdapter实例(构建一次,多次验证)。
注意事项
populate_by_name即将弃用。 在 Pydantic 2.11+ 中,优先使用ConfigDict(validate_by_name=True, validate_by_alias=True)。populate_by_name在 2.x 中仍可用(计划在 v3 中弃用)——旧版 2.x 可以继续使用,但当前版本应写 新的这两个配置项。class Config虽然仍会静默生效,但已弃用——它不会报错,因此 v1 代码“看起来没问题”, 实际上却不符合规范,并且无法使用 v2 选项。请改为model_config = ConfigDict(...)。- 在
@field_validator上忘记加@classmethod会在类定义时引发错误,或导致行为异常—— 装饰器顺序应为顶部的@field_validator(...),其下方紧接@classmethod。 - 将
model_config写成普通dict虽然可用,但会失去类型检查器的辅助提示——请使用ConfigDict(...)构造函数,以便 IDE 和ty/mypy 验证键名。 - 宽松强制转换可能带来意外。 默认情况下,
"123"会被强制转换为123。如果需要 精确匹配,请设置ConfigDict(strict=True)(或针对每个字段设置Field(strict=True))。 exclude_unset与exclude_defaults的区别。exclude_unset会排除调用方从未设置的字段 (非常适合 PATCH 请求体);exclude_defaults会排除值等于默认值的字段,即使该字段已设置。 两者不可互换。
反模式
- “这只是个小模型,我就用旧的
.dict()吧。” 不行——.dict()已被弃用,实际上不应再用; 请使用model_dump()。混用 v1/v2 风格是 Pydantic 代码出错的首要原因。 - 因为“记得”而使用
@root_validator——v2 中已不存在此装饰器;请使用@model_validator(mode="before"|"after")。 - 为了验证列表/字典而将基本类型包装进只有一个字段的
RootModel——请改用TypeAdapter(list[T])。 - 在这里重新实现 FastAPI 请求/响应处理——那属于
fastapi技能;本技能负责定义模型,由框架使用这些模型。
输出
正确、最新的 Pydantic v2 源码:使用 ConfigDict 的模型类、带类型和约束的字段、通过 @field_validator/@model_validator 进行验证、使用 model_dump* 序列化、pydantic-settings 配置类,以及 TypeAdapter/判别联合结构——不含任何 v1 时代的惯用写法。使用方是任何需要经过验证、可序列化数据模型的代码或工作流阶段;当这些模型用于 HTTP API 时,fastapi 技能负责框架接线。
相关内容
fastapi— 框架细节:请求/响应模型、response_model、通过依赖注入进行验证。此技能定义模型;fastapi使用这些模型。此处不要重复介绍框架机制。- 官方文档(来源):参见
references/sources.md。
渐进式披露
详细内容位于 references/ 中,按需加载:
references/validators.md— 编写验证器时加载:所有@field_validator/@model_validator模式、ValidationInfo、@classmethod规则,以及 before/after 顺序。references/serialization.md— 自定义输出时加载:所有model_dump*参数、@field_serializer、@model_serializer、@computed_field、别名类型(validation_alias/serialization_alias/AliasChoices)。references/settings.md— 深入了解pydantic-settings时加载:嵌套设置、env_nested_delimiter、来源优先级、secrets_dir。references/migration-v1-to-v2.md— 将 v1 代码现代化时加载:完整的重命名表和行为变更。references/sources.md— 研究来源。
正文预算
description≤ 1,024 个字符(符合要求)。- 正文约 290 行,远低于 5,000 个 token 的软性目标。
- 每个参考文件都远低于 10k 个 token 的警告阈值。