SQLAlchemy 0.7 的新特性?

关于本文档

本文档描述了 SQLAlchemy 0.6 版本(最后发布于 2012 年 5 月 5 日)和 SQLAlchemy 0.7 版本(截至 2012 年 10 月正在进行维护版本发布)之间的更改。

文档日期:2011 年 7 月 27 日

简介

本指南介绍了 SQLAlchemy 0.7 版本的新特性,并记录了将应用程序从 SQLAlchemy 0.6 系列迁移到 0.7 系列时会影响用户的更改。

为了尽可能地实现最大程度的兼容性,更改的目的是不破坏为 0.6 构建的应用程序的兼容性。 必然不向后兼容的更改非常少,除了一个更改(即可变属性默认值的更改)之外,所有更改都应该只影响极小部分的应用程序 - 许多更改都与一些用户可能试图使用的非公共 API 和未记录的 hack 有关。

还记录了第二类,甚至更小的非向后兼容的更改。 这类更改涉及至少自 0.5 版本以来已弃用,并且自弃用以来一直在发出警告的功能和行为。 这些更改只会影响仍在 使用 0.4 或早期 0.5 风格 API 的应用程序。 随着项目的成熟,我们在 0.x 级别版本中进行的此类更改越来越少,这是因为我们的 API 中越来越少有不太适合其预期用例的功能。

SQLAlchemy 0.7 中已取代一系列现有功能。 “取代”和“弃用”这两个术语之间没有太大区别,只是前者对旧功能是否会被删除的暗示要弱得多。 在 0.7 中,像 synonymcomparable_property 以及所有 Extension 和其他事件类这样的功能已被取代。 但是,这些“取代”的功能已经过重新实现,使得它们的实现主要存在于核心 ORM 代码之外,因此它们持续的“存在”不会影响 SQLAlchemy 进一步简化和改进其内部结构的能力,我们希望它们在可预见的未来仍然保留在 API 中。

新特性

新的事件系统

SQLAlchemy 早期开始了 MapperExtension 类,它提供了 mapper 持久化周期的钩子。 随着 SQLAlchemy 迅速变得更加组件化,将 mapper 推向更集中的配置角色,出现了更多“extension”、“listener”和“proxy”类,以临时方式解决各种活动拦截用例。 部分原因是活动的分歧; ConnectionProxy 对象希望提供语句和参数重写系统; AttributeExtension 提供了一个替换传入值的系统,而 DDL 对象具有可以关闭方言敏感的可调用对象的事件。

0.7 使用一种新的统一方法重新实现了几乎所有这些插件点,这种方法保留了不同系统的所有功能,提供了更大的灵活性和更少的样板代码,性能更好,并且消除了为每个事件子系统学习截然不同的 API 的需要。 先前存在的类 MapperExtensionSessionExtensionAttributeExtensionConnectionProxyPoolListener 以及 DDLElement.execute_at 方法已被弃用,现在根据新系统实现 - 这些 API 仍然完全可以工作,并且预计在可预见的未来将保留。

新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。 API 的外观和感觉受到 JQuery、Blinker 和 Hibernate 等不同来源的驱动,并且在与 Twitter 上数十名用户的会议期间进一步修改,Twitter 在此类问题上的响应率似乎远高于邮件列表。

它还具有一个开放式的目标规范系统,允许事件与 API 类关联,例如所有 SessionEngine 对象,与 API 类的特定实例关联,例如特定的 PoolMapper,以及与相关对象关联,例如用户定义的映射类,或特定映射父类的特定子类的实例上的特定属性。 单个监听器子系统可以对传入的用户定义的监听器函数应用包装器,这些包装器修改它们的调用方式 - mapper 事件可以接收正在操作的对象的实例,或其底层的 InstanceState 对象。 属性事件可以选择是否负责返回新值。

现在,几个系统都建立在新的事件 API 之上,包括新的“可变属性”API 以及复合属性。 对事件的更大强调也导致引入了一些新的事件,包括属性过期和刷新操作、pickle 加载/转储操作、已完成的 mapper 构造操作。

另请参阅

事件

#1902

混合属性,实现/取代 synonym(), comparable_property()

“派生属性”示例现在已转换为官方扩展。 synonym() 的典型用例是提供对映射列的描述符访问; comparable_property() 的用例是从任何描述符返回 PropComparator。 实际上,“派生”方法更易于使用,更易于扩展,在几十行纯 Python 代码中实现,几乎没有导入,并且不需要 ORM 核心甚至意识到它的存在。 该功能现在称为“混合属性”扩展。

synonym()comparable_property() 仍然是 ORM 的一部分,尽管它们的实现已经向外移动,建立在类似于混合扩展的方法之上,因此核心 ORM mapper/query/property 模块实际上并不知道它们的存在。

