SQLAlchemy 2.0 文档
变更与迁移
- SQLAlchemy 2.0 - 主要迁移指南¶
- 1.4->2.0 迁移路径
- 1.x -> 2.x 迁移概览
- 首要前提,第一步 - 工作的 1.3 应用程序
- 首要前提,第二步 - 工作的 1.4 应用程序
- 迁移到 2.0 第一步 - 仅限 Python 3 (Python 3.7 最低兼容版本)
- 迁移到 2.0 第二步 - 开启 RemovedIn20Warnings
- 迁移到 2.0 第三步 - 解决所有 RemovedIn20Warnings
- 迁移到 2.0 第四步 - 在 Engine 上使用
future
标志 - 迁移到 2.0 第五步 - 在 Session 上使用
future
标志 - 迁移到 2.0 第六步 - 将
__allow_unmapped__
添加到显式类型的 ORM 模型 - 迁移到 2.0 第七步 - 针对 SQLAlchemy 2.0 版本进行测试
- 2.0 迁移 - Core 连接 / 事务
- 2.0 迁移 - Core 用法
- 2.0 迁移 - ORM 配置
- 2.0 迁移 - ORM 用法
- ORM Query 与 Core Select 统一
- ORM Query - get() 方法移至 Session
- ORM Query - 关系上的 Joining / 加载使用属性,而不是字符串
- ORM Query - 使用属性列表而不是单独调用进行链式调用已移除
- ORM Query - join(…, aliased=True), from_joinpoint 已移除
- 将 DISTINCT 与附加列一起使用,但仅选择实体
- 从查询本身选择作为子查询,例如 “from_self()”
- 从备选 selectable 中选择实体;Query.select_entity_from()
- 默认情况下,ORM 行不进行唯一化
- “动态” 关系加载器被 “Write Only” 取代
- Session 中移除了 Autocommit 模式;添加了 autobegin 支持
- Session “子事务” 行为已移除
- 2.0 迁移 - ORM 扩展和 Recipe 变更
- Asyncio 支持
- SQLAlchemy 2.0 中的新特性?
- 2.0 更新日志
- 1.4 更新日志
- 1.3 更新日志
- 1.2 更新日志
- 1.1 更新日志
- 1.0 更新日志
- 0.9 更新日志
- 0.8 更新日志
- 0.7 更新日志
- 0.6 更新日志
- 0.5 更新日志
- 0.4 更新日志
- 0.3 更新日志
- 0.2 更新日志
- 0.1 更新日志
- SQLAlchemy 1.4 中的新特性?
- SQLAlchemy 1.3 中的新特性?
- SQLAlchemy 1.2 中的新特性?
- SQLAlchemy 1.1 中的新特性?
- SQLAlchemy 1.0 中的新特性?
- SQLAlchemy 0.9 中的新特性?
- SQLAlchemy 0.8 中的新特性?
- SQLAlchemy 0.7 中的新特性?
- SQLAlchemy 0.6 中的新特性?
- SQLAlchemy 0.5 中的新特性?
- SQLAlchemy 0.4 中的新特性?
项目版本
- 上一篇: 变更与迁移
- 下一篇: SQLAlchemy 2.0 中的新特性?
- 上一级: 首页
- 本页目录
- SQLAlchemy 2.0 - 主要迁移指南
- 1.4->2.0 迁移路径
- 1.x -> 2.x 迁移概览
- 首要前提,第一步 - 工作的 1.3 应用程序
- 首要前提,第二步 - 工作的 1.4 应用程序
- 迁移到 2.0 第一步 - 仅限 Python 3 (Python 3.7 最低兼容版本)
- 迁移到 2.0 第二步 - 开启 RemovedIn20Warnings
- 迁移到 2.0 第三步 - 解决所有 RemovedIn20Warnings
- 迁移到 2.0 第四步 - 在 Engine 上使用
future
标志 - 迁移到 2.0 第五步 - 在 Session 上使用
future
标志 - 迁移到 2.0 第六步 - 将
__allow_unmapped__
添加到显式类型的 ORM 模型 - 迁移到 2.0 第七步 - 针对 SQLAlchemy 2.0 版本进行测试
- 2.0 迁移 - Core 连接 / 事务
- 2.0 迁移 - Core 用法
- 2.0 迁移 - ORM 配置
- 2.0 迁移 - ORM 用法
- ORM Query 与 Core Select 统一
- ORM Query - get() 方法移至 Session
- ORM Query - 关系上的 Joining / 加载使用属性,而不是字符串
- ORM Query - 使用属性列表而不是单独调用进行链式调用已移除
- ORM Query - join(…, aliased=True), from_joinpoint 已移除
- 将 DISTINCT 与附加列一起使用,但仅选择实体
- 从查询本身选择作为子查询,例如 “from_self()”
- 从备选 selectable 中选择实体;Query.select_entity_from()
- 默认情况下,ORM 行不进行唯一化
- “动态” 关系加载器被 “Write Only” 取代
- Session 中移除了 Autocommit 模式;添加了 autobegin 支持
- Session “子事务” 行为已移除
- 2.0 迁移 - ORM 扩展和 Recipe 变更
- Asyncio 支持
SQLAlchemy 2.0 - 主要迁移指南¶
读者须知
SQLAlchemy 2.0 的过渡文档分为两份文档 - 一份详细介绍了从 1.x 到 2.x 系列的主要 API 变化,另一份详细介绍了相对于 SQLAlchemy 1.4 的新特性和行为
SQLAlchemy 2.0 - 主要迁移指南 - 本文档,1.x 到 2.x API 变化
SQLAlchemy 2.0 中的新特性? - SQLAlchemy 2.0 的新特性和行为
已经更新其 1.4 应用程序以遵循 SQLAlchemy 2.0 引擎和 ORM 约定的读者可以导航到 SQLAlchemy 2.0 中的新特性? 以了解新特性和功能的概述。
关于本文档
本文档描述了 SQLAlchemy 1.4 版本和 SQLAlchemy 2.0 版本之间的更改。
SQLAlchemy 2.0 对 Core 和 ORM 组件中各种关键 SQLAlchemy 用法模式进行了重大转变。此版本的目的是对 SQLAlchemy 自早期以来一些最基本的假设进行轻微调整,并提供一种新的精简用法模型,希望该模型在 Core 和 ORM 组件之间更加简约和一致,并且功能更强大。Python 转向仅支持 Python 3 以及 Python 3 渐进式类型系统的出现是此转变的最初灵感,Python 社区的性质也在发生变化,现在不仅包括硬核数据库程序员,还包括庞大的数据科学家和各个学科学生的社区。
SQLAlchemy 起步于 Python 2.3,当时没有上下文管理器、没有函数装饰器、Unicode 作为二等公民特性,以及今天看来是各种各样的缺点。SQLAlchemy 2.0 中最大的变化是针对 SQLAlchemy 早期开发阶段遗留下来的假设,以及 Query
和 Declarative 等关键 API 特性的逐步引入而产生的遗留产物。它还希望标准化一些已被证明非常有效的新功能。
1.4->2.0 迁移路径¶
被认为是 “SQLAlchemy 2.0” 的最突出的架构特性和 API 更改实际上已在 1.4 系列中完全发布,以便为从 1.x 到 2.x 系列的平稳升级路径提供支持,并作为这些特性本身的 Beta 测试平台。这些更改包括
以上要点链接到 SQLAlchemy 1.4 中的新特性? 文档中介绍的 SQLAlchemy 1.4 中引入的这些新范式的描述。
对于 SQLAlchemy 2.0,所有标记为 为 2.0 弃用 的 API 特性和行为现已最终确定;特别是,不再存在的主要 API 包括
Python 2 支持
以上要点指的是在 2.0 版本中最终确定的最突出的完全向后不兼容的更改。应用程序适应这些更改以及其他更改的迁移路径被构建为首先过渡到 SQLAlchemy 1.4 系列,其中 “future” API 可用于提供 “2.0” 的工作方式,然后再过渡到 2.0 系列,其中移除了上述及其他不再使用的 API。
此迁移路径的完整步骤稍后将在本文档的 1.x -> 2.x 迁移概览 中介绍。
1.x -> 2.x 迁移概览¶
SQLAlchemy 2.0 过渡在 SQLAlchemy 1.4 版本中呈现为一系列步骤,这些步骤允许任何规模或复杂程度的应用程序通过渐进的迭代过程迁移到 SQLAlchemy 2.0。从 Python 2 到 Python 3 过渡中吸取的教训启发了一个系统,该系统旨在尽可能地不要求任何 “破坏性” 更改,或任何需要普遍进行而非完全不进行的更改。
作为证明 2.0 架构以及允许完全迭代过渡环境的一种手段,2.0 的所有新 API 和特性的范围都存在于 1.4 系列中并可用;这包括主要的新功能领域,例如 SQL 缓存系统、新的 ORM 语句执行模型、ORM 和 Core 的新事务范式、统一经典映射和声明式映射的新 ORM declarative 系统、对 Python 数据类的支持以及 Core 和 ORM 的 asyncio 支持。
实现 2.0 迁移的步骤在以下小节中;总的来说,总体策略是一旦应用程序在 1.4 上运行,所有警告标志都已打开,并且没有发出任何 2.0-弃用警告,则它现在基本上与 SQLAlchemy 2.0 交叉兼容。请注意,在针对 SQLAlchemy 2.0 运行时,可能存在其他 API 和行为更改可能会表现不同;始终针对实际的 SQLAlchemy 2.0 版本测试代码,作为迁移的最后一步。
首要前提,第一步 - 工作的 1.3 应用程序¶
第一步是将现有应用程序迁移到 1.4,对于典型的非平凡应用程序,是确保它在 SQLAlchemy 1.3 上运行且没有弃用警告。1.4 版本确实有一些与之前版本中警告的条件相关的更改,包括 1.3 中引入的一些警告,特别是 relationship.viewonly
和 relationship.sync_backref
标志的行为的一些更改。
为了获得最佳结果,应用程序应该能够在最新的 SQLAlchemy 1.3 版本上运行,或者通过其所有测试,并且没有 SQLAlchemy 弃用警告;这些是针对 SADeprecationWarning
类发出的警告。
首要前提,第二步 - 工作的 1.4 应用程序¶
一旦应用程序在 SQLAlchemy 1.3 上运行良好,下一步就是使其在 SQLAlchemy 1.4 上运行。在绝大多数情况下,应用程序从 SQLAlchemy 1.3 到 1.4 应该可以无问题运行。但是,在任何 1.x 和 1.y 版本之间,API 和行为都发生了细微的更改,或者在某些情况下稍微不那么细微的更改,并且 SQLAlchemy 项目通常在前几个月会收到大量的回归报告。
1.x->1.y 版本发布过程通常在边缘有一些更引人注目的更改,并且这些更改基于预期很少甚至从未使用过的用例。对于 1.4,被确定为属于此范围的更改如下
URL 对象现在是不可变的 - 这会影响操作
URL
对象的代码,并且可能会影响使用CreateEnginePlugin
扩展点的代码。这是一种不常见的情况,但可能会特别影响某些使用特殊数据库配置逻辑的测试套件。Github 搜索使用相对较新且鲜为人知的CreateEnginePlugin
类的代码,发现有两个项目未受此更改的影响。SELECT 语句不再隐式地被视为 FROM 子句 - 此更改可能会影响以某种方式依赖于
Select
构造中几乎无法使用的行为的代码,它会创建通常令人困惑且无法工作的未命名子查询。无论如何,这些子查询都会被大多数数据库拒绝,因为通常需要名称,但在 SQLite 上除外,但是某些应用程序可能需要调整一些无意中依赖于此的查询。select().join() 和 outerjoin() 将 JOIN 条件添加到当前查询,而不是创建子查询 - 在某种程度上相关,
Select
类具有.join()
和.outerjoin()
方法,这些方法隐式地创建子查询,然后返回Join
构造,这再次几乎没用,并产生了很多困惑。决定推进使用更有用的 2.0 风格的连接构建方法,其中这些方法现在的工作方式与 ORMQuery.join()
方法相同。许多 Core 和 ORM 语句对象现在在其编译阶段执行大部分构造和验证 - 与
Query
或Select
的构造相关的某些错误消息可能要到编译/执行时才会发出,而不是在构造时发出。这可能会影响某些针对失败模式进行测试的测试套件。
有关 SQLAlchemy 1.4 更改的完整概述,请参阅 SQLAlchemy 1.4 中的新特性? 文档。
迁移到 2.0 第一步 - 仅限 Python 3 (Python 3.7 最低兼容版本)¶
SQLAlchemy 2.0 最初的灵感来自于 Python 2 的 EOL 在 2020 年。SQLAlchemy 比其他主要项目花费了更长的时间来放弃 Python 2.7 支持。但是,为了使用 SQLAlchemy 2.0,应用程序需要能够在至少 Python 3.7 上运行。SQLAlchemy 1.4 支持 Python 3 系列中的 Python 3.6 或更高版本;在整个 1.4 系列中,应用程序可以继续在 Python 2.7 或至少 Python 3.6 上运行。但是,2.0 版本从 Python 3.7 开始。
迁移到 2.0 第二步 - 开启 RemovedIn20Warnings¶
SQLAlchemy 1.4 具有受 Python “-3” 标志启发的条件弃用警告系统,该标志将指示正在运行的应用程序中的旧模式。对于 SQLAlchemy 1.4,只有当环境变量 SQLALCHEMY_WARN_20
设置为 true
或 1
中的任何一个时,才会发出 RemovedIn20Warning
弃用类。
给定以下示例程序
from sqlalchemy import column
from sqlalchemy import create_engine
from sqlalchemy import select
from sqlalchemy import table
engine = create_engine("sqlite://")
engine.execute("CREATE TABLE foo (id integer)")
engine.execute("INSERT INTO foo (id) VALUES (1)")
foo = table("foo", column("id"))
result = engine.execute(select([foo.c.id]))
print(result.fetchall())
上面的程序使用了几种许多用户已经识别为 “旧式” 的模式,即使用作为 “无连接执行” API 一部分的 Engine.execute()
方法。当我们在 1.4 上运行上面的程序时,它返回一行
$ python test3.py
[(1,)]
为了启用 “2.0 弃用模式”,我们启用 SQLALCHEMY_WARN_20=1
变量,并额外确保选择了不会抑制任何警告的 警告过滤器
SQLALCHEMY_WARN_20=1 python -W always::DeprecationWarning test3.py
由于报告的警告位置并不总是在正确的位置,因此在没有完整堆栈跟踪的情况下可能很难找到有问题的代码。这可以通过指定 error
警告过滤器,使用 Python 选项 -W error::DeprecationWarning
将警告转换为异常来实现。
启用警告后,我们的程序现在有很多话要说
$ SQLALCHEMY_WARN_20=1 python -W always::DeprecationWarning test3.py
test3.py:9: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
engine.execute("CREATE TABLE foo (id integer)")
/home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:2856: RemovedIn20Warning: Passing a string to Connection.execute() is deprecated and will be removed in version 2.0. Use the text() construct, or the Connection.exec_driver_sql() method to invoke a driver-level SQL string. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
return connection.execute(statement, *multiparams, **params)
/home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:1639: RemovedIn20Warning: The current statement is being autocommitted using implicit autocommit.Implicit autocommit will be removed in SQLAlchemy 2.0. Use the .begin() method of Engine or Connection in order to use an explicit transaction for DML and DDL statements. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
self._commit_impl(autocommit=True)
test3.py:10: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
engine.execute("INSERT INTO foo (id) VALUES (1)")
/home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:2856: RemovedIn20Warning: Passing a string to Connection.execute() is deprecated and will be removed in version 2.0. Use the text() construct, or the Connection.exec_driver_sql() method to invoke a driver-level SQL string. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
return connection.execute(statement, *multiparams, **params)
/home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:1639: RemovedIn20Warning: The current statement is being autocommitted using implicit autocommit.Implicit autocommit will be removed in SQLAlchemy 2.0. Use the .begin() method of Engine or Connection in order to use an explicit transaction for DML and DDL statements. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
self._commit_impl(autocommit=True)
/home/classic/dev/sqlalchemy/lib/sqlalchemy/sql/selectable.py:4271: RemovedIn20Warning: The legacy calling style of select() is deprecated and will be removed in SQLAlchemy 2.0. Please use the new calling style described at select(). (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
return cls.create_legacy_select(*args, **kw)
test3.py:14: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
result = engine.execute(select([foo.c.id]))
[(1,)]
根据以上指导,我们可以迁移我们的程序以使用 2.0 样式,并且作为奖励,我们的程序更加清晰
from sqlalchemy import column
from sqlalchemy import create_engine
from sqlalchemy import select
from sqlalchemy import table
from sqlalchemy import text
engine = create_engine("sqlite://")
# don't rely on autocommit for DML and DDL
with engine.begin() as connection:
# use connection.execute(), not engine.execute()
# use the text() construct to execute textual SQL
connection.execute(text("CREATE TABLE foo (id integer)"))
connection.execute(text("INSERT INTO foo (id) VALUES (1)"))
foo = table("foo", column("id"))
with engine.connect() as connection:
# use connection.execute(), not engine.execute()
# select() now accepts column / table expressions positionally
result = connection.execute(select(foo.c.id))
print(result.fetchall())
“2.0 弃用模式” 的目标是,在启用 “2.0 弃用模式” 的情况下,运行且没有 RemovedIn20Warning
警告的程序就可以在 SQLAlchemy 2.0 中运行。
迁移到 2.0 第三步 - 解决所有 RemovedIn20Warnings¶
可以迭代地开发代码以解决这些警告。在 SQLAlchemy 项目本身中,采取的方法如下
在测试套件中启用
SQLALCHEMY_WARN_20=1
环境变量,对于 SQLAlchemy,这在 tox.ini 文件中在测试套件的设置中,设置一系列警告过滤器,这些过滤器将选择特定子集的警告以引发异常,或被忽略(或记录)。一次只处理一个警告子组。下面,为应用程序配置了一个警告过滤器,其中为了使所有测试通过,将需要更改 Core 级别的
.execute()
调用,但所有其他 2.0 风格的警告都将被抑制import warnings from sqlalchemy import exc # for warnings not included in regex-based filter below, just log warnings.filterwarnings("always", category=exc.RemovedIn20Warning) # for warnings related to execute() / scalar(), raise for msg in [ r"The (?:Executable|Engine)\.(?:execute|scalar)\(\) function", r"The current statement is being autocommitted using implicit autocommit,", r"The connection.execute\(\) method in SQLAlchemy 2.0 will accept " "parameters as a single dictionary or a single sequence of " "dictionaries only.", r"The Connection.connect\(\) function/method is considered legacy", r".*DefaultGenerator.execute\(\)", ]: warnings.filterwarnings( "error", message=msg, category=exc.RemovedIn20Warning, )
随着应用程序中每个子类别的警告得到解决,可以通过 “always” 过滤器捕获的新警告可以添加到要解决的 “errors” 列表中。
一旦不再发出警告,就可以删除过滤器。
迁移到 2.0 第四步 - 在 Engine 上使用 future
标志¶
Engine
对象在 2.0 版本中具有更新的事务级别 API。在 1.4 中,通过将标志 future=True
传递给 create_engine()
函数,可以使用此新 API。
当使用 create_engine.future
标志时,Engine
和 Connection
对象完全支持 2.0 API,而不支持任何旧功能,包括 Connection.execute()
的新参数格式、移除 “隐式 autocommit”、字符串语句需要 text()
构造(除非使用 Connection.exec_driver_sql()
方法),以及从 Engine
中移除无连接执行。
如果关于 Engine
和 Connection
的使用的所有 RemovedIn20Warning
警告都已解决,则可以启用 create_engine.future
标志,并且不应引发任何错误。
新的引擎在 Engine
中描述,它提供了一个新的 Connection
对象。除了上述更改之外,Connection
对象还具有 Connection.commit()
和 Connection.rollback()
方法,以支持新的 “边提交边执行” 操作模式
from sqlalchemy import create_engine
engine = create_engine("postgresql+psycopg2:///")
with engine.connect() as conn:
conn.execute(text("insert into table (x) values (:some_x)"), {"some_x": 10})
conn.commit() # commit as you go
迁移到 2.0 第五步 - 在 Session 上使用 future
标志¶
Session
对象还在 2.0 版本中具有更新的事务/连接级别 API。可以使用 Session.future
标志在 Session
或 sessionmaker
上使用此 API。
Session
对象以就地方式支持 “future” 模式,并且涉及以下更改
当
Session
解析要用于连接的引擎时,不再支持 “绑定的元数据”。这意味着 必须 将Engine
对象传递给构造函数(这可以是旧式或 future 风格的对象)。不再支持
Session.begin.subtransactions
标志。Session.commit()
方法始终向数据库发出 COMMIT,而不是尝试协调 “子事务”。Session.rollback()
方法始终一次性回滚整个事务堆栈,而不是尝试保留 “子事务”。
`Session
` 在 1.4 版本中也支持更灵活的创建模式,这些模式现在与 `Connection
` 对象使用的模式非常相似。 重点包括 `Session
` 可以用作上下文管理器
from sqlalchemy.orm import Session
with Session(engine) as session:
session.add(MyObject())
session.commit()
此外,`sessionmaker
` 对象支持 `sessionmaker.begin()
` 上下文管理器,它将创建一个 `Session
` 并在一个代码块中开始/提交事务
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(engine)
with Session.begin() as session:
session.add(MyObject())
有关 `Session
` 创建模式与 `Connection
` 的创建模式的比较,请参阅 `Session 级别与 Engine 级别事务控制` 部分。
一旦应用程序通过所有测试/在 `SQLALCHEMY_WARN_20=1
` 下运行,并且所有 `exc.RemovedIn20Warning
` 出现的地方都设置为引发错误,**应用程序就准备就绪了!**。
以下各节将详细介绍所有主要 API 修改需要进行的具体更改。
迁移到 2.0 第六步 - 将 `__allow_unmapped__
` 添加到显式类型 ORM 模型¶
SQLAlchemy 2.0 新增了对 ORM 模型上 `PEP 484` 类型注解的运行时解释的支持。 这些注解的一个要求是它们必须使用 `Mapped
` 泛型容器。 不使用 `Mapped
` 且链接到诸如 `relationship()
` 等构造的注解将在 Python 中引发错误,因为它们暗示了配置错误。
使用 `Mypy 插件` 和显式注解的 SQLAlchemy 应用程序,如果这些注解中不使用 `Mapped
`,则会受到这些错误的影响,如下例所示
Base = declarative_base()
class Foo(Base):
__tablename__ = "foo"
id: int = Column(Integer, primary_key=True)
# will raise
bars: List["Bar"] = relationship("Bar", back_populates="foo")
class Bar(Base):
__tablename__ = "bar"
id: int = Column(Integer, primary_key=True)
foo_id = Column(ForeignKey("foo.id"))
# will raise
foo: Foo = relationship(Foo, back_populates="bars", cascade="all")
上面,`Foo.bars
` 和 `Bar.foo
` `relationship()
` 声明将在类构造时引发错误,因为它们没有使用 `Mapped
` (相比之下,使用 `Column
` 的注解会被 2.0 忽略,因为这些可以被识别为旧的配置风格)。 为了允许所有不使用 `Mapped
` 的注解无错误地通过,可以在类或任何子类上使用 `__allow_unmapped__
` 属性,这将导致新声明式系统完全忽略这些情况下的注解。
注意
`__allow_unmapped__
` 指令 **仅** 适用于 ORM 的 *运行时* 行为。 它不影响 Mypy 的行为,并且上面编写的映射仍然需要安装 Mypy 插件。 对于完全 2.0 风格的 ORM 模型,这些模型在 *没有* 插件的情况下在 Mypy 下也能正确键入,请按照 `迁移现有映射` 中的迁移步骤进行操作。
下面的示例说明了如何将 `__allow_unmapped__
` 应用于声明式 `Base
` 类,它将对所有从 `Base
` 派生的类生效
# qualify the base with __allow_unmapped__. Can also be
# applied to classes directly if preferred
class Base:
__allow_unmapped__ = True
Base = declarative_base(cls=Base)
# existing mapping proceeds, Declarative will ignore any annotations
# which don't include ``Mapped[]``
class Foo(Base):
__tablename__ = "foo"
id: int = Column(Integer, primary_key=True)
bars: List["Bar"] = relationship("Bar", back_populates="foo")
class Bar(Base):
__tablename__ = "bar"
id: int = Column(Integer, primary_key=True)
foo_id = Column(ForeignKey("foo.id"))
foo: Foo = relationship(Foo, back_populates="bars", cascade="all")
在 2.0.0beta3 版本中更改: - 改进了 `__allow_unmapped__
` 属性支持,以允许不使用 `Mapped
` 的 1.4 风格的显式注解关系保持可用。
迁移到 2.0 第七步 - 针对 SQLAlchemy 2.0 版本进行测试¶
如前所述,SQLAlchemy 2.0 具有额外的 API 和行为更改,这些更改旨在向后兼容,但仍然可能引入一些不兼容性。 因此,在整体移植过程完成后,最后一步是针对 SQLAlchemy 2.0 的最新版本进行测试,以纠正可能存在的任何剩余问题。
`SQLAlchemy 2.0 的新特性?` 指南概述了 SQLAlchemy 2.0 的新功能和行为,这些功能和行为超出了 1.4->2.0 API 更改的基本范围。
2.0 迁移 - Core 连接/事务¶
库级别(但不是驱动程序级别)的 “自动提交” 从 Core 和 ORM 中移除¶
概要
在 SQLAlchemy 1.x 中,以下语句将自动提交底层 DBAPI 事务,但在 SQLAlchemy 2.0 中不会发生这种情况
conn = engine.connect()
# won't autocommit in 2.0
conn.execute(some_table.insert().values(foo="bar"))
这也不会自动提交
conn = engine.connect()
# won't autocommit in 2.0
conn.execute(text("INSERT INTO table (foo) VALUES ('bar')"))
需要提交的自定义 DML 的常见解决方法,“自动提交” 执行选项将被删除
conn = engine.connect()
# won't autocommit in 2.0
conn.execute(text("EXEC my_procedural_thing()").execution_options(autocommit=True))
迁移到 2.0
与 `1.x 风格` 和 `2.0 风格` 执行交叉兼容的方法是使用 `Connection.begin()
` 方法,或 `Engine.begin()
` 上下文管理器
with engine.begin() as conn:
conn.execute(some_table.insert().values(foo="bar"))
conn.execute(some_other_table.insert().values(bat="hoho"))
with engine.connect() as conn:
with conn.begin():
conn.execute(some_table.insert().values(foo="bar"))
conn.execute(some_other_table.insert().values(bat="hoho"))
with engine.begin() as conn:
conn.execute(text("EXEC my_procedural_thing()"))
当使用带有 `create_engine.future
` 标志的 `2.0 风格` 时,也可以使用 “边执行边提交” 风格,因为 `Connection
` 具有 **autobegin** 行为,当在没有显式调用 `Connection.begin()
` 的情况下首次调用语句时,就会发生这种情况
with engine.connect() as conn:
conn.execute(some_table.insert().values(foo="bar"))
conn.execute(some_other_table.insert().values(bat="hoho"))
conn.commit()
当启用 `2.0 弃用模式` 时,当发生已弃用的 “自动提交” 功能时,将发出警告,指示应注意显式事务的位置。
讨论
SQLAlchemy 的最初版本与 Python DBAPI (`PEP 249`) 的精神相悖,因为它试图隐藏 `PEP 249` 对事务的 “隐式开始” 和 “显式提交” 的强调。 十五年后,我们现在意识到这本质上是一个错误,因为 SQLAlchemy 的许多试图 “隐藏” 事务存在的模式导致 API 更加复杂,工作方式不一致,并且尤其对于那些不熟悉关系数据库和 ACID 事务的用户来说,极其令人困惑。 SQLAlchemy 2.0 将摒弃所有隐式提交事务的尝试,并且使用模式将始终要求用户以某种方式标明事务的 “开始” 和 “结束”,就像在 Python 中读取或写入文件一样,具有 “开始” 和 “结束”。
在纯文本语句的自动提交的情况下,实际上有一个正则表达式解析每个语句,以便检测自动提交! 毫不奇怪,这个正则表达式一直未能适应各种暗示 “写入” 数据库的语句和存储过程,导致持续的困惑,因为某些语句在数据库中产生结果,而另一些则不然。 通过阻止用户意识到事务概念,我们在这个问题上收到了很多错误报告,因为用户不明白数据库总是使用事务,无论某些层是否自动提交它。
SQLAlchemy 2.0 将要求每个级别的所有数据库操作都显式说明应如何使用事务。 对于绝大多数 Core 用例,这已经是推荐的模式
with engine.begin() as conn:
conn.execute(some_table.insert().values(foo="bar"))
对于 “边执行边提交,或者回滚” 的用法,这类似于今天 `Session
` 的常用方式,`Connection
` 的 “future” 版本,它是从使用 `create_engine.future
` 标志创建的 `Engine
` 返回的版本,包括新的 `Connection.commit()
` 和 `Connection.rollback()
` 方法,这些方法作用于现在在首次调用语句时自动开始的事务
# 1.4 / 2.0 code
from sqlalchemy import create_engine
engine = create_engine(..., future=True)
with engine.connect() as conn:
conn.execute(some_table.insert().values(foo="bar"))
conn.commit()
conn.execute(text("some other SQL"))
conn.rollback()
上面,`engine.connect()
` 方法将返回一个具有 **autobegin** 功能的 `Connection
`,这意味着当首次使用 execute 方法时,会发出 `begin()
` 事件(但请注意,Python DBAPI 中没有实际的 “BEGIN”)。 “autobegin” 是 SQLAlchemy 1.4 中的一种新模式,`Connection
` 和 ORM `Session
` 对象都具有此功能; autobegin 允许在首次获取对象时显式调用 `Connection.begin()
` 方法,对于希望标明事务开始的方案,但如果未调用该方法,则在首次对对象执行操作时隐式发生。
“自动提交” 的移除与 `“隐式” 和 “无连接” 执行,“绑定元数据” 已移除` 中讨论的 “无连接” 执行的移除密切相关。 所有这些遗留模式都是从 Python 在 SQLAlchemy 首次创建时没有上下文管理器或装饰器这一事实构建起来的,因此没有方便的惯用模式来标明资源的使用。
驱动程序级别的自动提交仍然可用¶
真正的 “自动提交” 行为现在在大多数 DBAPI 实现中广泛可用,并且 SQLAlchemy 通过 `Connection.execution_options.isolation_level
` 参数支持它,如 `设置事务隔离级别,包括 DBAPI 自动提交` 中所述。 真正的自动提交被视为 “隔离级别”,以便在使用自动提交时应用程序代码的结构不会改变; `Connection.begin()
` 上下文管理器以及诸如 `Connection.commit()
` 等方法仍然可以使用,当 DBAPI 级别的自动提交打开时,它们只是数据库驱动程序级别的空操作。
“隐式” 和 “无连接” 执行,“绑定元数据” 已移除¶
概要
将 `Engine
` 与 `MetaData
` 对象关联的能力(这使得一系列所谓的 “无连接” 执行模式可用)已被移除
from sqlalchemy import MetaData
metadata_obj = MetaData(bind=engine) # no longer supported
metadata_obj.create_all() # requires Engine or Connection
metadata_obj.reflect() # requires Engine or Connection
t = Table("t", metadata_obj, autoload=True) # use autoload_with=engine
result = engine.execute(t.select()) # no longer supported
result = t.select().execute() # no longer supported
迁移到 2.0
对于模式级别模式,需要显式使用 `Engine
` 或 `Connection
`。 `Engine
` 仍然可以直接用作 `MetaData.create_all()
` 操作或自动加载操作的连接源。 对于执行语句,只有 `Connection
` 对象具有 `Connection.execute()
` 方法(除了 ORM 级别的 `Session.execute()
` 方法之外)
from sqlalchemy import MetaData
metadata_obj = MetaData()
# engine level:
# create tables
metadata_obj.create_all(engine)
# reflect all tables
metadata_obj.reflect(engine)
# reflect individual table
t = Table("t", metadata_obj, autoload_with=engine)
# connection level:
with engine.connect() as connection:
# create tables, requires explicit begin and/or commit:
with connection.begin():
metadata_obj.create_all(connection)
# reflect all tables
metadata_obj.reflect(connection)
# reflect individual table
t = Table("t", metadata_obj, autoload_with=connection)
# execute SQL statements
result = connection.execute(t.select())
讨论
Core 文档已经在此处标准化了所需的模式,因此大多数现代应用程序可能不必进行太多更改,但是可能有许多应用程序仍然依赖于需要调整的 `engine.execute()
` 调用。
“无连接” 执行是指仍然相当流行的从 `Engine
` 调用 `.execute()
` 的模式
result = engine.execute(some_statement)
上述操作隐式获取一个 `Connection
` 对象,并在其上运行 `.execute()
` 方法。 虽然这看起来是一个简单的便利功能,但事实证明它会引起一些问题
以 `
engine.execute()
` 调用长字符串为特征的程序已经变得普遍,过度使用了一个旨在很少使用的功能,并导致了低效的非事务性应用程序。 新用户对 `engine.execute()
` 和 `connection.execute()
` 之间的区别感到困惑,并且通常不理解这两种方法之间的细微差别。该功能依赖于 “应用程序级别自动提交” 功能才能有意义,而该功能本身也被删除,因为它也 `效率低下且具有误导性`。
为了处理结果集,`
Engine.execute
` 返回一个带有未消耗游标结果的结果对象。 此游标结果必然仍然链接到保持在打开事务中的 DBAPI 连接,一旦结果集完全消耗了游标中等待的行,所有这些连接都将被释放。 这意味着 `Engine.execute
` 实际上并没有在调用完成时关闭它声称要管理的连接资源。 SQLAlchemy 的 “自动关闭” 行为经过了很好的调整,以至于用户通常不会报告来自该系统的任何负面影响,但这仍然是一个过于隐式和低效的系统,它是 SQLAlchemy 最早版本遗留下来的。
“无连接” 执行的移除然后导致了更旧的模式的移除,即 “隐式、无连接” 执行
result = some_statement.execute()
上述模式具有 “无连接” 执行的所有问题,此外,它还依赖于 “绑定元数据” 模式,SQLAlchemy 多年来一直试图淡化该模式。 这是 SQLAlchemy 在 0.1 版本中首次宣传的使用模型,当引入 `Connection
` 对象,以及后来的 Python 上下文管理器为在固定范围内使用资源提供了更好的模式时,该模型几乎立即过时。
随着隐式执行的移除,“绑定元数据” 本身在这个系统中也不再有用途。 在现代使用中,“绑定元数据” 仍然有点方便在 `MetaData.create_all()
` 调用以及 `Session
` 对象中使用,但是让这些函数显式接收 `Engine
` 可以提供更清晰的应用程序设计。
多种选择变为一种选择¶
总的来说,上述执行模式是在 SQLAlchemy 的第一个 0.1 版本中引入的,当时 `Connection
` 对象甚至还不存在。 经过多年淡化这些模式,“隐式、无连接” 执行和 “绑定元数据” 不再被广泛使用,因此在 2.0 中,我们试图最终将 Core 中执行语句的方式从 “多种选择” 减少到
# many choices
# bound metadata?
metadata_obj = MetaData(engine)
# or not?
metadata_obj = MetaData()
# execute from engine?
result = engine.execute(stmt)
# or execute the statement itself (but only if you did
# "bound metadata" above, which means you can't get rid of "bound" if any
# part of your program uses this form)
result = stmt.execute()
# execute from connection, but it autocommits?
conn = engine.connect()
conn.execute(stmt)
# execute from connection, but autocommit isn't working, so use the special
# option?
conn.execution_options(autocommit=True).execute(stmt)
# or on the statement ?!
conn.execute(stmt.execution_options(autocommit=True))
# or execute from connection, and we use explicit transaction?
with conn.begin():
conn.execute(stmt)
“一种选择”,其中 “一种选择” 是指 “显式连接和显式事务”; 根据需要,仍然有几种方法来划分事务块。 “一种选择” 是获取一个 `Connection
`,然后在操作是写入操作的情况下,显式划分事务
# one choice - work with explicit connection, explicit transaction
# (there remain a few variants on how to demarcate the transaction)
# "begin once" - one transaction only per checkout
with engine.begin() as conn:
result = conn.execute(stmt)
# "commit as you go" - zero or more commits per checkout
with engine.connect() as conn:
result = conn.execute(stmt)
conn.commit()
# "commit as you go" but with a transaction block instead of autobegin
with engine.connect() as conn:
with conn.begin():
result = conn.execute(stmt)
execute() 方法更严格,执行选项更突出¶
概要
在 SQLAlchemy 2.0 中,可以与 `sqlalchemy.engine.Connection()
` execute 方法一起使用的参数模式已得到高度简化,删除了许多以前可用的参数模式。 1.4 系列中的新 API 在 `sqlalchemy.engine.Connection()
` 中进行了描述。 下面的示例说明了需要修改的模式
connection = engine.connect()
# direct string SQL not supported; use text() or exec_driver_sql() method
result = connection.execute("select * from table")
# positional parameters no longer supported, only named
# unless using exec_driver_sql()
result = connection.execute(table.insert(), ("x", "y", "z"))
# **kwargs no longer accepted, pass a single dictionary
result = connection.execute(table.insert(), x=10, y=5)
# multiple *args no longer accepted, pass a list
result = connection.execute(
table.insert(), {"x": 10, "y": 5}, {"x": 15, "y": 12}, {"x": 9, "y": 8}
)
迁移到 2.0
新的 `Connection.execute()
` 方法现在接受 1.x `Connection.execute()
` 方法接受的参数风格的子集,因此以下代码在 1.x 和 2.0 之间是交叉兼容的
connection = engine.connect()
from sqlalchemy import text
result = connection.execute(text("select * from table"))
# pass a single dictionary for single statement execution
result = connection.execute(table.insert(), {"x": 10, "y": 5})
# pass a list of dictionaries for executemany
result = connection.execute(
table.insert(), [{"x": 10, "y": 5}, {"x": 15, "y": 12}, {"x": 9, "y": 8}]
)
讨论
删除 `*args
` 和 `**kwargs
` 的使用,既是为了消除猜测传递给方法的参数类型的复杂性,也是为了为其他选项腾出空间,即现在可用于为每个语句提供选项的 `Connection.execute.execution_options
` 字典。 该方法也经过修改,使其使用模式与 `Session.execute()
` 方法的模式匹配,后者是 2.0 风格中更突出的 API。
删除直接字符串 SQL 是为了解决 `Connection.execute()
` 和 `Session.execute()
` 之间的不一致性,在前一种情况下,字符串以原始形式传递给驱动程序,而在后一种情况下,它首先转换为 `text()
` 构造。 通过仅允许 `text()
`,这也将接受的参数格式限制为 “命名” 而不是 “位置”。 最后,从安全角度来看,字符串 SQL 用例正受到越来越多的审查,并且 `text()
` 构造已成为文本 SQL 领域的显式边界,必须注意不受信任的用户输入。
结果行表现得像命名元组¶
概要
1.4 版本引入了一个 `全新的 Result 对象`,它反过来返回 `Row
` 对象,这些对象在使用 “future” 模式时表现得像命名元组
engine = create_engine(..., future=True) # using future mode
with engine.connect() as conn:
result = conn.execute(text("select x, y from table"))
row = result.first() # suppose the row is (1, 2)
"x" in row # evaluates to False, in 1.x / future=False, this would be True
1 in row # evaluates to True, in 1.x / future=False, this would be False
迁移到 2.0
正在测试行中是否存在特定键的应用程序代码或测试套件需要改为测试 `row.keys()
` 集合。 然而,这是一个不寻常的用例,因为结果行通常由已经知道其中存在哪些列的代码使用。
讨论
之前的 `KeyedTuple
` 类(在 1.4 中已包含,当从 `Query
` 对象中选择行时使用)已被 `Row
` 类替换,`Row
` 类是使用带有 `Engine
` 的 `create_engine.future
` 标志时返回的 Core 语句结果的相同 `Row
` 的基础(当未设置 `create_engine.future
` 标志时,Core 结果集使用 `LegacyRow
` 子类,该子类维护 `__contains__()
` 方法的向后兼容行为; ORM 专门直接使用 `Row
` 类)。
此 `Row
` 的行为类似于命名元组,因为它充当序列,但也支持属性名称访问,例如 `row.some_column
`。 但是,它还通过特殊属性 `row._mapping
` 提供以前的 “映射” 行为,该属性生成 Python 映射,以便可以使用诸如 `row["some_column"]
` 之类的键控访问。
为了预先接收作为映射的结果,可以使用结果上的 `mappings()
` 修饰符
from sqlalchemy.future.orm import Session
session = Session(some_engine)
result = session.execute(stmt)
for row in result.mappings():
print("the user is: %s" % row["User"])
ORM 使用的 `Row
` 类也支持通过实体或属性进行访问
from sqlalchemy.future import select
stmt = select(User, Address).join(User.addresses)
for row in session.execute(stmt).mappings():
print("the user is: %s the address is: %s" % (row[User], row[Address]))
2.0 迁移 - Core 用法¶
select() 不再接受各种构造函数参数,列按位置传递¶
概要
构造以及相关方法 select()
将不再接受关键字参数来构建诸如 WHERE 子句、FROM 列表和 ORDER BY 等元素。列的列表现在可以按位置参数发送,而不是作为列表发送。此外,FromClause.select()
构造现在接受其 WHEN 条件作为位置参数,而不是作为列表。case()
# select_from / order_by keywords no longer supported
stmt = select([1], select_from=table, order_by=table.c.id)
# whereclause parameter no longer supported
stmt = select([table.c.x], table.c.id == 5)
# whereclause parameter no longer supported
stmt = table.select(table.c.id == 5)
# list emits a deprecation warning
stmt = select([table.c.x, table.c.y])
# list emits a deprecation warning
case_clause = case(
[(table.c.x == 5, "five"), (table.c.x == 7, "seven")],
else_="neither five nor seven",
)
迁移到 2.0
仅支持
的“生成式”风格。要从中 SELECT 的列/表列表应按位置传递。SQLAlchemy 1.4 中的 select()
构造同时接受旧式风格和新式风格,并使用自动检测方案,因此以下代码与 1.4 和 2.0 版本交叉兼容。select()
# use generative methods
stmt = select(1).select_from(table).order_by(table.c.id)
# use generative methods
stmt = select(table).where(table.c.id == 5)
# use generative methods
stmt = table.select().where(table.c.id == 5)
# pass columns clause expressions positionally
stmt = select(table.c.x, table.c.y)
# case conditions passed positionally
case_clause = case(
(table.c.x == 5, "five"), (table.c.x == 7, "seven"), else_="neither five nor seven"
)
讨论
多年来,SQLAlchemy 已经为 SQL 构造开发了一种约定,即接受列表参数或位置参数。此约定声明,结构性元素(即构成 SQL 语句结构的元素)应以位置参数传递。相反,数据元素(即构成 SQL 语句参数化数据的元素)应作为列表传递。多年来,由于 “WHERE” 子句会以位置参数传递的旧式调用模式,
构造无法顺利地参与此约定。SQLAlchemy 2.0 最终通过更改 select()
构造来解决此问题,使其仅接受 “生成式” 风格,而这种风格多年来一直是 Core 教程中唯一记录的风格。select()
“结构性”元素与“数据”元素的示例如下:
# table columns for CREATE TABLE - structural
table = Table("table", metadata_obj, Column("x", Integer), Column("y", Integer))
# columns in a SELECT statement - structural
stmt = select(table.c.x, table.c.y)
# literal elements in an IN clause - data
stmt = stmt.where(table.c.y.in_([1, 2, 3]))
insert/update/delete DML 不再接受关键字构造函数参数¶
概要
与之前对
的更改类似,select()
、insert()
和 update()
的构造函数参数(表参数除外)基本上已被移除。delete()
# no longer supported
stmt = insert(table, values={"x": 10, "y": 15}, inline=True)
# no longer supported
stmt = insert(table, values={"x": 10, "y": 15}, returning=[table.c.x])
# no longer supported
stmt = table.delete(table.c.x > 15)
# no longer supported
stmt = table.update(table.c.x < 15, preserve_parameter_order=True).values(
[(table.c.y, 20), (table.c.x, table.c.y + 10)]
)
迁移到 2.0
以下示例说明了上述示例的生成式方法用法
# use generative methods, **kwargs OK for values()
stmt = insert(table).values(x=10, y=15).inline()
# use generative methods, dictionary also still OK for values()
stmt = insert(table).values({"x": 10, "y": 15}).returning(table.c.x)
# use generative methods
stmt = table.delete().where(table.c.x > 15)
# use generative methods, ordered_values() replaces preserve_parameter_order
stmt = (
table.update()
.where(
table.c.x < 15,
)
.ordered_values((table.c.y, 20), (table.c.x, table.c.y + 10))
)
讨论
DML 构造的 API 和内部结构正在简化,方式与
构造类似。select()
2.0 迁移 - ORM 配置¶
声明式变为一级 API¶
概要
sqlalchemy.ext.declarative
包大部分(除了一些例外)已移至 sqlalchemy.orm
包。
和 declarative_base()
函数仍然存在,行为没有变化。declared_attr()
的一种新的超级实现,称为 declarative_base()
,现在充当顶级的 ORM 配置构造,它还为基于装饰器的声明式提供支持,并为与声明式注册表集成的经典映射提供新的支持。registry
迁移到 2.0
更改导入
from sqlalchemy.ext import declarative_base, declared_attr
改为
from sqlalchemy.orm import declarative_base, declared_attr
讨论
在流行了大约十年之后,sqlalchemy.ext.declarative
包现在已集成到 sqlalchemy.orm
命名空间中,但声明式的“扩展”类除外,它们仍然作为声明式扩展存在。有关更改的更多详细信息,请参阅 1.4 迁移指南,网址为 声明式现在已集成到 ORM 中,并具有新功能。
最初的“mapper()”函数现在是声明式的核心元素,已重命名¶
概要
sqlalchemy.orm.mapper()
独立函数移至幕后,由更高级别的 API 调用。此函数的新版本是从
对象中获取的 registry
方法。registry.map_imperatively()
迁移到 2.0
使用经典映射的代码应更改导入和代码,从
from sqlalchemy.orm import mapper
mapper(SomeClass, some_table, properties={"related": relationship(SomeRelatedClass)})
改为从中央
对象工作registry
from sqlalchemy.orm import registry
mapper_reg = registry()
mapper_reg.map_imperatively(
SomeClass, some_table, properties={"related": relationship(SomeRelatedClass)}
)
上面的
也是声明式映射的来源,经典映射现在可以访问此注册表,包括 registry
上的基于字符串的配置relationship()
from sqlalchemy.orm import registry
mapper_reg = registry()
Base = mapper_reg.generate_base()
class SomeRelatedClass(Base):
__tablename__ = "related"
# ...
mapper_reg.map_imperatively(
SomeClass,
some_table,
properties={
"related": relationship(
"SomeRelatedClass",
primaryjoin="SomeRelatedClass.related_id == SomeClass.id",
)
},
)
讨论
应大众需求,“经典映射” 将继续存在,但是它的新形式是基于
对象,并且可以作为 registry
使用。registry.map_imperatively()
此外,“经典映射” 的主要原理是保持
设置与类区分开。声明式始终允许使用所谓的 混合声明式 来实现此风格。但是,为了消除基类要求,添加了一流的 装饰器 形式。Table
作为又一项独立但相关的增强功能,声明式装饰器和经典映射形式都增加了对 Python 数据类 的支持。
另请参阅
ORM 映射类概述 - 适用于声明式、经典映射、数据类、attrs 等的所有新的统一文档。
2.0 迁移 - ORM 用法¶
SQLAlchemy 2.0 中最显著的变化是使用
结合 Session.execute()
来运行 ORM 查询,而不是使用 select()
。正如在其他地方提到的,实际上没有计划删除 Session.query()
API 本身,因为它现在通过在内部使用新的 API 来实现,它将保留为旧式 API,并且可以自由使用这两个 API。Session.query()
下表介绍了调用形式的总体变化,并提供了指向每种技术的文档的链接。个别迁移说明位于表后的嵌入部分中,并且可能包含此处未总结的其他说明。
1.x 风格 形式 |
2.0 风格 形式 |
另请参阅 |
---|---|---|
session.query(User).get(42) |
session.get(User, 42) |
|
session.query(User).all() |
session.execute(
select(User)
).scalars().all()
# or
session.scalars(
select(User)
).all() |
|
session.query(User).\
filter_by(name="some user").\
one() |
session.execute(
select(User).
filter_by(name="some user")
).scalar_one() |
|
session.query(User).\
filter_by(name="some user").\
first() |
session.scalars(
select(User).
filter_by(name="some user").
limit(1)
).first() |
|
session.query(User).options(
joinedload(User.addresses)
).all() |
session.scalars(
select(User).
options(
joinedload(User.addresses)
)
).unique().all() |
|
session.query(User).\
join(Address).\
filter(
Address.email == "e@sa.us"
).\
all() |
session.execute(
select(User).
join(Address).
where(
Address.email == "e@sa.us"
)
).scalars().all() |
|
session.query(User).\
from_statement(
text("select * from users")
).\
all() |
session.scalars(
select(User).
from_statement(
text("select * from users")
)
).all() |
|
session.query(User).\
join(User.addresses).\
options(
contains_eager(User.addresses)
).\
populate_existing().all() |
session.execute(
select(User)
.join(User.addresses)
.options(
contains_eager(User.addresses)
)
.execution_options(
populate_existing=True
)
).scalars().all() |
|
session.query(User).\
filter(User.name == "foo").\
update(
{"fullname": "Foo Bar"},
synchronize_session="evaluate"
) |
session.execute(
update(User)
.where(User.name == "foo")
.values(fullname="Foo Bar")
.execution_options(
synchronize_session="evaluate"
)
) |
|
session.query(User).count() |
session.scalar(
select(func.count()).
select_from(User)
)
# or
session.scalar(
select(func.count(User.id))
) |
ORM 查询与 Core Select 统一¶
概要
对象(以及 Query
和 BakedQuery
扩展)成为长期遗留对象,被直接使用 ShardedQuery
构造并结合 select()
方法所取代。从 Session.execute()
返回的对象列表或元组形式的结果,或者作为标量 ORM 对象的结果,将统一地从 Query
返回为 Session.execute()
对象,这些对象具有与 Core 执行一致的接口。Result
旧代码示例如下所示
session = Session(engine)
# becomes legacy use case
user = session.query(User).filter_by(name="some user").one()
# becomes legacy use case
user = session.query(User).filter_by(name="some user").first()
# becomes legacy use case
user = session.query(User).get(5)
# becomes legacy use case
for user in (
session.query(User).join(User.addresses).filter(Address.email == "some@email.com")
):
...
# becomes legacy use case
users = session.query(User).options(joinedload(User.addresses)).order_by(User.id).all()
# becomes legacy use case
users = session.query(User).from_statement(text("select * from users")).all()
# etc
迁移到 2.0
由于绝大多数 ORM 应用程序都期望使用
对象,并且 Query
接口的可用性不会影响新的接口,因此该对象将在 2.0 中保留,但将不再是文档的一部分,并且在很大程度上也不会受到支持。Query
构造现在适用于 Core 和 ORM 用例,当通过 select()
方法调用时,它将返回面向 ORM 的结果,即 ORM 对象(如果这是请求的内容)。Session.execute()
构造添加了许多新方法,以与 Select()
兼容,包括 Query
、Select.filter()
、新近重做的 Select.filter_by()
和 Select.join()
方法、Select.outerjoin()
等。Select.options()
的其他更多补充方法(例如 Query
)通过执行选项实现。Query.populate_existing()
返回结果的形式为
对象,它是 SQLAlchemy Result
ResultProxy
对象的新版本,它还添加了许多新方法以与
兼容,包括 Query
、Result.one()
、Result.all()
、Result.first()
等。Result.one_or_none()
但是,
对象确实需要一些不同的调用模式,因为它在首次返回时将始终返回元组,并且不会在内存中删除重复结果。为了像 Result
那样返回单个 ORM 对象,必须首先调用 Query
修饰符。为了返回唯一对象(这在使用连接迫切加载时是必需的),必须首先调用 Result.scalars()
修饰符。Result.unique()
有关
的所有新功能(包括执行选项等)的文档,请访问 ORM 查询指南。select()
以下是一些关于如何迁移到
的示例select()
session = Session(engine)
user = session.execute(select(User).filter_by(name="some user")).scalar_one()
# for first(), no LIMIT is applied automatically; add limit(1) if LIMIT
# is desired on the query
user = (
session.execute(select(User).filter_by(name="some user").limit(1)).scalars().first()
)
# get() moves to the Session directly
user = session.get(User, 5)
for user in session.execute(
select(User).join(User.addresses).filter(Address.email == "some@email.case")
).scalars():
...
# when using joinedload() against collections, use unique() on the result
users = (
session.execute(select(User).options(joinedload(User.addresses)).order_by(User.id))
.unique()
.all()
)
# select() has ORM-ish methods like from_statement() that only work
# if the statement is against ORM entities
users = (
session.execute(select(User).from_statement(text("select * from users")))
.scalars()
.all()
)
讨论
SQLAlchemy 既具有
构造,又具有单独的 select()
对象,后者具有极其相似但从根本上不兼容的接口,这可能是 SQLAlchemy 中最大的不一致之处,这是随着时间的推移,小的增量添加累积成两个主要 API(它们是发散的)而产生的。Query
在 SQLAlchemy 的最初版本中,
对象根本不存在。最初的想法是,Query
构造本身将能够选择行,并且将使用 Mapper
对象(而不是类)来创建 Core 风格方法中的各种条件。Table
在 SQLAlchemy 历史上的几个月/几年后才出现,作为用户对一个新的“可构建”查询对象(最初称为 Query
SelectResults
)的提议被接受。诸如 .where()
方法(SelectResults
称为 .filter()
)之类的概念在以前的 SQLAlchemy 中不存在,并且
构造仅使用 “all-at-once”(一次性)构造风格,该风格现在在 select() 不再接受各种构造函数参数,列按位置传递 中已弃用。select()
随着新方法的兴起,该对象演变为
对象,并添加了新功能,例如能够选择单个列、能够一次选择多个实体、能够从 Query
对象而不是从 Query
对象构建子查询。目标是 select
应该具有 Query
的全部功能,因为它可以通过组合来完全构建 SELECT 语句,而无需显式使用 select
。与此同时,select()
也发展出了 “生成式” 方法,例如 select()
和 Select.where()
。Select.order_by()
在现代 SQLAlchemy 中,此目标已实现,这两个对象现在在功能上完全重叠。统一这些对象的主要挑战是
对象需要保持完全独立于 ORM。为了实现这一点,select()
的绝大多数逻辑已移至 SQL 编译阶段,在该阶段,ORM 特定的编译器插件接收 Query
构造,并根据 ORM 风格的查询解释其内容,然后再传递给核心级别的编译器以创建 SQL 字符串。随着新的 SQL 编译缓存系统 的出现,此 ORM 逻辑的大部分也被缓存。Select
ORM 查询 - get() 方法移至 Session¶
概要
方法保留用于旧式目的,但主要接口现在是 Query.get()
方法Session.get()
# legacy usage
user_obj = session.query(User).get(5)
迁移到 2.0
在 1.4 / 2.0 中,
对象添加了一个新的 Session
方法Session.get()
# 1.4 / 2.0 cross-compatible use
user_obj = session.get(User, 5)
讨论
对象将在 2.0 中成为遗留对象,因为现在可以使用 Query
对象进行 ORM 查询。由于 select()
方法定义了与 Query.get()
的特殊交互,并且甚至不一定发出查询,因此它更适合成为 Session
的一部分,它类似于其他“标识”方法,例如 Session
和 refresh
。merge
SQLAlchemy 最初包含 “get()” 是为了类似于 Hibernate Session.load()
方法。正如经常发生的情况一样,我们有点弄错了,因为此方法实际上更多地与
有关,而不是与编写 SQL 查询有关。Session
ORM 查询 - 关系上的连接/加载使用属性,而不是字符串¶
概要
这指的是
以及查询选项(如 Query.join()
)之类的模式,这些模式当前接受字符串属性名称或实际类属性的混合形式。字符串形式将在 2.0 中全部删除joinedload()
# string use removed
q = session.query(User).join("addresses")
# string use removed
q = session.query(User).options(joinedload("addresses"))
# string use removed
q = session.query(Address).filter(with_parent(u1, "addresses"))
迁移到 2.0
现代 SQLAlchemy 1.x 版本支持推荐的技术,即使用映射属性
# compatible with all modern SQLAlchemy versions
q = session.query(User).join(User.addresses)
q = session.query(User).options(joinedload(User.addresses))
q = session.query(Address).filter(with_parent(u1, User.addresses))
相同的技术适用于 2.0 风格 的用法
# SQLAlchemy 1.4 / 2.0 cross compatible use
stmt = select(User).join(User.addresses)
result = session.execute(stmt)
stmt = select(User).options(joinedload(User.addresses))
result = session.execute(stmt)
stmt = select(Address).where(with_parent(u1, User.addresses))
result = session.execute(stmt)
讨论
字符串调用形式是模糊的,需要内部机制进行额外的工作来确定合适的路径并检索正确的映射属性。通过直接传递 ORM 映射属性,不仅可以预先传递必要的信息,而且该属性也是类型化的,并且更可能与 IDE 和 pep-484 集成兼容。
ORM 查询 - 移除使用属性列表而非单独调用进行链式操作¶
概要
接受属性列表形式的“链式”连接和加载器选项将被移除
# chaining removed
q = session.query(User).join("orders", "items", "keywords")
迁移到 2.0
使用单独调用 Query.join()
以实现 1.x/2.0 交叉兼容使用
q = session.query(User).join(User.orders).join(Order.items).join(Item.keywords)
对于 2.0 风格 的用法,Select
具有与 Select.join()
相同的行为,并且还具有新的 Select.join_from()
方法,允许显式指定左侧
# 1.4 / 2.0 cross compatible
stmt = select(User).join(User.orders).join(Order.items).join(Item.keywords)
result = session.execute(stmt)
# join_from can also be helpful
stmt = select(User).join_from(User, Order).join_from(Order, Item, Order.items)
result = session.execute(stmt)
讨论
移除属性链式操作符合简化方法(例如 Select.join()
)的调用接口的目标。
ORM 查询 - 移除 join(…, aliased=True), from_joinpoint¶
概要
移除了 Query.join()
上的 aliased=True
选项,以及 from_joinpoint
标志
# no longer supported
q = (
session.query(Node)
.join("children", aliased=True)
.filter(Node.name == "some sub child")
.join("children", from_joinpoint=True, aliased=True)
.filter(Node.name == "some sub sub child")
)
迁移到 2.0
使用显式别名代替
n1 = aliased(Node)
n2 = aliased(Node)
q = (
select(Node)
.join(Node.children.of_type(n1))
.where(n1.name == "some sub child")
.join(n1.children.of_type(n2))
.where(n2.name == "some sub child")
)
讨论
Query.join()
上的 aliased=True
选项是另一个似乎几乎不被使用的功能,这是基于广泛的代码搜索以查找此功能的实际使用情况得出的结论。 aliased=True
标志所需的内部复杂性是巨大的,并且将在 2.0 版本中被移除。
大多数用户不熟悉这个标志,但它允许对连接中的元素进行自动别名,然后将自动别名应用于过滤器条件。 最初的用例是协助处理长的自引用连接链,如上面的示例所示。 然而,过滤器条件的自动调整在内部非常复杂,并且几乎从不在实际应用中使用。 这种模式还会导致一些问题,例如如果需要在链中的每个链接处添加过滤器条件; 那么该模式必须使用 from_joinpoint
标志,但 SQLAlchemy 开发人员绝对找不到任何在实际应用中使用此参数的案例。
aliased=True
和 from_joinpoint
参数是在 Query
对象在沿关系属性进行连接方面尚不具备良好功能时开发的,诸如 PropComparator.of_type()
之类的函数不存在,并且 aliased()
构造本身在早期也不存在。
将 DISTINCT 与附加列一起使用,但仅选择实体¶
概要
当使用 distinct 时,Query
将自动在 ORDER BY 中添加列。 以下查询将从所有 User 列以及 “address.email_address” 中选择,但仅返回 User 对象
# 1.xx code
result = (
session.query(User)
.join(User.addresses)
.distinct()
.order_by(Address.email_address)
.all()
)
在 2.0 版本中,“email_address” 列将不会自动添加到 columns 子句中,并且上述查询将失败,因为关系数据库不允许您在使用 DISTINCT 时对 “address.email_address” 进行 ORDER BY,如果它也不在 columns 子句中。
迁移到 2.0
在 2.0 版本中,必须显式添加该列。 要解决仅返回主实体对象,而不返回额外列的问题,请使用 Result.columns()
方法
# 1.4 / 2.0 code
stmt = (
select(User, Address.email_address)
.join(User.addresses)
.distinct()
.order_by(Address.email_address)
)
result = session.execute(stmt).columns(User).all()
讨论
这个例子说明了 Query
有限的灵活性导致需要添加隐式、“神奇”行为的情况; “email_address” 列被隐式添加到 columns 子句中,然后额外的内部逻辑将从实际返回的结果中省略该列。
新方法简化了交互,并使正在发生的事情变得显式,同时仍然可以在不造成不便的情况下满足原始用例。
从查询本身作为子查询进行选择,例如 “from_self()”¶
概要
Query.from_self()
方法将从 Query
中移除
# from_self is removed
q = (
session.query(User, Address.email_address)
.join(User.addresses)
.from_self(User)
.order_by(Address.email_address)
)
迁移到 2.0
aliased()
构造可用于针对根据任何任意可选对象定义的实体发出 ORM 查询。 它在 1.4 版本中得到了增强,可以顺利地适应针对同一子查询多次用于不同实体的情况。 这可以在 1.x 风格 中与 Query
一起使用,如下所示; 请注意,由于最终查询希望根据 User
和 Address
实体进行查询,因此创建了两个单独的 aliased()
构造
from sqlalchemy.orm import aliased
subq = session.query(User, Address.email_address).join(User.addresses).subquery()
ua = aliased(User, subq)
aa = aliased(Address, subq)
q = session.query(ua, aa).order_by(aa.email_address)
相同的形式可以在 2.0 风格 中使用
from sqlalchemy.orm import aliased
subq = select(User, Address.email_address).join(User.addresses).subquery()
ua = aliased(User, subq)
aa = aliased(Address, subq)
stmt = select(ua, aa).order_by(aa.email_address)
result = session.execute(stmt)
讨论
Query.from_self()
方法是一个非常复杂且很少使用的方法。 此方法的目的是将 Query
转换为子查询,然后返回一个新的 Query
,该查询从该子查询中进行 SELECT 操作。 此方法的复杂之处在于,返回的查询会对 ORM 实体和列进行自动转换,以便在子查询的上下文中在 SELECT 中声明,并且它允许修改要从中进行 SELECT 操作的实体和列。
因为 Query.from_self()
将大量的隐式转换打包到它生成的 SQL 中,虽然它确实允许以非常简洁的方式执行某种模式,但由于它不容易理解,因此在实际应用中很少使用此方法。
新方法利用 aliased()
构造,以便 ORM 内部机制无需猜测应调整哪些实体和列以及以何种方式进行调整; 在上面的示例中,ua
和 aa
对象(均为 AliasedClass
实例)为内部机制提供了明确的标记,指示子查询应在何处被引用,以及正在考虑查询的给定组件的哪个实体列或关系。
SQLAlchemy 1.4 还具有改进的标签样式,不再需要使用包含表名的长标签来消除来自不同表的同名列的歧义。 在上面的示例中,即使我们的 User
和 Address
实体具有重叠的列名,我们也可以同时从这两个实体中选择,而无需指定任何特定的标签
# 1.4 / 2.0 code
subq = select(User, Address).join(User.addresses).subquery()
ua = aliased(User, subq)
aa = aliased(Address, subq)
stmt = select(ua, aa).order_by(aa.email_address)
result = session.execute(stmt)
上述查询将消除 User
和 Address
的 .id
列的歧义,其中 Address.id
将被渲染和跟踪为 id_1
SELECT anon_1.id AS anon_1_id, anon_1.id_1 AS anon_1_id_1,
anon_1.user_id AS anon_1_user_id,
anon_1.email_address AS anon_1_email_address
FROM (
SELECT "user".id AS id, address.id AS id_1,
address.user_id AS user_id, address.email_address AS email_address
FROM "user" JOIN address ON "user".id = address.user_id
) AS anon_1 ORDER BY anon_1.email_address
从备选可选对象中选择实体; Query.select_entity_from()¶
概要
Query.select_entity_from()
方法将在 2.0 版本中移除
subquery = session.query(User).filter(User.id == 5).subquery()
user = session.query(User).select_entity_from(subquery).first()
迁移到 2.0
正如 从查询本身作为子查询进行选择,例如 “from_self()” 中描述的情况一样,aliased()
对象提供了一个可以实现 “从子查询中选择实体” 等操作的统一位置。 使用 1.x 风格
from sqlalchemy.orm import aliased
subquery = session.query(User).filter(User.name.like("%somename%")).subquery()
ua = aliased(User, subquery)
user = session.query(ua).order_by(ua.id).first()
使用 2.0 风格
from sqlalchemy.orm import aliased
subquery = select(User).where(User.name.like("%somename%")).subquery()
ua = aliased(User, subquery)
# note that LIMIT 1 is not automatically supplied, if needed
user = session.execute(select(ua).order_by(ua.id).limit(1)).scalars().first()
讨论
这里的要点与 从查询本身作为子查询进行选择,例如 “from_self()” 中讨论的要点基本相同。 Query.select_from_entity()
方法是另一种指示查询从备选可选对象加载特定 ORM 映射实体行的方法,这涉及到让 ORM 对该实体应用自动别名,无论它在查询的后续位置(例如在 WHERE 子句或 ORDER BY 中)使用该实体。 这种极其复杂的功能很少以这种方式使用,与 Query.from_self()
的情况一样,当使用显式的 aliased()
对象时,无论从用户角度还是从 SQLAlchemy ORM 内部机制如何处理它的角度来看,都更容易理解正在发生的事情。
默认情况下 ORM 行不再去重¶
概要
session.execute(stmt)
返回的 ORM 行不再自动 “去重”。 这通常会是一个受欢迎的更改,除非在使用集合时使用了 “连接迫切加载” 加载器策略的情况。
# In the legacy API, many rows each have the same User primary key, but
# only one User per primary key is returned
users = session.query(User).options(joinedload(User.addresses))
# In the new API, uniquing is available but not implicitly
# enabled
result = session.execute(select(User).options(joinedload(User.addresses)))
# this actually will raise an error to let the user know that
# uniquing should be applied
rows = result.all()
迁移到 2.0 版本
当使用集合的连接加载时,需要调用 Result.unique()
方法。 ORM 实际上将设置一个默认的行处理器,如果未完成此操作,该处理器将引发错误,以确保连接迫切加载的集合不会返回重复的行,同时仍保持显式性
# 1.4 / 2.0 code
stmt = select(User).options(joinedload(User.addresses))
# statement will raise if unique() is not used, due to joinedload()
# of a collection. in all other cases, unique() is not needed.
# By stating unique() explicitly, confusion over discrepancies between
# number of objects/ rows returned vs. "SELECT COUNT(*)" is resolved
rows = session.execute(stmt).unique().all()
讨论
这里的情况有点不寻常,因为 SQLAlchemy 要求调用一个它实际上完全能够自动执行的方法。 要求调用该方法的原因是确保开发人员 “选择加入” 使用 Result.unique()
方法,这样当行的直接计数与实际结果集中的记录计数不冲突时,他们就不会感到困惑,这多年来一直是用户困惑和错误报告的长期来源。 默认情况下,去重不会在任何其他情况下发生,这将提高性能,并提高在自动去重导致混淆结果的情况下的清晰度。
尽管当使用连接迫切加载集合时,必须调用 Result.unique()
方法会带来不便,但在现代 SQLAlchemy 中,selectinload()
策略提供了一种面向集合的迫切加载器,在大多数方面都优于 joinedload()
,应该优先使用。
“动态” 关系加载器被 “仅写入” 取代¶
概要
lazy="dynamic"
关系加载器策略(在 动态关系加载器 中讨论)使用了 Query
对象,该对象在 2.0 版本中已成为遗留对象。 “动态” 关系与 asyncio 不直接兼容,需要使用解决方法,此外,它也没有实现其最初的防止大型集合迭代的目的,因为它有几种行为会隐式发生迭代。
引入了一种新的加载器策略,称为 lazy="write_only"
,它通过 WriteOnlyCollection
集合类提供了一个非常严格的 “无隐式迭代” API,并且还与 2.0 风格的语句执行集成,支持 asyncio 以及与新的 启用 ORM 的批量 DML 功能集的直接集成。
同时,lazy="dynamic"
在 2.0 版本中仍然完全支持; 应用程序可以延迟迁移此特定模式,直到它们完全使用 2.0 系列。
迁移到 2.0
新的 “仅写入” 功能仅在 SQLAlchemy 2.0 中可用,不属于 1.4 版本。 同时,lazy="dynamic"
加载器策略在 2.0 版本中仍然完全支持,甚至包括新的 pep-484 和注解映射支持。
因此,从 “动态” 迁移的最佳策略是等到应用程序完全在 2.0 版本上运行,然后直接从 AppenderQuery
(“动态” 策略使用的集合类型)迁移到 WriteOnlyCollection
(“仅写入” 策略使用的集合类型)。
然而,在 1.4 版本下,可以使用一些技术以更 “2.0” 风格的方式使用 lazy="dynamic"
。 有两种方法可以实现 2.0 风格的、根据特定关系进行的查询
利用现有
lazy="dynamic"
关系上的Query.statement
属性。 我们可以直接将诸如Session.scalars()
之类的方法与动态加载器一起使用,如下所示class User(Base): __tablename__ = "user" posts = relationship(Post, lazy="dynamic") jack = session.get(User, 5) # filter Jack's blog posts posts = session.scalars(jack.posts.statement.where(Post.headline == "this is a post"))
使用
with_parent()
函数直接构造select()
构造from sqlalchemy.orm import with_parent jack = session.get(User, 5) posts = session.scalars( select(Post) .where(with_parent(jack, User.posts)) .where(Post.headline == "this is a post") )
讨论
最初的想法是 with_parent()
函数应该足够了,但是继续利用关系本身上的特殊属性仍然很有吸引力,并且没有理由不能使 2.0 风格的构造也在这里工作。
新的 “仅写入” 加载器策略提供了一种新型集合,它不支持隐式迭代或项访问。 相反,读取集合的内容是通过调用其 .select()
方法来帮助构造适当的 SELECT 语句来执行的。 该集合还包括方法 .insert()
、.update()
、.delete()
,这些方法可用于为集合中的项发出批量 DML 语句。 以类似于 “动态” 功能的方式,还有方法 .add()
、.add_all()
和 .remove()
,这些方法将单个成员排队,以便使用工作单元进程进行添加或删除。 新功能的介绍请参见 新的 “仅写入” 关系策略取代 “动态”。
从 Session 中移除自动提交模式; 添加自动开始支持¶
概要
Session
将不再支持 “自动提交” 模式,即此模式
from sqlalchemy.orm import Session
sess = Session(engine, autocommit=True)
# no transaction begun, but emits SQL, won't be supported
obj = sess.query(Class).first()
# session flushes in a transaction that it begins and
# commits, won't be supported
sess.flush()
迁移到 2.0
在 “自动提交” 模式下使用 Session
的主要原因是使 Session.begin()
方法可用,以便框架集成和事件钩子可以控制此事件何时发生。 在 1.4 版本中,Session
现在具有 自动开始行为,这解决了此问题; 现在可以调用 Session.begin()
方法
from sqlalchemy.orm import Session
sess = Session(engine)
sess.begin() # begin explicitly; if not called, will autobegin
# when database access is needed
sess.add(obj)
sess.commit()
讨论
“自动提交” 模式是 SQLAlchemy 早期版本的另一个遗留物。 该标志一直存在,主要是为了支持显式使用 Session.begin()
,这已在 1.4 版本中得到解决,以及允许使用 “子事务”,子事务也在 2.0 版本中被移除。
Session “子事务” 行为已移除¶
概要
常与自动提交模式一起使用的 “子事务” 模式在 1.4 版本中也被弃用。 这种模式允许在已开始事务时使用 Session.begin()
方法,从而产生一个称为 “子事务” 的构造,它本质上是一个阻止 Session.commit()
方法实际提交的块。
迁移到 2.0
为了为使用此模式的应用程序提供向后兼容性,可以使用以下上下文管理器或基于装饰器的类似实现
import contextlib
@contextlib.contextmanager
def transaction(session):
if not session.in_transaction():
with session.begin():
yield
else:
yield
上述上下文管理器可以以与 “子事务” 标志工作方式相同的方式使用,例如在以下示例中
# method_a starts a transaction and calls method_b
def method_a(session):
with transaction(session):
method_b(session)
# method_b also starts a transaction, but when
# called from method_a participates in the ongoing
# transaction.
def method_b(session):
with transaction(session):
session.add(SomeObject("bat", "lala"))
Session = sessionmaker(engine)
# create a Session and call method_a
with Session() as session:
method_a(session)
为了与首选的惯用模式进行比较,begin 块应位于最外层。 这消除了单个函数或方法需要关注事务划分细节的需求
def method_a(session):
method_b(session)
def method_b(session):
session.add(SomeObject("bat", "lala"))
Session = sessionmaker(engine)
# create a Session and call method_a
with Session() as session:
with session.begin():
method_a(session)
讨论
这种模式已被证明在实际应用中令人困惑,并且应用程序最好确保最高级别的数据库操作使用单个 begin/commit 对执行。
2.0 迁移 - ORM 扩展和配方更改¶
Dogpile 缓存配方和水平分片使用新的 Session API¶
随着 Query
对象成为遗留对象,这两个以前依赖于 Query
对象子类化的配方现在使用 SessionEvents.do_orm_execute()
钩子。 有关示例,请参见 重新执行语句 部分。
Baked Query 扩展被内置缓存取代¶
baked query 扩展已被内置缓存系统取代,并且 ORM 内部机制不再使用它。
有关新缓存系统的完整背景信息,请参见 SQL 编译缓存。
Asyncio 支持¶
SQLAlchemy 1.4 包括对 Core 和 ORM 的 asyncio 支持。 新的 API 专门使用上面提到的 “future” 模式。 有关背景信息,请参见 Core 和 ORM 的异步 IO 支持。
flambé! 龙和 炼金术士 图像设计由 Rotem Yaari 创作并慷慨捐赠。
使用 Sphinx 7.2.6 创建。 文档上次生成时间:Tue 11 Mar 2025 02:40:17 PM EDT