FastAPI之Pydantic 与 ORM 模型转换~`Menu(**menus.model_dump())`

发布时间:2026/10/25 1:47:06
FastAPI之Pydantic 与 ORM 模型转换~`Menu(**menus.model_dump())`

结论前置

new_menu = Menu(**menus.model_dump())这行代码的作用是:将一个 Pydantic 模型实例menus转换为字典,然后以关键字参数的形式传递给Menu类,创建一个新的Menu实例。

典型场景:在 FastAPI + SQLAlchemy 项目中,接收前端提交的 Pydantic 请求体,然后将其转换为 SQLAlchemy ORM 对象并存入数据库。

核心拆解:

  • menus.model_dump():Pydantic v2 方法,将模型转为字典
  • **:字典解包运算符,将字典的键值对展开为关键字参数
  • Menu(...):调用Menu类的构造函数,传入解包后的参数

第一部分:理解menus.model_dump()

1.1 什么是model_dump()

model_dump()是 Pydantic v2 中BaseModel的方法,用于将模型实例转换为Python 字典。

frompydanticimportBaseModelclassMenuCreate(BaseModel):name:strpath:strstatus:int=1menus=MenuCreate(name="首页",path="/home",status=1)data=menus.model_dump()print(data)# {'name': '首页', 'path': '/home', 'status': 1}

1.2 v1 与 v2 的区别

版本方法
Pydantic v1.dict()
Pydantic v2.model_dump()

v2 中.dict()仍可用但已弃用,推荐使用model_dump()。

1.3 常用参数