另请参阅

混合属性

#1903

速度提升

与所有主要的 SQLA 版本一样,对内部结构进行了广泛的遍历,以减少开销和调用计数,从而进一步减少常见场景中所需的工作。 此版本的亮点包括

  • flush 过程现在会将 INSERT 语句捆绑到批处理中,并将其馈送到 cursor.executemany(),用于主键已存在的行。 特别是,这通常适用于连接表继承配置中的“子”表,这意味着对 cursor.execute 的调用次数对于大量连接表对象的批量插入可以减少一半,从而允许本机 DBAPI 优化对传递给 cursor.executemany() 的语句进行(例如,重用预准备语句)。

  • 当访问已加载的相关对象的 many-to-one 引用时调用的代码路径已大大简化。 直接检查身份映射,而无需首先生成新的 Query 对象,这在访问数千个内存中的 many-to-one 时开销很大。 用于大多数惰性属性加载的每个调用构造的“加载器”对象的使用也不再使用。

  • 复合的重写允许 mapper 内部结构在 flush 中访问映射属性时使用更短的代码路径。

  • 新的内联属性访问函数取代了先前在 “save-update” 和其他级联操作需要在与属性关联的所有数据成员的完整范围内级联时对 “history” 的使用。 这减少了为此速度关键操作生成新的 History 对象的开销。

  • 与语句执行对应的对象 ExecutionContext 的内部结构已内联和简化。

  • 类型为每个语句执行生成的 bind_processor()result_processor() 可调用对象现在被缓存(小心地,以避免临时类型和方言的内存泄漏),以便在该类型的生命周期内进一步减少每个语句的调用开销。

  • 特定 Compiled 语句实例的 “绑定处理器” 集合也缓存在 Compiled 对象上,从而进一步利用 flush 过程使用的 “编译缓存” 来重用 INSERT、UPDATE、DELETE 语句的相同编译形式。

调用计数减少的演示,包括示例基准测试脚本,请访问 https://techspot.zzzeek.org/2010/12/12/a-tale-of-three-profiles/

复合重写

“复合”功能已像 synonym()comparable_property() 一样被重写,以使用基于描述符和事件的更轻量级的实现,而不是构建到 ORM 内部结构中。 这允许从 mapper/unit of work 内部结构中删除一些延迟,并简化复合的工作方式。 复合属性现在不再隐藏它所构建的底层列,这些列现在仍然是常规属性。 复合也可以充当 relationship() 以及 Column() 属性的代理。

复合的主要向后不兼容更改是它们不再使用 mutable=True 系统来检测就地突变。 请使用 Mutation Tracking 扩展来为现有复合用法建立就地更改事件。

#2008 #2024

更简洁的 query.join(target, onclause) 形式

发出具有显式 onclause 的目标的 query.join() 的默认方法现在是

query.join(SomeClass, SomeClass.id == ParentClass.some_id)

在 0.6 中,此用法被认为是错误的,因为 join() 接受与多个 JOIN 子句对应的多个参数 - 双参数形式需要放在元组中,以消除单参数和双参数连接目标之间的歧义。 在 0.6 中间,我们为此特定调用样式添加了检测和错误消息,因为它太常见了。 在 0.7 中,由于我们无论如何都在检测确切的模式,并且由于必须键入元组而没有任何理由是非常烦人的,因此非元组方法现在成为执行此操作的 “正常” 方式。 与单连接情况相比,“多 JOIN” 用例极为罕见,并且如今多连接更清楚地由对 join() 的多次调用表示。

元组形式将保留以实现向后兼容性。

请注意,所有其他形式的 query.join() 保持不变

query.join(MyClass.somerelation)
query.join("somerelation")
query.join(MyTarget)
# ... etc

使用连接查询

#1923

突变事件扩展,取代 “mutable=True”

新的扩展 Mutation Tracking 提供了一种机制,用户定义的数据类型可以通过该机制将更改事件返回给所有者父项或父项。Mutation Tracking 扩展包括标量数据库值的方法,例如由 PickleTypepostgresql.ARRAY 或其他自定义 MutableType 类管理的值,以及 ORM “复合” 的方法,这些复合是使用 composite() 配置的。

另请参阅

Mutation Tracking

NULLS FIRST / NULLS LAST 操作符

这些被实现为 asc()desc() 操作符的扩展,称为 nullsfirst()nullslast()

另请参阅

nullsfirst()

nullslast()

#723

select.distinct(), query.distinct() 接受 *args 用于 PostgreSQL DISTINCT ON

这已经可以通过将表达式列表传递给 select()distinct 关键字参数来使用,select()Querydistinct() 方法现在接受位置参数,当使用 PostgreSQL 后端时,这些位置参数呈现为 DISTINCT ON。

