在探索使用 FastAPI, SQLAlchemy, Pydantic,Redis, JWT 构建的项目的时候,其中数据库访问采用SQLAlchemy,并采用异步方式。数据库操作和控制器操作,采用基类继承的方式减少重复代码,提高代码复用性。在这个过程中设计接口和测试的时候,对一些问题进行跟踪解决,并记录供参考。

1、SQLAlchemy事务处理

在异步环境中,批量更新操作需要使用异步方法来执行查询和提交事务。

async def update_range(self, obj_in_list: List[DtoType], db: AsyncSession) -> bool:
"""批量更新对象"""
try:
async with db.begin(): # 使用事务块确保批量操作的一致性
for obj_in in obj_in_list:
# 查询对象
query = select(self.model).filter(self.model.id == obj_in.id)
result = await db.execute(query)
db_obj = result.scalars().first() if db_obj:
# 获取更新数据
update_data = obj_in.model_dump(skip_defaults=True) # 更新对象字段
for field, value in update_data.items():
setattr(db_obj, field, value) return True
except SQLAlchemyError as e:
print(e)
# 异常处理时,事务会自动回滚
return False

在这个改进后的代码中:

  1. 事务块:使用 async with db.begin() 创建事务块,以确保批量操作的一致性。事务块会在操作完成后自动提交,并在出现异常时回滚。
  2. 查询对象:使用 select(self.model).filter(self.model.id == obj_in.id) 进行异步查询,并使用 await db.execute(query) 执行查询。
  3. 更新对象字段:用 setattr 更新对象的字段。
  4. 异常处理:捕获 SQLAlchemyError 异常,并在异常发生时回滚事务。事务块会自动处理回滚,因此不需要手动回滚。

这种方式确保了在异步环境中批量更新操作的正确性和一致性。

在使用 async with db.begin() 进行事务管理时,事务会自动提交。如果在事务块内执行的所有操作都成功,事务会在退出时自动提交;如果出现异常,事务会自动回滚。

因此,手动调用 await db.commit() 是不必要的,因为事务块会处理这些操作。如果你不使用事务块,并希望手动控制事务的提交,可以如下修改:

async def update_range(self, obj_in_list: List[DtoType], db: AsyncSession) -> bool:
"""批量更新对象"""
try:
for obj_in in obj_in_list:
query = select(self.model).filter(self.model.id == obj_in.id)
result = await db.execute(query)
db_obj = result.scalars().first() if db_obj:
update_data = obj_in.model_dump(skip_defaults=True) for field, value in update_data.items():
setattr(db_obj, field, value) await db.commit() # 手动提交事务
return True
except SQLAlchemyError as e:
print(e)
await db.rollback() # 确保在出错时回滚事务
return False

在这个手动提交事务的例子中:

  • 在更新对象的操作完成后,使用 await db.commit() 来提交事务。
  • 如果发生异常,使用 await db.rollback() 来回滚事务。

根据需求选择合适的方法进行事务管理。事务块方式通常是更安全和简洁的选择。

在异步环境中,create_update 方法需要对数据库进行异步查询、更新或创建操作。

async def create_update(
self, obj_in: DtoType, id: PrimaryKeyType, db: AsyncSession
) -> bool:
"""创建或更新对象"""
try:
# 查询对象
query = select(self.model).filter(self.model.id == id)
result = await db.execute(query)
db_obj = result.scalars().first() if db_obj:
# 更新对象
return await self.update(obj_in, db)
else:
# 创建对象
return await self.create(obj_in, db)
except SQLAlchemyError as e:
print(e)
# 确保在出错时回滚事务
await db.rollback()
return False

在这个代码中:

  1. 异步查询:使用 select(self.model).filter(self.model.id == id) 来构建查询,并用 await db.execute(query) 执行查询。
  2. 获取对象:使用 result.scalars().first() 来获取查询结果中的第一个对象。
  3. 调用更新或创建方法:根据查询结果的有无,分别调用 self.updateself.create 方法。确保这两个方法都是异步的,并在调用时使用 await
  4. 异常处理:捕获 SQLAlchemyError 异常,并在发生异常时使用 await db.rollback() 来回滚事务。

