迁移到Alchemy
关于此文档
SQLAlchemy 2.0为核心组件和ORM组件中的各种关键SQLAlchemy使用模式带来了重大转变。这个版本的目标是对SQLAlchemy早期以来的一些最基本的假设做一点小小的调整,并提供一个新的简化的使用模型,这个模型希望在核心和ORM组件之间更加简洁和一致,并且更加有能力。Python从python3变成python3以及python3逐渐出现的类型系统是这种转变的最初启示,Python社区的性质也在不断变化,Python社区现在不仅包括核心数据库程序员,而且还包括一个由数据科学家和许多不同学科的学生组成的新社区。
SQLAlChemy从Python2.3开始,它没有上下文管理器,没有函数修饰符,Unicode是第二类特性,以及今天未知的各种其他缺点。SQLAlChemy 2.0中最大的变化是针对SQLAlChemy开发的早期阶段遗留下来的剩余假设,以及逐步引入关键API功能(如 Query
而且是陈述性的。它还希望标准化一些已被证明非常有效的较新功能。
概述
sqlalchemy2.0转换在sqlalchemy1.4版本中表现为一系列步骤,这些步骤允许使用渐进的迭代过程将任何大小或复杂程度的应用程序迁移到sqlalchemy2.0。从Python2到Python3过渡的经验教训启发了一个系统,它希望尽可能地不需要任何“突破性”的更改,或者不需要进行任何普遍或根本不需要的更改。
作为证明2.0体系结构和允许完全迭代的过渡环境的一种手段,2.0的新API和特性的整个范围都在1.4系列中提供,这包括主要的新功能领域,如SQL缓存系统、新的ORM语句执行模型,ORM和Core的新事务范式,一个新的ORM声明性系统,它统一了经典和声明性映射,支持Python数据类,以及对Core和ORM的异步支持。
实现2.0迁移的步骤如下;总体而言,总体策略是,一旦应用程序在1.4上运行,并且所有警告标志都打开,并且不发出任何2.0弃用警告,它现在就与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版应用程序
一旦应用程序可以在sqlalchemy1.3上运行,下一步就是让它在sqlalchemy1.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()将连接条件添加到当前查询,而不是创建子查询 -有点关系
Select
班级特色.join()
和.outerjoin()
方法隐式创建子查询,然后返回Join
构造,这将再次基本上是无用的,并产生了许多混乱。决定继续使用更有用的2.0风格的连接构建方法,这些方法现在的工作方式与ORM相同Query.join()
方法。许多Core和ORM语句对象现在在编译阶段执行大部分构造和验证 -一些与构造
Query
或Select
在编译/执行之前,而不是在构建时,才可以发射。这可能会影响一些针对故障模式进行测试的测试套件。
有关SQLAlchemy 1.4更改的完整概述,请参见 SQLAlchemy 1.4有什么新功能? 文件。
迁移到2.0步骤一-仅限于python3(python3.6最低)
SQLAlchemy 2.0最初的灵感来自Python2的EOL是在2020年。与其他主要项目相比,SQLAlchemy要花费更长的时间来放弃对python2.7的支持,因为它目前还不太碍事。然而,版本2.0希望开始接受 PEP 484 以及其他新特性,因此1.4版很可能是最后一个支持Python2的版本,即使有SQLAlchemy 1.5(目前也不太可能)。
为了使用SQLAlchemy 2.0,应用程序至少需要在 Python 3.6 写这篇文章的时候。SQLAlchemy 1.4现在在Python3系列中支持Python3.6或更高版本;在整个1.4系列中,应用程序可以在Python2.7或至少Python3.6上运行。
迁移到2.0步骤2-打开RemovedIn20警告
SQLAlchemy 1.4提供了一个条件弃用警告系统,其灵感来自Python“-3”标志,该标志指示正在运行的应用程序中的遗留模式。对于SQLAlchemy 1.4,则 RemovedIn20Warning
只有当环境变量 SQLALCHEMY_WARN_20
设置为 true
或 1
.
给出下面的示例程序:
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()
方法是 connectionless execution 系统。当我们针对1.4运行上述程序时,它返回一行:
$ python test3.py [(1,)]
要启用“2.0弃用模式”,我们将启用 SQLALCHEMY_WARN_20=1
变量,并另外确保 warnings filter 选择了不会抑制任何警告的选项:
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弃用模式”的目标是运行一个没有 RemovedIn20Warning
打开“2.0不推荐模式”的警告将准备好在SQLAlchemy 2.0中运行。
迁移到2.0步骤3-解决所有删除的20警告
可以迭代地开发代码来解决这些警告。在SQLAlchemy项目本身中,采用的方法如下:
启用
SQLALCHEMY_WARN_20=1
测试套件中的环境变量,对于SQLAlchemy,此变量位于毒性试验文件在测试套件的设置中,设置一系列警告过滤器,这些过滤器将为特定的警告子集选择引发异常或被忽略(或记录)。一次只处理一个子组警告。下面,为更改到核心级别的应用程序配置警告筛选器
.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, )
在应用程序中解决每个警告子类别时,可以将“始终”筛选器捕获的新警告添加到要解决的“错误”列表中。
一旦不再发出警告,就可以移除过滤器。
迁移到2.0步骤4-使用 future
发动机上的标志
这个 Engine
对象在版本2.0中具有更新的事务级API。在1.4中,这个新的API通过传递标志可用 future=True
到 create_engine()
功能。
当 create_engine.future
使用了标志 Engine
和 Connection
对象完全支持2.0API,完全不支持任何旧功能,包括 Connection.execute()
,删除“隐式自动提交”字符串语句需要 text()
除非 Connection.exec_driver_sql()
方法,并且 Engine
被移除。
如果全部 RemovedIn20Warning
已解决有关使用的警告 Engine
和 Connection
然后 create_engine.future
标志可能已启用,不应引发错误。
有关新发动机的说明,请参见 Engine
它提供了一个新的 Connection
对象。除上述变更外, Connection
对象特征 Connection.commit()
和 Connection.rollback()
方法,以支持新的“按需提交”操作模式:
from sqlalchemy import create_engine engine = create_engine("postgresql:///") 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步骤4-使用 future
会话标志
这个 Session
对象还具有2.0版中更新的事务/连接级别API。此API在1.4中可用,使用 Session.future
旗上 Session
或在 sessionmaker
.
这个 Session
对象支持“未来”模式,并包含以下更改:
这个
Session
当它解析引擎以用于连接时,不再支持“绑定元数据”。这意味着Engine
对象 must 传递给构造函数(这可能是一个遗留或未来样式的对象)。这个
Session.begin.subtransactions
不再支持标志。这个
Session.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
.
一旦应用程序通过所有测试/运行 SQLALCHEMY_WARN_20=1
以及所有 exc.RemovedIn20Warning
事件设置为引发错误, 申请就绪! .
接下来的部分将详细说明对所有主要API修改所做的具体更改。
2.0迁移-核心连接/事务
库级(但不是驱动程序级)“自动提交”从核心和ORM中删除
Synopsis
在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的常见解决方法“autocommit”执行选项:
conn = engine.connect() # won't autocommit in 2.0 conn.execute( text("EXEC my_procedural_thing()").execution_options(autocommit=True) )
迁移到2.0
交叉兼容的方法 1.x style 和 2.0 style 执行就是利用 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()"))
使用时 2.0 style 与 create_engine.future
也可以使用标志“边走边提交”样式,作为 Connection
特征 汽车域 行为,在没有显式调用的情况下首次调用语句时发生 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 deprecations mode 启用时,将在发生不推荐使用的“自动提交”功能时发出警告,指示应在哪些位置记录显式事务。
Discussion
SQLAlchemy的第一个版本与PythonDBAPI的精神不符 (PEP 249 )它试图隐藏 PEP 249 强调事务的“隐式开始”和“显式提交”。15年后,我们现在看到这本质上是一个错误,因为SQLAlchemy的许多模式试图“隐藏”事务的存在,从而导致了一个更加复杂的API,它的工作方式不一致,特别是对于那些不熟悉关系数据库和ACID事务的用户来说尤其如此。SQLAlchemy 2.0将消除所有隐式提交事务的尝试,并且使用模式总是要求用户以某种方式划分事务的“开始”和“结束”,就像在Python中读写文件有“开始”和“结束”一样。
对于纯文本语句的autocommit,实际上有一个正则表达式来解析每个语句以检测autocommit!毫不奇怪,此正则表达式一直无法适应各种类型的语句和存储过程,这些语句和存储过程意味着对数据库进行“写入”,从而导致持续的混乱,因为有些语句会在数据库中产生结果,而另一些则不会。通过阻止用户意识到事务性的概念,我们会得到很多错误报告这一个是因为用户不明白数据库总是使用事务,不管某个层是否自动提交它。
SQLAlchemy 2.0将要求每个级别上的所有数据库操作都显式地说明事务应该如何使用。对于绝大多数核心用例,已经推荐了以下模式:
with engine.begin() as conn: conn.execute(some_table.insert().values(foo='bar'))
对于“边执行边提交,或者改为回滚”的用法,类似于 Session
通常用于今天,“未来”版本 Connection
,它是从 Engine
它是使用 create_engine.future
标志,包括新的 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()
方法将返回 Connection
特点 汽车域 ,意思是 begin()
事件在首次使用execute方法时发出(但是请注意,Python DBAPI中没有实际的“BEGIN”)autobegin”是sqlalchemy1.4中的一个新模式,它的特点是 Connection
以及ORM Session
对象;autobegin允许 Connection.begin()
方法可以在第一次获取对象时显式调用,对于希望划分事务开始的方案,但如果不调用该方法,则在首次对对象执行操作时隐式地调用该方法。
删除“自动提交”与删除中讨论的“无连接”执行密切相关 “隐式”和“无连接”执行,“绑定元数据”已删除 . 所有这些遗留模式都是建立在这样一个事实之上的:当SQLAlchemy第一次创建时,Python没有上下文管理器或装饰器,因此没有方便的惯用模式来划分资源的使用。
驱动程序级别的自动提交仍然可用
真正的“autocommit”行为现在在大多数DBAPI实现中广泛可用,并且SQLAlchemy通过 Connection.execution_options.isolation_level
中讨论的参数 设置事务隔离级别,包括DBAPI Autocommit . 真正的自动提交被视为一个“隔离级别”,以便在使用自动提交时应用程序代码的结构不会发生变化 Connection.begin()
上下文管理器以及诸如 Connection.commit()
可能仍在使用,当DBAPI级别的autocommit被打开时,它们只是数据库驱动程序级别的无操作。
“隐式”和“无连接”执行,“绑定元数据”已删除
Synopsis
将 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 = conn.execute(t.select())
Discussion
核心文档已经在这里标准化了所需的模式,因此大多数现代应用程序可能在任何情况下都不必进行太多更改,但是仍然有许多应用程序仍然依赖于这些应用程序 engine.execute()
需要调整的呼叫。
“无连接”执行是指仍然相当流行的调用模式 .execute()
从 Engine
::
result = engine.execute(some_statement)
上述操作隐式获取 Connection
对象,并运行 .execute()
方法。虽然这似乎是一个简单的便利功能,但它已被证明会引发几个问题:
具有扩展字符串的程序
engine.execute()
调用变得越来越普遍,过度使用原本很少使用的功能,导致非事务性应用程序效率低下。新用户对两者之间的区别感到困惑engine.execute()
和connection.execute()
而这两种方法之间的细微差别往往不被理解。该功能依赖于“应用程序级自动提交”功能来实现,它本身也被删除了 inefficient and misleading .
为了处理结果集,
Engine.execute
返回包含未使用的游标结果的结果对象。这个游标结果必然仍然链接到DBAPI连接,该连接仍在打开的事务中,一旦结果集完全消耗了游标中等待的行,所有这些连接都将被释放。这意味着Engine.execute
当调用完成时,不会实际关闭它声称正在管理的连接资源。SQLAlchemy的“autoclose”行为已经过了很好的调整,用户一般不会报告来自这个系统的任何负面影响,但是它仍然是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()方法越严格,执行选项就越突出
Synopsis
可以与 sqlalchemy.engine.Connection()
SQLAlchemy 2.0中的execute方法被高度简化,删除了许多以前可用的参数模式。1.4系列中的新API在 sqlalchemy.future.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}] )
Discussion
使用 *args
和 **kwargs
已被移除,这既是为了消除猜测传递给该方法的参数类型的复杂性,也是为了为其他选项腾出空间,即 Connection.execute.execution_options
现在可以按语句提供选项的字典。该方法也进行了修改,以使其使用模式与 Session.execute()
方法,这是2.0风格中更为突出的API。
删除直接字符串SQL是为了解决 Connection.execute()
和 Session.execute()
,在前一种情况下,字符串被传递给驱动程序raw,在后一种情况下,它首先被转换为 text()
构造。只允许 text()
这也将可接受的参数格式限制为“命名”而不是“位置”。最后,从安全角度来看,stringsql用例越来越容易受到审查,而且 text()
construct已经成为文本SQL领域的一个显式边界,在这里必须关注不可信的用户输入。
结果行的行为类似于命名元组
Synopsis
版本1.4引入了 all new Result object 反过来又回来了 Row
对象,在使用“未来”模式时的行为类似于命名元组:
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()
取而代之的是收集。然而,这是一个不寻常的用例,因为结果行通常由已经知道其中存在哪些列的代码使用。
Discussion
已经是1.4的一部分,以前的 KeyedTuple
类中选择行时使用的 Query
对象已被替换为 Row
类,该类是同一 Row
方法时返回的Core语句结果。 create_engine.future
标记为 Engine
(当 create_engine.future
标志未设置,则核心结果集使用 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"])
这个 Row
ORM使用的类还支持通过实体或属性进行访问:
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] ))
参见
RowProxy不再是“代理”;现在称为Row,其行为类似于增强的命名元组
2.0迁移-核心使用
select()不再接受各种构造函数参数,列按位置传递
synopsis
这个 select()
构造以及相关的方法 FromClause.select()
将不再接受关键字参数来构建WHERE子句、FROM LIST和ORDER BY等元素。现在可以按位置发送列的列表,而不是以列表的形式发送。此外, case()
构造现在按位置接受其WHEN条件,而不是以列表形式接受::
# 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()
将得到支持。应从中选择的列/表列表应按位置传递。这个 select()
SQLAlchemy 1.4中的construct使用自动检测方案同时接受旧样式和新样式,因此下面的代码与1.4和2.0交叉兼容:
# 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" )
Discussion
多年来,SQLAlchemy已经为SQL构造开发了一种约定,可以将参数作为列表或位置参数接受。本公约规定 结构的 构成SQL语句结构的元素应该被传递 位置上 . 相反, data 构成SQL语句参数化数据的元素应该被传递 作为列表 . 多年来 select()
由于“where”子句将按位置传递的非常遗留的调用模式,construct无法顺利地参与此约定。SQLAlchemy 2.0通过更改 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不再接受关键字构造函数参数
Synopsis
以类似于之前对 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) )
Discussion
对于DML构造,API和内部构件的简化方式与 select()
构造。
2.0迁移-ORM配置
声明性成为一级API
Synopsis
这个 sqlalchemy.ext.declarative
包通常被移动到 sqlalchemy.orm
包裹。这个 declarative_base()
和 declared_attr()
功能的存在没有任何行为变化。一个新的超级实现 declarative_base()
被称为 registry
现在充当顶层ORM配置结构,它还提供了基于decorator的声明性和对与声明性注册表集成的经典映射的新支持。
迁移到2.0
更改导入:
from sqlalchemy.ext import declarative_base, declared_attr
到:
from sqlalchemy.orm import declarative_base, declared_attr
Discussion
在流行了十年左右之后 sqlalchemy.ext.declarative
包现在集成到 sqlalchemy.orm
命名空间,但声明性“extension”类除外,这些类仍然是声明性扩展。有关此更改的详细信息,请参阅1.4迁移指南 声明式现在集成到ORM中,具有新的特性 .
参见
映射Python类 -声明性、经典映射、数据类、属性等的所有新的统一文档。
原来的“mapper()”函数现在是声明性的核心元素,重命名为
Synopsis
这个 mapper()
函数移到后台,由更高级别的api调用。这个函数的新版本是方法 registry.map_imperatively()
取自 registry
对象。
迁移到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" ) })
Discussion
受大众需求的影响,“经典地图”仍然存在,然而新形式的地图是基于 registry
对象,可用作 registry.map_imperatively()
.
此外,“经典映射”的基本原理是保持 Table
不同于类的设置。声明式总是允许使用 hybrid declarative . 但是,要除去基类要求,第一个类 decorator 表单已添加。
作为另一个单独但相关的增强,支持 Python dataclasses 同时添加到声明性装饰器和经典映射表单中。
参见
映射Python类 -声明性、经典映射、数据类、属性等的所有新的统一文档。
2.0迁移-ORM使用
SQLAlchemy 2.0中最大的明显变化是使用 Session.execute()
与 select()
运行ORM查询,而不是使用 Session.query()
. 正如其他地方所提到的,目前还没有计划实际移除 Session.query()
API本身,因为它现在是通过内部使用新的API来实现的,所以它将保留为一个旧API,并且这两个API都可以自由使用。
下表介绍了调用表单的一般更改,并提供了每种技术的文档链接。各个迁移注释在下表的嵌入部分中,可能包括此处未概述的其他注释。