第 13 章 常见数据场景实践
前面已经分别练过字段规则、嵌套模型、校验器、序列化和 Settings。写真实代码时,往往得把它们放在一起用。本章围绕几个常见场景展开:
- 分页参数
- 文章创建数据
- 文章详情数据
- 错误整理
例如分页输入可能是:
query_data = {
"page": "1",
"page_size": "20",
"keyword": "Pydantic",
}文章创建输入可能是:
article_data = {
"title": "Pydantic 常见数据场景",
"content": "这是一篇介绍 Pydantic 常见数据场景的文章内容",
"tags": ["Python", "Pydantic"],
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}错误输入可能是:
bad_article_data = {
"title": "广",
"content": "太短",
"tags": ["Python", "Pydantic", "实战", "校验", "错误", "超出"],
"author": {
"username": "xiao ming",
"email": "not-email",
},
}我们会把前面学过的能力组合起来,处理分页、文章创建与详情数据,并整理校验错误。
本章学习目标
把前面的知识放进同一个业务场景:分页参数单独建模,文章创建和详情也各用自己的模型。你会在其中组合 Field、嵌套模型、校验器和序列化器,再用 model_dump()、model_dump_json() 导出结果。出现校验错误时,还要能用 ValidationError.errors() 整理出更好读的错误列表。
场景与数据流
继续用“文章应用”做例子。程序要接收三类数据,先看分页参数:
page
page_size
keyword创建文章时,提交的数据大致是:
title
content
tags
summary
author程序补充处理后字段后,才组成完整详情:
创建数据本身
+ id
+ views
+ is_published
+ created_at输入有问题时,我们也会把异常整理成更易读的结果。整章流程如下:
每个示例都可以复制到 .code/main.py 运行;进入下一步时,用新的代码覆盖它。
分页查询参数
查询模型与分页上限
查询参数模型常用来处理:
- 分页
- 搜索
- 筛选
- 排序
例如:
class PageQuery(BaseModel):
page: int
page_size: int
keyword: str | None分页上限配置
上一章用 BaseSettings 读取应用配置。这里的 PageLimits 是普通模型,不读取环境变量,只用来演示分页上限怎样参与规则判断。
class PageLimits(BaseModel):
max_page_size: int = 100它会参与“分页大小不能超过上限”的判断。
将下面的完整代码保存为 .code/main.py:
from pydantic import BaseModel, Field, field_validator
class PageLimits(BaseModel):
max_page_size: int = 100
class PageQuery(BaseModel):
page: int = Field(default=1, ge=1)
page_size: int = Field(default=10, ge=1)
keyword: str | None = Field(default=None)
@field_validator("keyword")
@classmethod
def clean_keyword(cls, value: str | None) -> str | None:
if value is None:
return value
value = value.strip()
return value or None
query_data = {
"page": "1",
"page_size": "20",
"keyword": " Pydantic ",
}
page_limits = PageLimits(max_page_size=100)
query = PageQuery.model_validate(query_data)
if query.page_size > page_limits.max_page_size:
raise ValueError("分页大小超过应用配置限制")
print(query.page, type(query.page))
print(query.page_size, type(query.page_size))
print(query.keyword)
print(query.model_dump(exclude_none=True))运行:
uv run python main.pyfrom pydantic import BaseModel, Field, field_validator
class PageLimits(BaseModel):
max_page_size: int = 100
class PageQuery(BaseModel):
page: int = Field(default=1, ge=1)
page_size: int = Field(default=10, ge=1)
keyword: str | None = Field(default=None)
@field_validator("keyword")
@classmethod
def clean_keyword(cls, value: str | None) -> str | None:
if value is None:
return value
value = value.strip()
return value or None
query_data = {
"page": "1",
"page_size": "20",
"keyword": " Pydantic ",
}
page_limits = PageLimits(max_page_size=100)
query = PageQuery.model_validate(query_data)
if query.page_size > page_limits.max_page_size:
raise ValueError("分页大小超过应用配置限制")
print(query.page, type(query.page))
print(query.page_size, type(query.page_size))
print(query.keyword)
print(query.model_dump(exclude_none=True))运行结果与规则
运行后,字符串数字已转成整数,搜索词也去掉了前后空格:
1 <class 'int'>
20 <class 'int'>
Pydantic
{'page': 1, 'page_size': 20, 'keyword': 'Pydantic'}页码与分页大小的类型和下限
page: int = Field(default=1, ge=1)
page_size: int = Field(default=10, ge=1)字符串数字会转成整数,页码和分页大小至少为 1;最大分页大小再由 PageLimits 提供。
可选搜索词
keyword: str | None = Field(default=None)搜索词清理
value = value.strip()
return value or None这表示:
- 去掉前后空格
- 如果最后变成空字符串,就转成
None
分页大小上限
这里不再演示 BaseSettings,只说明配置对象可以参与规则判断。超过上限时,这段检查会抛 ValueError;本章的 format_validation_errors() 只接收 Pydantic 的 ValidationError。最后一章会把两类错误放进同一条处理流程。
查询参数导出
搜索词清理后若变成 None,导出时就可以把它省略。
分页参数处理流程
文章创建数据
创建模型与输入
创建模型描述用户提交的内容。创建文章时,通常包括:
- 标题
- 内容
- 标签
- 摘要
- 作者
而这些字段一般由程序补充:
idviewscreated_at
接下来定义作者模型和文章创建模型。
将下面的完整代码保存为 .code/main.py:
from pydantic import BaseModel, Field, field_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
article_data = {
"title": "Pydantic 常见数据场景",
"content": "这是一篇介绍 Pydantic 常见数据场景的文章内容",
"tags": ["Python", "Pydantic"],
"summary": "把常见数据场景串起来",
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
article_create = ArticleCreate.model_validate(article_data)
print(article_create.title)
print(article_create.author.username)
print(article_create.model_dump())运行:
uv run python main.py程序会打印文章标题、作者用户名,以及包含嵌套 author 的完整字典。这里先列出前两行:
Pydantic 常见数据场景
xiaoming作者与字段规则
作者模型 UserInfo
单独定义作者结构,让文章和作者各自的字段规则更清楚。
用户名校验
用户名会去掉前后空格,并拒绝中间带空格的输入。
邮箱基本结构
邮箱继续沿用前面章节的轻量规则:要有用户名和域名,中间只能有一个 @。
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")创建模型 ArticleCreate
它只描述创建时需要提交的字段。
嵌套作者信息
嵌套的作者数据也会按 UserInfo 的规则校验。
标题敏感词规则
标题的敏感词检查由 field_validator 处理。
创建数据结构
文章详情与导出
详情模型与导出
详情模型表示程序处理后的完整数据,通常在创建字段之外再补上:
idviewsis_publishedcreated_at
也就是说,创建模型描述用户交来的内容,详情模型描述程序处理后保存或返回的数据。
将下面的完整代码保存为 .code/main.py:
from datetime import datetime
from pydantic import BaseModel, Field, field_serializer, field_validator, model_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
class ArticleDetail(ArticleCreate):
id: int
views: int = Field(default=0, ge=0)
is_published: bool = Field(default=False)
created_at: datetime
@model_validator(mode="after")
def validate_publish_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
return self
@field_serializer("created_at")
def serialize_created_at(self, value: datetime) -> str:
return value.strftime("%Y-%m-%d %H:%M")
article_data = {
"title": "Pydantic 常见数据场景",
"content": "这是一篇介绍 Pydantic 常见数据场景的文章内容",
"tags": ["Python", "Pydantic"],
"summary": "把常见数据场景串起来",
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
article_create = ArticleCreate.model_validate(article_data)
detail_data = {
**article_create.model_dump(),
"id": 1,
"views": 0,
"is_published": True,
"created_at": datetime.now(),
}
article_detail = ArticleDetail.model_validate(detail_data)
print(article_detail.model_dump())
print(article_detail.model_dump_json(indent=2))运行:
uv run python main.pyfrom datetime import datetime
from pydantic import BaseModel, Field, field_serializer, field_validator, model_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
class ArticleDetail(ArticleCreate):
id: int
views: int = Field(default=0, ge=0)
is_published: bool = Field(default=False)
created_at: datetime
@model_validator(mode="after")
def validate_publish_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
return self
@field_serializer("created_at")
def serialize_created_at(self, value: datetime) -> str:
return value.strftime("%Y-%m-%d %H:%M")
article_data = {
"title": "Pydantic 常见数据场景",
"content": "这是一篇介绍 Pydantic 常见数据场景的文章内容",
"tags": ["Python", "Pydantic"],
"summary": "把常见数据场景串起来",
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
article_create = ArticleCreate.model_validate(article_data)
detail_data = {
**article_create.model_dump(),
"id": 1,
"views": 0,
"is_published": True,
"created_at": datetime.now(),
}
article_detail = ArticleDetail.model_validate(detail_data)
print(article_detail.model_dump())
print(article_detail.model_dump_json(indent=2))字典和 JSON 中的 created_at 都会按 %Y-%m-%d %H:%M 格式输出;具体时间随运行时刻变化。模型内部的 article_detail.created_at 仍是 datetime 对象。
详情字段与发布规则
继承创建字段
ArticleDetail 继承 ArticleCreate,复用这些创建字段:
titlecontenttagssummaryauthor
补充详情字段
详情模型再增加:
idviewsis_publishedcreated_at
这些通常不由用户在创建时直接提供。
发布规则
它同时检查 is_published 和 summary,属于模型级联合规则。
创建时间序列化
created_at 在模型内部是 datetime,导出时才格式化成字符串。
详情数据来源
从创建数据到详情数据的变化,也能看出两类模型的职责不同。
创建模型到详情模型流程
校验错误整理
字段错误示例
直接打印异常对象:
print(e)可以看到错误概况。若要按字段展示,就读取结构化错误列表:
e.errors()将下面的完整代码保存为 .code/main.py:
from pydantic import BaseModel, Field, ValidationError, field_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
def format_validation_errors(error: ValidationError) -> list[dict]:
result = []
for item in error.errors():
result.append(
{
"field": ".".join(str(loc) for loc in item["loc"]) or "模型",
"message": item["msg"],
"input": item.get("input"),
}
)
return result
bad_article_data = {
"title": "广",
"content": "太短",
"tags": ["Python", "Pydantic", "实战", "校验", "错误", "超出"],
"author": {
"username": "xiao ming",
"email": "not-email",
},
}
try:
article_create = ArticleCreate.model_validate(bad_article_data)
print(article_create)
except ValidationError as error:
errors = format_validation_errors(error)
for item in errors:
print(item)运行:
uv run python main.pyfrom pydantic import BaseModel, Field, ValidationError, field_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
def format_validation_errors(error: ValidationError) -> list[dict]:
result = []
for item in error.errors():
result.append(
{
"field": ".".join(str(loc) for loc in item["loc"]) or "模型",
"message": item["msg"],
"input": item.get("input"),
}
)
return result
bad_article_data = {
"title": "广",
"content": "太短",
"tags": ["Python", "Pydantic", "实战", "校验", "错误", "超出"],
"author": {
"username": "xiao ming",
"email": "not-email",
},
}
try:
article_create = ArticleCreate.model_validate(bad_article_data)
print(article_create)
except ValidationError as error:
errors = format_validation_errors(error)
for item in errors:
print(item)例如,作者邮箱字段的错误会整理成:
{'field': 'author.email', 'message': 'Value error, 邮箱必须包含用户名和域名', 'input': 'not-email'}错误位置与输出边界
结构化错误列表
errors() 返回的结构比直接打印异常更方便继续处理。
字段路径
例如:
("author", "email")可以变成:
author.email如果错误来自 model_validator,loc 可能是空元组。函数会把这种位置标成“模型”,完整案例里能看到这个分支。
错误说明
item["msg"]原始输入
本地排错时可以用原始输入定位问题。如果要把错误显示在界面或返回给调用方,只保留需要的字段,避免直接暴露输入内容:
public_errors = [
{"field": item["field"], "message": item["message"]}
for item in errors
]统一格式
可以按场景挑选要输出的字段,但尽量保持错误结构稳定。
错误整理流程
文章数据处理综合案例
最后用一个例子把本章几个场景串起来:
数据场景组合与整体处理流程
将下面的完整代码保存为 .code/main.py:
from datetime import datetime
from pydantic import (
BaseModel,
Field,
ValidationError,
field_serializer,
field_validator,
model_validator,
)
class PageLimits(BaseModel):
max_page_size: int = 100
class PageQuery(BaseModel):
page: int = Field(default=1, ge=1)
page_size: int = Field(default=10, ge=1)
keyword: str | None = Field(default=None)
@field_validator("keyword")
@classmethod
def clean_keyword(cls, value: str | None) -> str | None:
if value is None:
return value
value = value.strip()
return value or None
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
class ArticleDetail(ArticleCreate):
id: int
views: int = Field(default=0, ge=0)
is_published: bool = Field(default=False)
created_at: datetime
@model_validator(mode="after")
def validate_publish_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
return self
@field_serializer("created_at")
def serialize_created_at(self, value: datetime) -> str:
return value.strftime("%Y-%m-%d %H:%M")
def format_validation_errors(error: ValidationError) -> list[dict]:
result = []
for item in error.errors():
result.append(
{
"field": ".".join(str(loc) for loc in item["loc"]) or "模型",
"message": item["msg"],
"input": item.get("input"),
}
)
return result
page_limits = PageLimits(max_page_size=100)
query_data = {
"page": "1",
"page_size": "20",
"keyword": " Pydantic ",
}
article_data = {
"title": "Pydantic 常见数据场景",
"content": "这是一篇介绍 Pydantic 常见数据场景的文章内容",
"tags": ["Python", "Pydantic"],
"summary": "把常见数据场景串起来",
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
bad_article_data = {
"title": "广",
"content": "太短",
"tags": ["Python", "Pydantic", "实战", "校验", "错误", "超出"],
"author": {
"username": "xiao ming",
"email": "not-email",
},
}
query = PageQuery.model_validate(query_data)
if query.page_size > page_limits.max_page_size:
raise ValueError("分页大小超过应用配置限制")
print(query.model_dump(exclude_none=True))
article_create = ArticleCreate.model_validate(article_data)
print(article_create.model_dump())
detail_data = {
**article_create.model_dump(),
"id": 1,
"views": 0,
"is_published": True,
"created_at": datetime.now(),
}
article_detail = ArticleDetail.model_validate(detail_data)
print(article_detail.model_dump())
print(article_detail.model_dump_json(indent=2))
try:
ArticleCreate.model_validate(bad_article_data)
except ValidationError as error:
print(format_validation_errors(error))
bad_detail_data = {**detail_data, "summary": None}
try:
ArticleDetail.model_validate(bad_detail_data)
except ValidationError as error:
for item in format_validation_errors(error):
print({"field": item["field"], "message": item["message"]})运行:
uv run python main.pyfrom datetime import datetime
from pydantic import (
BaseModel,
Field,
ValidationError,
field_serializer,
field_validator,
model_validator,
)
class PageLimits(BaseModel):
max_page_size: int = 100
class PageQuery(BaseModel):
page: int = Field(default=1, ge=1)
page_size: int = Field(default=10, ge=1)
keyword: str | None = Field(default=None)
@field_validator("keyword")
@classmethod
def clean_keyword(cls, value: str | None) -> str | None:
if value is None:
return value
value = value.strip()
return value or None
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
name, separator, domain = value.partition("@")
if not name or not separator or not domain or "@" in domain:
raise ValueError("邮箱必须包含用户名和域名")
return value
class ArticleCreate(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
tags: list[str] = Field(default_factory=list, max_length=5)
summary: str | None = Field(default=None, max_length=100)
author: UserInfo
@field_validator("title")
@classmethod
def validate_title(cls, value: str) -> str:
blocked_words = ["广告", "推广"]
for word in blocked_words:
if word in value:
raise ValueError("文章标题不能包含敏感词")
return value
class ArticleDetail(ArticleCreate):
id: int
views: int = Field(default=0, ge=0)
is_published: bool = Field(default=False)
created_at: datetime
@model_validator(mode="after")
def validate_publish_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
return self
@field_serializer("created_at")
def serialize_created_at(self, value: datetime) -> str:
return value.strftime("%Y-%m-%d %H:%M")
def format_validation_errors(error: ValidationError) -> list[dict]:
result = []
for item in error.errors():
result.append(
{
"field": ".".join(str(loc) for loc in item["loc"]) or "模型",
"message": item["msg"],
"input": item.get("input"),
}
)
return result
page_limits = PageLimits(max_page_size=100)
query_data = {
"page": "1",
"page_size": "20",
"keyword": " Pydantic ",
}
article_data = {
"title": "Pydantic 常见数据场景",
"content": "这是一篇介绍 Pydantic 常见数据场景的文章内容",
"tags": ["Python", "Pydantic"],
"summary": "把常见数据场景串起来",
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
bad_article_data = {
"title": "广",
"content": "太短",
"tags": ["Python", "Pydantic", "实战", "校验", "错误", "超出"],
"author": {
"username": "xiao ming",
"email": "not-email",
},
}
query = PageQuery.model_validate(query_data)
if query.page_size > page_limits.max_page_size:
raise ValueError("分页大小超过应用配置限制")
print(query.model_dump(exclude_none=True))
article_create = ArticleCreate.model_validate(article_data)
print(article_create.model_dump())
detail_data = {
**article_create.model_dump(),
"id": 1,
"views": 0,
"is_published": True,
"created_at": datetime.now(),
}
article_detail = ArticleDetail.model_validate(detail_data)
print(article_detail.model_dump())
print(article_detail.model_dump_json(indent=2))
try:
ArticleCreate.model_validate(bad_article_data)
except ValidationError as error:
print(format_validation_errors(error))
bad_detail_data = {**detail_data, "summary": None}
try:
ArticleDetail.model_validate(bad_detail_data)
except ValidationError as error:
for item in format_validation_errors(error):
print({"field": item["field"], "message": item["message"]})运行后,分页结果中的 page 和 page_size 是整数,详情导出的 created_at 是字符串。错误输出则分别展示字段路径和模型级位置:
{'field': 'author.email', 'message': 'Value error, 邮箱必须包含用户名和域名', 'input': 'not-email'}
{'field': '模型', 'message': 'Value error, 文章发布时必须填写摘要'}第一行来自创建数据的错误列表,第二行来自 bad_detail_data。模型级错误只输出字段名和说明,没有把整份文章输入带到界面上。
完整案例执行流程
本章小结
分页查询、文章创建、文章详情各有自己的数据边界,拆成不同模型更容易处理。固定的子结构交给嵌套模型,程序处理后新增的字段放进详情模型。出错时,ValidationError.errors() 提供结构化信息,可以整理成后续展示或处理的错误列表。