在异步环境中,批量插入对象通常需要使用异步方法来执行数据库操作。由于 bulk_insert_mappings 在 SQLAlchemy 的异步版本中可能不直接支持,你可以使用 add_all 方法来批量添加对象。

async def save_import(self, data: List[DtoType], db: AsyncSession) -> bool:
"""批量导入对象"""
try:
# 将 DTO 转换为模型实例
db_objs = [self.model(**obj_in.model_dump()) for obj_in in data] # 批量添加对象
db.add_all(db_objs) # 提交事务
await db.commit() return True
except SQLAlchemyError as e:
print(e)
await db.rollback() # 确保在出错时回滚事务
return False

代码说明:

  1. 转换 DTO 为模型实例:使用 [self.model(**obj_in.model_dump()) for obj_in in data]data 列表中的 DTO 转换为模型实例列表。
  2. 批量添加对象:使用 db.add_all(db_objs) 批量添加对象到数据库会话。
  3. 提交事务:使用 await db.commit() 异步提交事务。
  4. 异常处理:捕获 SQLAlchemyError 异常,使用 await db.rollback() 回滚事务以确保在出错时数据库状态的一致性。

这种方式确保了在异步环境中正确地进行批量导入操作,并处理可能出现的异常。

2、在 SQLAlchemy 中select(...).where(...)select(...).filter(...)的差异

在 SQLAlchemy 中,select(...).where(...)select(...).filter(...) 都用于构造查询条件,但它们有一些细微的差别和适用场景。

1. where(...)

  • 定义where 是 SQLAlchemy 中 select 对象的方法,用于添加查询的条件。
  • 用法query = select(self.model).where(self.model.id == id)
  • 描述where 方法用于指定 SQL WHERE 子句的条件。在大多数情况下,它的行为和 filter 是等效的。

2. filter(...)

  • 定义filter 是 SQLAlchemy 中 Query 对象的方法,用于添加查询的条件。
  • 用法query = select(self.model).filter(self.model.id == id)
  • 描述filter 方法也用于指定 SQL WHERE 子句的条件。它通常用于更复杂的查询构建中,尤其是在 ORM 查询中。

主要差异

  • 上下文whereselect 对象的一部分,通常用于构建 SQL 查询(SQLAlchemy Core)。而 filterQuery 对象的一部分,通常用于 ORM 查询(SQLAlchemy ORM)。然而,在 SQLAlchemy 2.0+ 中,selectfilter 的使用变得更加一致。

  • 语义:在使用 SQLAlchemy Core 时,where 更加明确地表示你正在添加 SQL 语句中的 WHERE 子句。在 ORM 查询中,filter 也做了类似的事情,但它提供了更多 ORM 相关的功能。

使用 where 的示例(SQLAlchemy Core):

from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession async def get(self, id: int, db: AsyncSession) -> Optional[ModelType]:
query = select(self.model).where(self.model.id == id)
result = await db.execute(query)
return result.scalars().first()

使用 filter 的示例(SQLAlchemy ORM):

from sqlalchemy.orm import sessionmaker

async def get(self, id: int, db: AsyncSession) -> Optional[ModelType]:
query = select(self.model).filter(self.model.id == id)
result = await db.execute(query)
return result.scalars().first()

总结

  • 在 SQLAlchemy Core 中where 是构建查询条件的标准方法。
  • 在 SQLAlchemy ORM 中filter 用于构建查询条件,但在 Core 中,filter 的使用相对较少。

在 SQLAlchemy 2.0 及更高版本中,selectwherefilter 的用法变得越来越一致,你可以根据自己的习惯和需求选择其中一种。在实际开发中,选择哪一种方法通常取决于你的代码上下文和个人偏好。

3、model_dump(exclude_unset=True) 和model_dump(skip_defaults=True)有什么差异

model_dump(exclude_unset=True)model_dump(skip_defaults=True) 是用于处理模型实例的序列化方法,它们的用途和行为略有不同。这两个方法通常用于将模型实例转换为字典,以便进行进一步的处理或传输。

model_dump(exclude_unset=True)

