Release: 1.4.25 | Release Date: September 22, 2021

SQLAlchemy 1.4 Documentation

更改和迁移

迁移到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.viewonlyrelationship.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,此领域中确定的变更如下:

有关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 设置为 true1 .

给出下面的示例程序:

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项目本身中,采用的方法如下:

  1. 启用 SQLALCHEMY_WARN_20=1 测试套件中的环境变量,对于SQLAlchemy,此变量位于毒性试验文件

  2. 在测试套件的设置中,设置一系列警告过滤器,这些过滤器将为特定的警告子集选择引发异常或被忽略(或记录)。一次只处理一个子组警告。下面,为更改到核心级别的应用程序配置警告筛选器 .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,
      )
  3. 在应用程序中解决每个警告子类别时,可以将“始终”筛选器捕获的新警告添加到要解决的“错误”列表中。

  4. 一旦不再发出警告,就可以移除过滤器。

迁移到2.0步骤4-使用 future 发动机上的标志

这个 Engine 对象在版本2.0中具有更新的事务级API。在1.4中,这个新的API通过传递标志可用 future=Truecreate_engine() 功能。

create_engine.future 使用了标志 EngineConnection 对象完全支持2.0API,完全不支持任何旧功能,包括 Connection.execute() ,删除“隐式自动提交”字符串语句需要 text() 除非 Connection.exec_driver_sql() 方法,并且 Engine 被移除。

如果全部 RemovedIn20Warning 已解决有关使用的警告 EngineConnection 然后 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 对象支持“未来”模式,并包含以下更改:

  1. 这个 Session 当它解析引擎以用于连接时,不再支持“绑定元数据”。这意味着 Engine 对象 must 传递给构造函数(这可能是一个遗留或未来样式的对象)。

  2. 这个 Session.begin.subtransactions 不再支持标志。

  3. 这个 Session.commit() 方法总是向数据库发出提交,而不是尝试协调“子事务”。

  4. 这个 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 style2.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 stylecreate_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

对于架构级模式,显式使用 EngineConnection 是必需的。这个 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]
    ))

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类 -声明性、经典映射、数据类、属性等的所有新的统一文档。

声明式现在集成到ORM中,具有新的特性

原来的“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都可以自由使用。

下表介绍了调用表单的一般更改,并提供了每种技术的文档链接。各个迁移注释在下表的嵌入部分中,可能包括此处未概述的其他注释。

主要ORM查询模式概述

1.x style 形式

2.0 style 形式

也见

session.query(User).get(42)
session.get(User, 42)

ORM Query-get()方法移动到会话

session.query(User).all()
session.execute(
    select(User)
).scalars().all()

与Core Select统一的ORM查询

Result.scalars()

session.query(User).\
filter_by(name='some user').one()
session.execute(
    select(User).
    filter_by(name="some user")
).scalar_one()

与Core Select统一的ORM查询

Result.scalar_one()

session.query(User).\
filter_by(name='some user').first()
session.execute(
  select(User).
  filter_by(name="some user").
  limit(1)
).scalars().first()

与Core Select统一的ORM查询

Result.first()

session.query(User).options(
    joinedload(User.addresses)
).all()
session.execute(
    select(User).
    options(
      joinedload(User.addresses)
    )
).unique().all()

默认情况下,ORM行未被验证

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()

与Core Select统一的ORM查询

联接

session.query(User).from_statement(
    text("select * from users")
).all()
session.execute(
    select(User).
    from_statement(
        text("select * from users")
    )
).scalars().all()

从文本和核心语句获取ORM结果

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()

ORM执行选项

填充现有

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")
)

使用任意WHERE子句更新和删除

session.query(User).count()
session.scalar(select(func.count()).select_from(User))
session.scalar(select(func.count(User.id)))

Session.scalar()

与Core Select统一的ORM查询

Synopsis

这个 Query 对象(以及 BakedQueryShardedQuery 扩展)成为长期遗留对象,由直接使用 select()Session.execute() 方法。从返回的结果 Query 以对象或元组列表的形式,或作为标量ORM对象从 Session.execute() 一致地 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 对象以及 Query 接口的可用性不会影响新的接口,对象将保留在2.0中,但不再是文档的一部分,也不会在大多数情况下支持它。这个 select() 构造现在适用于核心和ORM用例,当通过 Session.execute() 方法将返回面向ORM的结果,即,如果请求的是ORM对象。

这个 Select() 建造 添加了许多新方法 为了与 Query 包括 Select.filter() Select.filter_by() ,新返工 Select.join()Select.outerjoin() 方法, Select.options() 其他更多的补充方法 QueryQuery.populate_existing() 通过执行选项实现。

返回结果以 Result 对象,SQLAlchemy的新版本 ResultProxy 对象,它还添加了许多新方法以与 Query 包括 Result.one()Result.all()Result.first()Result.one_or_none() 等。

这个 Result 但是,对象确实需要一些不同的调用模式,因为当第一次返回时它将 总是返回元组 它会的 不进行重复数据消除会导致内存不足 . 以返回单个ORM对象的方式 Query 是的,那个 Result.scalars() 必须首先调用修饰符。为了返回唯一的对象(这在使用联接的急切加载时是必需的),方法 Result.unique() 必须首先调用修饰符。