distinct()

Query.distinct()

#1069

Index() 可以内联放置在 Table, __table_args__

Index() 构造可以与 Table 定义内联创建,使用字符串作为列名,作为在 Table 外部创建索引的替代方法。 也就是说

Table(
    "mytable",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50), nullable=False),
    Index("idx_name", "name"),
)

这里的主要理由是为了声明式 __table_args__ 的好处,特别是与 mixin 一起使用时

class HasNameMixin(object):
    name = Column("name", String(50), nullable=False)

    @declared_attr
    def __table_args__(cls):
        return (Index("name"), {})


class User(HasNameMixin, Base):
    __tablename__ = "user"
    id = Column("id", Integer, primary_key=True)

索引

窗口函数 SQL 构造

“窗口函数” 向语句提供有关生成的结果集的信息。 这允许针对诸如 “行号”、“排名” 等各种内容的标准。 众所周知,至少 PostgreSQL、SQL Server 和 Oracle 支持它们,可能还有其他数据库。

窗口函数的最佳介绍在 PostgreSQL 的网站上,自 8.4 版本以来,PostgreSQL 一直支持窗口函数

https://postgresql.ac.cn/docs/current/static/tutorial-window.html

SQLAlchemy 提供了一个简单的构造,通常通过现有的函数子句调用,使用 over() 方法,该方法接受 order_bypartition_by 关键字参数。 下面我们复制 PG 教程中的第一个示例

from sqlalchemy.sql import table, column, select, func

empsalary = table("empsalary", column("depname"), column("empno"), column("salary"))

s = select(
    [
        empsalary,
        func.avg(empsalary.c.salary)
        .over(partition_by=empsalary.c.depname)
        .label("avg"),
    ]
)

print(s)

SQL

SELECT empsalary.depname, empsalary.empno, empsalary.salary,
avg(empsalary.salary) OVER (PARTITION BY empsalary.depname) AS avg
FROM empsalary

sqlalchemy.sql.expression.over

#1844

Connection 上的 execution_options() 接受 “isolation_level” 参数

这为单个 Connection 设置事务隔离级别,直到 Connection 关闭并且其底层 DBAPI 资源返回到连接池,之后隔离级别将重置回默认值。 默认隔离级别使用 create_engine()isolation_level 参数设置。

事务隔离支持目前仅受 PostgreSQL 和 SQLite 后端支持。

execution_options()

#2001

TypeDecorator 适用于整数主键列

扩展 Integer 行为的 TypeDecorator 可以与主键列一起使用。 Column 的 “autoincrement” 功能现在将识别底层数据库列仍然是整数,以便 lastrowid 机制继续工作。 TypeDecorator 本身将将其结果值处理器应用于新生成的主键,包括 DBAPI cursor.lastrowid 访问器接收的主键。

#2005 #2006

TypeDecorator 出现在 “sqlalchemy” 导入空间中

不再需要从 sqlalchemy.types 导入它,它现在镜像在 sqlalchemy 中。

新的方言

已添加方言

  • 用于 Drizzle 数据库的 MySQLdb 驱动程序

    Drizzle

  • 对 pymysql DBAPI 的支持

    pymsql 注意事项

  • psycopg2 现在适用于 Python 3

行为变更(向后兼容)

默认构建 C 扩展

这是自 0.7b4 起的情况。 如果检测到 cPython 2.xx,则将构建 exts。 如果构建失败,例如在 windows 安装上,则会捕获该条件并继续进行非 C 安装。 如果使用 Python 3 或 PyPy,则不会构建 C exts。

Query.count() 简化,应几乎总是有效

Query.count() 中发生的非常古老的猜测已被现代化为使用 .from_self()。 也就是说,query.count() 现在等效于

query.from_self(func.count(literal_column("1"))).scalar()

以前,内部逻辑试图重写查询本身的 columns 子句,并且在检测到 “子查询” 条件(例如可能包含聚合的基于列的查询,或具有 DISTINCT 的查询)时,将经历重写 columns 子句的复杂过程。 此逻辑在复杂条件下失败,特别是那些涉及连接表继承的条件,并且对于更全面的 .from_self() 调用而言,早已过时。

query.count() 发出的 SQL 现在始终采用以下形式

SELECT count(1) AS count_1 FROM (
    SELECT user.id AS user_id, user.name AS user_name from user
) AS anon_1

也就是说,原始查询完全保留在子查询内部,不再猜测应如何应用 count。

#2093

要发出非子查询形式的 count()

MySQL 用户已经报告说,MyISAM 引擎在这种简单的更改下完全崩溃,这并不令人意外。请注意,对于一个简单的 count(),它针对无法处理简单子查询的数据库进行了优化,应该使用 func.count()

from sqlalchemy import func