exclude_unset=True 是一个选项,通常用于序列化方法中,表示在转换模型实例为字典时,排除那些未设置的字段。

  • 功能:排除所有未显式设置(即使用默认值)的字段。
  • 使用场景:适用于需要忽略那些未被用户设置的字段,以避免在输出中包含默认值。
# 假设模型有字段 'name' 和 'age',且 'age' 使用了默认值
model_instance = MyModel(name='Alice', age=25)
# 如果 age 的默认值是 0, exclude_unset=True 将只包含 'name'
serialized_data = model_instance.model_dump(exclude_unset=True)

model_dump(skip_defaults=True)

skip_defaults=True 是另一个选项,表示在转换模型实例为字典时,排除那些使用了默认值的字段。

  • 功能:排除所有字段的值等于其默认值的字段。
  • 使用场景:适用于需要排除那些显式设置为默认值的字段,以减少输出的冗余信息。
# 假设模型有字段 'name' 和 'age',且 'age' 使用了默认值
model_instance = MyModel(name='Alice', age=25)
# 如果 age 的默认值是 0, skip_defaults=True 将只包含 'name'
serialized_data = model_instance.model_dump(skip_defaults=True)

主要区别

  • 排除条件

    • exclude_unset=True 排除那些在模型实例中未显式设置的字段(即字段值为默认值或未赋值)。
    • skip_defaults=True 排除那些字段值等于其默认值的字段。
  • 适用场景

    • 使用 exclude_unset=True 时,目的是排除那些在实例化过程中未被显式赋值的字段,这通常用于避免包含那些尚未配置的字段。
    • 使用 skip_defaults=True 时,目的是去掉那些显式设置为默认值的字段,以避免输出不必要的信息。

4、使用**kwargs 参数,在接口中实现数据软删除的处理

例如我们在删除接口中,如果传递了 kwargs 参数,则进行软删除(更新记录),否则进行硬删除(删除记录)。

    async def delete_byid(self, id: PrimaryKeyType, db: AsyncSession, **kwargs) -> bool:
"""根据主键删除一个对象 :param kwargs: for soft deletion only
"""
if not kwargs:
result = await db.execute(sa_delete(self.model).where(self.model.id == id))
else:
result = await db.execute(
sa_update(self.model).where(self.model.id == id).values(**kwargs)
) await db.commit()
return result.rowcount > 0

实例代码如下所示。

# 示例模型
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Boolean Base = declarative_base() class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
is_deleted = Column(Boolean, default=False) # 示例使用
async def main():
async with AsyncSession(engine) as session:
controller = BaseController(Customer) # 硬删除
result = await controller.delete_byid(1, session)
print(f"Hard delete successful: {result}") # 软删除
result = await controller.delete_byid(2, session, is_deleted=True)
print(f"Soft delete successful: {result}") # 确保运行主程序
import asyncio
if __name__ == "__main__":
asyncio.run(main())

注意事项

  1. 模型定义:确保你的模型中包含 is_deleted 字段,并且字段名正确。

  2. 传递参数:在调用 delete_byid 方法时,正确传递 kwargs 参数。例如,如果你要进行软删除,可以传递 is_deleted=True

  3. 调试输出:你可以添加一些调试输出(如 print(kwargs)),以确保正确传递了参数。

# 示例硬删除调用
await controller.delete_byid(1, session) # 示例软删除调用
await controller.delete_byid(2, session, is_deleted=True)

如果我们的is_deleted 字段是Int类型的,如下所示,那么处理有所不同

class Customer(Base):
__tablename__ = "t_customer" id = Column(String, primary_key=True, comment="主键")
name = Column(String, comment="姓名")
age = Column(Integer, comment="年龄")
creator = Column(String, comment="创建人")
createtime = Column(DateTime, comment="创建时间")
is_deleted = Column(Integer, comment="是否删除")

操作代码

        # 硬删除
result = await controller.delete_byid("1", session)
print(f"Hard delete successful: {result}") # 软删除
result = await controller.delete_byid("2", session, is_deleted=1)
print(f"Soft delete successful: {result}")

注意事项

  1. 模型定义:你的 Customer 模型定义看起来是正确的,确保所有字段和注释都符合你的要求。

  2. 硬删除和软删除

    • 硬删除:直接从数据库中删除记录。
    • 软删除:通过更新 is_deleted 字段来标记记录为已删除,而不是实际删除记录。
  3. 正确传递参数

    • 硬删除时,不需要传递额外参数。
    • 软删除时,传递 is_deleted=1 作为参数。