的所有新功能的文档 select() 包括执行选项等 ORM查询指南 .

下面是一些如何迁移到的示例 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()

Discussion

事实上SQLAlchemy同时拥有 select() 建造一个独立的 Query 对象具有一个极其相似但基本上不兼容的接口,这可能是SQLAlchemy中最大的不一致性,这种不一致性是随着时间的推移进行的小的增量添加而产生的,这两个主要api是不同的。

Alchemy的第一个版本 Query 对象根本不存在。最初的想法是 Mapper 构造本身可以选择行,并且 Table 对象(而不是类)将用于在核心样式方法中创建各种标准。这个 Query 在SQLAlchemy的历史上,作为一个新的“可构建”查询对象的用户建议,最初名为 SelectResults 被接受了。像 .where() 方法,其中 SelectResults 打电话 .filter() ,以前在SQLAlchemy中不存在,并且 select() construct只使用了现在不推荐使用的“all-at-once”构造样式 select()不再接受各种构造函数参数,列按位置传递 .

随着新方法的出现,对象演变成 Query 对象作为新功能,例如可以选择单个列、能够一次选择多个实体、能够从 Query 对象而不是来自 select 对象已添加。目标就是这样 Query 应该具有 select 因为它可以被组合成完全构建SELECT语句,而不显式地使用 select() 需要。同时, select() 也进化出了“生成”方法,比如 Select.where()Select.order_by() .

在现代SQLAlchemy中,这个目标已经实现,现在这两个对象在功能上完全重叠。统一这些对象的主要挑战是 select() 需要保留的对象 完全不可知论者 . 为了实现这一点,绝大多数逻辑来自 Query 已经进入了SQL编译阶段,ORM特定的编译器插件接收 Select 在传递给核心级编译器以创建SQL字符串之前,根据ORM样式的查询构造和解释其内容。随着新的 SQL compilation caching system <change_4639> ,该ORM逻辑的大部分也被缓存。

ORM Query-get()方法移动到会话

Synopsis

这个 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)

Discussion

这个 Query 对象将成为2.0中的遗留对象,因为ORM查询现在可以使用 select() 对象。作为 Query.get() 方法定义与 Session 甚至不一定发出查询,它是 Session ,与其他“身份”方法类似,例如 refreshmerge .

SQLAlchemy最初包含“get()”以类似于Hibernate Session.load() 方法。就像通常的情况一样,我们把它弄错了,因为这个方法实际上更多的是关于 Session 而不是编写SQL查询。

ORM查询-连接/加载关系使用属性,而不是字符串

Synopsis

这指的是 Query.join() 以及查询选项,如 joinedload() 当前接受字符串属性名或实际类属性的混合。字符串形式将在2.0中全部删除:

# 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-style 风格使用:

# 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)

Discussion

字符串调用表单是不明确的,需要内部执行额外的工作来确定适当的路径并检索正确的映射属性。通过直接传递ORM-mapped属性,不仅预先传递了必要的信息,而且属性也是类型化的,并且更可能与IDEs和pep-484集成兼容。

ORM查询-删除使用属性列表而不是单个调用的链接

Synopsis

将删除接受列表中多个映射属性的联接和装入器选项的“链式”形式:

# 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-style 使用, 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)

Discussion

删除属性的链接是为了简化方法的调用接口,例如 Select.join() .

从连接点(…u)删除(…连接)

Synopsis

这个 aliased=True 选择权 Query.join() 被移除,就像 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")

Discussion

这个 aliased=True 选择权 Query.join() 是另一个几乎从未使用过的功能,它基于大量的代码搜索来查找此功能的实际使用情况。内部的复杂性 aliased=True 标志需要is 巨大的 ,并将在2.0中消失。

大多数用户不熟悉这个标志,但是它允许沿着连接对元素进行自动别名,然后将自动别名应用到过滤条件。最初的用例是协助长链的自引用联接,如上面的示例所示。然而,过滤标准的自动适应在内部非常复杂,几乎从未在现实世界的应用程序中使用过。该模式还会导致一些问题,例如是否需要在链中的每个环节添加过滤标准;然后该模式必须使用 from_joinpoint SQLAlChemy开发人员绝对不会发现此参数在实际应用程序中使用的标志。

这个 aliased=Truefrom_joinpoint 参数是在 Query 对象在连接关系属性方面还没有很好的能力,比如 PropComparator.of_type() 不存在,而且 aliased() 构造本身在早期并不存在。

对其他列使用DISTINCT,但只选择实体

Synopsis

Query 当使用distinct时,将自动按顺序添加列。以下查询将从所有用户列中选择以及“地址。电子邮件地址但仅返回用户对象:

# 1.xx code

result = session.query(User).join(User.addresses).\
    distinct().order_by(Address.email_address).all()

在2.0版中,“email_address”列不会自动添加到columns子句中,并且上面的查询将失败,因为关系数据库不允许您按地址。电子邮件地址如果不在columns子句中使用DISTINCT。