session.query(func.count(MyClass.id)).scalar()

或者对于 count(*)

from sqlalchemy import func, literal_column

session.query(func.count(literal_column("*"))).select_from(MyClass).scalar()

LIMIT/OFFSET 子句现在使用绑定参数

LIMIT 和 OFFSET 子句,或其后端等效项(例如 TOP、ROW NUMBER OVER 等),对实际值使用绑定参数,适用于所有支持它的后端(大多数,Sybase 除外)。这可以提高查询优化器的性能,因为具有不同 LIMIT/OFFSET 的多个语句的文本字符串现在是相同的。

#805

日志增强功能

Vinay Sajip 为我们的日志系统提供了一个补丁,这样就不再需要嵌入在引擎和连接池的日志语句中的“十六进制字符串”来使 echo 标志正常工作。一个新的使用过滤日志对象的系统使我们能够保持当前 echo 行为,使其对各个引擎是本地的,而无需额外的、特定于这些引擎的标识字符串。

#1926

简化 polymorphic_on 赋值

当在继承场景中使用时,polymorphic_on 列映射属性的填充现在发生在对象构造时,即调用其 __init__ 方法时,使用 init 事件。然后,该属性的行为与任何其他列映射属性相同。以前,特殊的逻辑会在 flush 期间触发以填充此列,这阻止了任何用户代码修改其行为。新方法在以下三个方面对此进行了改进:1. polymorphic 标识现在在对象构造后立即存在于对象上;2. polymorphic 标识可以被用户代码更改,而与任何其他列映射属性的行为没有任何差异;3. flush 期间 mapper 的内部结构得到简化,不再需要对此列进行特殊检查。

#1895

contains_eager() 跨多个路径链接(即 “all()”)

`contains_eager()`` 修饰符现在将为更长的路径链接自身,而无需发出单独的 ``contains_eager()` 调用。代替

session.query(A).options(contains_eager(A.b), contains_eager(A.b, B.c))

你可以说

session.query(A).options(contains_eager(A.b, B.c))

#2032

允许刷新没有父对象的孤立对象

我们有一个长期存在的行为,在 flush 期间检查所谓的“孤立对象”,即与指定 “delete-orphan” 级联的 relationship() 关联的对象,已为 INSERT 新添加到会话中,并且未建立父关系。添加此检查是在多年前为了适应一些测试用例,这些用例测试了孤立行为的一致性。在现代 SQLA 中,Python 端不再需要此检查。“孤立检查” 的等效行为是通过将对外键的引用设置为对象的父行 NOT NULL 来实现的,数据库以与 SQLA 允许大多数其他操作相同的方式完成建立数据一致性的工作。如果对象的父外键是可为空的,则可以插入该行。“孤立” 行为在对象与特定父对象一起持久化时运行,然后与该父对象解除关联,从而为其发出 DELETE 语句。

#1912

当集合成员、标量引用对象不属于 flush 时生成警告

现在,当通过在标记为 “dirty” 的父对象上加载的 relationship() 引用的相关对象不存在于当前 Session 中时,会发出警告。

save-update 级联在对象添加到 Session 时,或在对象首次与父对象关联时生效,因此对象及其所有相关内容通常都存在于同一个 Session 中。但是,如果为特定的 relationship() 禁用 save-update 级联,则不会发生此行为,并且 flush 过程不会尝试对其进行纠正,而是保持与配置的级联行为一致。以前,当在 flush 期间检测到此类对象时,它们会被静默跳过。新行为是发出警告,目的是提醒人们注意这种情况,这种情况通常是意外行为的根源。

#1973

安装程序不再安装 Nose 插件

自从我们迁移到 nose 以来,我们一直使用通过 setuptools 安装的插件,以便 nosetests 脚本可以自动运行 SQLA 的插件代码,这对于我们的测试拥有完整的环境是必要的。在 0.6 版本中期,我们意识到这里的导入模式意味着 Nose 的 “coverage” 插件会中断,因为 “coverage” 要求它在导入任何要覆盖的模块之前启动;因此在 0.6 版本中期,我们通过向构建添加一个单独的 sqlalchemy-nose 包来克服这个问题,使情况变得更糟。

在 0.7 版本中,我们不再尝试让 nosetests 自动工作,因为 SQLAlchemy 模块会为 nosetests 的所有用法生成大量的 nose 配置选项,而不仅仅是 SQLAlchemy 单元测试本身,并且额外的 sqlalchemy-nose 安装甚至更糟糕,在 Python 环境中产生了一个额外的包。0.7 版本中的 sqla_nose.py 脚本现在是使用 nose 运行测试的唯一方法。

#1949

可以映射非 Table 派生的构造

一个完全不针对任何 Table 的构造,例如函数,可以被映射。