通过确保正确传递参数并且模型包含正确的字段,你应该能够正确执行软删除和硬删除操作。

5、Python处理接口的时候,Iterable 和List有什么差异

在 Python 中,IterableList 是两个不同的概念,它们有各自的特点和用途:

Iterable

Iterable 是一个更广泛的概念,指的是任何可以返回一个迭代器的对象。迭代器是一个实现了 __iter__() 方法的对象,能够逐个返回元素。几乎所有的容器类型(如列表、元组、字典、集合等)都是可迭代的。要检查一个对象是否是可迭代的,可以使用 collections.abc.Iterable 来进行检查。

特点

  • 通用性Iterable 是一个通用的接口,表示对象可以被迭代。
  • 惰性:一些 Iterable 可能是惰性计算的(如生成器),即它们不会立即计算所有元素,而是按需生成元素。
  • 示例:列表(List)、元组(Tuple)、字典(Dict)、集合(Set)、生成器(Generator)等都是可迭代对象。
from collections.abc import Iterable

print(isinstance([1, 2, 3], Iterable))  # True
print(isinstance((1, 2, 3), Iterable)) # True
print(isinstance({1, 2, 3}, Iterable)) # True
print(isinstance({'a': 1}, Iterable)) # True
print(isinstance((x for x in range(3)), Iterable)) # True

List

List 是 Python 中的一种具体的容器类型,表示一个有序的元素集合,可以包含重复的元素。它是最常用的可变序列类型之一,支持索引访问、切片操作以及其他多种方法来操作列表中的元素。

特点

  • 具体实现List 是一个具体的类型,表示一个动态数组,可以存储多个对象。
  • 有序:列表保持元素的插入顺序。
  • 可变:可以对列表中的元素进行修改(如添加、删除、更新)。
  • 示例[1, 2, 3] 是一个列表。
my_list = [1, 2, 3]
print(my_list) # [1, 2, 3]
my_list.append(4) # [1, 2, 3, 4]
my_list[0] = 10 # [10, 2, 3, 4]

总结一下:

  • Iterable:一个广泛的概念,表示可以被迭代的对象,不一定是具体的数据结构。例如,生成器是可迭代的但不是列表。
  • List:一个具体的容器类型,是一种有序的可变集合。列表是 Iterable 的一种实现,但并不是所有的 Iterable 都是列表。

Iterable 是一个抽象概念,而 List 是一个具体的实现。你可以在 List 之上使用许多操作和方法来处理数据,而 Iterable 主要关注的是是否可以进行迭代。

因此接收结合的处理,我们可以使用Iterable接口更加通用一些。

    async def create_range(
self, obj_in_list: Iterable[DtoType], db: AsyncSession
) -> bool:
"""批量创建对象"""
try:
# 将 DTO 转换为模型实例
db_objs = [self.model(**obj_in.model_dump()) for obj_in in obj_in_list] # 批量添加到数据库
db.add_all(db_objs)
await db.commit()
return True
except SQLAlchemyError as e:
print(e)
await db.rollback() # 确保在出错时回滚事务
return False

以上就是在Python中使用sqlalchemy来操作数据库的时候,对一些小问题的总结,供大家参考。

