SqlAlchemy 2.0 中文文档(五十六)(4)https://developer.aliyun.com/article/1563157
异步 IO 支持
SQLAlchemy 1.4 包括 Core 和 ORM 的异步 IO 支持。新 API 专门使用上述“future”模式。请参阅 Core 和 ORM 的异步 IO 支持以获取背景信息。
1.4->2.0 迁移路径
被认为是“SQLAlchemy 2.0”的最突出的架构特性和 API 更改实际上在 1.4 系列中已经完全可用,以提供从 1.x 到 2.x 系列的清晰升级路径,同时作为这些功能的 beta 平台。这些更改包括:
- 新的 ORM 语句范式
- Core 和 ORM 中的 SQL 缓存
- 新的声明性特性,ORM 集成
- 新的 Result 对象
- select() / case()接受位置表达式
- Core 和 ORM 的 asyncio 支持
上述要点链接到在 SQLAlchemy 1.4 中介绍的这些新范例的描述中。在 SQLAlchemy 1.4 有什么新功能?文档中。
对于 SQLAlchemy 2.0,所有标记为 2.0 不推荐使用的 API 功能和行为现已最终确定;特别是不再存在的主要 API 包括:
- 绑定的 MetaData 和无连接执行
- 连接上的模拟自动提交
- Session.autocommit 参数/模式
- select()的列表/关键字参数
- Python 2 支持
上述要点涉及 2.0 版本中最显著的完全不兼容的更改。应用程序适应这些更改以及其他更改的迁移路径首先被构建为转换到 SQLAlchemy 1.4 系列,其中“未来”API 可用以提供“2.0”工作方式,然后转换到 2.0 系列,其中上述不再使用的 API 以及其他 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 声明性系统、对 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
扩展点的代码。这是一个不常见的情况,但可能会特别影响一些使用特殊数据库提供逻辑的测试套件。通过搜索使用相对较新且鲜为人知的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 最低版本兼容 2.0)
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())
上述程序使用了许多用户可能已经将其识别为“遗留”的模式,即使用 Engine.execute()
方法的“无连接执行”API 的使用。当我们针对 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 python2 -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 文件中 - 在测试套件的设置中,设置一系列警告过滤器,以选择特定的警告子集来引发异常,或者被忽略(或记录)。逐个子组警告地进行工作。下面,为一个应用程序配置了一个警告过滤器,其中需要对核心级别的
.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”过滤器捕获的新警告可以添加到“错误”列表中以解决。
- 一旦不再发出警告,过滤器可以被移除。
迁移到 2.0 步骤四 - 在引擎上使用future
标志
Engine
对象在 2.0 版本中具有更新的事务级 API。在 1.4 中,通过将标志future=True
传递给create_engine()
函数,可以使用此新 API。
当使用create_engine.future
标志时,Engine
和Connection
对象完全支持 2.0 API,不再支持任何旧特性,包括Connection.execute()
的新参数格式,删除了“隐式自动提交”,字符串语句需要使用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 步骤五 - 在会话上使用future
标志
在 2.0 版本中,Session
对象还具有更新的事务/连接级 API。在 1.4 中可通过在 Session
或 sessionmaker
上使用 Session.future
标志来使用此 API。
Session
对象支持“future”模式,并涉及以下更改:
- 当解析用于连接的引擎时,
Session
不再支持“绑定的元数据”。这意味着必须将一个Engine
对象传递给构造函数(这可以是传统或未来风格的对象)。 Session.begin.subtransactions
标志不再受支持。Session.commit()
方法始终向数据库发出 COMMIT,而不是尝试调和“子事务”。Session.rollback()
方法总是一次性回滚整个事务堆栈,而不是尝试保留“子事务”。
在 1.4 中,Session
还支持更灵活的创建模式,现在与 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 级 vs. Engine 级事务控制 部分,比较 Session
创建模式与 Connection
创建模式的对比。
一旦应用程序通过了所有测试/使用 SQLALCHEMY_WARN_20=1
运行,并且所有 exc.RemovedIn20Warning
的出现都设置为引发错误,应用程序就准备好了!。
接下来的章节将详细介绍所有主要 API 修改的具体更改。
迁移到 2.0 第六步 - 在显式类型的 ORM 模型中添加 __allow_unmapped__
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__
属性,这将导致在这些情况下完全忽略新 Declarative 系统中的注解。
注:
__allow_unmapped__
指令仅适用于 ORM 的运行时行为。它不会影响 Mypy 的行为,上述映射仍然需要安装 Mypy 插件。对于完全符合 2.0 样式的 ORM 模型,在不需要插件的情况下可以正确进行类型标注,请遵循 迁移现有映射 中的迁移步骤。
下面的示例说明了将 __allow_unmapped__
应用于 Declarative 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__
属性支持,使得能够保持对 1.4 风格的显式注释关系的支持,而不使用Mapped
也能够保持可用性。### 迁移到 2.0 第七步 - 对 SQLAlchemy 2.0 发行版进行测试
如前所述,SQLAlchemy 2.0 具有额外的 API 和行为更改,旨在向后兼容,但仍然可能引入一些不兼容性。 因此,在整个迁移过程完成后,最后一步是针对最新版本的 SQLAlchemy 2.0 进行测试,以纠正可能存在的任何剩余问题。
在 SQLAlchemy 2.0 的新特性是什么? 指南中提供了对超出基本 1.4->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()
方法的工作方式相同。 - 许多核心和 ORM 语句对象现在在编译阶段执行大部分构建和验证工作 - 一些与
Query
或Select
构建相关的错误消息可能要等到编译/执行阶段才会被发出,而不是在构建时。这可能会影响一些针对失败模式进行测试的测试套件。
要查看 SQLAlchemy 1.4 变更的完整概述,请参阅 SQLAlchemy 1.4 有什么新特性?文档。
迁移到 2.0 第一步 - 仅支持 Python 3(Python 3.7 最低版本以实现 2.0 兼容性)
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())
上述程序使用了许多用户已经将其识别为“遗留”的模式,即使用Engine.execute()
方法的“无连接执行”API。当我们对上述程序运行 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 python2 -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”列表中以解决。
- 一旦不再发出任何警告,过滤器就可以被移除。
SqlAlchemy 2.0 中文文档(五十六)(6)https://developer.aliyun.com/article/1563159