from sqlalchemy import select, func
from sqlalchemy.orm import mapper


class Subset(object):
    pass


selectable = select(["x", "y", "z"]).select_from(func.some_db_function()).alias()
mapper(Subset, selectable, primary_key=[selectable.c.x])

#1876

aliased() 接受 FromClause 元素

这是一个方便的助手,这样,如果一个普通的 FromClause,例如 selectTablejoin 被传递给 orm.aliased() 构造,它会传递到该 from 构造的 .alias() 方法,而不是构造 ORM 级别的 AliasedClass

#2018

Session.connection(), Session.execute() 接受 ‘bind’

这是为了允许 execute/connection 操作显式地参与引擎的打开事务。它还允许 Session 的自定义子类实现他们自己的 get_bind() 方法和参数,以便将这些自定义参数与 execute()connection() 方法同等使用。

Session.connection Session.execute

#1996

columns 子句中的独立绑定参数自动标记。

select 的 “columns 子句” 中存在的绑定参数现在像其他 “匿名” 子句一样自动标记,这尤其使得它们的 “type” 在提取行时(如在结果行处理器中)变得有意义。

SQLite - 相对文件路径通过 os.path.abspath() 规范化。

这样,更改当前目录的脚本将继续以相同的位置为目标,因为后续的 SQLite 连接已建立。

#2036

MS-SQL - String/Unicode/VARCHAR/NVARCHAR/VARBINARY 在没有长度时发出 “max”

在 MS-SQL 后端,String/Unicode 类型及其对应项 VARCHAR/NVARCHAR,以及 VARBINARY (#1833) 在未指定长度时发出 “max” 作为长度。这使其与 PostgreSQL 的 VARCHAR 类型更加兼容,后者在未指定长度时也类似地无界。当未指定长度时,SQL Server 将这些类型的长度默认为 ‘1’。

行为变更(向后不兼容)

再次注意,除了默认的可变性更改之外,这些更改中的大多数都 *非常小*,并且不会影响大多数用户。

默认情况下,PickleType 和 ARRAY 可变性已关闭

此更改指的是 ORM 在映射具有 PickleTypepostgresql.ARRAY 数据类型的列时的默认行为。mutable 标志现在默认设置为 False。如果现有应用程序使用这些类型并依赖于就地突变的检测,则必须使用 mutable=True 构造类型对象以恢复 0.6 行为

Table(
    "mytable",
    metadata,
    # ....
    Column("pickled_data", PickleType(mutable=True)),
)

mutable=True 标志正在逐步淘汰,取而代之的是新的 Mutation Tracking 扩展。此扩展提供了一种机制,用户定义的数据类型可以通过该机制将更改事件发送回所有者父对象或父对象们。

以前使用 mutable=True 的方法不提供更改事件 - 相反,ORM 必须扫描会话中存在的所有可变值,并在每次调用 flush() 时将它们与其原始值进行比较以查找更改,这是一个非常耗时的事件。这是 SQLAlchemy 非常早期的遗留问题,当时 flush() 不是自动的,并且历史记录跟踪系统远没有现在这么复杂。

使用 PickleTypepostgresql.ARRAY 或其他 MutableType 子类,并且需要就地突变检测的现有应用程序,应该迁移到新的突变跟踪系统,因为 mutable=True 将来可能会被弃用。

#1980

composite() 的可变性检测需要 Mutation Tracking Extension

所谓的 “composite” 映射属性,即使用 Composite Column Types 中描述的技术配置的那些属性,已经重新实现,使得 ORM 内部不再感知它们(从而在关键部分中产生更短更高效的代码路径)。虽然 composite 类型通常旨在被视为不可变的值对象,但这从未被强制执行。对于使用具有可变性的 composite 的应用程序,Mutation Tracking 扩展提供了一个基类,该基类为用户定义的 composite 类型建立了一种机制,以将更改事件消息发送回每个对象的所有者父对象或父对象们。

使用 composite 类型并依赖于这些对象的就地突变检测的应用程序,应该迁移到 “mutation tracking” 扩展,或者更改 composite 类型的使用方式,使得不再需要就地更改(即,将它们视为不可变的值对象)。

SQLite - SQLite 方言现在对基于文件的数据库使用 NullPool

此更改 99.999% 向后兼容,除非您正在跨连接池连接使用临时表。

基于文件的 SQLite 连接速度非常快,并且使用 NullPool 意味着每次调用 Engine.connect 都会创建一个新的 pysqlite 连接。

以前,使用 SingletonThreadPool,这意味着线程中对特定引擎的所有连接都将是同一个连接。新方法旨在更直观,尤其是在使用多个连接时。

当使用 :memory: 数据库时,SingletonThreadPool 仍然是默认引擎。

请注意,由于 SQLite 处理临时表的方式,此更改会 破坏跨 Session 提交使用的临时表。如果需要超出单个连接池范围的临时表,请参阅 https://sqlalchemy.org.cn/docs/dialects/sqlite.html#using- temporary-tables-with-sqlite 中的注释。

#1921

Session.merge() 检查版本化映射器的版本 ID

Session.merge() 将检查传入状态的版本 ID 与数据库的版本 ID 是否一致(假设映射使用版本 ID 并且传入状态已分配 version_id),如果它们不匹配,则引发 StaleDataError。这是正确的行为,因为如果传入状态包含过时的版本 ID,则应假定该状态已过时。

如果将数据合并到版本化状态,则版本 ID 属性可以保持未定义,并且不会进行版本检查。

通过检查 Hibernate 的行为确认了此检查 - merge() 和版本控制功能最初都是从 Hibernate 调整过来的。

#2027

查询中元组标签名称得到改进

对于依赖于旧行为的应用程序,此改进可能略微向后不兼容。

给定两个映射类 FooBar,每个类都带有一个列 spam

qa = session.query(Foo.spam)
qb = session.query(Bar.spam)

qu = qa.union(qb)

qu 生成的单列的名称将是 spam。以前,由于 union 组合事物的方式,它会类似于 foo_spam,这与非联合查询情况下的名称 spam 不一致。

#1942

映射的列属性首先引用最具体的列

这是对映射的列属性引用多个列时涉及的行为的更改,特别是当处理连接表子类上的属性时,该属性的名称与超类上的属性的名称相同。

使用声明式方法,场景如下

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)


