- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在我的设计框架业务中,字典大类、部门机构、系统菜单等这些表,都存在id、pid的字段,主要是作为自引用关系,实现树形列表数据的处理的,因为这样可以实现无限层级的树形列表。在实际使用Pydantic和SqlAlchemy来直接处理嵌套关系的时候,总是出现数据在Pydantic的对象转换验证上,爬坑一段时间才发现是模型定义使用上的问题,本篇随笔介绍使用Pydantic和SqlAlchemy实现树形列表数据(自引用表关系)的处理,以及递归方式处理数据差异.
默认的机构表的sqlalchemy的模型定义如下所示.
class Ou(Base): """机构(部门)信息-表模型""" __tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True, comment="主键", autoincrement=True) pid = Column(Integer, ForeignKey("t_acl_ou.id"), comment="父级机构ID")
****其他信息
# 定义 parent 关系 parent = relationship( "Ou", remote_side=[id], back_populates="children") # 定义 children 关系 children = relationship("Ou", back_populates="parent")
然后对应的DTO(Schema)数据类定义如下.
class OuDto(BaseModel): id: Optional[int] = None pid: Optional[int] = None
***其他信息 class OuNodeDto(OuDto): """部门机构节点对象""" children: Optional[List["OuNodeDto"]] = None # 这里使用 Optional class Config: orm_mode = True # 启用 orm_mod from_attributes = True extra = "allow"
然后我在机构的Crud类里面定义了一个get_children的函数,如下所示 。
async def get_children(self, db: AsyncSession, id: int) -> Ou: """ 获取子列表 :param db: :param id: :return: """ result = await db.execute( select(Ou).options(selectinload(Ou.children)).where(Ou.id == id) ) result = result.scalar_one_or_none() return result
这里通过 selectinload 的加载方式,可以再数据检索的时候,同时获得子列表的处理.
为了验证数据能够再CRUD中正常的检索出来,我对CRUD类的接口进行测试,并查询获得其中的children集合,代码如下所示 。
async def test_list_ou(): async with async_session() as db: ou = await ou_crud.get_children(db, "3") print(vars(ou)) for o in ou.children: print(vars(o)) await db.close()
其中机构id为3的,是广州分公司,它把该公司下属所有的机构都能正常读取出来,因此底层没有问题。 。
但是利用FastAPI的接口处理,通过pydantic的数据转换就是不能正常获得,如下是FastAPI的路由接口实现.
@router.get( "/get-children", response_model=AjaxResponse[OuNodeDto | None], summary="根据名称获取客户", dependencies=[DependsJwtAuth], ) async def get_children( id: Annotated[int | None, Query()] = None, db: AsyncSession = Depends(get_db), ): ou = await ou_crud.get_children(db, id) try: result = OuNodeDto.model_validate(ou) except Exception as e: print(e.json()) return AjaxResponse( success=False, result=None, errorInfo=ErrorInfo(message=str(e)) ) return AjaxResponse(result)
这里注意,我使用 OuNodeDto.model_validate(ou) 对嵌套列表对象进行转换的,出错就是在这里.
具体我们可以再Swagger界面中调试获得错误信息.
我反复核对模型Model和Schema的对象都是一一对应的,错误不是字段名称的问题,因此可能是数据处理上的问题,但是很难从其中获得更多有用的信息。在Chatgpt中获得的错误提示如下所示.
你遇到的这个问题可能是因为 Pydantic 的模型验证过程中遇到了与嵌套数据结构相关的不一致性或问题。以下是一些常见的原因及其解决方法:
1)嵌套结构不匹配 。
确保你的嵌套数据结构与 Pydantic 模型定义一致。如果你的模型预期的是一个嵌套的列表,但数据库查询返回的结果中缺少某些字段或类型不匹配,可能会导致验证失败.
解决方法:2)前向引用 (Forward References) 。
如果你的 Pydantic 模型有自引用或互相引用的情况,确保你正确使用了 update_forward_refs()。如果在嵌套引用中没有正确处理前向引用,可能会导致验证失败.
解决方法
确保你在 Pydantic 模型定义之后调用了 update_forward_refs(),pydantic新版使用model_rebuild,特别是当模型包含递归引用时.
3) 使用from_attributes
选项
如果你的数据来自 ORM 对象(或任何非字典的对象),而你使用的是 Pydantic v2,可以尝试使用 from_attributes 属性.
解决方法
Config
中启用 from_attributes
选项以确保 Pydantic 模型能够从属性中提取数据。4) 数据库返回的数据类型问题 。
确保从数据库返回的数据类型(特别是 children 字段)是你预期的类型。如果 children 返回的不是一个列表或是一个包含子对象的对象,则会导致验证失败.
解决方法
检查你的查询逻辑,确保 children 字段返回的是一个列表,并且列表中的每个项符合 OuNodeDto模型的要求.
在使用 SQLAlchemy 时,确保相关数据(如 children)已被正确加载。如果尝试访问未加载的关系,可能会抛出 StatementError.
result = await db.execute( select(Ou).options(selectinload(Ou.children)).where(Ou.id == id) )
。
最后发现这些我都已经做了,我的pydantic模型定义如下所示,还是会出错.
class OuNodeDto(OuDto): """部门机构节点对象""" children: Optional[List["OuNodeDto"]] = None # 这里使用 Optional class Config: orm_mode = True # 启用 orm_mod from_attributes = True extra = "allow" # 更新前向引用 OuNodeDto.model_rebuild(force=True)
如果记录返回的对象是正常的,但在使用 OuNodeDto.model_validate(ou) 转换时出现错误,可能的问题出在 Pydantic 模型的定义或对象结构与 Pydantic 模型预期的格式不完全匹配.
最后发现是 relationship 的 lazy 参数的加载策略的影响.
lazy
加载策略的概述在 SQLAlchemy 中,relationship 的 lazy 参数决定了如何和何时加载相关的对象。常见的 lazy 加载策略有:
lazy="immediate"
lazy="immediate"
会立即加载所有相关的对象,当你使用 Pydantic 的 model_validate
进行数据验证时,相关对象已经被加载并可供访问。lazy="select"
model_validate
验证时,可能子对象还没有被加载,导致验证失败。lazy="dynamic"
lazy="dynamic"
返回的是查询对象而不是实际的对象实例。因此,Pydantic 的 model_validate
无法直接处理这些查询对象,必须先执行查询来获取实际的对象。使用 lazy="immediate" 。
class Ou(Base): __tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True) pid = Column(Integer, ForeignKey("t_acl_ou.id")) name = Column(String) parent = relationship("Ou", remote_side=[id], back_populates="children", lazy="immediate") children = relationship("Ou", back_populates="parent", lazy="immediate")
在这种情况下,当你查询一个 Ou 对象时,children 已经被立即加载,可以直接用于 Pydantic 的 model_validate.
使用 lazy="select" 。
class Ou(Base): __tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True) pid = Column(Integer, ForeignKey("t_acl_ou.id")) name = Column(String) parent = relationship("Ou", remote_side=[id], back_populates="children", lazy="select") children = relationship("Ou", back_populates="parent", lazy="select")
在这种策略下,children 只有在访问时才会被加载,这可能导致在进行 model_validate 验证时 children 尚未加载完成.
使用 lazy="dynamic" 。
class Ou(Base): __tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True) pid = Column(Integer, ForeignKey("t_acl_ou.id")) name = Column(String) parent = relationship("Ou", remote_side=[id], back_populates="children", lazy="dynamic") children = relationship("Ou", back_populates="parent", lazy="dynamic")
lazy="dynamic" 返回的是一个查询对象,而不是实际的 children 对象。为了使用 Pydantic 进行验证,你必须先执行这个查询来获取实际的对象.
lazy
加载策略总结lazy="immediate"
: 立即加载相关对象,使其在 Pydantic 的 model_validate
中可用。lazy="select"
: 需要在访问时加载相关对象,可能在验证时尚未加载。lazy="dynamic"
: 返回查询对象,需要额外的查询步骤,model_validate
无法直接处理。在 SQLAlchemy 中,lazy 模式是模型定义的一部分,决定了如何加载相关的对象.
为了确保 model_validate 正常工作,通常建议使用 lazy="immediate" 以确保所有相关数据在进行验证时已经被完全加载.
最后修改为lazy="immediate" 后,工作正常,顺利进行列表的转换了.
。
为了确保 Pydantic 的 model_validate 能正确处理嵌套关系,推荐使用 lazy="immediate" 策略。这样可以保证在 Pydantic 进行数据验证时,所有相关数据已经完整加载.
除了这样对树形列表进行处理外,还有什么好办法处理?
处理树形列表(或树形结构)的常见方法包括以下几种,除了直接使用 SQLAlchemy 的 relationship 和 lazy="immediate" 加载策略外,还可以采用其他一些技术和工具来处理和操作树形数据结构.
下面介绍使用递归检索方式进行列表数据的处理。在 Python 中,递归处理树形列表的常见方法是使用递归函数遍历树结构.
假设你有一个部门(Ou)模型,每个部门可以有多个子部门(树形结构)。你想要使用递归方法遍历并填充树形结构中的每个节点.
通过 Pydantic 的 BaseModel 类进行遍历填充,可以结合递归和 Pydantic 的模型来处理树形结构的数据。以下是一个详细的示例,展示如何使用 Pydantic 的 BaseModel 类进行递归遍历和填充树形结构.
首先,定义一个 Pydantic 模型,用于表示树形结构中的节点。为了处理嵌套的子节点,可以在模型中使用递归类型注解.
from typing import List, Optional from pydantic import BaseModel class OuNodeDto(BaseModel): id: int name: str children: Optional[List['OuNodeDto']] = None # 递归类型注解 class Config: orm_mode = True
假设我们有一组嵌套的字典数据,表示树形结构:
ou_data = [ { "id": 1, "name": "Root Department", "children": [ { "id": 2, "name": "Child Department 1", "children": [ {"id": 4, "name": "Grandchild Department 1"}, {"id": 5, "name": "Grandchild Department 2"} ] }, {"id": 3, "name": "Child Department 2"} ] } ]
通过 Pydantic 的 BaseModel,你可以直接进行递归填充。假设 ou_data 是从数据库或者其他外部来源获取的字典列表,你可以通过递归构造 OuNodeDto 实例.
def build_tree(data: List[dict]) -> List[OuNodeDto]: """ 递归遍历并构建 Pydantic 模型树。 :param data: 包含树结构的字典列表 :return: 填充后的 Pydantic 模型列表 """ tree = [] for node_data in data: # 处理子节点递归填充 children = build_tree(node_data.get("children", [])) # 使用 Pydantic 的模型验证和创建对象 node = OuNodeDto( id=node_data["id"], name=node_data["name"], children=children if children else None ) tree.append(node) return tree
使用上面的 build_tree 函数,你可以递归地遍历数据,并使用 Pydantic 模型来构建整个树形结构.
ou_tree = build_tree(ou_data) # 输出树形结构的Pydantic模型 for node in ou_tree: print(node)
运行上面的代码后,输出将是一个 Pydantic 模型的树形结构列表,已填充好所有数据.
OuNodeDto(id=1, name='Root Department', children=[ OuNodeDto(id=2, name='Child Department 1', children=[ OuNodeDto(id=4, name='Grandchild Department 1', children=None), OuNodeDto(id=5, name='Grandchild Department 2', children=None) ]), OuNodeDto(id=3, name='Child Department 2', children=None) ])
通过这种方式,你可以使用 Pydantic 的 BaseModel 结合递归函数来处理树形结构的数据填充和验证。Pydantic 提供了强大的数据验证和解析能力,使得处理复杂的嵌套结构变得更加容易.
。
如果是从数据库中检索获得的SqlAlchemy的模型类,应该如何递归遍历?
要从数据库中检索并递归遍历 SQLAlchemy 的模型类,然后将其转换为 Pydantic 的模型类,可以按照以下步骤进行操作。假设你已经定义了 SQLAlchemy 模型类和对应的 Pydantic 模型类,接下来将展示如何递归遍历和填充这些数据.
首先,我们定义一个包含自引用关系的 SQLAlchemy 模型.
from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, declarative_base Base = declarative_base() class Ou(Base): __tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True, autoincrement=True) pid = Column(Integer, ForeignKey("t_acl_ou.id")) name = Column(String) # 自引用关系 parent = relationship("Ou", remote_side=[id], back_populates="children") children = relationship("Ou", back_populates="parent")
然后定义一个对应的 Pydantic 模型,支持嵌套的子节点.
from typing import List, Optional from pydantic import BaseModel class OuNodeDto(BaseModel): id: int name: str children: Optional[List['OuNodeDto']] = None # 递归类型注解 class Config: orm_mode = True # 允许从 ORM 对象转换
使用 SQLAlchemy 的查询从数据库中获取组织结构数据。为了处理嵌套的关系,你可以使用 selectinload 或其他类似的加载策略来预先加载子节点.
from sqlalchemy.future import select from sqlalchemy.orm import selectinload, sessionmaker from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine # 假设使用异步引擎 engine = create_async_engine('sqlite+aiosqlite:///./test.db') Session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async def get_ou_tree_by_id(ou_id: int): async with Session() as session: # 使用 selectinload 预先加载子节点 result = await session.execute( select(Ou).options(selectinload(Ou.children)).filter_by(id=ou_id) ) ou = result.scalars().first() return ou
编写一个递归函数,将 SQLAlchemy 的 ORM 对象转换为 Pydantic 模型对象.
def build_tree(node: Ou) -> OuNodeDto: """ 递归遍历 SQLAlchemy 模型,并转换为 Pydantic 模型。 :param node: SQLAlchemy 模型实例 :return: 填充后的 Pydantic 模型实例 """ # 使用 Pydantic 模型验证和创建对象 node_dto = OuNodeDto( id=node.id, name=node.name, # 递归填充子节点 children=[build_tree(child) for child in node.children] if node.children else None ) return node_dto
结合以上步骤,从数据库中获取数据并构建树形结构.
import asyncio async def main(): # 获取指定 ID 的组织结构树 ou = await get_ou_tree_by_id(1) # 递归构建树形结构的 Pydantic 模型 ou_tree = build_tree(ou) # 输出树形结构 print(ou_tree) # 运行异步任务 asyncio.run(main())
执行上述代码后,ou_tree 将是一个包含组织结构树的 Pydantic 模型对象,所有嵌套的子节点将被递归地转换和填充.
。
使用 Pydantic 的 model_validate 处理并且relationship加载策略lazy=immediate 和通过递归SqlAlchemy的模型类填充,哪个性能更好?
在比较使用 Pydantic 的 model_validate 与通过递归手动填充 SQLAlchemy 模型类的性能时,主要考量点是数据加载策略和模型转换方式的效率。以下是两者的详细分析:
model_validate
+ lazy="immediate"
工作原理
lazy="immediate"
会在 SQLAlchemy 实例化对象时立即加载关联的子对象。这意味着在访问主对象时,子对象已经完全加载,不需要再发起额外的数据库查询。model_validate
可以将 SQLAlchemy 的 ORM 对象直接转换为 Pydantic 模型。性能考虑
lazy="immediate"
会在访问对象时自动加载相关数据,适合需要立即访问完整数据结构的场景,但它可能会导致不必要的加载,尤其是在不需要所有子对象的情况下。model_validate
是一个单步操作,自动处理复杂嵌套对象的转换。虽然方便,但它的开销取决于对象的复杂性和嵌套深度。对于深度嵌套的大量对象,转换的时间可能较长。优点
缺点
工作原理
selectinload
, joinedload
等),从而在需要时再加载数据。性能考虑
lazy="immediate"
带来的过度加载。优点
缺点
model_validate
自动化,容易出错。数据规模和复杂性较小时: model_validate + lazy="immediate" 更方便,且性能影响不大,可以快速实现自动化的 Pydantic 模型转换.
数据规模大且嵌套深度较高时: 手动递归填充可能会更高效,尤其是在你需要精细控制数据加载策略时。这样可以避免过度加载,并且优化性能.
结论:如果你的应用场景对性能要求较高,并且数据结构较为复杂,手动递归可能更优。如果优先考虑代码的简洁性和开发效率,且数据规模不大,那么 model_validate 配合 lazy="immediate" 是更好的选择.
。
最后此篇关于使用Pydantic和SqlAlchemy实现树形列表数据(自引用表关系)的处理,以及递归方式处理数据差异的文章就讲到这里了,如果你想了解更多关于使用Pydantic和SqlAlchemy实现树形列表数据(自引用表关系)的处理,以及递归方式处理数据差异的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这个问题在这里已经有了答案: 关闭 10 年前。 Possible Duplicate: template pass by value or const reference or…? 以下对于将函数
我用相同的参数列表重载了一个运算符两次。但返回类型不同: T& operator()(par_list){blablabla} const T& operator()(par_list){bla
假设我有实现接口(interface) I 的 Activity A。我的 ViewModel 类 (VM) 持有对实现接口(interface) I 的对象的引用: class A extends
PHP 如何解释 &$this ?为什么允许? 我遇到了以下问题,这看起来像是 PHP 7.1 和 7.2 中的错误。它与 &$this 引用和跨命名空间调用以及 call_user_func_arr
谁能解释一下下面“&”的作用: class TEST { } $abc =& new TEST(); 我知道这是引用。但是有人可以说明我为什么以及什么时候需要这样的东西吗?或者给我指向一个对此有很好解
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。 C++ 引用 vs 指针 引用很容易与指针混淆,它们之间有三
目录 引言 背景 结论 引言 我选择写C++中的引用是因为我感觉大多数人误解了引用。而我之所以有这个感受是因为我主持过很多C++的面试,并且我很少
Perl 中的引用是指一个标量类型可以指向变量、数组、哈希表(也叫关联数组)甚至函数,可以应用在程序的任何地方 创建引用 定义变量的时候,在变量名前面加个 \,就得到了这个变量的一个引用 $sc
我编写了一个将从主脚本加载的 Perl 模块。该模块使用在主脚本中定义的子程序(我不是维护者)。 对于主脚本中的一个子例程,需要扩展,但我不想修补主脚本。相反,我想覆盖我的模块中的函数并保存对原始子例
我花了几个小时试图掌握 F# Quotations,但我遇到了一些障碍。我的要求是从可区分的联合类型中取出简单的函数(只是整数、+、-、/、*)并生成一个表达式树,最终将用于生成 C 代码。我知道使用
很多时候,问题(尤其是那些标记为 regex 的问题)询问验证密码的方法。似乎用户通常会寻求密码验证方法,包括确保密码包含特定字符、匹配特定模式和/或遵守最少字符数。这篇文章旨在帮助用户找到合适的密码
我想通过 MIN 函数内的地址(例如,C800)引用包含文本的最后一个单元格。你能帮忙吗? Sub Set_Formula() ' ----------------------------- Dim
使用常规的 for 循环,我可以做类似的事情: for (let i = 0; i < objects.length; i++) { delete objects[i]; } 常规的 for-
在 Cucumber 中,您定义了定义 BDD 语法的步骤;例如,您的测试可能有: When I navigate to step 3 然后你可以定义一个步骤: When /^I navigate t
这是什么UnaryExpression的目的,以及应该怎样使用? 最佳答案 它需要一个 Expression对象并用另一个 Expression 包裹它.例如,如果您有一个用于 lambda 的表达式
给出以下内容 $("#identifier div:first, #idetifier2").fadeOut(300,function() { // I need to reference jus
我不知道我要找的东西的正确术语,但我要找的是一个完整的引用,可以放在双引号之间的语句,比如 *, node()、@* 以及所有列出的 here加上任何其他存在的。 我链接到的答案提供了一些细节,但还
This question's answers are a community effort。编辑现有答案以改善此职位。它当前不接受新的答案或互动。 这是什么? 这是常见问答的集合。这也是一个社区Wi
Closed. This question does not meet Stack Overflow guidelines。它当前不接受答案。 想改善这个问题吗?更新问题,以便将其作为on-topic
考虑下一个代码: fn get_ref(slice: &'a Vec, f: fn(&'a Vec) -> R) -> R where R: 'a, { f(slice) } fn m
我是一名优秀的程序员,十分优秀!