变更和迁移
SQLAlchemy 变更日志和迁移指南现在已集成到主要文档中。
当前迁移指南
对于 SQLAlchemy 2.0,有两个单独的文档;"主要迁移指南"详细介绍了如何将 SQLAlchemy 1.4 应用程序更新为兼容 SQLAlchemy 2.0。"有什么新内容?"文档详细介绍了 SQLAlchemy 2.0 中的主要新功能、功能和行为。
- SQLAlchemy 2.0 - 主要迁移指南
- 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 1.4 应用程序更新为兼容 SQLAlchemy 2.0。"有什么新内容?"文档详细介绍了 SQLAlchemy 2.0 中的主要新功能、功能和行为。
- SQLAlchemy 2.0 - 主要迁移指南
- 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.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 发展早期留下的残余假设以及由于增量引入关键 API 特性(如 Query 和声明)而导致的残余残留物。它还希望标准化一些已被证明非常有效的较新功能。
1.4->2.0 迁移路径
被认为是“SQLAlchemy 2.0”的最突出的架构特性和 API 变化实际上已经在 1.4 系列中完全可用,以提供从 1.x 到 2.x 系列的清晰升级路径,并为这些特性本身提供一个 beta 平台。这些变化包括:
- 新的 ORM 语句范例
- 核心和 ORM 中的 SQL 缓存
- 新的声明特性,ORM 集成
- 新的结果对象
- select() / case() 接受位置表达式
- Core 和 ORM 的 asyncio 支持
上述项目链接到在 SQLAlchemy 1.4 中介绍这些新范式的描述,位于 SQLAlchemy 1.4 中的新功能是什么?文档中。
对于 SQLAlchemy 2.0,所有被标记为 2.0 废弃的 API 特性和行为现在已经被最终确定;特别是,不再存在的主要 API包括:
- 元数据限制和无连接执行
- 在 Connection 上模拟的自动提交
- Session.autocommit 参数/模式
- 对 select() 的列表 / 关键字参数
- Python 2 支持
上述项目指的是在 2.0 版本中最突出的完全不兼容的更改,这些更改已在 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 和行为更改,这些更改在运行时可能会表现出不同的行为。
第一个先决条件,第一步 - 一个可用的 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类的代码进行 Github 搜索,找到了两个不受此更改影响的项目。 - 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(2.0 兼容性最低要求 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 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”过滤器捕获的新警告添加到要解决的“错误”列表中。
- 一旦不再发出警告,就可以移除过滤器。
迁移到 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()的新参数格式、去除“隐式自动提交”、字符串语句要求使用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标志
Session对象还在版本 2.0 中提供了更新的事务/连接级 API。此 API 在 1.4 中可通过在Session或sessionmaker上使用Session.future标志获得。
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-level vs. Engine level transaction control 部分,了解 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 模型,可以在 Migrating an Existing Mapping 中遵循迁移步骤,以便在 Mypy 下正确类型化无需插件。
下面的示例说明了将__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__属性支持,以允许不使用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 变化的基本集合。
SqlAlchemy 2.0 中文文档(五十六)(2)https://developer.aliyun.com/article/1563155