class Child(Parent):
    __tablename__ = "child"
    id = Column(Integer, ForeignKey("parent.id"), primary_key=True)

上面,属性 Child.id 同时引用 child.id 列和 parent.id - 这是由于属性的名称。如果在类上以不同的方式命名,例如 Child.child_id,则它会明确地映射到 child.id,其中 Child.idParent.id 是相同的属性。

id 属性被设置为同时引用 parent.idchild.id 时,它会将它们存储在一个有序列表中。诸如 Child.id 之类的表达式在呈现时仅指 *其中一个* 列。在 0.6 版本之前,此列将是 parent.id。在 0.7 版本中,它是不太令人意外的 child.id

此行为的遗留问题处理的是 ORM 的行为和限制,这些行为和限制实际上已不再适用;所需要的只是颠倒顺序。

这种方法的主要优点是,现在更容易构造引用本地列的 primaryjoin 表达式

class Child(Parent):
    __tablename__ = "child"
    id = Column(Integer, ForeignKey("parent.id"), primary_key=True)
    some_related = relationship(
        "SomeRelated", primaryjoin="Child.id==SomeRelated.child_id"
    )


class SomeRelated(Base):
    __tablename__ = "some_related"
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey("child.id"))

在 0.7 版本之前,Child.id 表达式将引用 Parent.id,并且有必要将 child.id 映射到不同的属性。

这也意味着像这样的查询会改变其行为

session.query(Parent).filter(Child.id > 7)

在 0.6 版本中,这将呈现为

SELECT parent.id AS parent_id
FROM parent
WHERE parent.id > :id_1

在 0.7 版本中,你得到

SELECT parent.id AS parent_id
FROM parent, child
WHERE child.id > :id_1

你会注意到这是一个笛卡尔积 - 此行为现在等同于任何其他本地于 Child 的属性的行为。with_polymorphic() 方法,或显式连接底层 Table 对象的类似策略,用于针对所有 Parent 对象呈现查询,并使用针对 Child 的条件,方式与 0.5 和 0.6 相同

print(s.query(Parent).with_polymorphic([Child]).filter(Child.id > 7))

在 0.6 和 0.7 版本中都呈现为

SELECT parent.id AS parent_id, child.id AS child_id
FROM parent LEFT OUTER JOIN child ON parent.id = child.id
WHERE child.id > :id_1

此更改的另一个影响是,跨两个表的连接继承加载将从子表的值而不是父表的值填充。一个不寻常的情况是,使用 with_polymorphic="*" 对 “Parent” 的查询会发出针对 “parent” 的查询,并对 “child” 进行 LEFT OUTER JOIN。该行位于 “Parent” 中,看到 polymorphic 标识对应于 “Child”,但是假设 “child” 中的实际行已被 *删除*。由于这种损坏,该行传入时,与 “child” 对应的所有列都设置为 NULL - 这现在是填充的值,而不是父表中的值。

#1892

映射到具有两个或多个同名列的连接需要显式声明

这在某种程度上与 #1892 中的先前更改有关。当映射到连接时,同名列必须显式链接到映射的属性,即如 Mapping a Class Against Multiple Tables 中所述。