迁移到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()

Discussion

本案是 Query 导致需要添加隐式“神奇”行为的情况;将“email_address”列隐式添加到columns子句中,然后附加的内部逻辑将从实际返回的结果中忽略该列。

新的方法简化了交互,使所发生的事情变得明确,同时也使实现原始用例而不带来不便成为可能。

从查询本身作为子查询进行选择,例如“from_self()”

Synopsis

这个 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 style 具有 Query 如下所示;请注意,由于最终查询希望根据 UserAddress 实体,两个独立的 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 style ::

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)

Discussion

这个 Query.from_self() 方法是一种非常复杂的方法,很少使用。方法的目的是 Query 然后返回一个新的 Query 从该子查询中选择。这种方法的精妙之处在于,返回的查询应用了根据子查询在SELECT中声明的ORM实体和列的自动转换,并且允许从中选择实体和列进行修改。

因为 Query.from_self() 将大量的隐式转换打包到它生成的SQL中,虽然它确实允许某种模式非常简洁地执行,但是在现实世界中很少使用这种方法,因为它不容易理解。

新方法利用 aliased() 构造,这样ORM内部就不需要猜测哪些实体和列应该调整,以及以什么方式进行调整;在上面的示例中 uaaa 对象,它们都是 AliasedClass 实例,则向内部提供一个明确的标记,说明子查询应指向何处,以及为查询的给定组件考虑哪个实体列或关系。

SQLAlchemy 1.4还提供了一种改进的标签样式,不再需要使用包含表名的长标签来消除不同表中同名列的歧义。在上面的例子中,即使我们 UserAddress 实体具有重叠的列名,我们可以同时从两个实体中进行选择,而不必指定任何特定的标签:

# 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)

上面的查询将消除 .idUserAddress 在哪里 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

#5221

从可选选项中选择实体;Query.select_实体_from()

Synopsis

这个 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()” , the aliased() 对象提供了一个可以实现“从子查询中选择实体”等操作的单一位置。使用 1.x style ::

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 style ::

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()

Discussion

这里的要点基本上与 从查询本身作为子查询进行选择,例如“from_self()” . 这个 Query.select_from_entity() 方法是另一种指示查询从可选的可选实体加载特定ORM映射实体的行的方法,这涉及到ORM将自动别名应用于以后在查询中使用的实体,例如在WHERE子句或ORDER BY中。这种非常复杂的特性很少以这种方式使用,在这种情况下也是如此 Query.from_self() ,当使用显式 aliased() 对象,从用户的角度以及SQLAlchemy ORM内部必须如何处理它。

默认情况下,ORM行未被验证

Synopsis

ORM返回的行 session.execute(stmt) 不再自动“唯一”。这通常是一个受欢迎的更改,除非在集合中使用“joined eager loading”加载程序策略:

# 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实际上会设置一个默认的行处理程序,如果不这样做,该处理程序将引发错误,以确保联接的eager load集合在保持明确性的同时不会返回重复的行:

# 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()

Discussion

这里的情况有点不寻常,因为SQLAlchemy要求调用一个实际上完全能够自动执行的方法。要求调用该方法的原因是为了确保开发人员“选择”使用 Result.unique() 方法,这样当直接的行数与实际结果集中的记录数不冲突时,它们就不会被混淆,而实际结果集多年来一直是用户混淆和错误报告的来源。在其他情况下,默认情况下不会发生取消验证,这将提高性能,并且在自动uniquing导致混乱结果的情况下也会提高清晰度。

到了不得不打电话的程度 Result.unique() 当使用联接的急切加载集合是不方便的,在现代SQLAlchemy中 selectinload() 该策略提供了一个面向集合的急切加载器,在大多数方面优于 joinedload() 应优先考虑。

自动提交模式已从会话中删除;添加了自动注册支持

Synopsis

这个 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

主要原因a Session 在“自动提交”模式下使用是为了 Session.begin() 方法可用,因此框架集成和事件挂钩可以控制此事件何时发生。在1.4中 Session now功能 autobegin behavior 它解决了这个问题 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()

Discussion

“autocommit”模式是SQLAlchemy第一版的另一个保留。这面旗帜一直存在,主要是为了支持明确使用 Session.begin() 也可以用“1.0”中的“transactions”来解决。

会话“子事务”行为已删除

见剖面图 从“子事务”模式迁移 关于这个变化的背景。

2.0迁移-ORM扩展和配方更改

Dogpile缓存配方和水平分片使用新的会话API

作为 Query 对象成为遗留的,这两个配方以前依赖于 Query 对象现在使用 SessionEvents.do_orm_execute() 钩子。参见章节 重新执行语句 举个例子。

烘焙查询扩展被内置缓存取代

baked查询扩展被内置的缓存系统取代,ORM内部不再使用它。

SQL编译缓存 新系统的完整后台缓存。

异步支持

SQLAlchemy 1.4包括对Core和ORM的异步支持。新的API专门使用上面提到的“未来”模式。看到了吗 对内核和ORM的异步IO支持 背景。

Previous: SQLAlchemy 1.4有什么新功能? Next: 1.4变更日志