在Python中使用sqlalchemy来操作数据库的几个小总结的更多相关文章

  1. 第二百八十九节,MySQL数据库-ORM之sqlalchemy模块操作数据库

    MySQL数据库-ORM之sqlalchemy模块操作数据库 sqlalchemy第三方模块 sqlalchemysqlalchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API ...

  2. 【转】python 历险记(四)— python 中常用的 json 操作

    [转]python 历险记(四)— python 中常用的 json 操作 目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编 ...

  3. 在Python中使用lambda高效操作列表的教程

    在Python中使用lambda高效操作列表的教程 这篇文章主要介绍了在Python中使用lambda高效操作列表的教程,结合了包括map.filter.reduce.sorted等函数,需要的朋友可 ...

  4. 【简说Python WEB】视图函数操作数据库

    目录 [简说Python WEB]视图函数操作数据库 系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:virutalenv Python的版本:Python 3.6.9 ...

  5. 48.Python中ORM模型实现mysql数据库基本的增删改查操作

    首先需要配置settings.py文件中的DATABASES与数据库的连接信息, DATABASES = { 'default': { 'ENGINE': 'django.db.backends.my ...

  6. Python中的SQLAlchemy

    在Python中,使用SQLAlchemy可以对数据库进行操作. SQLAlchemy是Python中的一个标准库. 要使用SQLAlchemy,首先要创建连接: url = mysql+pymysq ...

  7. Python中实现异步并发查询数据库

    这周又填了一个以前挖下的坑. 这个博客系统使用Psycopy库实现与PostgreSQL数据库的通信.前期,只是泛泛地了解了一下SQL语言,然后就胡乱拼凑出这么一个简易博客系统. 10月份找到工作以后 ...

  8. Django框架中的model(操作数据库)

    什么是ORM ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复 ...

  9. python 历险记(四)— python 中常用的 json 操作

    目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编码和解码? 常用的 json 操作有哪些? json 操作需要什么库? 如何 ...

  10. 在PHP中使用MySQL Mysqli操作数据库 ,以及类操作方法

    先来操作函数部分,普遍的MySQL 函数方法,但随着PHP5的发展,有些函数使用的要求加重了,有些则将废弃不用,有些则参数必填... ================================= ...

随机推荐

  1. RabbitMQ 进阶使用之延迟队列 → 订单在30分钟之内未支付则自动取消

    开心一刻 晚上,媳妇和儿子躺在沙发上 儿子疑惑的问道:妈妈,你为什么不去上班 媳妇:妈妈的人生目标是前20年靠父母养,后40年靠你爸爸养,再往后20年就靠你和妹妹养 儿子:我可养不起 媳妇:为什么 儿 ...

  2. itestwork(爱测试) 一站式接口测试&敏捷测试工作站 9.0.1 发布,ui 及Bug fix

    (一)itest 简介 itest work (爱测试)  一站式工作站让测试变得简单.敏捷.itest work 包含极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock 6合1 ...

  3. Vue.js 动画与过渡效果实战

    title: Vue.js 动画与过渡效果实战 date: 2024/6/4 updated: 2024/6/4 description: 这篇文章介绍了如何在网页设计中使用过渡动画和组件效果,以及如 ...

  4. 纯css+html+js模仿elementui组件

    文件下载链接地址https://files.cnblogs.com/files/ht955/UIcomponents.zip?t=1717405975&download=true

  5. css做多列瀑布流

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

  6. js重复赋值 js数据交换 js调式方法

    重复赋值 对一个变量,对此赋值,存储数据 定义变量时对变量初始化 此时js语法规定,必须带有关键词var 或者let 或者const 之后使用变量时,不需要带有关键词 第一次定义变量,初始化变量,必须 ...

  7. 夜莺官方文档优化第一弹:手把手教你部署和架构讲解,消灭所有部署失败的 case!干!

    前置说明 各种环境的选型建议 Docker compose 方式:仅仅用于简单测试,不推荐在生产环境使用 Docker compose,升级起来挺麻烦的,除非你对 Docker compose 真的很 ...

  8. nginx虚拟主机实战

    基于nginx部署网站 虚拟主机指的就是一个独立的站点,具有独立的域名,有完整的www服务,例如网站.FTP.邮件等. Nginx支持多虚拟主机,在一台机器上可以运行完全独立的多个站点. 一.为什么配 ...

  9. 在System身份运行的.NET程序中以指定的用户身份启动可交互式进程

    今天在技术群里,石头哥向大家提了个问题:"如何在一个以System身份运行的.NET程序(Windows Services)中,以其它活动的用户身份启动可交互式进程(桌面应用程序.控制台程序 ...

  10. Nginx+Fail2ban 实现同一ip在一分钟内连续三次请求同一接口并响应成功时进行封禁

    1. 安装 Fail2Ban 和 Nginx 如果尚未安装 Fail2Ban 和 Nginx,可以使用以下命令进行安装: # CentOS默认的仓库中可能不包含Nginx,所以需要添加EPEL(Ext ...