给定两个表 foobar,每个表都带有一个主键列 id,以下代码现在会产生错误

foobar = foo.join(bar, foo.c.id == bar.c.foo_id)
mapper(FooBar, foobar)

这是因为 mapper() 拒绝猜测哪个列是 FooBar.id 的主要表示形式 - 是 foo.c.id 还是 bar.c.id?该属性必须是显式的

foobar = foo.join(bar, foo.c.id == bar.c.foo_id)
mapper(FooBar, foobar, properties={"id": [foo.c.id, bar.c.id]})

#1896

Mapper 要求 polymorphic_on 列存在于映射的可选对象中

这是 0.6 版本中的警告,现在是 0.7 版本中的错误。为 polymorphic_on 给定的列必须在映射的可选对象中。这是为了防止一些偶尔发生的用户错误,例如

mapper(SomeClass, sometable, polymorphic_on=some_lookup_table.c.id)

在上面的示例中,polymorphic_on 需要位于 sometable 列上,在这种情况下,可能是 sometable.c.some_lookup_id。在某些 “polymorphic union” 场景中,有时也会发生类似的错误。

这样的配置错误一直都是 “错误” 的,并且上述映射无法按指定方式工作 - 该列将被忽略。但是,在极少数情况下,如果应用程序在不知不觉中依赖于此行为,则它可能会向后不兼容。

#1875

DDL() 构造现在转义百分号

以前,DDL() 字符串中的百分号必须转义,即 %%,具体取决于 DBAPI,对于那些接受 pyformatformat 绑定的 DBAPI(即 psycopg2、mysql-python),这与自动执行此操作的 text() 构造不一致。现在,DDL() 的转义与 text() 相同。

#1897

Table.c / MetaData.tables 稍微改进了一下,不允许直接突变

另一个领域是一些用户以某种方式进行修改,这种方式实际上并不能按预期工作,但仍然留下极小的可能性,即某些应用程序依赖于此行为,Table 上的 .c 属性和 MetaData 上的 .tables 属性返回的构造被显式地设置为不可变。构造的 “mutable” 版本现在是私有的。向 .c 添加列涉及使用 Tableappend_column() 方法,这确保了事物以适当的方式与父 Table 关联;同样,MetaData.tables 与存储在此字典中的 Table 对象有一个约定,并且在新的簿记中也有一点,即跟踪所有模式名称的 set(),这只能通过使用公共 Table 构造函数以及 Table.tometadata() 来满足。

当然,这些属性查询的 ColumnCollectiondict 集合有一天可能会在其所有突变方法上实现事件,以便在直接突变集合时发生适当的簿记,但是在有人有动力实现所有这些以及数十个新的单元测试之前,缩小这些集合的突变路径将确保没有应用程序试图依赖当前不支持的用法。

#1893 #1917

server_default 对于所有 inserted_primary_key 值始终如一地返回 None

当 Integer PK 列上存在 server_default 时,建立了一致性。SQLA 不会预先获取这些值,它们也不会在 cursor.lastrowid (DBAPI) 中返回。确保所有后端在 result.inserted_primary_key 中始终如一地返回 None - 某些后端以前可能返回了一个值。在主键列上使用 server_default 非常不寻常。如果使用特殊函数或 SQL 表达式来生成主键默认值,则应将其建立为 Python 端的 “default”,而不是 server_default。

关于这种情况的反射,具有 server_default 的 int PK 列的反射将 “autoincrement” 标志设置为 False,除非在检测到序列默认值的 PG SERIAL 列的情况下。

#2020 #2021

sys.modules 中的 sqlalchemy.exceptions 别名已删除

几年来,我们已将字符串 sqlalchemy.exceptions 添加到 sys.modules,以便像 “import sqlalchemy.exceptions” 这样的语句可以工作。核心异常模块的名称长期以来一直是 exc,因此此模块的推荐导入方式是

from sqlalchemy import exc

exceptions 名称仍然存在于 “sqlalchemy” 中,对于可能说过 from sqlalchemy import exceptions 的应用程序,但他们也应该开始使用 exc 名称。

查询计时配方更改

虽然不是 SQLAlchemy 本身的一部分,但值得一提的是,将 ConnectionProxy 重构为新的事件系统意味着它不再适用于 “Timing all Queries” 配方。请调整查询计时器以使用 before_cursor_execute()after_cursor_execute() 事件,这在更新的配方 UsageRecipes/Profiling 中进行了演示。

弃用的 API

类型的默认构造函数将不接受参数

核心类型模块中的简单类型(如 IntegerDate 等)不接受参数。接受/忽略 catchall \*args, \**kwargs 的默认构造函数已在 0.7b4/0.7.0 中恢复,但会发出弃用警告。