menus.model_dump(exclude_unset=True,# 只包含显式设置的字段exclude={"id"},# 排除某些字段include={"name"},# 只包含某些字段by_alias=True,# 使用字段别名作为键mode="json",# 转为 JSON 兼容类型(如 datetime → str))

示例:

classMenuCreate(BaseModel):name:strpath:strstatus:int=1id:int|None=Nonemenus=MenuCreate(name="首页",path="/home")# 默认:包含所有字段,包括默认值print(menus.model_dump())# {'name': '首页', 'path': '/home', 'status': 1, 'id': None}# 只包含显式设置的字段print(menus.model_dump(exclude_unset=True))# {'name': '首页', 'path': '/home'}# 排除 idprint(menus.model_dump(exclude={"id"}))# {'name': '首页', 'path': '/home', 'status': 1}# JSON 兼容模式print(menus.model_dump(mode="json"))# {'name': '首页', 'path': '/home', 'status': 1, 'id': None}

第二部分:理解**字典解包

2.1 基本概念

Python 中的**运算符用于将字典解包为关键字参数。

data={"name":"首页","path":"/home","status":1}# 不用 **defcreate_menu(name,path,status):print(name,path,status)create_menu(name=data["name"],path=data["path"],status=data["status"])# 用 ** 解包create_menu(**data)# 等价于 create_menu(name="首页", path="/home", status=1)

2.2 在类构造中的应用

classMenu:def__init__(self,name,path,status):self.name=name self.path=path self.status=status data={"name":"首页","path":"/home","status":1}menu=Menu(**data)print(menu.name)# 首页

关键要求:字典的键必须与函数/类的参数名完全匹配,否则会抛出TypeError。

data={"title":"首页","path":"/home"}# 键名不匹配Menu(**data)# TypeError: __init__() got an unexpected keyword argument 'title'

第三部分:理解Menu(**...)

假设Menu是 SQLAlchemy 的 ORM 模型类:

fromsqlalchemyimportColumn,Integer,String,DateTimefromsqlalchemy.ormimportdeclarative_basefromdatetimeimportdatetime Base=declarative_base()classMenu(Base):__tablename__="menus"id=Column(Integer,primary_key=True,autoincrement=True)name=Column(String(50),nullable=False)path=Column(String(200),nullable=False)status=Column(Integer,default=1)created_at=Column(DateTime,default=datetime.utcnow)updated_at=Column(DateTime,default=datetime.utcnow,onupdate=datetime.utcnow)def__repr__(self):returnf"<Menu(id={self.id}, name={self.name})>"

SQLAlchemy 的模型类允许通过关键字参数初始化:

menu=Menu(name="首页",path="/home",status=1)

因此,Menu(**menus.model_dump())就是将 Pydantic 模型的字典解包后传给 SQLAlchemy 模型构造函数。


第四部分:完整示例

4.1 定义模型

frompydanticimportBaseModel,ConfigDictfromsqlalchemyimportColumn,Integer,String,DateTimefromsqlalchemy.ormimportdeclarative_basefromdatetimeimportdatetime# SQLAlchemy ORM 模型Base=declarative_base()classMenu(Base):__tablename__="menus"id=Column(Integer,primary_key=True,autoincrement=True)name=Column(String(50),nullable=False)path=Column(String(200),nullable=False)status=Column(Integer,default=1)created_at=Column(DateTime,default=datetime.utcnow)updated_at=Column(DateTime,default=datetime.utcnow,onupdate=datetime.utcnow)# Pydantic 请求模型classMenuCreate(BaseModel):name:strpath:strstatus:int=1# Pydantic 响应模型classMenuResponse(BaseModel):model_config=ConfigDict(from_attributes=True)id:intname:strpath:strstatus:intcreated_at:datetime updated_at:datetime

4.2 在 FastAPI 中使用

fromfastapiimportFastAPI,Dependsfromsqlalchemy.ormimportSession app=FastAPI()defget_db():# 省略数据库会话创建...@app.post("/menus",response_model=MenuResponse)defcreate_menu(menus:MenuCreate,db:Session=Depends(get_db)):# 核心转换代码new_menu=Menu(**menus.model_dump())db.add(new_menu)db.commit()db.refresh(new_menu)returnnew_menu

流程:

  1. FastAPI 将请求体解析为MenuCreate实例menus
  2. menus.model_dump()转为字典{'name': '首页', 'path': '/home', 'status': 1}
  3. Menu(**...)创建 SQLAlchemy 对象
  4. 存入数据库并返回

4.3 手动调用示例

# 模拟前端提交的数据menus=MenuCreate(name="首页",path="/home",status=1)# 转换为 ORM 对象new_menu=Menu(**menus.model_dump())print(new_menu.name)# 首页print(new_menu.path)# /homeprint(new_menu.status)# 1print(new_menu.id)# None(尚未入库)

第五部分:注意事项与常见问题

5.1 字段名必须匹配

Pydantic 模型的字段名必须与 SQLAlchemy 模型的属性名一致,否则解包会失败。

解决方案:使用别名或手动映射。

classMenuCreate(BaseModel):model_config=ConfigDict(populate_by_name=True)menu_name:str=Field(alias="name")menu_path:str=Field(alias="path")# 使用别名解包data=menus.model_dump(by_alias=True)new_menu=Menu(**data)

5.2 排除不需要的字段

Pydantic 模型可能包含 ORM 模型没有的字段(如id、created_at),直接解包会报错。

# 排除 id 和时间戳data=menus.model_dump(exclude={"id","created_at","updated_at"})new_menu=Menu(**data)

5.3 只包含用户显式设置的字段

使用exclude_unset=True避免将默认值覆盖数据库默认值:

data=menus.model_dump(exclude_unset=True)new_menu=Menu(**data)

5.4 类型转换问题

model_dump()默认返回 Python 原生类型,但mode="json"会转为 JSON 兼容类型(如datetime→ 字符串)。如果 SQLAlchemy 需要datetime对象,不要用mode="json"。

# 正确:保留 datetime 对象data=menus.model_dump()# 错误:datetime 变成字符串,可能导致数据库错误data=menus.model_dump(mode="json")

5.5 嵌套模型

如果 Pydantic 模型包含嵌套模型,model_dump()会递归转为字典。但 SQLAlchemy 模型通常需要关联对象,不能直接接收字典。

classMenuCreate(BaseModel):name:strchildren:list["MenuCreate"]=[]# 嵌套字典不能直接用于 SQLAlchemy 关系# 需要手动处理

5.6 别名与populate_by_name

如果 Pydantic 模型使用了别名,model_dump()默认使用字段名。若 SQLAlchemy 模型期望别名字段,需by_alias=True。


第六部分:替代方案与最佳实践

6.1 使用model_validate反向转换

如果Menu也是一个 Pydantic 模型,可以直接:

new_menu=Menu.model_validate(menus)

但 SQLAlchemy 模型不是 Pydantic 模型,所以不适用。

6.2 使用from_orm(v1)或model_validate(v2)

反向:从 ORM 对象创建 Pydantic 模型:

# v2menu_response=MenuResponse.model_validate(menu_orm)# v1menu_response=MenuResponse.from_orm(menu_orm)

6.3 封装转换函数

defpydantic_to_sqlalchemy(pydantic_obj:BaseModel,sqlalchemy_class):data=pydantic_obj.model_dump(exclude_unset=True)returnsqlalchemy_class(**data)new_menu=pydantic_to_sqlalchemy(menus,Menu)

6.4 使用dict()兼容 v1

如果项目还在 Pydantic v1:

new_menu=Menu(**menus.dict())

6.5 处理额外字段

classMenuCreate(BaseModel):model_config=ConfigDict(extra="forbid")# 禁止额外字段name:strpath:str

第七部分:常见错误与调试

错误 1:TypeError: __init__() got an unexpected keyword argument

原因:Pydantic 模型有 ORM 模型没有的字段。

解决:使用exclude排除多余字段。

data=menus.model_dump(exclude={"id","created_at"})

错误 2:AttributeError: 'MenuCreate' object has no attribute 'model_dump'

原因:使用的是 Pydantic v1。

解决:改用.dict()或升级到 v2。

错误 3:字段值为None导致数据库非空约束失败

原因:Pydantic 模型默认值None被解包传入。

解决:使用exclude_unset=True或exclude_none=True。

data=menus.model_dump(exclude_none=True)

错误 4:datetime类型错误

原因:使用了mode="json"导致datetime变成字符串。

解决:去掉mode="json"。


总结

部分作用
menus.model_dump()Pydantic 模型 → Python 字典
**字典 → 关键字参数
Menu(...)调用 ORM 类构造函数,创建实例
整体Pydantic 请求模型 → SQLAlchemy ORM 对象

最佳实践:

  • 使用exclude_unset=True避免覆盖默认值
  • 使用exclude排除 ORM 不需要的字段
  • 确保字段名匹配,必要时使用别名
  • 不要使用mode="json"除非 ORM 需要 JSON 类型
  • 封装转换函数以提高复用性

这行代码虽然简短,但涵盖了 Pydantic 与 SQLAlchemy 集成的核心模式,是 FastAPI 项目中最常见的转换之一。