如果参数与核心类型(如 Integer)一起使用,则可能是您打算使用特定于方言的类型,例如 sqlalchemy.dialects.mysql.INTEGER,它确实接受 “display_width” 参数。

compile_mappers() 重命名为 configure_mappers(),简化了配置内部结构

该系统从一个小的东西慢慢演变成一个全局 “registry-” 级别的函数,并且名称不佳,它最初是在单个 mapper 本地实现的,并且名称也很差,因此我们通过将实现移出 Mapper 并将其重命名为 configure_mappers() 来修复了这两个问题。当然,通常应用程序不需要调用 configure_mappers(),因为此过程是根据需要发生的,一旦通过属性或查询访问需要映射。

#1966

核心监听器/代理被事件监听器取代

PoolListenerConnectionProxyDDLElement.execute_at 分别被 event.listen() 取代,使用 PoolEventsEngineEventsDDLEvents 分派目标。

ORM 扩展被事件监听器取代

MapperExtensionAttributeExtensionSessionExtension 分别被 event.listen() 取代,使用 MapperEvents/InstanceEventsAttributeEventsSessionEvents 分派目标。

对于 MySQL,应通过前缀在 select() 中向 ‘distinct’ 发送字符串

这种晦涩的功能允许在 MySQL 后端使用这种模式

select([mytable], distinct="ALL", prefixes=["HIGH_PRIORITY"])

prefixes 关键字或 prefix_with() 方法应用于非标准或不常见的前缀

select([mytable]).prefix_with("HIGH_PRIORITY", "ALL")

useexistingextend_existingkeep_existing 取代

Table 上的 useexisting 标志已被一对新的标志 keep_existingextend_existing 取代。 extend_existing 等同于 useexisting - 返回现有的 Table,并添加额外的构造器元素。 使用 keep_existing,将返回现有的 Table,但不会添加额外的构造器元素 - 这些元素仅在 Table 首次创建时应用。

向后不兼容的 API 变更

传递给 bindparam() 的可调用对象不会被求值 - 影响 Beaker 示例

#1950

请注意,这会影响 Beaker 缓存示例,其中 _params_from_query() 函数的工作方式需要稍作调整。 如果您正在使用 Beaker 示例中的代码,则应应用此更改。

types.type_map 现在是私有的,types._type_map

我们注意到一些用户利用 sqlalchemy.types 中的这个字典作为将 Python 类型与 SQL 类型关联的快捷方式。 我们无法保证此字典的内容或格式,此外,以一对一方式关联 Python 类型的方式存在一些灰色地带,最好由各个应用程序自行决定,因此我们已将此属性加下划线。

#1870

将独立 alias() 函数的 alias 关键字参数重命名为 name

这样做是为了使关键字参数 name 与所有 FromClause 对象上的 alias() 方法以及 Query.subquery() 上的 name 参数相匹配。

只有使用独立的 alias() 函数,而不是方法绑定函数,并使用显式关键字名称 alias 而不是位置方式传递别名名称的代码才需要在此处进行修改。

非公共 Pool 方法已添加下划线

Pool 及其子类的所有非公共使用方法都已重命名并添加了下划线。 之前未以此方式命名是一个错误。

连接池方法现在已添加下划线或已删除

Pool.create_connection() -> Pool._create_connection()

Pool.do_get() -> Pool._do_get()

Pool.do_return_conn() -> Pool._do_return_conn()

Pool.do_return_invalid() -> 已删除,未使用

Pool.return_conn() -> Pool._return_conn()

Pool.get() -> Pool._get(),公共 API 是 Pool.connect()

SingletonThreadPool.cleanup() -> _cleanup()

SingletonThreadPool.dispose_local() -> 已删除,请使用 conn.invalidate()

#1982

之前已弃用,现在已删除

Query.join()、Query.outerjoin()、eagerload()、eagerload_all() 和其他方法不再允许将属性列表作为参数

自 0.5 版本起,传递属性列表或属性名称到 Query.joineagerload() 和类似方法已被弃用。

# old way, deprecated since 0.5
session.query(Houses).join([Houses.rooms, Room.closets])
session.query(Houses).options(eagerload_all([Houses.rooms, Room.closets]))

自 0.5 系列起,这些方法都接受 *args。

# current way, in place since 0.5
session.query(Houses).join(Houses.rooms, Room.closets)
session.query(Houses).options(eagerload_all(Houses.rooms, Room.closets))

ScopedSession.mapper 已删除

此功能提供了一个 mapper 扩展,它将基于类的功能与特定的 ScopedSession 链接,特别是提供了新对象实例将自动与该会话关联的行为。 该功能被教程和框架过度使用,由于其隐式行为导致用户非常困惑,并在 0.5.5 中被弃用。 复制其功能的技术位于 [wiki:UsageRecipes/SessionAwareMapper]。