SqlAlchemy-2-0-中文文档-五十二-

SqlAlchemy 2.0 中文文档(五十二)

原文:docs.sqlalchemy.org/en/20/contents.html

SQLAlchemy 0.7 中的新功能?

原文:docs.sqlalchemy.org/en/20/changelog/migration_07.html

关于本文档

本文描述了 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 和一些用户可能一直在尝试使用的未记录的黑客。

还有第二个更小的非向后兼容更改类别也有文档记录。这类更改涉及那些至少自 0.5 版本以来已被弃用并自弃用以来一直引发警告的功能和行为。这些更改只会影响仍在使用 0.4 或早期 0.5 风格 API 的应用程序。随着项目的成熟,我们在 0.x 级别发布中有越来越少这类更改,这是由于我们的 API 具有越来越少的功能,这些功能不太适合它们原本要解决的用例。

一系列现有功能在 SQLAlchemy 0.7 中已被取代。术语“取代”和“弃用”之间没有太大区别,只是前者更弱地暗示了旧功能可能会被移除。在 0.7 中,像synonymcomparable_property这样的功能,以及所有的Extension和其他事件类都已被取代。但这些“被取代”的功能已被重新实现,使得它们的实现大部分存在于核心 ORM 代码之外,因此它们的持续“挂在那里”并不影响 SQLAlchemy 进一步简化和完善其内部结构的能力,我们预计它们将在可预见的未来保留在 API 中。

新功能

新事件系统

SQLAlchemy 早期使用了MapperExtension类,该类提供了对映射器持久性周期的钩子。随着 SQLAlchemy 迅速变得更加组件化,将映射器推入更专注的配置角色,许多更多的“扩展”,“监听器”和“代理”类出现,以以一种临时方式解决各种活动拦截用例。部分原因是由活动的分歧驱动的;ConnectionProxy对象希望提供一个重写语句和参数的系统;AttributeExtension提供了一个替换传入值的系统,而DDL对象具有可以根据方言敏感的可调用函数进行切换的事件。

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

新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。API 的外观和感觉受到了 JQuery、Blinker 和 Hibernate 等多样化来源的驱动,并且在与数十位用户进行的 Twitter 会议期间进行了多次修改,这似乎比邮件列表对此类问题的回应率要高得多。

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

几个系统现在基于新的事件 API 进行构建,包括新的“可变属性” API 以及复合属性。对事件的更大强调还导致了一些新事件的引入,包括属性过期和刷新操作,pickle 加载/转储操作,完成的映射器构建操作。

另请参阅

事件

#1902

Hybrid Attributes,实现/取代了 synonym()、comparable_property()

“派生属性”示例现在已成为官方扩展。synonym() 的典型用例是为映射列提供描述符访问;comparable_property() 的用例是能够从任何描述符返回 PropComparator。实际上,“派生”的方法更易于使用,更具可扩展性,用几十行纯 Python 实现,几乎不需要导入,甚至不需要 ORM 核心知道它。该功能现在被称为“Hybrid Attributes”扩展。

synonym()comparable_property()仍然是 ORM 的一部分,尽管它们的实现已经移出,建立在类似于混合扩展的方法上,因此核心 ORM 映射器/查询/属性模块在其他方面并不真正意识到它们。

另请参见

混合属性

#1903

速度增强

与所有主要 SQLA 版本一样,通过内部进行广泛遍历以减少开销和调用次数,进一步减少了常见情况下所需的工作量。此版本的亮点包括:

  • 刷新过程现在将 INSERT 语句捆绑成批次提供给cursor.executemany(),对于主键已经存在的行。特别是这通常适用于连接表继承配置中的“子”表,这意味着对于大量连接表对象的大量插入,可以将对cursor.execute的调用次数减半,从而允许本地 DBAPI 优化为那些传递给cursor.executemany()的语句(如重用准备好的语句)。

  • 当访问已加载的相关对象的多对一引用时调用的代码路径已经大大简化。直接检查标识映射,无需首先生成新的Query对象,这在上下文中访问成千上万个内存中的多对一时是昂贵的。对于大多数延迟属性加载,也不再使用每次调用构造的“加载器”对象。

  • 重新编写组合使得在映射器内部访问刷新时映射属性的代码路径更短。

  • 新的内联属性访问函数取代了以前在“保存-更新”和其他级联操作需要在属性关联的所有数据成员范围内级联时使用“历史”时的用法。这减少了为这个速度关键操作生成新的History对象的开销。

  • ExecutionContext的内部,即对语句执行的对象,已经被内联和简化。

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

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

包括一个示例基准脚本在内的减少调用次数的演示可在techspot.zzzeek.org/2010/12/12/a-tale-of-three- profiles/中找到。

重写组合

“复合”特性已被重写,与synonym()comparable_property()一样,使用了基于描述符和事件的轻量级实现,而不是构建到 ORM 内部。这使得从映射器/工作单元内部删除了一些延迟,并简化了复合的工作原理。复合属性现在不再隐藏其建立在其上的基础列,这些列现在保持为常规属性。复合对象还可以充当relationship()以及Column()属性的代理。

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

另请参阅

复合列类型

突变追踪

#2008 #2024

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

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

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

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

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

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

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

使用联接查询

#1923

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

一个新的扩展,突变追踪,提供了一种机制,通过该机制,用户定义的数据类型可以向拥有的父对象提供更改事件。该扩展包括了一种用于标量数据库值的方法,例如由PickleTypepostgresql.ARRAY或其他自定义MutableType类管理的值,以及一种用于 ORM“复合”对象的方法,这些对象使用composite()进行配置。

另请参阅

突变追踪

NULLS FIRST / NULLS LAST 运算符

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

另见

nullsfirst()

nullslast()

#723

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

通过向 select()distinct 关键字参数传递表达式列表,现在当使用 PostgreSQL 后端时,select()Querydistinct() 方法接受位置参数,这些参数将被渲染为 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__ 的利益,特别是与混入类一起使用时:

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)

Indexes

窗口函数 SQL 结构

“窗口函数”在语句生成结果集时向其提供信息。这允许针对诸如“行号”、“排名”等各种条件进行判断。已知至少由 PostgreSQL、SQL Server 和 Oracle 支持。可能还有其他数据库也支持。

最好的窗口函数介绍在 PostgreSQL 网站上,自从版本 8.4 开始就支持窗口函数:

www.postgresql.org/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 Notes

  • psycopg2 现在支持 Python 3

行为变化(向后兼容)

默认情况下构建 C 扩展

这是从 0.7b4 开始的。如果检测到 cPython 2.xx,则会构建扩展。如果构建失败,例如在 Windows 安装中,会捕获该条件并继续非 C 安装。如果使用 Python 3 或 PyPy,则不会构建 C 扩展。

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

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

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

以前,内部逻辑尝试重写查询本身的列子句,并在检测到“子查询”条件时,例如可能在其中包含聚合的基于列的查询,或者具有 DISTINCT 的查询时,会经历一个复杂的过程来重写列子句。这种逻辑在复杂条件下失败,特别是涉及联接表继承的条件,并且长期以来已经被更全面的.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 事件。然后,该属性的行为与任何其他列映射属性相同。以前,特殊逻辑会在刷新期间触发以填充此列,这会阻止任何用户代码修改其行为。新方法在三个方面改进了这一点:1. 多态标识现在在对象构造时立即存在;2. 多态标识可以被用户代码更改,而不会与任何其他列映射属性的行为有任何区别;3. 刷新期间映射器的内部简化,不再需要对此列进行特殊检查。

#1895

在多个路径(即“all()”)上进行 contains_eager() 链

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

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

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

#1912

在刷新时生成警告,当集合成员、标量引用不在刷新中时

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

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

#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,比如一个 selectTablejoinorm.aliased() 构造时,它会通过到达该 from 构造的 .alias() 方法,而不是构造一个 ORM 级别的 AliasedClass

#2018

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

这是为了允许 execute/connection 操作明确参与引擎的开放事务。它还允许自定义的 Session 子类实现自己的 get_bind() 方法和参数,以便在 execute()connection() 方法中同样使用这些自定义参数。

Session.connection Session.execute

#1996

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

存在于 select 的“columns clause”中的绑定参数现在像其他“匿名”子句一样自动标记,这样在获取行时它们的“类型”就有意义,就像结果行处理器一样。

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()的可变性检测需要变异跟踪扩展

所谓的“复合”映射属性,使用在复合列类型中描述的技术配置的那些,已经重新实现,以使 ORM 内部不再意识到它们(导致关键部分中的代码路径更短更高效)。虽然复合类型通常应被视为不可变值对象,但从未强制执行。对于使用具有可变性的复合的应用程序,变异跟踪扩展提供了一个基类,该基类建立了一个机制,使用户定义的复合类型能够向每个对象的拥有父对象或父对象发送更改事件消息。

使用复合类型并依赖于这些对象的原地变异检测的应用程序应该迁移到“变异跟踪”扩展,或者更改复合类型的使用,以便不再需要原地更改(即将它们视为不可变值对象)。

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

这个改变是99.999%向后兼容,除非您在连接池连接之间使用临时表。

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

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

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

请注意,这个改变破坏了跨会话提交使用的临时表,这是由于 SQLite 处理临时表的方式。如果需要超出一个连接池连接范围的临时表,请参阅www.sqlalchemy.org/docs/dialects/sqlite.html#using- temporary-tables-with-sqlite 中的说明。

#1921

Session.merge()为具有版本控制的映射器检查版本 id

Session.merge()将会检查传入状态的版本 id 与数据库中的版本 id 是否匹配,假设映射使用了版本 id,并且传入状态已经分配了一个版本 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.id将是与Parent.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 对象的策略,用于对所有带有Child条件的 Parent 对象进行查询,方式与 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”。行位于“Parent”中,看到多态标识对应于“Child”,但是假设“child”中的实际行已被删除。由于这种损坏,行会带有所有对应于“child”的列设置为 NULL 的值 - 这是现在被填充的值,而不是父表中的值。

#1892

将两个或更多同名列映射到连接时需要明确声明

这与之前的变更#1892有些相关。在映射到连接时,同名列必须显式地链接到映射属性,即如将类映射到多个表中描述的那样。

给定两个表 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

映射器要求多态性的列在映射的可选择项中存在

在 0.6 中是一个警告,现在在 0.7 中是一个错误。给定用于 polymorphic_on 的列必须在映射的可选择项中。这是为了防止一些偶发的用户错误,例如:

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

在这种情况下,polymorphic_on 需要在sometable列上,也许是sometable.c.some_lookup_id。有时还会出现一些“多态联合”场景,类似的错误有时也会发生。

这样的配置错误一直都是“错误”的,并且上述映射不按照指定的方式工作 - 列将被忽略。然而,在极少数情况下,这可能是向后不兼容的,因为应用程序可能一直在无意中依赖于这种行为。

#1875

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

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

#1897

Table.c / MetaData.tables 稍微精炼了一下,不允许直接变异。

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

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

#1893 #1917

server_default 对所有 inserted_primary_key 值始终返回 None

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

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

查询时间配方更改

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

弃用的 API

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

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

如果在核心类型如 Integer 中使用参数,可能是你打算使用特定于方言的类型,比如 sqlalchemy.dialects.mysql.INTEGER,它接受一个“display_width”参数。

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

这个系统从最初是一个小型的、实现在单个映射器本地的东西,命名不当,逐渐演变成了更像是一个全局“注册表”级别的功能,命名不当,因此我们通过将实现移出 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.join, eagerload() 等已被弃用:

# 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 已移除

这一功能提供了一个映射器扩展,将基于类的功能与特定的ScopedSession链接起来,特别是提供了这样的行为,即新对象实例将自动与该会话关联。 该功能被教程和框架过度使用,导致用户混淆,因为其隐式行为,并在 0.5.5 中被弃用。 复制其功能的技术在[wiki:UsageRecipes/SessionAwareMapper]中。

介绍

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

尽可能地,更改是以不破坏为 0.6 构建的应用程序的兼容性的方式进行的。 必然不向后兼容的更改非常少,除了一个,即对可变属性默认值的更改,应该影响极小部分应用程序 - 许多更改涉及非公共 API 和未记录的一些用户可能一直在尝试使用的黑客技巧。

还有第二个更小的一类不向后兼容的更改也有文档记录。 这类更改涉及那些自 0.5 版本以来已被弃用并自弃用以来一直引发警告的功能和行为。 这些更改只会影响仍在使用 0.4 或早期 0.5 样式 API 的应用程序。 随着项目的成熟,我们在 0.x 级别发布中有越来越少这类更改,这是由于我们的 API 具有越来越少的功能,这些功能对于它们旨在解决的用例来说不太理想。

在 SQLAlchemy 0.7 中,一系列现有功能已被取代。 “取代”和“弃用”之间没有太大区别,只是前者对旧功能的暗示要被删除的可能性较小。 在 0.7 中,像synonymcomparable_property以及所有Extension和其他事件类等功能已被取代。 但是这些“取代”功能已被重新实现,使得它们的实现主要存在于核心 ORM 代码之外,因此它们的持续“挂在”不会影响 SQLAlchemy 进一步简化和完善其内部的能力,并且我们预计它们将在可预见的未来保留在 API 中。

新功能

新事件系统

SQLAlchemy 早期开始使用 MapperExtension 类,该类提供了对映射器持久性周期的钩子。随着 SQLAlchemy 快速变得更加组件化,将映射器推入更专注的配置角色,许多更多的“扩展”、“监听器”和“代理”类出现,以以一种临时的方式解决各种活动拦截用例。其中一部分是由活动的分歧驱动的;ConnectionProxy 对象希望提供一个重写语句和参数的系统;AttributeExtension 提供了一个替换传入值的系统,而 DDL 对象具有可以切换到方言敏感可调用对象的事件。

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

新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。API 的外观和感觉受到了 JQuery、Blinker 和 Hibernate 等多样化来源的驱动,并且在与数十位用户进行的 Twitter 会议期间进行了多次修改,这似乎比邮件列表对这类问题的回应率要高得多。

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

几个系统现在基于新的事件 API 构建,包括新的“可变属性” API 以及复合属性。对事件的更大强调还导致引入了一些新事件,包括属性过期和刷新操作,pickle 加载/转储操作,完成的映射器构造操作。

另请参见

事件

#1902

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

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

synonym()comparable_property()仍然是 ORM 的一部分,尽管它们的实现已经移出,建立在类似于混合扩展的方法之上,以便核心 ORM 映射器/查询/属性模块在其他情况下并不真正意识到它们。

另请参阅

混合属性

#1903

速度增强

与所有主要 SQLA 版本一样,通过内部进行广泛的遍历以减少开销和调用次数,进一步减少了在常见情况下所需的工作量。此版本的亮点包括:

  • 刷新过程现在将 INSERT 语句捆绑成批次供cursor.executemany()执行,对于主键已经存在的行。特别是在连接表继承配置中通常适用于“子”表,这意味着对于大量连接表对象的大批量插入,可以将对cursor.execute的调用次数减半,从而允许本地 DBAPI 优化对传递给cursor.executemany()的语句进行(例如重用准备好的语句)。

  • 当访问已加载的相关对象的多对一引用时,调用的代码路径已经大大简化。直接检查标识映射,无需首先生成新的Query对象,这在访问成千上万个内存中的多对一时是昂贵的。对于大多数延迟属性加载,也不再使用每次构造的“加载器”对象。

  • 重新编写组合使得在映射器内部访问映射属性时可以走更短的代码路径。

  • 新的内联属性访问函数取代了以前在“保存-更新”和其他级联操作需要在属性关联的所有数据成员范围内级联时使用“history”的做法。这减少了为这个速度关键操作生成新的History对象的开销。

  • ExecutionContext的内部,即对语句执行的对象,已经被内联和简化。

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

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

包括一个示例基准脚本的调用次数减少演示在techspot.zzzeek.org/2010/12/12/a-tale-of-three- profiles/

复合已重写

“composite”功能已被重写,类似于synonym()comparable_property(),使用了基于描述符和事件的轻量级实现,而不是构建到 ORM 内部。这样做可以从映射器/工作单元内部删除一些延迟,并简化复合属性的工作方式。复合属性现在不再隐藏其构建在其上的基础列,这些列现在保持为常规属性。复合还可以充当relationship()以及Column()属性的代理。

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

另请参阅

复合列类型

Mutation Tracking

#2008 #2024

更简洁的查询.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,提供了一种机制,用户定义的数据类型可以向拥有的父级或父级提供更改事件。该扩展包括一种用于标量数据库值的方法,例如由PickleTypepostgresql.ARRAY或其他自定义MutableType类管理的值,以及一种用于 ORM “组合”配置的方法,这些配置使用composite()

另请参阅

Mutation Tracking

NULLS FIRST / NULLS LAST 操作符

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

另请参阅

nullsfirst()

nullslast()

#723

select.distinct()、query.distinct()接受 PostgreSQL DISTINCT ON 的*args

通过将表达式列表传递给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__的好处,特别是在与 mixins 一起使用时:

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)

Indexes

窗口函数 SQL 构造

“窗口函数”向语句提供有关生成的结果集的信息。这允许根据诸如“行号”、“排名”等各种条件进行筛选。它们至少被 PostgreSQL、SQL Server 和 Oracle 支持,可能还有其他数据库。

关于窗口函数的最佳介绍在 PostgreSQL 的网站上,自从 8.4 版本以来就支持窗口函数:

www.postgresql.org/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与整数主键列一起使用

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

#2005 #2006

TypeDecorator存在于“sqlalchemy”导入空间中

不再需要从sqlalchemy.types导入此内容,现在在sqlalchemy中有镜像。

新方言

已添加方言:

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

    Drizzle

  • 支持 pymysql DBAPI:

    pymsql Notes

  • psycopg2 现在与 Python 3 兼容

新事件系统

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

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

新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。API 的外观和感觉受到了 JQuery、Blinder 和 Hibernate 等多样化来源的驱动,并且在与数十名用户进行的会议期间多次进行了修改,这些会议在 Twitter 上的响应率似乎比邮件列表更高。

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

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

另请参阅

事件

#1902

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

“派生属性”示例现在已成为官方扩展。synonym()的典型用例是为映射列提供描述符访问;comparable_property()的用例是能够从任何描述符返回PropComparator。实际上,“派生”的方法更容易使用,更具可扩展性,用几十行纯 Python 实现,几乎不需要导入,甚至不需要 ORM 核心知道它。该功能现在被称为“混合属性”扩展。

synonym()comparable_property()仍然是 ORM 的一部分,尽管它们的实现已经移出,建立在类似于混合扩展的方法上,因此核心 ORM 映射器/查询/属性模块在其他情况下并不真正意识到它们。

另请参见

混合属性

#1903

速度增强

与所有主要 SQLA 版本一样,通过内部进行广泛的遍历以减少开销和调用次数,进一步减少了常见情况下所需的工作量。此版本的亮点包括:

  • 刷新过程现在将 INSERT 语句捆绑成批次提供给cursor.executemany(),对于主键已经存在的行。特别是这通常适用于连接表继承配置中的“子”表,这意味着对于大量连接表对象的批量插入,可以将cursor.execute的调用次数减少一半,从而允许针对那些传递给cursor.executemany()的语句进行本地 DBAPI 优化(例如重用准备好的语句)。

  • 当访问已加载的相关对象的多对一引用时,调用的代码路径已经大大简化。直接检查标识映射,无需首先生成一个新的Query对象,这在访问成千上万个内存中的多对一时是昂贵的。对于大多数延迟属性加载,也不再使用每次构造的“加载器”对象。

  • 重写复合体允许在映射器内部访问在刷新中与映射属性相关的属性时,使用更短的代码路径。

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

  • ExecutionContext的内部,对应于语句执行的对象,已经内联并简化。

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

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

一个减少调用次数的演示,包括一个示例基准脚本,位于techspot.zzzeek.org/2010/12/12/a-tale-of-three- profiles/。

复合体重写

“复合”功能已经被重写,就像synonym()comparable_property()一样,使用基于描述符和事件的轻量级实现,而不是构建到 ORM 内部。这允许从映射器/工作单元内部删除一些延迟,并简化复合的工作方式。复合属性现在不再隐藏其构建在其上的基础列,这些列现在保持为常规属性。复合还可以充当relationship()以及Column()属性的代理。

复合的主要不兼容变更是,它们不再使用mutable=True系统来检测原地变异。请使用Mutation Tracking扩展来建立对现有复合使用的原地更改事件。

另请参见

复合列类型

变异跟踪

#2008 #2024

更简洁的查询.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”

一个新的扩展,变异跟踪,提供了一种机制,通过该机制,用户定义的数据类型可以向拥有的父级或父级提供更改事件。该扩展包括一种用于标量数据库值的方法,例如由PickleType管理的值,postgresql.ARRAY或其他自定义MutableType类,以及一种用于 ORM“复合”配置的方法,这些配置使用composite()

另请参见

变异跟踪

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__的好处,特别是在与混合使用时:

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)

Indexes

窗口函数 SQL 构造

“窗口函数”为语句提供了有关生成的结果集的信息。这允许根据诸如“行号”、“排名”等各种条件进行查询。它们至少被已知支持的 PostgreSQL、SQL Server 和 Oracle 支持,可能还有其他数据库。

关于窗口函数的最佳介绍在 PostgreSQL 的网站上,窗口函数自 8.4 版本起就得到支持:

www.postgresql.org/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 Notes

  • psycopg2 现在与 Python 3 兼容

行为变更(向后兼容)

默认情况下构建 C 扩展

这是从 0.7b4 开始的。如果检测到 cPython 2.xx,则会构建扩展。如果构建失败,例如在 Windows 安装中,会捕获该条件并继续非 C 安装。如果使用 Python 3 或 PyPy,则不会构建 C 扩展。

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

Query.count()内部的非常古老的猜测现在已经被现代化,使用.from_self()。也就是说,query.count()现在等效于:

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

以前,内部逻辑尝试重写查询本身的列子句,并在检测到“子查询”条件时,例如可能在其中具有聚合的基于列的查询,或具有 DISTINCT 的查询时,会经历一个繁琐的过程来重写列子句。这种逻辑在复杂条件下失败,特别是涉及联接表继承的条件,并且长期以来已经被更全面的.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

换句话说,原始查询完全保留在子查询中,不再需要猜测如何应用计数。

#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

简化的多态 _on 赋值

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

#1895

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

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

允许刷新没有父级的孤儿

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

#1912

在收集成员,不是刷新的标量引用时生成的警告

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

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

#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,比如一个selectTablejoinorm.aliased()构造时,它会通过到该 from 构造的.alias()方法,而不是构造一个 ORM 级别的AliasedClass

#2018

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

这是为了允许执行/连接操作明确参与引擎的打开事务。它还允许Session的自定义子类实现自己的get_bind()方法和参数,以便在execute()connection()方法中等效地使用这些自定义参数。

Session.connection Session.execute

#1996

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

在选择的“列子句”中存在的绑定参数现在像其他“匿名”子句一样自动标记,这样在获取行时它们的“类型”就有意义,就像结果行处理器一样。

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’。

默认构建 C 扩展

这是从 0.7b4 开始的。如果检测到 cPython 2.xx,则将构建扩展。如果构建失败,例如在 Windows 安装中,将捕获该条件并继续非 C 安装。如果使用 Python 3 或 PyPy,则不会构建 C 扩展。

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

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

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

以前,内部逻辑尝试重写查询本身的列子句,并在检测到“子查询”条件时,例如可能在其中具有聚合函数的基于列的查询,或具有 DISTINCT 的查询,将经历一个复杂的过程来重写列子句。这种逻辑在复杂条件下失败,特别是涉及联接表继承的条件,并且已经被更全面的.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

即原始查询完全保留在子查询中,不再猜测应如何应用计数。

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

发出非子查询形式的 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行为,即echo局限于各个引擎,而无需额外的标识字符串局限于这些引擎。

#1926

简化的多态 _on 赋值

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

#1895

包含跨多个路径(即“all()”)的 contains_eager()链

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

在刷新时生成的警告,集合成员,不是刷新的一部分的标量引用

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

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

#1973

设置不再安装 Nose 插件。

自从我们使用 nose 以来,我们已经使用通过 setuptools 安装的插件,以便 nosetests 脚本会自动运行 SQLA 的插件代码,这对于我们的测试来说是必要的,以获得完整的环境。在 0.6 版本中间,我们意识到这里的导入模式意味着 Nose 的“覆盖率”插件会中断,因为“覆盖率”要求在导入要覆盖的任何模块之前启动;因此,在 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,比如一个selectTablejoinorm.aliased()构造时,它会通过到该 from 构造的.alias()方法,而不是构造一个 ORM 级别的AliasedClass

#2018

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

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

Session.connection Session.execute

#1996

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

在 select 的“columns clause”中存在的绑定参数现在会像其他“匿名”子句一样自动标记,这样在获取行时它们的“类型”就会有意义,就像结果行处理器一样。

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 Column Types描述的技术配置的属性,已经重新实现,ORM 内部不再意识到它们(导致关键部分的代码路径更短更高效)。虽然复合类型通常被视为不可变的值对象,但从未强制执行。对于使用具有可变性的复合对象的应用程序,Mutation Tracking扩展提供了一个基类,该基类建立了一个机制,使用户定义的复合类型能够向每个对象的拥有父对象发送更改事件消息。

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

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

这个改变是99.999%向后兼容的,除非你在连接池连接之间使用临时表。

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

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

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

请注意,这个改变破坏了跨 Session 提交使用的临时表,这是由于 SQLite 处理临时表的方式造成的。如果需要超出一个连接池范围的临时表,请参阅www.sqlalchemy.org/docs/dialects/sqlite.html#using- temporary-tables-with-sqlite 中的说明。

#1921

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

Session.merge()将检查传入状态的版本 id 与数据库的版本 id 是否匹配,假设映射使用版本 id 并且传入状态已分配版本 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。以前会是类似foo_spam的东西,这是由于union组合事物的方式,这与非联合查询的情况下的名称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.id将与Parent.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对象的策略,用于以与 0.5 和 0.6 相同的方式渲染针对所有Parent对象的查询,并针对Child进行条件限制:

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”。行位于“Parent”,看到多态标识对应于“Child”,但假设“child”中的实际行已经被删除。由于这种损坏,行将带有所有对应于“child”的列设置为 NULL 的列 - 这现在是被填充的值,而不是父表中的值。

#1892

映射到具有两个或更多同名列的连接需要明确声明。

这与先前在#1892中的更改有些相关。在映射到连接时,同名列必须明确链接到映射的属性,即如在多个表上映射一个类中所述。

给定两个表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。还有一些“多态联合”场景,类似的错误有时会发生。

这样的配置错误一直是“错误的”,上述映射不像规定的那样工作 - 该列将被忽略。然而,如果某个应用程序不知情地依赖于此行为,则这可能是潜在的向后不兼容的情况。

#1875

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

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

#1897

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

另一个区域是,一些用户以一种实际上不按预期工作的方式进行尝试,但仍然留下了极小的可能性,即某些应用程序正在依赖于此行为,Table.c属性返回的构造和MetaData.tables属性是明确不可变的。构造的“可变”版本现在是私有的。向.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 端的“默认”而不是 server_default。

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

#2020 #2021

sqlalchemy.exceptions在 sys.modules 中的别名已被移除

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

from sqlalchemy import exc

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

查询时间配方更改

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

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 扩展

所谓“复合”映射属性,即使用复合列类型描述的技术配置的属性,已经重新实现,使得 ORM 内部不再意识到它们(导致关键部分的代码路径更短更高效)。虽然复合类型通常被视为不可变的值对象,但从未强制执行。对于使用具有可变性的复合类型的应用程序,变异跟踪扩展提供了一个基类,建立了一个机制,使用户定义的复合类型能够向每个对象的拥有父对象发送更改事件消息。

使用复合类型并依赖于这些对象的原地变异检测的应用程序应该迁移到“变异跟踪”扩展,或者更改复合类型的使用方式,使得不再需要原地更改(即将其视为不可变的值对象)。

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

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

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

以前使用的是SingletonThreadPool,这意味着线程中对某个引擎的所有连接都是相同的连接。新方法的目的是更直观,特别是在使用多个连接时。

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

请注意,这个改变破坏了跨 Session 提交使用的临时表,这是由于 SQLite 处理临时表的方式。如果需要超出一个连接池连接范围的临时表,请参阅www.sqlalchemy.org/docs/dialects/sqlite.html#using- temporary-tables-with-sqlite 中的说明。

#1921

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

Session.merge()将检查传入状态的版本 ID 与数据库的版本 ID 是否匹配,假设映射使用版本 ID 并且传入状态已分配版本 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。以前会是类似于foo_spam的东西,这是由于union的组合方式导致的,这与非联合查询中的名称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.id将成为与Parent.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对象的策略,用于对所有具有针对Child的条件的Parent对象进行查询,方式与 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”进行左外连接。行位于“Parent”,看到多态标识对应于“Child”,但假设“child”中的实际行已经删除。由于这种损坏,行中所有与“child”对应的列都设置为 NULL - 这现在是被填充的值,而不是父表中的值。

#1892

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

这与#1892中的先前更改有些相关。在映射到连接时,同名列必须明确链接到映射属性,即如映射一个类到多个表中所述。

给定两个表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

映射器要求在映射的可选择项中存在多态列

这在 0.6 中是一个警告,现在在 0.7 中是一个错误。为polymorphic_on指定的列必须在映射的可选择项中。这是为了防止一些偶尔发生的用户错误,例如:

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

在上面的例子中,polymorphic_on需要在sometable列上,此时可能是sometable.c.some_lookup_id。有时也会发生一些类似的“多态联合”场景中的错误。

这种配置错误一直是“错误的”,上述映射不按规定工作 - 列将被忽略。然而,在极少数情况下,如果应用程序无意中依赖于这种行为,则可能会产生潜在的向后不兼容性。

#1875

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

以前,在DDL()字符串中的百分号必须进行转义,即根据 DBAPI,对于那些接受pyformatformat绑定(例如 psycopg2,mysql-python)的 DBAPI,这是不一致的,与text()构造自动执行的操作不同。现在,DDL()text()一样进行相同的转义。

#1897

Table.c / MetaData.tables稍作调整,不允许直接变异

另一个领域,一些用户在进行尝试时,并不按预期工作,但仍然存在极小的可能性,即某些应用程序依赖于这种行为,Table.c属性和MetaData.tables属性返回的构造明确是不可变的。构造的“可变”版本现在是私有的。向.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 端的“默认”而不是 server_default。

关于此情况的反射,具有服务器默认值的 int PK 列的反射将 “autoincrement” 标志设置为 False,但在检测到 PG SERIAL 列的序列默认值的情况下除外。

#2020 #2021

sqlalchemy.exceptions 在 sys.modules 中的别名已被移除。

几年来,我们已经将字符串 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 等简单类型不接受参数。 从 0.7b4/0.7.0 开始,接受/忽略 catchall \*args, \**kwargs 的默认构造函数已经恢复,但会发出弃用警告。

如果正在使用诸如 Integer 等核心类型的参数,可能是您打算使用特定于方言的类型,例如 sqlalchemy.dialects.mysql.INTEGER,该类型接受“display_width”参数,例如。

compile_mappers()重命名为configure_mappers(),简化配置内部。

这个系统从一个小型、局部实现在单个映射器上,并且命名不当的东西慢慢演变成了更像是全局“注册表”级别函数且命名不当的东西,所以我们通过将实现移出Mapper并将其重命名为configure_mappers()来修复了这两个问题。当然,一般情况下应用程序通常不需要调用configure_mappers(),因为这个过程是根据需要的,一旦通过属性或查询访问需要映射时就会发生。

#1966

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

PoolListenerConnectionProxyDDLElement.execute_atevent.listen()取代,分别使用PoolEventsEngineEventsDDLEvents作为分派目标。

ORM 扩展被事件监听器取代。

MapperExtensionAttributeExtensionSessionExtensionevent.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取代。

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

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

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

如果在核心类型如Integer中使用参数,可能是您打算使用特定于方言的类型,例如sqlalchemy.dialects.mysql.INTEGER,例如它接受“display_width”参数。

compile_mappers()重命名为configure_mappers(),简化配置内部。

这个系统从一个小型、局部实现在单个映射器上,并且命名不当的东西慢慢演变成了更像是全局“注册表”级别函数且命名不当的东西,所以我们通过将实现移出Mapper并将其重命名为configure_mappers()来修复了这两个问题。当然,一般情况下应用程序通常不需要调用configure_mappers(),因为这个过程是根据需要的,一旦通过属性或查询访问需要映射时就会发生。

#1966

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

PoolListenerConnectionProxyDDLElement.execute_atevent.listen() 取代,分别使用 PoolEventsEngineEventsDDLEvents 分发目标。

ORM 扩展被事件监听器取代

MapperExtensionAttributeExtensionSessionExtensionevent.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

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

#1950

请注意,这会影响 Beaker 缓存示例,其中 _params_from_query() 函数的工作需要进行轻微调整。如果你正在使用 Beaker 示例中的代码,应用这一变更。

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

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

#1870

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

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

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

非公共 Pool 方法已被标记下划线

所有 Pool 及其子类的不打算公开使用的方法都已重命名为下划线。它们之前没有以这种方式命名是一个 bug。

池化方法现在已经强调或移除:

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 已被移除

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

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已移除。

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

SQLAlchemy 0.6 中的新功能是什么?

原文:docs.sqlalchemy.org/en/20/changelog/migration_06.html

关于本文档

本文档描述了 SQLAlchemy 版本 0.5(上次发布于 2010 年 1 月 16 日)与 SQLAlchemy 版本 0.6(上次发布于 2012 年 5 月 5 日)之间的变化。

文档日期:2010 年 6 月 6 日

本指南记录了影响用户将其应用程序从 SQLAlchemy 0.5 系列迁移到 0.6 版本的 API 更改。请注意,SQLAlchemy 0.6 移除了一些在 0.5 系列期间已弃用的行为,并且还弃用了更多与 0.5 版本特定的行为。

平台支持

  • cPython 版本 2.4 及以上,遍及 2.xx 系列

  • Jython 2.5.1 - 使用 Jython 随附的 zxJDBC DBAPI。

  • cPython 3.x - 有关如何为 python3 构建的信息,请参见 [source:sqlalchemy/trunk/README.py3k]。

新的方言系统

方言模块现在被分解为单个数据库后端范围内的不同子组件。方言实现现在位于 sqlalchemy.dialects 包中。sqlalchemy.databases 包仍然存在作为占位符,以提供一定程度的向后兼容性,用于简单的导入。

对于每个受支持的数据库,在 sqlalchemy.dialects 中都存在一个子包,其中包含几个文件。每个包包含一个名为 base.py 的模块,该模块定义了该数据库使用的特定 SQL 方言。它还包含一个或多个“driver”模块,每个模块对应一个特定的 DBAPI - 这些文件的名称与 DBAPI 本身相对应,例如 pysqlitecx_oraclepyodbc。SQLAlchemy 方言使用的类首先在 base.py 模块中声明,定义数据库定义的所有行为特征。这些包括能力映射,例如“支持序列”,“支持返回”等,类型定义和 SQL 编译规则。然后,每个“driver”模块根据需要提供那些类的子类,这些子类覆盖默认行为,以适应该 DBAPI 的附加功能、行为和怪癖。对于支持多个后端的 DBAPI(如 pyodbc、zxJDBC、mxODBC),方言模块将使用来自 sqlalchemy.connectors 包的混合物,这些混合物提供了跨所有后端的该 DBAPI 的功能,最常见的是处理连接参数。这意味着使用 pyodbc、zxJDBC 或 mxODBC(当实现时)进行连接在受支持的后端上是非常一致的。

create_engine() 使用的 URL 格式已经增强,以处理特定后端的任意数量的 DBAPI,使用的方案受到 JDBC 的启发。以前的格式仍然有效,并且将选择“默认”DBAPI 实现,例如下面的 PostgreSQL URL 将使用 psycopg2:

create_engine("postgresql://scott:tiger@localhost/test")

但是,要指定特定的 DBAPI 后端,比如 pg8000,请将其添加到 URL 的“protocol”部分,使用加号“+”:

create_engine("postgresql+pg8000://scott:tiger@localhost/test")

重要的方言链接:

关于方言的其他注意事项:

  • SQLAlchemy 0.6 中类型系统发生了巨大变化。这对所有方言都有影响,包括命名约定、行为和实现。请参阅下面关于“类型”的部分。

  • ResultProxy对象现在在某些情况下提供了 2 倍的速度改进,这要归功于一些重构。

  • RowProxy,即单个结果行对象,现在可以直接进行 pickle。

  • setuptools 入口点现在用于定位外部方言的名称是sqlalchemy.dialects。针对 0.4 或 0.5 编写的外部方言需要修改以适应 0.6,在任何情况下这个改变并不增加任何额外的困难。

  • 方言现在在初始连接时接收一个 initialize()事件来确定连接属性。

  • 编译器生成的函数和操作符现在使用(几乎)常规的调度函数形式“visit_”和“visit__fn”来提供定制处理。这取代了在编译器子类中复制“functions”和“operators”字典的需要,改为使用直接的访问者方法,同时也允许编译器子类完全控制渲染,因为完整的 _Function 或 _BinaryExpression 对象被传递进来。

方言导入

方言的导入结构已经改变。每个方言现在通过sqlalchemy.dialects.<name>导出其基本的“dialect”类以及该方言支持的完整一组 SQL 类型。例如,要导入一组 PG 类型:

from sqlalchemy.dialects.postgresql import (
    INTEGER,
    BIGINT,
    SMALLINT,
    VARCHAR,
    MACADDR,
    DATE,
    BYTEA,
)

在上面,INTEGER实际上是来自sqlalchemy.types的普通INTEGER类型,但 PG 方言使其以与那些特定于 PG 的类型相同的方式可用,例如BYTEAMACADDR

表达式语言变化

一个重要的表达式语言陷阱

表达式语言有一个相当重要的行为变化,可能会影响一些应用程序。Python 布尔表达式的布尔值,即==!=等,现在在比较两个子句对象时会准确评估。

正如我们所知,将ClauseElement与任何其他对象进行比较会返回另一个ClauseElement

>>> from sqlalchemy.sql import column
>>> column("foo") == 5
<sqlalchemy.sql.expression._BinaryExpression object at 0x1252490>

这样 Python 表达式在转换为字符串时会产生 SQL 表达式:

>>> str(column("foo") == 5)
'foo = :foo_1'

但是如果我们这样说会发生什么?

>>> if column("foo") == 5:
...     print("yes")

在之前的 SQLAlchemy 版本中,返回的_BinaryExpression是一个普通的 Python 对象,其求值为True。现在它的求值取决于实际的ClauseElement是否应该具有与被比较的相同哈希值。意思是:

>>> bool(column("foo") == 5)
False
>>> bool(column("foo") == column("foo"))
False
>>> c = column("foo")
>>> bool(c == c)
True
>>>

这意味着如下代码:

if expression:
    print("the expression is:", expression)

如果 expression 是二进制子句,则不会被评估。由于上述模式永远不应该被使用,因此基本的 ClauseElement 现在在布尔上下文中调用时会引发异常:

>>> bool(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
  raise TypeError("Boolean value of this clause is not defined")
TypeError: Boolean value of this clause is not defined

希望检查 ClauseElement 表达式是否存在的代码应该这样说:

if expression is not None:
    print("the expression is:", expression)

请记住,这也适用于 Table 和 Column 对象

更改的理由有两个:

  • 形如 if c1 == c2:  <do something> 的比较现在可以这样写了。

  • 现在支持在其他平台(即 Jython)上正确对 ClauseElement 对象进行哈希处理。直到这一点,SQLAlchemy 在这方面严重依赖 cPython 的特定行为(并且仍然偶尔出现问题)。

更严格的“executemany”行为

SQLAlchemy 中的“executemany”对应于调用 execute(),传递一系列绑定参数集:

connection.execute(table.insert(), {"data": "row1"}, {"data": "row2"}, {"data": "row3"})

Connection 对象发送给定的 insert() 构造进行编译时,它传递给编译器传递的第一组绑定中存在的键名,以确定语句的 VALUES 子句的构造。熟悉这种构造的用户会知道,剩余字典中存在的额外键没有任何影响。现在的不同之处在于,所有后续字典都需要至少包含第一个字典中存在的每个键。这意味着像这样的调用不再起作用:

connection.execute(
    table.insert(),
    {"timestamp": today, "data": "row1"},
    {"timestamp": today, "data": "row2"},
    {"data": "row3"},
)

因为第三行没有指定timestamp列。之前的 SQLAlchemy 版本会简单地为这些缺失的列插入 NULL。然而,如果上面示例中的 timestamp 列包含 Python 端的默认值或函数,则不会被使用。这是因为“executemany”操作针对大量参数集的最大性能进行了优化,并且不会尝试评估那些缺失键的 Python 端默认值。由于默认值通常被实现为嵌入在 INSERT 语句中的 SQL 表达式,或者是服务器端表达式,再次根据 INSERT 字符串的结构触发,这些默认值无法根据每个参数集有条件地触发,因此 Python 端默认值与 SQL/服务器端默认值的行为不同是不一致的。(基于 SQL 表达式的默认值从 0.5 系列开始嵌入在内部,再次为了最小化大量参数集的影响)。

因此,SQLAlchemy 0.6 通过禁止任何后续参数集留下任何字段空白来建立可预测的一致性。这样,Python 端默认值和函数就不会再默默失败,而且它们的行为与 SQL 和服务器端默认值保持一致。

UNION 和其他“复合”结构都有一致的括号配对。

为了帮助 SQLite 而设计的规则已被移除,即在另一个复合元素内的第一个复合元素(例如,在except_()内部的union())不会被括号括起来。这是不一致的,并且在 PostgreSQL 上产生错误的结果,因为它对 INTERSECTION 有优先规则,这通常会让人感到惊讶。在与 SQLite 使用复杂复合时,现在需要将第一个元素转换为子查询(这也在 PG 上兼容)。在 SQL 表达式教程的最后有一个新的示例[www.sqlalchemy.org/docs/06/sqlexpression.html #unions-and-other-set-operations]。更多背景信息请参见#1665和 r6690。

用于结果提取的 C 扩展

ResultProxy及其相关元素,包括大多数常见的“行处理”函数,如 Unicode 转换、数值/布尔转换和日期解析,已经被重新实现为可选的 C 扩展,以提高性能。这标志着 SQLAlchemy 走向“黑暗面”的开始,我们希望通过在 C 中重新实现关键部分来持续改进性能。可以通过指定--with-cextensions来构建这些扩展,即python setup.py --with- cextensions install

这些扩展对使用直接ResultProxy访问的结果提取影响最为显著,即由engine.execute()connection.execute()session.execute()返回的结果。在 ORM Query对象返回的结果中,结果提取不占很高的开销比例,因此 ORM 性能改善较为适度,主要体现在提取大型结果集方面。性能改进高度依赖于使用的 dbapi 以及访问每行列的语法(例如,row['name']row.name快得多)。当前的扩展对插入/更新/删除的速度没有影响,也不会改善 SQL 执行的延迟,也就是说,一个大部分时间用于执行许多语句且结果集非常小的应用程序不会看到太多改进。

与扩展无关,0.6 版本的性能比 0.5 版本有所提高。使用 SQLite 连接和提取 50000 行的快速概述,主要使用直接的 SQLite 访问、ResultProxy和简单的映射 ORM 对象:

sqlite select/native: 0.260s

0.6 / C extension

sqlalchemy.sql select: 0.360s
sqlalchemy.orm fetch: 2.500s

0.6 / Pure Python

sqlalchemy.sql select: 0.600s
sqlalchemy.orm fetch: 3.000s

0.5 / Pure Python

sqlalchemy.sql select: 0.790s
sqlalchemy.orm fetch: 4.030s

在上述情况下,ORM 由于 Python 中的性能增强,比 0.5 版本快 33%。使用 C 扩展,我们又获得了 20%的提升。然而,与不使用 C 扩展相比,ResultProxy的提取速度提高了 67%。其他测试报告显示,在某些场景中,如发生大量字符串转换的情况下,速度提高了多达 200%。

新的模式功能

sqlalchemy.schema包得到了一些长期需要的关注。最显著的变化是新扩展的 DDL 系统。在 SQLAlchemy 中,自版本 0.5 以来,可以创建自定义的 DDL 字符串并将其与表或元数据对象关联:

from sqlalchemy.schema import DDL

DDL("CREATE TRIGGER users_trigger ...").execute_at("after-create", metadata)

现在,完整的 DDL 构造都在同一系统下可用,包括用于 CREATE TABLE、ADD CONSTRAINT 等的构造:

from sqlalchemy.schema import Constraint, AddConstraint

AddContraint(CheckConstraint("value > 5")).execute_at("after-create", mytable)

此外,所有 DDL 对象现在都是常规的ClauseElement对象,就像任何其他 SQLAlchemy 表达式对象一样:

from sqlalchemy.schema import CreateTable

create = CreateTable(mytable)

# dumps the CREATE TABLE as a string
print(create)

# executes the CREATE TABLE statement
engine.execute(create)

并且使用sqlalchemy.ext.compiler扩展,你可以自己制作:

from sqlalchemy.schema import DDLElement
from sqlalchemy.ext.compiler import compiles

class AlterColumn(DDLElement):
    def __init__(self, column, cmd):
        self.column = column
        self.cmd = cmd

@compiles(AlterColumn)
def visit_alter_column(element, compiler, **kw):
    return "ALTER TABLE %s ALTER COLUMN %s  %s ..." % (
        element.column.table.name,
        element.column.name,
        element.cmd,
    )

engine.execute(AlterColumn(table.c.mycolumn, "SET DEFAULT 'test'"))

废弃/移除的模式元素

模式包也经过了大幅简化。在整个 0.5 版本中被废弃的许多选项和方法已被移除。其他鲜为人知的访问器和方法也已被移除。

  • “owner”关键字参数从Table中移除。使用“schema”表示要预置到表名之前的任何命名空间。

  • 废弃的MetaData.connect()ThreadLocalMetaData.connect()已被移除 - 将“bind”属性发送到绑定元数据。

  • 废弃的 metadata.table_iterator()方法已移除(使用 sorted_tables)

  • DefaultGenerator和子类中移除了“metadata”参数,但在Sequence上仍然本地存在,Sequence是 DDL 中的一个独立构造。

  • 废弃的PassiveDefault - 使用DefaultClause

  • IndexConstraint对象中移除了公共可变性:

    • ForeignKeyConstraint.append_element()

    • Index.append_column()

    • UniqueConstraint.append_column()

    • PrimaryKeyConstraint.add()

    • PrimaryKeyConstraint.remove()

这些应该以声明方式构建(即一次构建)。

  • 其他已移除的内容:

    • Table.key(不知道这是干什么的)

    • Column.bind(通过 column.table.bind 获取)

    • Column.metadata(通过 column.table.metadata 获取)

    • Column.sequence(使用 column.default)

其他行为变化

  • UniqueConstraintIndexPrimaryKeyConstraint都接受列名或列对象的列表作为参数。

  • ForeignKey上的use_alter标志现在是一个快捷选项,用于可以使用DDL()事件系统手动构建的操作。这次重构的一个副作用是,具有use_alter=TrueForeignKeyConstraint对象将不会在 SQLite 上发出,因为 SQLite 不支持外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上不遵守外键约束。

  • Table.primary_key不可分配 - 使用table.append_constraint(PrimaryKeyConstraint(...))

  • 具有ForeignKey但没有类型的Column定义,例如Column(name, ForeignKey(sometable.c.somecol))曾经可以获得引用列的类型。现在对于该自动类型推断的支持是部分的,可能不适用于所有情况。

日志打开了

通过在引擎、池或映射器创建后进行一些额外的方法调用,您可以设置 INFO 和 DEBUG 的日志级别,日志记录将开始。现在,isEnabledFor(INFO)方法将每个 Connection 调用一次,isEnabledFor(DEBUG)将每个 ResultProxy 调用一次,如果已在父连接上启用。池日志发送到 log.info()和 log.debug(),无需检查 - 请注意,池的检出/归还通常是每个事务一次。

反射/检查器 API

反射系统,允许通过 Table('sometable', metadata, autoload=True)反射表列的系统已经开放为自己的细粒度 API,允许直接检查数据库元素,如表、列、约束、索引等。这个 API 将返回值表达为简单的字符串列表、字典和 TypeEngine 对象。现在,autoload=True 的内部构建在这个系统之上,将原始数据库信息转换为 sqlalchemy.schema 构造集中化,各个方言的契约大大简化,大大减少了不同后端之间的错误和不一致性。

要使用检查器:

from sqlalchemy.engine.reflection import Inspector

insp = Inspector.from_engine(my_engine)

print(insp.get_schema_names())

在某些情况下,from_engine()方法将提供一个特定于后端的检查器,具有额外的功能,例如 PostgreSQL 提供了一个 get_table_oid()方法:

my_engine = create_engine("postgresql://...")
pg_insp = Inspector.from_engine(my_engine)

print(pg_insp.get_table_oid("my_table"))

RETURNING 支持

insert()、update()和 delete()构造现在支持一个 returning()方法,对应于 SQL RETURNING 子句,支持 PostgreSQL、Oracle、MS-SQL 和 Firebird。目前不支持其他后端。

给定一个与 select()构造相同方式的列表达式列表,这些列的值将作为常规结果集返回:

result = connection.execute(
    table.insert().values(data="some data").returning(table.c.id, table.c.timestamp)
)
row = result.first()
print("ID:", row["id"], "Timestamp:", row["timestamp"])

四个支持后端的 RETURNING 实现差异很大,Oracle 需要复杂使用 OUT 参数,这些参数被重新路由到一个“模拟”结果集中,而 MS-SQL 使用笨拙的 SQL 语法。RETURNING 的使用受到限制:

  • 它不适用于任何“executemany()”风格的执行。这是所有支持的 DBAPI 的限制。

  • 一些后端,比如 Oracle,只支持返回单行的 RETURNING - 这包括 UPDATE 和 DELETE 语句,这意味着 update()或 delete()构造必须仅匹配单行,否则会引发错误(由 Oracle 而不是 SQLAlchemy 引发)。

当单行 INSERT 语句需要获取新生成的主键值时,SQLAlchemy 也会自动使用 RETURNING,当没有通过显式的returning()调用另行指定时。这意味着在需要主键值的插入语句中不再需要“SELECT nextval(sequence)”预执行。说实话,隐式 RETURNING 功能确实比旧的“select nextval()”系统产生更多的方法开销,后者使用快速而简单的 cursor.execute()来获取序列值,并且在 Oracle 的情况下需要额外绑定输出参数。因此,如果方法/协议开销比额外的数据库往返更昂贵,可以通过在create_engine()中指定implicit_returning=False来禁用该功能。

类型系统更改

新架构

类型系统在幕后完全重建,以实现两个目标:

  • 将绑定参数和结果行值的处理分开,通常是 DBAPI 的要求,与类型本身的 SQL 规范分开,这是与总体方言重构一致的,将数据库 SQL 行为与 DBAPI 分开。

  • 为从TypeEngine对象生成 DDL 和基于列反射构造TypeEngine对象建立明确一致的契约。

这些变化的亮点包括:

  • 方言中类型的构建已经彻底改变。方言现在专门将公开可用的类型定义为大写名称,并使用下划线标识符(即私有)来定义内部实现类型。用于在 SQL 和 DDL 中表达类型的系统已移至编译器系统。这样做的效果是大多数方言中的类型对象要少得多。有关此架构的详细文档供方言作者参考在[source:/lib/sqlalchemy/dialects/type_migration_guidelines.txt]中。

  • 现在类型的反射返回 types.py 中的确切大写类型,或者如果类型不是标准 SQL 类型,则返回方言本身中的大写类型。这意味着反射现在返回有关反射类型的更准确信息。

  • 用户定义的类型,如果要提供get_col_spec()方法,现在应该继承UserDefinedType

  • 所有类型类上的result_processor()方法现在接受额外的参数coltype。这是附加到 cursor.description 的 DBAPI 类型对象,应在适用时使用,以便更好地决定应返回什么类型的结果处理可调用函数。理想情况下,结果处理函数永远不应该使用isinstance(),因为在这个级别上这是一个昂贵的调用。

本地 Unicode 模式

随着更多的 DBAPI 支持直接返回 Python Unicode 对象,基本方言现在在第一次连接时执行检查,以确定 DBAPI 是否为基本的 VARCHAR 值的基本选择返回 Python Unicode 对象。如果是这样,String类型和所有子类(即TextUnicode等)将在接收到结果行时跳过“unicode”检查/转换步骤。这为大型结果集提供了显著的性能提升。目前已知“unicode 模式”可以与以下一起使用:

  • sqlite3 / pysqlite

  • psycopg2 - SQLA 0.6 现在默认在每个 psycopg2 连接对象上使用“UNICODE”类型扩展

  • pg8000

  • cx_oracle(我们使用输出处理器 - 很好的功能!)

其他类型可能会根据需要禁用 Unicode 处理,例如在与 MS-SQL 一起使用时的NVARCHAR类型。

特别是,如果迁移基于以前返回非 Unicode 字符串的 DBAPI 的应用程序,则“本地 Unicode”模式具有明显不同的默认行为 - 声明为StringVARCHAR的列现在默认返回 Unicode,而以前会返回字符串。这可能会破坏期望非 Unicode 字符串的代码。可以通过向create_engine()传递use_native_unicode=False来禁用 psycopg2 的“本地 Unicode”模式。

一个更通用的解决方案是针对明确不想要 Unicode 对象的字符串列使用TypeDecorator,将 Unicode 转换回 utf-8,或者其他所需的格式:

class UTF8Encoded(TypeDecorator):
  """Unicode type which coerces to utf-8."""

    impl = sa.VARCHAR

    def process_result_value(self, value, dialect):
        if isinstance(value, unicode):
            value = value.encode("utf-8")
        return value

请注意,assert_unicode标志现已弃用。SQLAlchemy 允许 DBAPI 和后端数据库在可用时处理 Unicode 参数,并且不会通过检查传入类型增加操作开销;现代系统如 sqlite 和 PostgreSQL 会在其端引发编码错误,如果传递了无效数据。在 SQLAlchemy 确实需要将绑定参数从 Python Unicode 强制转换为编码字符串时,或者显式使用 Unicode 类型时,如果对象是字节串,则会发出警告。可以使用 Python 警告过滤器文档中记录的警告来抑制或将其转换为异常:docs.python.org/library/warnings.html

通用枚举类型

现在在 types 模块中有一个 Enum。这是一个字符串类型,给定一组“标签”,限制了给这些标签赋予的可能值。默认情况下,该类型生成一个VARCHAR,使用最大标签的大小,并在 CREATE TABLE 语句中对表应用 CHECK 约束。当使用 MySQL 时,默认情况下,该类型使用 MySQL 的 ENUM 类型;当使用 PostgreSQL 时,该类型将使用 CREATE TYPE <mytype> AS ENUM 生成用户定义类型。为了使用 PostgreSQL 创建类型,必须在构造函数中指定 name 参数。该类型还接受一个 native_enum=False 选项,该选项将为所有数据库发出 VARCHAR/CHECK 策略。请注意,当前 PostgreSQL ENUM 类型不能与 pg8000 或 zxjdbc 一起使用。

反射返回方言特定类型

反射现在从数据库返回尽可能最具体的类型。也就是说,如果您使用 String 创建一个表,然后反射它,那么反射的列可能是 VARCHAR。对于支持更特定形式类型的方言,您将得到相应的类型。因此,在 Oracle 上,Text 类型将返回为 oracle.CLOBLargeBinary 可能是 mysql.MEDIUMBLOB 等等。这里的明显优势是反射尽可能地保留了数据库要说的信息。

一些处理表元数据的应用程序可能希望比较反映的表和/或非反射的表上的类型。TypeEngine 上有一个半私有访问器叫做 _type_affinity,以及一个相关的比较助手 _compare_type_affinity。此访问器返回类型对应的“通用” types 类:

>>> String(50)._compare_type_affinity(postgresql.VARCHAR(50))
True
>>> Integer()._compare_type_affinity(mysql.REAL)
False

杂项 API 更改

通常的“通用”类型仍然是正在使用的一般系统,即 StringFloatDateTime。在那里有一些变化:

  • 类型不再猜测默认参数。特别是,NumericFloat,以及 NUMERIC、FLOAT、DECIMAL 的子类不生成长度或比例,除非指定。这也包括有争议的 StringVARCHAR 类型(尽管 MySQL 方言在要求不带长度渲染 VARCHAR 时会预先引发)。不假设默认值,如果它们在 CREATE TABLE 语句中使用,则在底层数据库不允许这些类型的非长度版本时会引发错误。

  • Binary 类型已更名为 LargeBinary,用于 BLOB/BYTEA/类似类型。对于 BINARYVARBINARY,它们直接存在于 types.BINARYtypes.VARBINARY,以及 MySQL 和 MS-SQL 方言中。

  • 当 mutable=True 时,PickleType 现在使用 == 比较值,除非为该类型指定了带有比较函数的“comparator”参数。如果您要 pickle 自定义对象,应该实现一个 __eq__() 方法,以便基于值的比较准确。

  • Numeric 和 Float 的默认“precision”和“scale”参数已被移除,并且现在默认为 None。NUMERIC 和 FLOAT 默认情况下将不带有数值参数呈现,除非提供了这些值。

  • SQLite 上的 DATE、TIME 和 DATETIME 类型现在可以接受可选的“storage_format”和“regexp”参数。“storage_format”可用于使用自定义字符串格式存储这些类型。“regexp”允许使用自定义正则表达式来匹配数据库中的字符串值。

  • 不再支持 SQLite 上 TimeDateTime 类型的 __legacy_microseconds__。你应该使用新的 “storage_format” 参数。

  • SQLite 上的 DateTime 类型现在默认使用更严格的正则表达式来匹配数据库中的字符串。如果你使用存储在传统格式中的数据,请使用新的 “regexp” 参数。

ORM 变更

从 0.5 升级到 0.6 的 ORM 应用应该几乎不需要更改,因为 ORM 的行为基本保持不变。有一些默认参数和名称更改,以及一些加载行为已经改进。

新工作单元

工作单元的内部,主要是 topological.pyunitofwork.py,已经完全重写并且大大简化。这对使用没有影响,因为所有现有的行为在 flush 过程中都被完全保持了下来(或者至少在我们的测试套件和少数重度测试的生产环境中被保持了下来)。flush() 的性能现在减少了 20-30% 的方法调用,并且应该使用更少的内存。现在,源代码的意图和流程应该相当容易理解,而且 flush 的架构在这一点上相当开放,为潜在的新领域创造了空间。flush 过程不再依赖递归,因此可以刷新任意大小和复杂度的 flush 计划。此外,mapper 的 “save” 过程,发出 INSERT 和 UPDATE 语句,现在缓存了两个语句的 “compiled” 形式,因此在非常大的 flush 过程中进一步大幅减少了调用次数。

与早期版本 0.6 或 0.5 相比,在 flush 与 flush 之间观察到的任何行为变化都应该尽快向我们报告 - 我们将确保不会丢失任何功能。

query.update()query.delete() 的变更

  • query.update() 上的 ‘expire’ 选项已更名为 ‘fetch’,因此与 query.delete() 的匹配项相匹配

  • query.update()query.delete() 的 synchronize 策略都默认为 ‘evaluate’。

  • ‘synchronize’ 策略对 update() 和 delete() 抛出错误时会触发错误。在失败时没有隐式回退到“fetch”。评估的失败基于条件的结构,因此成功/失败是基于代码结构确定性的。

relation() 现在正式命名为 relationship()

这是为了解决长期存在的问题,“relation”在关系代数术语中意味着“表或派生表”。relation()名称,少打字,将在可预见的未来继续存在,因此这个改变应该完全没有痛苦。

子查询的急切加载

添加了一种新的急切加载方式,称为“subquery”加载。这是一种在第一个 SQL 查询之后立即发出第二个 SQL 查询的加载方式,为第一个查询中的所有父级加载完整集合,使用 INNER JOIN 向上连接到父级。子查询加载类似于当前的连接急切加载,使用subqueryload()subqueryload_all()选项,以及设置在relationship()上的lazy='subquery'。子查询加载通常比较高效,用于加载许多较大的集合,因为它无条件地使用 INNER JOIN,而且也不会重新加载父行。

eagerload(), eagerload_all()现在是joinedload(), joinedload_all()

为了为新的子查询加载功能腾出空间,现有的eagerload()/eagerload_all() options are now superseded by joinedload() and joinedload_all(). The old names will hang around for the foreseeable future just like relation()

lazy=False|None|True|'dynamic'现在接受lazy='noload'|'joined'|'subquery'|'select'|'dynamic'

在继续开放加载器策略的主题上,relationship()上的标准关键字lazy选项现在是,用于延迟加载的select(通过属性访问时发出的 SELECT),用于急切连接加载的joined,用于急切子查询加载的subquery,不应出现任何负载的noload,以及用于“动态”关系的dynamic。旧的True, False, None参数仍然被接受,行为与以前完全相同。

在关系、joinedload 上设置 innerjoin=True

现在可以指示连接急切加载的标量和集合使用 INNER JOIN 而不是 OUTER JOIN。在 PostgreSQL 上观察到这可以在某些查询上提供 300-600%的速度提升。为任何在 NOT NULLable 外键上的多对一设置此标志,以及对于任何保证存在相关项目的集合。

在映射器级别:

mapper(Child, child)
mapper(
    Parent,
    parent,
    properties={"child": relationship(Child, lazy="joined", innerjoin=True)},
)

在查询时间级别:

session.query(Parent).options(joinedload(Parent.child, innerjoin=True)).all()

relationship()级别设置innerjoin=True标志也将对任何不覆盖该值的joinedload()选项生效。

多对一增强

  • 多对一关系现在在更少的情况下会触发延迟加载,包括在大多数情况下不会在替换新值时获取“旧”值。

  • 多对一关系到一个连接表子类现在使用 get()进行简单加载(称为“use_get”条件),即Related->Sub(Base),无需重新定义基表的 primaryjoin 条件。[ticket:1186]

  • 使用声明性列指定外键,即ForeignKey(MyRelatedClass.id)不会导致“use_get”条件发生变化 [ticket:1492]

  • relationship(),joinedload()和 joinedload_all()现在具有一个名为“innerjoin”的选项。指定TrueFalse来控制是否构建内连接或外连接的预加载连接。默认始终为False。映射器选项将覆盖在 relationship()上指定的任何设置。通常应该为多对一、非空外键关系设置���以允许改进的连接性能。[ticket:1544]

  • 当存在 LIMIT/OFFSET 时,连接式预加载的行为会将主查询包装在子查询中,现在对所有预加载都是多对一连接的情况做了一个例外。在这些情况下,预加载连接直接针对父表进行,同时包括限制/偏移,而不需要额外的子查询开销,因为多对一连接不会向结果添加行。

    例如,在 0.5 中,这个查询:

    session.query(Address).options(eagerload(Address.user)).limit(10)
    

    会生成类似于以下的 SQL:

    SELECT  *  FROM
      (SELECT  *  FROM  addresses  LIMIT  10)  AS  anon_1
      LEFT  OUTER  JOIN  users  AS  users_1  ON  users_1.id  =  anon_1.addresses_user_id
    

    这是因为任何预加载的存在都暗示着其中一些或全部可能与多行集合相关联,这将需要将任何类似于 LIMIT 这样的行数敏感修饰符包装在子查询中。

    在 0.6 中,该逻辑更加敏感,可以检测到所有预加载是否都表示多对一关系,如果是这种情况,预加载连接不会影响行数:

    SELECT  *  FROM  addresses  LEFT  OUTER  JOIN  users  AS  users_1  ON  users_1.id  =  addresses.user_id  LIMIT  10
    

具有联接表继承的可变主键

在子表具有外键指向父表主键的联接表继承配置现在可以在像 PostgreSQL 这样支持级联的数据库上更新。mapper()现在有一个选项passive_updates=True,表示此外键将自动更新。如果在不支持级联的数据库上,如 SQLite 或 MySQL/MyISAM 上,将此标志设置为False。未来的功能增强将尝试根据使用的方言/表格样式自动配置此标志。

Beaker 缓存

Beaker 集成的一个有前途的新示例在examples/beaker_caching中。这是一个简单的示例,它在Query的结果生成引擎中应用了一个 Beaker 缓存。缓存参数通过query.options()提供,并允许完全控制缓存内容。SQLAlchemy 0.6 对Session.merge()方法进行了改进,以支持这种和类似的示例,并在大多数情况下提供了显著改进的性能。

其他更改

  • 当选择多个列/实体时,Query返回的“行元组”对象现在也是可序列化的,并且性能更高。

  • query.join()已经重新设计,以提供更一致的行为和更灵活的功能(包括[ticket:1537])

  • query.select_from()接受多个子句,以在 FROM 子句中生成多个逗号分隔的条目。在从多个 join()子句中选择时很有用。

  • Session.merge()上的“dont_load=True”标志已被弃用,现在是“load=False”。

  • 添加了“make_transient()”辅助函数,将持久化/分离实例转换为瞬态实例(即删除实例键并从任何会话中移除)。[ticket:1052]

  • mapper() 上的 allow_null_pks 标志已被废弃,并已重命名为 allow_partial_pks。默认情况下已打开此标志。这意味着对于任何主键列中有非空值的行将被视为标识。通常只有在映射到外连接时才需要此情景。当设置为 False 时,具有 NULL 值的 PK 不会被视为主键 - 特别是这意味着结果行将返回为 None(或不会填充到集合中),并且在 0.6 中还表示 session.merge() 不会为此类 PK 值发出数据库的往返。【票号:1680】

  • “backref”的机制已完全合并到更精细的 “back_populates” 系统中,并完全在 RelationProperty_generate_backref() 方法中进行。这使得 RelationProperty 的初始化过程更简单,并允许更容易地将设置(如 RelationProperty 的子类)传播到反向引用中。内部的 BackRef() 已经消失,backref() 返回一个普通元组,被 RelationProperty 理解。

  • ResultProxy 的 keys 属性现在是一个方法,因此对它的引用(result.keys)必须更改为方法调用(result.keys())。

  • ResultProxy.last_inserted_ids 现在已废弃,使用 ResultProxy.inserted_primary_key 替代。

废弃/移除的 ORM 元素

在 0.5 版本中废弃并引发废弃警告的大多数元素已被移除(有几个例外)。所有标记为 “待废弃” 的元素现在已被废弃,并在使用时引发警告。

  • sessionmaker() 和其他地方上的 ‘transactional’ 标志已移除。使用 ‘autocommit=True’ 表示 ‘transactional=False’。

  • mapper() 上的 ‘polymorphic_fetch’ 参数已移除。可以使用 ‘with_polymorphic’ 选项来控制加载。

  • mapper() 上的 ‘select_table’ 参数已移除。使用 ‘with_polymorphic=(“*”, )’ 实现此功能。

  • synonym() 上的 ‘proxy’ 参数已移除。在 0.5 版本中此标志没有任何作用,因为 “代理生成” 行为现在是自动的。

  • 将单个元素列表传递给 joinedload()、joinedload_all()、contains_eager()、lazyload()、defer() 和 undefer() 而不是多个位置 *args 已被废弃。

  • 将单个元素列表传递给 query.order_by()、query.group_by()、query.join() 或 query.outerjoin() 而不是多个位置 *args 已被废弃。

  • query.iterate_instances() 被移除了。使用 query.instances()

  • Query.query_from_parent() 被移除了。使用 sqlalchemy.orm.with_parent() 函数生成 “parent” 子句,或者使用 query.with_parent()

  • query._from_self() 被移除,使用 query.from_self() 代替。

  • composite() 方法的 “comparator” 参数被移除了。使用 “comparator_factory”。

  • RelationProperty._get_join() 已移除。

  • Session 上的 ‘echo_uow’ 标志已移除。在 “sqlalchemy.orm.unitofwork” 名称上使用日志记录。

  • session.clear() 已移除。使用 session.expunge_all()

  • session.save()session.update()session.save_or_update() 已移除。使用 session.add()session.add_all()

  • session.flush() 上的 “objects” 标志仍然被弃用。

  • session.merge() 上的 “dont_load=True” 标志已弃用,建议使用 “load=False”。

  • ScopedSession.mapper 仍然被弃用。请参阅用法配方在 Recipes/SessionAwareMapper

  • InstanceState(内部 SQLAlchemy 状态对象)传递给 attributes.init_collection()attributes.get_history() 已弃用。 这些函数是公共 API,并且通常希望是常规映射对象实例。

  • declarative_base() 的 “engine” 参数已移除。使用 “bind” 关键字参数。

扩展

SQLSoup

SQLSoup 已现代化并更新以反映常见的 0.5/0.6 功能,包括明确定义的会话集成。请阅读新文档[www.sqlalc hemy.org/docs/06/reference/ext/sqlsoup.html]。

声明

DeclarativeMetadeclarative_base 的默认元类)之前允许子类修改 dict_ 来添加类属性(例如列)。 这种方式不再有效,DeclarativeMeta 构造函数现在忽略 dict_。相反,类属性应直接赋值,例如 cls.id=Column(...),或者应该使用 MixIn 类 方法而不是元类方法。

平台支持

  • cPython 版本从 2.4 开始,在 2.xx 系列中

  • Jython 2.5.1 - 使用 Jython 自带的 zxJDBC DBAPI。

  • cPython 3.x - 参见[源码:sqlalchemy/trunk/README.py3k] 了解如何构建 Python3 版本。

新方言系统

方言模块现在被分解为单个数据库后端范围内的不同子组件。 方言实现现在在 sqlalchemy.dialects 包中。 sqlalchemy.databases 包仍然存在,作为一个占位符,为简单导入提供一定程度的向后兼容性。

对于每个支持的数据库,在sqlalchemy.dialects中都存在一个子包,其中包含几个文件。每个包都包含一个名为base.py的模块,该模块定义了该数据库使用的特定 SQL 方言。它还包含一个或多个“driver”模块,每个模块对应于特定的 DBAPI - 这些文件的命名与 DBAPI 本身相对应,例如pysqlitecx_oraclepyodbc。SQLAlchemy 方言使用的类首先在base.py模块中声明,定义了数据库定义的所有行为特征。这些包括功能映射,例如“支持序列”,“支持返回”等,类型定义和 SQL 编译规则。每个“driver”模块依次提供所需的那些类的子类,这些子类覆盖默认行为以适应该 DBAPI 的附加功能、行为和怪癖。对于支持多个后端的 DBAPI(pyodbc、zxJDBC、mxODBC),方言模块将使用sqlalchemy.connectors包中的 mixin,这些 mixin 提供了在所有后端上通用的功能,最常见的是处理连接参数。这意味着使用 pyodbc、zxJDBC 或 mxODBC(一旦实现)进行连接在支持的后端上是非常一致的。

create_engine()使用的 URL 格式已经改进,以处理特定后端的任意数量的 DBAPI,使用了受 JDBC 启发的方案。以前的格式仍然有效,并且将选择一个“默认”的 DBAPI 实现,例如下面将使用 psycopg2 的 PostgreSQL URL:

create_engine("postgresql://scott:tiger@localhost/test")

但是,要指定特定的 DBAPI 后端,例如 pg8000,请在 URL 的“protocol”部分使用加号“+”:

create_engine("postgresql+pg8000://scott:tiger@localhost/test")

重要的方言链接:

关于方言的其他注意事项:

  • SQLAlchemy 0.6 中类型系统发生了巨大变化。这对所有方言的命名约定、行为和实现都产生了影响。请参见下面关于“类型”的部分。

  • ResultProxy对象现在在某些情况下提供了 2 倍的速度改进,这要归功于一些重构。

  • RowProxy,即单个结果行对象,现在可以直接进行 pickle。

  • 用于定位外部方言的 setuptools entrypoint 现在称为sqlalchemy.dialects。针对 0.4 或 0.5 编写的外部方言需要修改以适应 0.6,在任何情况下,因此这一变化并不会增加任何额外的困难。

  • 方言现在在初始连接时会接收一个 initialize()事件,以确定连接属性。

  • 编译器生成的函数和操作符现在使用(几乎)常规的分发函数形式“visit_”和“visit__fn”来提供定制处理。这取代了在编译器子类中复制“functions”和“operators”字典的需要,改为使用直接的访问者方法,并且还允许编译器子类完全控制渲染,因为完整的 _Function 或 _BinaryExpression 对象被传递进来。

方���导入

方言的导入结构已经改变。每个方言现在通过 sqlalchemy.dialects.<name> 导出其基本的“dialect”类以及该方言支持的完整一组 SQL 类型。例如,要导入一组 PG 类型:

from sqlalchemy.dialects.postgresql import (
    INTEGER,
    BIGINT,
    SMALLINT,
    VARCHAR,
    MACADDR,
    DATE,
    BYTEA,
)

上面,INTEGER 实际上是 sqlalchemy.types 中的普通 INTEGER 类型,但 PG 方言使其以与那些特定于 PG 的类型相同的方式可用,比如 BYTEAMACADDR

方言导入

方言的导入结构已经改变。每个方言现在通过 sqlalchemy.dialects.<name> 导出其基本的“dialect”类以及该方言支持的完整一组 SQL 类型。例如,要导入一组 PG 类型:

from sqlalchemy.dialects.postgresql import (
    INTEGER,
    BIGINT,
    SMALLINT,
    VARCHAR,
    MACADDR,
    DATE,
    BYTEA,
)

上面,INTEGER 实际上是 sqlalchemy.types 中的普通 INTEGER 类型,但 PG 方言使其以与那些特定于 PG 的类型相同的方式可用,比如 BYTEAMACADDR

表达式语言变化

一个重要的表达式语言陷阱

表达式语言有一个相当重要的行为变化,可能会影响一些应用程序。Python 布尔表达式的布尔值,即 ==!= 等,现在在与被比较的两个子句对象相关时会准确求值。

正如我们所知,将 ClauseElement 与任何其他对象进行比较会返回另一个 ClauseElement

>>> from sqlalchemy.sql import column
>>> column("foo") == 5
<sqlalchemy.sql.expression._BinaryExpression object at 0x1252490>

这样当 Python 表达式转换为字符串时会产生 SQL 表达式:

>>> str(column("foo") == 5)
'foo = :foo_1'

但如果我们这样说会发生什么?

>>> if column("foo") == 5:
...     print("yes")

在之前的 SQLAlchemy 版本中,返回的 _BinaryExpression 是一个普通的 Python 对象,其求值为 True。现在它的求值取决于实际的 ClauseElement 是否应该具有与被比较的哈希值相同的值。意思是:

>>> bool(column("foo") == 5)
False
>>> bool(column("foo") == column("foo"))
False
>>> c = column("foo")
>>> bool(c == c)
True
>>>

这意味着像下面这样的代码:

if expression:
    print("the expression is:", expression)

如果 expression 是一个二进制子句,则不会求值。由于上述模式永远不应该被使用,基本的 ClauseElement 现在在布尔上下文中调用时会引发异常:

>>> bool(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
  raise TypeError("Boolean value of this clause is not defined")
TypeError: Boolean value of this clause is not defined

想要检查是否存在 ClauseElement 表达式的代码应该改为:

if expression is not None:
    print("the expression is:", expression)

请记住,这也适用于 Table 和 Column 对象

更改的理由有两个:

  • 形如 if c1 == c2:  <do something> 的比较现在可以这样写

  • 现在正确哈希 ClauseElement 对象的支持也适用于其他平台,比如 Jython。直到这一点,SQLAlchemy 在这方面严重依赖 cPython 的特定行为(并且仍然偶尔出现问题)。

更严格的 “executemany” 行为

在 SQLAlchemy 中,“executemany” 对应于调用 execute(),传递一系列绑定参数集:

connection.execute(table.insert(), {"data": "row1"}, {"data": "row2"}, {"data": "row3"})

Connection 对象将给定的 insert() 构造发送到编译时,它会传递给编译器在第一组传递的绑定中存在的键名,以确定语句的 VALUES 子句的构造。熟悉这种构造的用户会知道剩余字典中存在的额外键没有任何影响。现在不同的是,所有后续字典都需要至少包含第一个字典中存在的每个键。这意味着像这样的调用不再起作用:

connection.execute(
    table.insert(),
    {"timestamp": today, "data": "row1"},
    {"timestamp": today, "data": "row2"},
    {"data": "row3"},
)

因为第三行没有指定 timestamp 列。之前的 SQLAlchemy 版本会简单地为这些缺失的列插入 NULL。然而,在上面的示例中,如果 timestamp 列包含 Python 端默认值或函数,则会被使用。这是因为 “executemany” 操作被优化为在大量参数集上实现最大性能,并且不会尝试评估那些缺失键的 Python 端默认值。因为默认值通常被实现为嵌入在 INSERT 语句中的 SQL 表达式,或者是服务器端表达式,再次根据 INSERT 字符串的结构触发,这些默认值不能根据每个参数集有条件地触发,让 Python 端默认值与 SQL/服务器端默认值的行为不一致将是不一致的。 (从 0.5 系列开始,基于 SQL 表达式的默认值被嵌入到行内,以最小化大量参数集的影响)。

因此,SQLAlchemy 0.6 通过禁止任何后续参数集留下任何字段空白来建立可预测的一致性。这样,Python 端默认值和函数不再默默失败,此外,它们允许保持与 SQL 和服务器端默认值一致的行为。

UNION 和其他“复合”结构一致地加括号。

为了帮助 SQLite 而设计的规则已被移除,即在另一个复合元素内的第一个复合元素(例如,在 except_() 中的 union())不会被括号括起来。这是不一致的,并且在 PostgreSQL 上产生错误的结果,因为它有关于 INTERSECTION 的优先规则,通常会让人感到惊讶。在使用 SQLite 的复杂组合时,现在需要将第一个元素转换为子查询(这也与 PG 兼容)。在[www.sqlalchemy.org/docs/06/sqlexpression.html #unions-and-other-set-operations]的 SQL 表达式教程的末尾有一个新的示例。查看#1665和 r6690 以获取更多背景信息。

一个重要的表达语言陷阱

表达语言中有一个相当重要的行为变化,可能会影响一些应用程序。Python 布尔表达式的布尔值,即==!=等,现在在比较两个子句对象时会准确评估。

我们知道,将ClauseElement与任何其他对象进行比较会返回另一个ClauseElement

>>> from sqlalchemy.sql import column
>>> column("foo") == 5
<sqlalchemy.sql.expression._BinaryExpression object at 0x1252490>

这样 Python 表达式在转换为字符串时会产生 SQL 表达式:

>>> str(column("foo") == 5)
'foo = :foo_1'

但如果我们这样说会发生什么呢?

>>> if column("foo") == 5:
...     print("yes")

在以前的 SQLAlchemy 版本中,返回的_BinaryExpression是一个普通的 Python 对象,其求值为True。现在它的求值取决于实际的ClauseElement是否应该具有与被比较的相同哈希值。意思是:

>>> bool(column("foo") == 5)
False
>>> bool(column("foo") == column("foo"))
False
>>> c = column("foo")
>>> bool(c == c)
True
>>>

这意味着像下面这样的代码:

if expression:
    print("the expression is:", expression)

如果expression是一个二元子句,将不会评估。由于上述模式不应该被使用,基本的ClauseElement现在在布尔上下文中调用时会引发异常:

>>> bool(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
  raise TypeError("Boolean value of this clause is not defined")
TypeError: Boolean value of this clause is not defined

想要检查ClauseElement表达式是否存在的代码应该改为:

if expression is not None:
    print("the expression is:", expression)

请记住,这也适用于 Table 和 Column 对象

更改的原因有两个:

  • 现在实际上可以编写形式为if c1 == c2: <do something>的比较。

  • ClauseElement对象进行正确哈希的支持现在在其他平台上也能正常工作,即 Jython。直到这一点,SQLAlchemy 在这方面严重依赖 cPython 的特定行为(并且偶尔还会出现问题)。

更严格的“executemany”行为

在 SQLAlchemy 中,“executemany”对应于调用execute(),传递一系列绑定参数集合:

connection.execute(table.insert(), {"data": "row1"}, {"data": "row2"}, {"data": "row3"})

Connection对象发送给定的insert()构造进行编译时,它会传递给编译器在第一组传递的绑定中存在的键名,以确定语句的 VALUES 子句的构造。熟悉这种构造的用户将知道,剩余字典中存在的额外键不会产生任何影响。现在不同的是,所有后续字典都需要至少包含第一个字典中存在的每个键。这意味着像这样的调用不再起作用:

connection.execute(
    table.insert(),
    {"timestamp": today, "data": "row1"},
    {"timestamp": today, "data": "row2"},
    {"data": "row3"},
)

因为第三行未指定timestamp列。之前的 SQLAlchemy 版本会简单地为这些缺失的列插入 NULL。然而,在上面的示例中,如果timestamp列包含 Python 端默认值或函数,则不会被使用。这是因为“executemany”操作针对大量参数集进行了优化,不会尝试评估这些缺失键的 Python 端默认值。因为默认值通常被实现为嵌入在 INSERT 语句中的 SQL 表达式,或者是服务器端表达式,再次根据 INSERT 字符串的结构触发,这显然不能根据每个参数集有条件地触发,让 Python 端默认值与 SQL/服务器端默认值的行为不一致是不合理的(从 0.5 系列开始,基于 SQL 表达式的默认值被嵌入到内联,以最小化大量参数集的影响)。

SQLAlchemy 0.6 因此通过禁止任何后续参数集留空字段来确立可预测的一致性。这样,Python 端默认值和函数不再默默失败,而且它们的行为与 SQL 和服务器端默认值保持一致。

UNION 和其他“复合”结构一致地加括号

为了帮助 SQLite 而设计的规则已被移除,即另一个复合元素内的第一个复合元素(例如,在except_()内部的union())不会被括号括起来。这是不一致的,并且在 PostgreSQL 上产生错误的结果,因为它有关于 INTERSECTION 的优先规则,通常会让人感到惊讶。在与 SQLite 一起使用复杂的复合时,现在需要将第一个元素转换为子查询(这也在 PG 上兼容)。在[www.sqlalchemy.org/docs/06/sqlexpression.html #unions-and-other-set-operations]的 SQL 表达式教程的末尾有一个新示例。查看#1665和 r6690 以获取更多背景信息。

用于结果获取的 C 扩展

ResultProxy和相关元素,包括大多数常见的“行处理”函数,如 Unicode 转换、数值/布尔转换和日期解析,已被重新实现为可选的 C 扩展,以提高性能。这标志着 SQLAlchemy 走向“黑暗面”的开始,我们希望通过在 C 中重新实现关键部分来继续改进性能。可以通过指定--with-cextensions来构建这些扩展,即python setup.py --with- cextensions install

扩展对使用直接ResultProxy访问的结果获取具有最显著影响,即由engine.execute()connection.execute()session.execute()返回的结果。在 ORM Query对象返回的结果中,结果获取不是开销的高比例,因此 ORM 性能改善较为适度,主要在获取大型结果集的领域。性能改进高度依赖于使用的 dbapi 以及访问每行列的语法(例如row['name']row.name快得多)。当前的扩展对插入/更新/删除的速度没有影响,也不会提高 SQL 执行的延迟,也就是说,一个大部分时间用于执行许多具有非常小结果集的语句的应用程序不会看到太多改进。

与扩展无关,0.6 版本的性能比 0.5 版本有所提高。使用 SQLite 连接和获取 50,000 行的快速概述,主要使用直接 SQLite 访问、ResultProxy和简单映射的 ORM 对象:

sqlite select/native: 0.260s

0.6 / C extension

sqlalchemy.sql select: 0.360s
sqlalchemy.orm fetch: 2.500s

0.6 / Pure Python

sqlalchemy.sql select: 0.600s
sqlalchemy.orm fetch: 3.000s

0.5 / Pure Python

sqlalchemy.sql select: 0.790s
sqlalchemy.orm fetch: 4.030s

在上述例子中,ORM 比 0.5 版本快 33%获取行,这归功于 Python 内部性能的提升。使用 C 扩展我们可以再获得 20%的提升。然而,ResultProxy使用 C 扩展比不使用提升了 67%。其他测试报告显示在某些情况下,例如发生大量字符串转换的情况下,速度提高了高达 200%。

新的模式功能

sqlalchemy.schema包得到了一些长期需要的关注。最显著的变化是新扩展的 DDL 系统。在 SQLAlchemy 中,自 0.5 版本以来,可以创建自定义 DDL 字符串并将其与表或元数据对象关联:

from sqlalchemy.schema import DDL

DDL("CREATE TRIGGER users_trigger ...").execute_at("after-create", metadata)

现在完整的 DDL 构造都在同一系统下可用,包括用于 CREATE TABLE、ADD CONSTRAINT 等的构造:

from sqlalchemy.schema import Constraint, AddConstraint

AddContraint(CheckConstraint("value > 5")).execute_at("after-create", mytable)

此外,所有 DDL 对象现在都是常规的ClauseElement对象,就像任何其他 SQLAlchemy 表达式对象一样:

from sqlalchemy.schema import CreateTable

create = CreateTable(mytable)

# dumps the CREATE TABLE as a string
print(create)

# executes the CREATE TABLE statement
engine.execute(create)

并且使用sqlalchemy.ext.compiler扩展,您可以制作自己的:

from sqlalchemy.schema import DDLElement
from sqlalchemy.ext.compiler import compiles

class AlterColumn(DDLElement):
    def __init__(self, column, cmd):
        self.column = column
        self.cmd = cmd

@compiles(AlterColumn)
def visit_alter_column(element, compiler, **kw):
    return "ALTER TABLE %s ALTER COLUMN %s  %s ..." % (
        element.column.table.name,
        element.column.name,
        element.cmd,
    )

engine.execute(AlterColumn(table.c.mycolumn, "SET DEFAULT 'test'"))

废弃/移除的模式元素

schema 包也得到了极大简化。许多在 0.5 版本中被废弃的选项和方法已被移除。其他鲜为人知的访问器和方法也已被移除。

  • “owner”关键字参数已从Table中移除。使用“schema”表示要预先添加到表名的任何命名空间。

  • 废弃的MetaData.connect()ThreadLocalMetaData.connect()已被移除 - 将“bind”属性发送到绑定元数据。

  • 废弃的 metadata.table_iterator()方法已被移除(使用 sorted_tables)。

  • DefaultGenerator和子类中移除了“metadata”参数,但仍然在Sequence上本地存在,这是 DDL��的一个独立构造。

  • 废弃的PassiveDefault - 使用DefaultClause

  • IndexConstraint对象中移除了公共可变性:

    • ForeignKeyConstraint.append_element()

    • Index.append_column()

    • UniqueConstraint.append_column()

    • PrimaryKeyConstraint.add()

    • PrimaryKeyConstraint.remove()

这些应该以声明性的方式构造(即一次性构造)。

  • 其他已移除的内容:

    • Table.key(不知道用于什么)

    • Column.bind(通过列的table.bind获取)

    • Column.metadata(通过列的table.metadata获取)

    • Column.sequence(使用列的默认值column.default

其他行为变化

  • UniqueConstraintIndexPrimaryKeyConstraint 都接受列名或列对象的列表作为参数。

  • ForeignKey 上的 use_alter 标志现在是一个快捷选项,用于可以手动构造使用 DDL() 事件系统的操作。此重构的一个副作用是,具有 use_alter=TrueForeignKeyConstraint 对象将 不会 在 SQLite 上发出,因为 SQLite 不支持外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上不遵守外键约束。

  • Table.primary_key 不可分配 - 使用 table.append_constraint(PrimaryKeyConstraint(...))

  • 带有 ForeignKey 但没有类型定义的 Column 定义,例如 Column(name, ForeignKey(sometable.c.somecol)) 曾用于获取引用列的类型。现在,对于该自动类型推断的支持是部分的,可能并不适用于所有情况。

废弃/移除的模式元素

模式包也已经大大简化。在 0.5 版本中已弃用的许多选项和方法已被移除。其他不太常用的访问器和方法也已被移除。

  • Table 中移除了“owner”关键字参数。使用“schema”表示要预先添加到表名的任何命名空间。

  • 废弃的 MetaData.connect()ThreadLocalMetaData.connect() 已被移除 - 发送“bind”属性以绑定元数据。

  • 已移除的废弃的 metadata.table_iterator() 方法(使用 sorted_tables

  • DefaultGenerator 和子类中移除了“metadata”参数,但在 Sequence 中仍然局部存在,Sequence 是 DDL 中的一个独立构造。

  • 废弃的 PassiveDefault - 使用 DefaultClause

  • IndexConstraint 对象中移除了公共可变性:

    • ForeignKeyConstraint.append_element()

    • Index.append_column()

    • UniqueConstraint.append_column()

    • PrimaryKeyConstraint.add()

    • PrimaryKeyConstraint.remove()

这些应该以声明性的方式构造(即一次性构造)。

  • 其他已移除的内容:

    • Table.key(不知道用于什么)

    • Column.bind(通过列的table.bind获取)

    • Column.metadata(通过列的table.metadata获取)

    • Column.sequence(使用列的默认值column.default

其他行为变化

  • UniqueConstraintIndexPrimaryKeyConstraint 都接受列名或列对象的列表作为参数。

  • ForeignKey 上的 use_alter 标志现在是手动构造使用 DDL() 事件系统的操作的快捷选项。这个重构的副作用是,带有 use_alter=TrueForeignKeyConstraint 对象将不会在 SQLite 上发出,因为 SQLite 不支持外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上不遵守 FOREIGN KEY 约束。

  • Table.primary_key 不可分配 - 使用 table.append_constraint(PrimaryKeyConstraint(...))

  • Column 定义中有一个 ForeignKey 而没有类型,例如 Column(name, ForeignKey(sometable.c.somecol)) 用于获取引用列的类型。现在对于这种自动类型推断的支持是部分的,并且可能不适用于所有情况。

日志开放

通过多次额外的方法调用,你可以在创建引擎、池或映射器后设置 INFO 和 DEBUG 的日志级别,日志将开始记录。isEnabledFor(INFO) 方法现在每个 Connection 调用一次,如果已在父连接上启用,则每个 ResultProxy 调用一次 isEnabledFor(DEBUG)。池日志发送到 log.info()log.debug(),没有检查 - 请注意,池的检出/归还通常是每个事务一次。

反射/检查器 API

反射系统,允许通过 Table('sometable', metadata, autoload=True) 反射表列已被开放到其自己的细粒度 API 中,该 API 允许直接检查数据库元素,如表、列、约束、索引等等。此 API 将返回值表示为简单的字符串、字典和 TypeEngine 对象列表。现在 autoload=True 的内部构建在此系统之上,将原始数据库信息转换为 sqlalchemy.schema 构造的过程集中化,并且各个方言的契约大大简化,极大地减少了不同后端之间的错误和不一致性。

使用检查器:

from sqlalchemy.engine.reflection import Inspector

insp = Inspector.from_engine(my_engine)

print(insp.get_schema_names())

from_engine() 方法在某些情况下将提供一个具有额外功能的特定于后端的检查器,例如 PostgreSQL 提供一个 get_table_oid() 方法:

my_engine = create_engine("postgresql://...")
pg_insp = Inspector.from_engine(my_engine)

print(pg_insp.get_table_oid("my_table"))

RETURNING 支持

insert()update()delete() 构造现在支持一个 returning() 方法,该方法对应于 PostgreSQL、Oracle、MS-SQL 和 Firebird 支持的 SQL RETURNING 子句。目前其他后端不支持。

给定一个与 select() 构造方式相同的列表达式列表,这些列的值将作为常规结果集返回:

result = connection.execute(
    table.insert().values(data="some data").returning(table.c.id, table.c.timestamp)
)
row = result.first()
print("ID:", row["id"], "Timestamp:", row["timestamp"])

在四个支持的后端中,RETURNING 的实现差异很大,在 Oracle 的情况下,需要复杂地使用 OUT 参数,这些参数被重新路由到一个“模拟”结果集中,在 MS-SQL 的情况下使用笨拙的 SQL 语法。RETURNING 的使用受到限制:

  • 它不适用于任何“executemany()”风格的执行。这是所有支持的 DBAPI 的限制。

  • 某些后端,如 Oracle,仅支持返回单行的 RETURNING - 这包括 UPDATE 和 DELETE 语句,意味着 update()或 delete()构造必须仅匹配单行,否则会引发错误(由 Oracle 而不是 SQLAlchemy 引发)。

当单行 INSERT 语句需要获取新生成的主键值时,SQLAlchemy 也会自动使用 RETURNING,当其他地方没有通过显式的returning()调用指定时。这意味着对于需要主键值的插入语句,不再需要“SELECT nextval(sequence)”预执行。说实话,隐式的 RETURNING 特性确实比旧的“select nextval()”系统多产生了更多的方法开销,后者使用了一个快速而肮脏的 cursor.execute()来获取序列值,并且在 Oracle 的情况下需要额外绑定 out 参数。因此,如果方法/协议开销比额外的数据库往返开销更昂贵,则可以通过向create_engine()指定implicit_returning=False来禁用该特性。

类型系统更改

新架构

在幕后,类型系统已经完全重构,以实现两个目标:

  • 将绑定参数和结果行值的处理分开,通常是 DBAPI 的要求,与类型本身的 SQL 规范分开,这是数据库的要求。这与将数据库 SQL 行为与 DBAPI 分离的总体方言重构一致。

  • 为从TypeEngine对象生成 DDL 和基于列反射构造TypeEngine对象建立清晰一致的合同。

这些变化的亮点包括:

  • 方言中类型的构造已彻底改写。方言现在将公开可用的类型定义为仅大写名称,并使用下划线标识符(即私有)进行内部实现类型。类型在 SQL 和 DDL 中的表达方式已移至编译器系统。这样做的效果是大多数方言中几乎没有类型对象。关于此架构的详细文档可供方言作者使用,在[source:/lib/sqlalchemy/dialects/type_migration_guidelines.txt]中。

  • 现在类型的反射将返回 types.py 中的确切大写类型,或者如果该类型不是标准 SQL 类型,则在方言本身中返回大写类型。这意味着反射现在返回更准确的反射类型信息。

  • 用户定义的类型,其子类为TypeEngine且希望提供get_col_spec(),现在应该将其子类化为UserDefinedType

  • 所有类型类上的result_processor()方法现在接受一个额外的参数coltype。这是附加到 cursor.description 的 DBAPI 类型对象,并且在适用时应该使用它来做出更好的决定,以确定应返回什么类型的结果处理可调用对象。理想情况下,结果处理函数永远不应该使用isinstance(),因为这是一个在这个级别上昂贵的调用。

本地 Unicode 模式

随着更多的 DBAPI 支持直接返回 Python unicode 对象,基本方言现在在建立第一个连接时执行检查,以确定 DBAPI 是否为基本 VARCHAR 值的基本选择返回 Python unicode 对象。如果是这样,String类型及其所有子类(即TextUnicode等)在接收到结果行时将跳过“unicode”检查/转换步骤。对于大型结果集,这将大幅提高性能。目前已知“unicode 模式”可以与以下内容配合使用:

  • sqlite3 / pysqlite

  • psycopg2 - SQLA 0.6 现在在每个 psycopg2 连接对象上默认使用“UNICODE”类型扩展

  • pg8000

  • cx_oracle(我们使用输出处理器 - 很好的功能!)

其他类型可以根据需要选择禁用 unicode 处理,例如与 MS-SQL 一起使用时的NVARCHAR类型。

特别是,如果基于以前返回非 unicode 字符串的 DBAPI 的应用程序,则“本地 unicode”模式具有明显不同的默认行为 - 声明为StringVARCHAR的列现在默认返回 unicode,而以前则返回字符串。这可能会破坏期望非 unicode 字符串的代码。可以通过将use_native_unicode=False传递给create_engine()来禁用 psycopg2 的“本地 unicode”模式。

对于明确不希望使用 unicode 对象的字符串列,更一般的解决方案是使用TypeDecorator将 unicode 转换回 utf-8,或者任何所需的格式:

class UTF8Encoded(TypeDecorator):
  """Unicode type which coerces to utf-8."""

    impl = sa.VARCHAR

    def process_result_value(self, value, dialect):
        if isinstance(value, unicode):
            value = value.encode("utf-8")
        return value

请注意,assert_unicode标志现已弃用。SQLAlchemy 允许 DBAPI 和正在使用的后端数据库在可用时处理 Unicode 参数,并且通过检查传入类型来增加操作开销;像 sqlite 和 PostgreSQL 这样的现代系统将在其端口上引发编码错误,如果传递的数据无效。在 SQLAlchemy 确实需要将绑定参数从 Python Unicode 强制转换为编码字符串时,或者当显式使用 Unicode 类型时,如果对象是字节串,则会发出警告。可以使用 Python 警告过滤器抑制或将此警告转换为异常,该过滤器的文档在:docs.python.org/library/warnings.html

通用枚举类型

现在我们在 types 模块中有一个 Enum。这是一个字符串类型,给定一组“标签”,限制给这些标签的可能值。默认情况下,此类型生成一个 VARCHAR,其大小为最大标签的大小,并在 CREATE TABLE 语句中对表施加 CHECK 约束。当使用 MySQL 时,默认情况下该类型使用 MySQL 的 ENUM 类型;当使用 PostgreSQL 时,该类型将使用 CREATE TYPE <mytype> AS ENUM 生成用户定义类型。为了在 PostgreSQL 中创建该类型,必须在构造函数中指定 name 参数。该类型还接受一个 native_enum=False 选项,它将为所有数据库使用 VARCHAR/CHECK 策略。请注意,PostgreSQL 的 ENUM 类型目前无法与 pg8000 或 zxjdbc 一起使用。

反射返回方言特定类型

反射现在从数据库返回最具体的类型。也就是说,如果你使用 String 创建一个表,然后将其反射回来,反射的列可能是 VARCHAR。对于支持更具体类型形式的方言,你会得到相应的类型。因此,在 Oracle 上,Text 类型会返回 oracle.CLOBLargeBinary 可能是 mysql.MEDIUMBLOB 等等。这里的明显优势在于反射尽可能保留来自数据库的信息。

一些处理表元数据的应用程序可能希望在反射表和/或非反射表之间比较类型。TypeEngine 上有一个半私有访问器叫做 _type_affinity,以及一个相关的比较辅助函数 _compare_type_affinity。该访问器返回与类型对应的“通用” types 类:

>>> String(50)._compare_type_affinity(postgresql.VARCHAR(50))
True
>>> Integer()._compare_type_affinity(mysql.REAL)
False

杂项 API 变更

通常的“通用”类型仍然是通用系统中使用的一般类型,即 StringFloatDateTime。这里有一些变化:

  • 类型不再猜测默认参数。特别是 NumericFloat,以及 NUMERIC、FLOAT、DECIMAL 的子类,除非指定,否则不会生成任何长度或比例。这也包括有争议的 StringVARCHAR 类型(尽管 MySQL 方言在要求不带长度的 VARCHAR 时会预先引发错误)。不假设任何默认值,如果它们在 CREATE TABLE 语句中使用,并且底层数据库不允许这些类型的非长度版本,则会引发错误。

  • Binary 类型已经改名为 LargeBinary,用于 BLOB/BYTEA/类似类型。对于 BINARYVARBINARY,直接使用 types.BINARYtypes.VARBINARY,以及在 MySQL 和 MS-SQL 方言中。

  • PickleTypemutable=True 时,现在使用 == 进行值的比较,除非指定了带有比较函数的 “comparator” 参数给该类型。如果您要 pickle 一个自定义对象,应该实现一个 __eq__() 方法,以确保基于值的比较准确。

  • Numeric 和 Float 的默认 “precision” 和 “scale” 参数已移除,现在默认为 None。NUMERIC 和 FLOAT 将默认不带数字参数呈现,除非提供这些值。

  • SQLite 上的 DATE、TIME 和 DATETIME 类型现在可以使用可选的 “storage_format” 和 “regexp” 参数。“storage_format” 可用于使用自定义字符串格式存储这些类型。“regexp” 允许使用自定义正则表达式来匹配来自数据库的字符串值。

  • __legacy_microseconds__ 在 SQLite 的 TimeDateTime 类型上不再受支持。您应该使用新的 “storage_format” 参数代替。

  • SQLite 上的 DateTime 类型现在默认使用更严格的正则表达式来匹配来自数据库的字符串。如果您使用存储在遗留格式中的数据,请使用新的 “regexp” 参数。

新架构

类型系统已在幕后完全重做,以实现两个目标:

  • 将绑定参数和结果行值的处理分开,通常是 DBAPI 的要求,与类型本身的 SQL 规范分开,这是数据库的要求。这与将数据库 SQL 行为与 DBAPI 分开的整体方言重构保持一致。

  • 为从 TypeEngine 对象生成 DDL 和基于列反射构造 TypeEngine 对象建立清晰一致的合同。

这些变更的亮点包括:

  • 方言中类型的构造已完全重构。方言现在专门使用大写名称定义公开可用的类型,并使用下划线标识符(即私有)定义内部实现类型。用于在 SQL 和 DDL 中表达类型的系统已移至编译器系统。这意味着大多数方言中的类型对象大大减少。有关此架构的详细文档,供方言作者参考在 [source:/lib/sqlalchemy/dialects/type_migration_guidelines.txt]。

  • 现在,类型的反射返回 types.py 中的确切大写类型,或者如果类型不是标准 SQL 类型,则返回方言本身的大写类型。这意味着反射现在返回有关反射类型的更准确信息。

  • 子类化 TypeEngine 并希望提供 get_col_spec() 的用户定义类型现在应该子类化 UserDefinedType

  • 所有类型类上的 result_processor() 方法现在接受附加参数 coltype。这是附加到 cursor.description 的 DBAPI 类型对象,并且应该在适用时使用,以便更好地决定返回何种类型的结果处理可调用函数。理想情况下,结果处理函数永远不应该使用 isinstance(),因为这是在此级别的一个昂贵的调用。

本地 Unicode 模式

随着越来越多的 DBAPI 支持直接返回 Python Unicode 对象,基本方言现在在第一次连接时执行检查,以确定 DBAPI 是否为 VARCHAR 值的基本选择返回 Python Unicode 对象。如果是这样,String 类型和所有子类(即 TextUnicode 等)在接收到结果行时将跳过“unicode”检查/转换步骤。这为大型结果集提供了显著的性能提升。目前“unicode 模式”已知可与以下一起使用:

  • sqlite3 / pysqlite

  • psycopg2 - SQLA 0.6 现在默认在每个 psycopg2 连接对象上使用“UNICODE” 类型扩展

  • pg8000

  • cx_oracle(我们使用输出处理器 - 很好的功能!)

其他类型可能会根据需要禁用 Unicode 处理,例如在与 MS-SQL 一起使用时的 NVARCHAR 类型。

特别是,如果迁移基于以前返回非 Unicode 字符串的 DBAPI 的应用程序,则“本机 Unicode” 模式具有明显不同的默认行为 - 声明为 StringVARCHAR 的列现在默认返回 Unicode,而以前会返回字符串。这可能会破坏期望非 Unicode 字符串的代码。可以通过向 create_engine() 传递 use_native_unicode=False 来禁用 psycopg2 的“本机 Unicode” 模式。

对于明确不希望使用 Unicode 对象的字符串列的更一般解决方案是使用一个 TypeDecorator,将 Unicode 转换回 utf-8,或者其他所需的格式:

class UTF8Encoded(TypeDecorator):
  """Unicode type which coerces to utf-8."""

    impl = sa.VARCHAR

    def process_result_value(self, value, dialect):
        if isinstance(value, unicode):
            value = value.encode("utf-8")
        return value

请注意,assert_unicode 标志现已弃用。SQLAlchemy 允许 DBAPI 和后端数据库在可用时处理 Unicode 参数,并且不通过检查传入类型增加操作开销;现代系统如 sqlite 和 PostgreSQL 将在其端引发编码错误,如果传递了无效数据。在 SQLAlchemy 需要将绑定参数从 Python Unicode 强制转换为编码字符串时,或者显式使用 Unicode 类型时,如果对象是字节字符串,则会发出警告。可以使用 Python 警告过滤器文档中记录的警告过滤器将此警告抑制或转换为异常:docs.python.org/library/warnings.html

通用枚举类型

现在在 types 模块中有一个 Enum。这是一个字符串类型,给定一组“标签”,这些标签限制了给定给这些标签的可能值。默认情况下,此类型生成一个使用最大标签大小的 VARCHAR,并在 CREATE TABLE 语句中对表应用 CHECK 约束。在使用 MySQL 时,默认情况下,该类型使用 MySQL 的 ENUM 类型,而在使用 PostgreSQL 时,该类型将生成一个使用 CREATE TYPE <mytype> AS ENUM 的用户定义类型。为了在 PostgreSQL 中创建类型,必须在构造函数中指定 name 参数。该类型还接受一个 native_enum=False 选项,该选项将为所有数据库发出 VARCHAR/CHECK 策略。请注意,PostgreSQL ENUM 类型目前无法与 pg8000 或 zxjdbc 一起使用。

反射返回方言特定类型

反射现在从数据库返回尽可能具体的类型。也就是说,如果使用 String 创建表,然后反射它,反射的列可能是 VARCHAR。对于支持更具体形式的类型的方言,您将得到该类型。因此,在 Oracle 上,Text 类型将返回为 oracle.CLOB,在 MySQL 上,LargeBinary 可能是 mysql.MEDIUMBLOB 等。这里的明显优势是反射尽可能保留数据库所说的信息。

一些处理表元数据的应用程序可能希望比较反射表和/或非反射表上的类型。TypeEngine 上有一个半私有访问器叫做 _type_affinity,以及一个相关的比较助手 _compare_type_affinity。此访问器返回类型对应的“通用” types 类:

>>> String(50)._compare_type_affinity(postgresql.VARCHAR(50))
True
>>> Integer()._compare_type_affinity(mysql.REAL)
False

杂项 API 更改

通常的“通用”类型仍然是使用的一般系统,即 StringFloatDateTime。在那里有一些变化:

  • 类型不再对默认参数进行任何猜测。特别是,NumericFloat,以及子类 NUMERIC、FLOAT、DECIMAL 不会生成任何长度或精度,除非指定。这也包括有争议的 StringVARCHAR 类型(尽管 MySQL 方言在要求渲染没有长度的 VARCHAR 时会预先引发错误)。不会假设任何默认值,如果它们在 CREATE TABLE 语句中使用,如果底层数据库不允许这些类型的无长度版本,则会引发错误。

  • Binary 类型已更名为 LargeBinary,用于 BLOB/BYTEA/类似类型。对于 BINARYVARBINARY,它们直接存在于 types.BINARYtypes.VARBINARY,以及 MySQL 和 MS-SQL 方言中。

  • PickleType 的 mutable=True 时,现在使用 == 进行值比较,除非为该类型指定了带有比较函数的 “comparator” 参数。如果要对自定义对象进行 pickle,应实现一个 __eq__() 方法,以确保基于值的比较准确。

  • Numeric 和 Float 的默认“precision” 和 “scale” 参数已被移除,现在默认为 None。NUMERIC 和 FLOAT 现在默认不带任何数字参数呈现,除非提供这些值。

  • SQLite 上的 DATE、TIME 和 DATETIME 类型现在可以使用可选的 “storage_format” 和 “regexp” 参数。“storage_format” 可以用于使用自定义字符串格式存储这些类型。“regexp” 允许使用自定义正则表达式来匹配数据库中的字符串值。

  • 在 SQLite 的 TimeDateTime 类型上不再支持 __legacy_microseconds__。您应该使用新的“storage_format”参数。

  • SQLite 上的 DateTime 类型现在默认使用更严格的正则表达式来匹配来自数据库的字符串。如果使用存储在传统格式中的数据,则使用新的“regexp”参数。

ORM 更改

将 ORM 应用程序从 0.5 升级到 0.6 应该几乎不需要任何更改,因为 ORM 的行为几乎保持不变。有一些默认参数和名称更改,以及一些加载行为已经得到改进。

新的工作单元

工作单元的内部,主要是 topological.pyunitofwork.py,已完全重写并大大简化。这不应对使用产生任何影响,因为所有现有的刷新行为都已完全保持不变(或者至少在我们的测试套件和少数经过大量测试的生产环境中被使用)。刷新() 的性能现在使用 20-30% 更少的方法调用,并且还应该使用更少的内存。源代码的意图和流程现在应该相当容易理解,刷新的架构在这一点上相当开放,为潜在的新领域提供了空间。刷新过程不再依赖递归,因此可以刷新任意大小和复杂度的刷新计划。此外,映射器的“保存”过程,发出 INSERT 和 UPDATE 语句,现在缓存了这两个语句的“编译”形式,因此在非常大的刷新中进一步大幅减少了调用次数。

与 0.6 或 0.5 早期版本相比,刷新的任何行为变化都应尽快向我们报告 - 我们将确保不会丢失任何功能。

query.update()query.delete() 的更改

  • 查询.update() 上的 ‘expire’ 选项已更名为 ‘fetch’,与 query.delete() 的匹配方式相同。

  • query.update()query.delete() 的同步策略默认为 ‘evaluate’。

  • update() 和 delete() 的 ‘synchronize’ 策略在失败时会引发错误。没有隐式回退到 “fetch”。评估的失败基于条件的结构,因此成功/失败是基于代码结构的确定性的。

relation() 现在正式更名为 relationship()

这是为了解决长期存在的问题,“relation”在关系代数术语中意味着“表或派生表”。relation()名称,输入较少,将会持续存在可预见的未来,因此此更改应完全无痛。

子查询急切加载

添加了一种称为“子查询”加载的新型急切加载。这是一种在第一个 SQL 查询之后立即发出第二个 SQL 查询的加载,该查询为第一个查询中的所有父项加载完整集合,使用 INNER JOIN 向上连接到父项。子查询加载类似于当前的连接急切加载,使用subqueryload()subqueryload_all()选项,以及设置在relationship()上的lazy='subquery'。子查询加载通常对加载许多较大的集合更有效,因为它无条件地使用 INNER JOIN,并且还不会重新加载父行。

eagerload(), eagerload_all()现在是joinedload(), joinedload_all()

为了为新的子查询加载功能腾出空间,现有的eagerload()/eagerload_all() options are now superseded by joinedload() and joinedload_all(). The old names will hang around for the foreseeable future just like relation()将会改变。

lazy=False|None|True|'dynamic'现在接受lazy='noload'|'joined'|'subquery'|'select'|'dynamic'

继续开放加载器策略的主题,relationship()上的标准关键字lazy选项现在是,用于延迟加载的select(通过属性访问时发出的 SELECT),用于急切连接加载的joined,用于急切子查询加载的subquery,不应出现任何负载的noload,以及用于“动态”关系的dynamic。旧的True, False, None参数仍然被接受,行为与以前完全相同。

在关系、连接加载上的innerjoin=True

现在可以指示连接急切加载的标量和集合使用 INNER JOIN 而不是 OUTER JOIN。在 PostgreSQL 上,观察到这可以在某些查询中提供 300-600%的加速。为任何在 NOT NULLable 外键上的多对一关系设置此标志,类似地,为任何保证存在相关项的集合设置此标志。

在映射器级别:

mapper(Child, child)
mapper(
    Parent,
    parent,
    properties={"child": relationship(Child, lazy="joined", innerjoin=True)},
)

在查询时级别:

session.query(Parent).options(joinedload(Parent.child, innerjoin=True)).all()

relationship()级别使用innerjoin=True标志也将影响任何不覆盖该值的joinedload()选项。

多对一增强

  • 多对一关系现在在更少的情况下会触发惰性加载,包括在大多数情况下当新值替换旧值时不会获取“旧”值。

  • 与连接表子类的多对一关系现在使用get()进行简单加载(称为“use_get”条件),即Related->Sub(Base),无需重新定义基表的主连接条件。[ticket:1186]

  • 使用声明性列指定外键,即ForeignKey(MyRelatedClass.id)不会破坏“use_get”条件的发生。[ticket:1492]

  • relationship()、joinedload() 和 joinedload_all() 现在具有一个名为“innerjoin”的选项。指定 TrueFalse 来控制急切连接是构造为 INNER 还是 OUTER 连接。默认始终为 False。映射器选项将覆盖 relationship() 上指定的任何设置。通常应该为一对多、非空外键关系设置此选项,以允许改进的连接性能。[ticket:1544]

  • 联接急切加载的行为,当存在 LIMIT/OFFSET 时,使主查询包装在子查询中的情况现在除了所有急切加载都是一对多连接时有一个例外。在这些情况下,急切连接直接针对父表,同时限制/偏移量没有子查询的额外开销,因为一对多连接不会将行添加到结果中。

    例如,在 0.5 版本中这个查询:

    session.query(Address).options(eagerload(Address.user)).limit(10)
    

    将生成如下 SQL 语句:

    SELECT  *  FROM
      (SELECT  *  FROM  addresses  LIMIT  10)  AS  anon_1
      LEFT  OUTER  JOIN  users  AS  users_1  ON  users_1.id  =  anon_1.addresses_user_id
    

    这是因为任何急切的加载程序的存在都表明它们中的一部分或全部可能与多行集合相关,这将需要将任何种类的行数敏感修改器,如 LIMIT,包装在子查询中。

    在 0.6 版本中,该逻辑更加敏感,并且可以检测到所有急切加载是否表示一对多关系,在这种情况下,急切连接不会影响行数:

    SELECT  *  FROM  addresses  LEFT  OUTER  JOIN  users  AS  users_1  ON  users_1.id  =  addresses.user_id  LIMIT  10
    

具有联接表继承的可变主键

在具有子表主键外键到父表主键的联接表继承配置上,现在可以在类似于 PostgreSQL 的具有级联功能的数据库上更新子表。mapper() 现在有一个选项 passive_updates=True,表示此外键将自动更新。如果在不支持级联的数据库上,如 SQLite 或 MySQL/MyISAM,则将此标志设置为 False。将来的功能增强将尝试根据正在使用的方言/表样式来自动配置此标志。

Beaker 缓存

Beaker 集成的一个有前途的新示例在 examples/beaker_caching 中。这是一个简单的示例,它在 Query 的结果生成引擎中应用了 Beaker 缓存。缓存参数通过 query.options() 提供,并允许完全控制缓存内容。SQLAlchemy 0.6 对 Session.merge() 方法进行了改进,以支持此类示例,并在大多数情况下提供了显著改进的性能。

其他更改

  • 当选择多列/实体时,Query 返回的“行元组”对象现在可以进行序列化,性能更高。

  • query.join() 已重新设计以提供更一致的行为和更灵活的功能(包括 [ticket:1537])

  • query.select_from() 接受多个子句,以在 FROM 子句中生成多个逗号分隔的条目。在从多个 join() 子句中选择时非常有用。

  • Session.merge() 上的“dont_load=True”标志已弃用,现在为“load=False”。

  • 添加了“make_transient()”助手函数,它将一个持久化/分离的实例转换为瞬态实例(即删除实例键并从任何会话中删除)。[ticket:1052]

  • 在 mapper()上的 allow_null_pks 标志已弃用,并已更名为 allow_partial_pks。它默认为“on”。这意味着对于任何主键列具有非空值的行都将被视为标识。这种情况的需要通常仅在映射到外连接时发生。当设置为 False 时,具有 NULL 的 PK 将不被视为主键 - 特别是这意味着结果行将返回为 None(或不填入集合中),并且新的 0.6 版本还表示 session.merge()不会为此类 PK 值向数据库发出往返传输。【票号:1680】

  • “backref”的机制已完全合并到更精细的“back_populates”系统中,并完全在RelationProperty_generate_backref()方法中进行。这使得RelationProperty的初始化过程更简单,并允许更轻松地传播设置(例如从RelationProperty的子类)。内部的BackRef()已经消失,backref()返回一个纯元组,RelationProperty理解这个元组。

  • ResultProxy的 keys 属性现在是一个方法,因此对它的引用(result.keys)必须改为方法调用(result.keys()

  • ResultProxy.last_inserted_ids现已弃用,请改用ResultProxy.inserted_primary_key

已弃用/移除的 ORM 元素

大多数在 0.5 版本中已弃用并引发弃用警告的元素已移除(有几个例外)。所有标记为“待弃用”的元素现在已弃用,并将在使用时引发警告。

  • ‘transactional’标志在 sessionmaker()和其他函数中已移除。使用‘autocommit=True’表示‘transactional=False’。

  • 在 mapper()上的‘polymorphic_fetch’参数已移除。加载可以使用‘with_polymorphic’选项来控制。

  • 在 mapper()上的‘select_table’参数已移除。为了实现此功能,请使用‘with_polymorphic=(“*”, )’。

  • 在 synonym()上的‘proxy’参数已移除。此标志在 0.5 版本中未起作用,因为“proxy generation”行为现在是自动的。

  • 对 joinedload()、joinedload_all()、contains_eager()、lazyload()、defer()和 undefer()传递单个元素列表而不是多个位置参数的做法已弃用。

  • 对 query.order_by()、query.group_by()、query.join()或 query.outerjoin()传递单个元素列表而不是多个位置参数的做法已弃用。

  • query.iterate_instances()已移除。使用query.instances()

  • Query.query_from_parent()已移除。使用 sqlalchemy.orm.with_parent()函数生成一个“parent”子句,或者使用query.with_parent()

  • query._from_self()已移除,请改用query.from_self()

  • 对 composite()的“comparator”参数已移除。使用“comparator_factory”。

  • RelationProperty._get_join()已移除。

  • Session 上的‘echo_uow’标志已移除。在“sqlalchemy.orm.unitofwork”名称上使用日志记录。

  • session.clear() 已移除。请使用 session.expunge_all()

  • session.save()session.update()session.save_or_update() 已移除。请使用 session.add()session.add_all()

  • session.flush() 中的 “objects” 标志仍然被弃用。

  • session.merge() 中的 “dont_load=True” 标志已弃用,改为使用 “load=False”。

  • ScopedSession.mapper 仍然被弃用。请参阅www.sqlalchemy.org/trac/wiki/Usag eRecipes/SessionAwareMapper 上的使用配方。

  • InstanceState(内部 SQLAlchemy 状态对象)传递给 attributes.init_collection()attributes.get_history() 已被弃用。这些函数是公共 API,通常期望一个常规映射对象实例。

  • declarative_base() 中的 ‘engine’ 参数已被移除。请使用 ‘bind’ 关键字参数。

新的工作单元

工作单元的内部,主要是 topological.pyunitofwork.py,已完全重写并大大简化。这对使用没有影响,因为所有现有的刷新行为都被完全保留了(或者至少在我们的测试套件和少数大量测试的生产环境中被保留了)。刷新(flush)的性能现在使用的方法调用减少了 20-30%,而且还应该使用更少的内存。源代码的意图和流程现在应该相当容易跟踪,并且刷新的架构在这一点上相当开放,为潜在的新技术领域提供了空间。刷新过程不再依赖于递归,因此可以刷新任意大小和复杂度的刷新计划。此外,映射器的“保存”过程,发出 INSERT 和 UPDATE 语句,现在缓存了这两个语句的“编译”形式,以便在非常大的刷新中进一步大幅减少调用次数。

请尽快向我们报告在刷新与 0.6 或 0.5 早期版本之间观察到的任何行为变化——我们将确保不会丢失任何功能。

query.update()query.delete() 的更改

  • query.update() 中的‘expire’选项已更名为‘fetch’,与 query.delete() 的命名一致。

  • query.update()query.delete() 在同步策略上都默认为 ‘evaluate’。

  • update()delete() 的 ‘synchronize’ 策略在失败时会引发错误。没有隐式回退到“fetch”。评估的失败是基于条件结构的,因此基于代码结构,成功/失败是可以确定的。

relation() 正式更名为 relationship()

这是为了解决“relation”在关系代数中表示“表或派生表”的长期问题。relation() 这个名字,打字更少,将在可预见的将来继续存在,所以这个变化应该完全没有痛苦。

子查询的贪婪加载

添加了一种名为“subquery”加载的新类型的急切加载。这是一种加载,它在第一个加载完整集合的 SQL 查询之后立即发出第二个 SQL 查询,通过 INNER JOIN 连接到第一个查询中的所有父级。子查询加载类似于当前的连接预加载,使用subqueryload()subqueryload_all()选项,以及设置在relationship()上的lazy='subquery'。子查询加载通常更有效地加载许多较大的集合,因为它无条件地使用 INNER JOIN,而且也不会重新加载父行。

eagerload(), eagerload_all()现在是joinedload(), joinedload_all()

为了为新的子查询加载功能腾出空间,现有的eagerload()/eagerload_all() options are now superseded by joinedload() and joinedload_all(). The old names will hang around for the foreseeable future just like relation()

lazy=False|None|True|'dynamic'现在接受lazy='noload'|'joined'|'subquery'|'select'|'dynamic'

在加载器策略开放的主题上继续,relationship()上的标准关键字lazy选项现在是,用于延迟加载的select(通过属性访问时发出的 SELECT),用于急切连接加载的joined,用于急切子查询加载的subquery,不应出现任何负载的noload,以及用于“动态”关系的dynamic。旧的True, False, None参数仍然被接受,行为与以前完全相同。

在关系、joinedload 上设置 innerjoin=True

现在可以指示使用 INNER JOIN 而不是 OUTER JOIN 来连接预加载的标量和集合。在 PostgreSQL 上,这被观察到可以为某些查询提供 300-600% 的速度提升。为任何在 NOT NULLable 外键上的多对一设置此标志,以及对于任何保证存在相关项目的集合。

在映射器级别:

mapper(Child, child)
mapper(
    Parent,
    parent,
    properties={"child": relationship(Child, lazy="joined", innerjoin=True)},
)

在查询时间级别:

session.query(Parent).options(joinedload(Parent.child, innerjoin=True)).all()

relationship() 级别的 innerjoin=True 标志也将对任何不覆盖该值的 joinedload() 选项产生影响。

对许多对一的增强

  • 许多对一关系现在在更少的情况下会触发延迟加载,包括在大多数情况下不会在替换新值时获取“旧”值。

  • 对于连接表子类的多对一关系现在使用 get() 进行简单加载(称为“use_get”条件),即 Related->Sub(Base), 无需重新定义基表的主连接条件。[ticket:1186]

  • 指定具有声明列的外键,即 ForeignKey(MyRelatedClass.id) 不会阻止“use_get”条件的发生 [ticket:1492]

  • relationship()、joinedload() 和 joinedload_all() 现在具有一个名为“innerjoin”的选项。指定 TrueFalse 来控制是否构建一个 INNER 或 OUTER 连接的急切连接。默认始终为 False。映射器选项将覆盖在 relationship() 上指定的任何设置。通常应为多对一、非空外键关系设置以允许改进的连接性能。[ticket:1544]

  • 联接急切加载的行为,即当 LIMIT/OFFSET 存在时,主查询被包装在子查询中,现在对所有急切加载都是多对一联接的情况做了一个例外。在这些情况下,急切连接直接针对父表进行,同时限制/偏移量没有额外的子查询开销,因为多对一连接不会向结果添加行。

    例如,在 0.5 版本中,这个查询:

    session.query(Address).options(eagerload(Address.user)).limit(10)
    

    会生成类似于以下的 SQL:

    SELECT  *  FROM
      (SELECT  *  FROM  addresses  LIMIT  10)  AS  anon_1
      LEFT  OUTER  JOIN  users  AS  users_1  ON  users_1.id  =  anon_1.addresses_user_id
    

    这是因为任何急切加载器的存在都表明它们中的一些或全部可能与多行集合相关联,这将需要将任何种类的行计数敏感修饰符(如 LIMIT)包装在子查询中。

    在 0.6 版本中,该逻辑更加敏感,可以检测到所有急切加载器是否代表多对一关系,如果是这种情况,则急切连接不会影响行数:

    SELECT  *  FROM  addresses  LEFT  OUTER  JOIN  users  AS  users_1  ON  users_1.id  =  addresses.user_id  LIMIT  10
    

使用联接表继承的可变主键

在子表具有外键到父表主键的联接表继承配置中,现在可以在类似 PostgreSQL 这样支持级联的数据库上进行更新。mapper()现在有一个选项passive_updates=True,表示此外键将自动更新。如果在不支持级联的数据库上,如 SQLite 或 MySQL/MyISAM 上,将此标志设置为False。未来的功能增强将尝试根据使用的方言/表样式自动配置此标志。

Beaker 缓存

Beaker 集成的一个有前途的新例子在examples/beaker_caching中。这是一个简单的配方,将 Beaker 缓存应用于Query的结果生成引擎中。缓存参数通过query.options()提供,并允许完全控制缓存的内容。SQLAlchemy 0.6 对Session.merge()方法进行了改进,以支持这种和类似的配方,并在大多数情况下提供了显著改进的性能。

其他变化

  • 当选择多列/实体时,Query返回的“行元组”对象现在也是可序列化的,并且性能更高。

  • query.join()已经重新设计,以提供更一致的行为和更灵活性(包括[ticket:1537])

  • query.select_from()接受多个子句,以在 FROM 子句中产生多个逗号分隔的条目。在从多个 join()子句中选择时很有用。

  • Session.merge()上的“dont_load=True”标志已被弃用,现在是“load=False”。

  • 添加了“make_transient()”辅助函数,将持久/分离实例转换为瞬态实例(即删除实例键并从任何会话中移除。)[ticket:1052]

  • mapper()上的allow_null_pks标志已被弃用,并已重命名为allow_partial_pks。默认情况下已打开。这意味着对于任何主键列中有非空值的行将被视为标识。这种情况通常只在映射到外连接时发生。当设置为 False 时,具有 NULL 值的 PK 将不被视为主键 - 特别是这意味着结果行将返回为 None(或不会填充到集合中),并且在 0.6 版本中还表示session.merge()不会为此类 PK 值发出往返数据库的请求。[ticket:1680]

  • “backref”的机制已完全合并到更精细的“back_populates”系统中,并完全在RelationProperty_generate_backref()方法中进行。这使得RelationProperty的初始化过程更简单,并允许更容易地传播设置(例如从RelationProperty的子类)到反向引用。内部的BackRef()已经消失,backref()返回一个被RelationProperty理解的普通元组。

  • ResultProxykeys属性现在是一个方法,因此对它的引用(result.keys)必须更改为方法调用(result.keys())。

  • ResultProxy.last_inserted_ids现已弃用,改用ResultProxy.inserted_primary_key

弃用/移除的 ORM 元素

在 0.5 版本中被弃用并引发弃用警告的大多数元素已被移除(有少数例外)。所有标记为“即将弃用”的元素现在已被弃用,并在使用时会引发警告。

  • sessionmaker()和其他地方的‘transactional’标志已被移除。使用‘autocommit=True’来表示‘transactional=False’。

  • mapper()上的‘polymorphic_fetch’参数已被移除。可以使用‘with_polymorphic’选项来控制加载。

  • mapper()上的‘select_table’参数已被移除。使用‘with_polymorphic=(“*”, )’来实现此功能。

  • synonym()上的‘proxy’参数已被移除。在 0.5 版本中,此标志没有任何作用,因为“代理生成”行为现在是自动的。

  • 将元素的单个列表传递给joinedload()joinedload_all()contains_eager()lazyload()defer()undefer(),而不是多个位置*args,已被弃用。

  • 将元素的单个列表传递给query.order_by()query.group_by()query.join()query.outerjoin(),而不是多个位置*args,已被弃用。

  • 移除了query.iterate_instances()。使用query.instances()

  • 移除了Query.query_from_parent()。使用sqlalchemy.orm.with_parent()函数生成“parent”子句,或者使用query.with_parent()

  • 移除了query._from_self(),请改用query.from_self()

  • composite()的“comparator”参数已被移除。使用“comparator_factory”。

  • 移除了RelationProperty._get_join()

  • Session上的‘echo_uow’标志已被移除。在“sqlalchemy.orm.unitofwork”名称上使用日志记录。

  • session.clear() 被移除。使用 session.expunge_all()

  • session.save()session.update()session.save_or_update() 被移除。使用 session.add()session.add_all()

  • session.flush() 上的 “objects” 标志仍然被弃用。

  • session.merge() 上的 “dont_load=True” 标志已被弃用,改用 “load=False”。

  • ScopedSession.mapper 仍然被弃用。参见使用方法的示例:www.sqlalchemy.org/trac/wiki/Usag eRecipes/SessionAwareMapper

  • attributes.init_collection()attributes.get_history() 中传递 InstanceState(内部 SQLAlchemy 状态对象)已被弃用。这些函数是公共 API,通常期望普通映射对象实例。

  • declarative_base() 上的 ‘engine’ 参数已被移除。使用 ‘bind’ 关键字参数。

扩展

SQLSoup

SQLSoup 已经现代化并更新以反映常见的 0.5/0.6 功能,包括明确定义的会话集成。请阅读新文档:[www.sqlalc hemy.org/docs/06/reference/ext/sqlsoup.html]。

Declarative

DeclarativeMetadeclarative_base 的默认元类)以前允许子类修改 dict_ 来添加类属性(例如列)。这种方式已不再起作用,DeclarativeMeta 构造函数现在忽略 dict_。相反,类属性应直接赋值,例如 cls.id=Column(...),或者应该使用 MixIn 类 方法而不是元类方法。

SQLSoup

SQLSoup 已经现代化并更新以反映常见的 0.5/0.6 功能,包括明确定义的会话集成。请阅读新文档:[www.sqlalc hemy.org/docs/06/reference/ext/sqlsoup.html]。

Declarative

DeclarativeMetadeclarative_base 的默认元类)以前允许子类修改 dict_ 来添加类属性(例如列)。这种方式已不再起作用,DeclarativeMeta 构造函数现在忽略 dict_。相反,类属性应直接赋值,例如 cls.id=Column(...),或者应该使用 MixIn 类 方法而不是元类方法。

SQLAlchemy 0.5 中有什么新功能?

原文:docs.sqlalchemy.org/en/20/changelog/migration_05.html

关于本文档

本文档描述了 SQLAlchemy 版本 0.4(最后发布于 2008 年 10 月 12 日)与 SQLAlchemy 版本 0.5(最后发布于 2010 年 1 月 16 日)之间的变化。

文档日期:2009 年 8 月 4 日

本指南记录了影响用户从 SQLAlchemy 0.4 系列迁移到 0.5 系列的 API 更改。对于那些从Essential SQLAlchemy开始工作的人也是推荐的,该书只涵盖了 0.4 版本,甚至在其中有一些旧的 0.3 版本的内容。请注意,SQLAlchemy 0.5 删除了在整个 0.4 系列中已弃用的许多行为,并且还弃用了更多与 0.4 特定的行为。

主要文档更改

文档的一些部分已经完全重写,可以作为新 ORM 功能的介绍。特别是QuerySession对象在 API 和行为上有一些明显的区别,这些区别从根本上改变了许多基本操作的方式,特别是构建高度定制的 ORM 查询和处理过时的会话状态、提交和回滚。

弃用来源

另一个信息源记录在一系列单元测试中,展示了一些常见Query模式的最新用法;此文件可在[source:sqlalchemy/trunk/test/orm/test_deprecations.py]中查看。

要求更改

  • 需要 Python 2.4 或更高版本。SQLAlchemy 0.4 系列是最后一个支持 Python 2.3 的版本。

对象关系映射

  • Query 中的列级表达式。 - 如教程中所述,Query具有创建特定 SELECT 语句的能力,而不仅仅是针对完整行的语句:

    session.query(User.name, func.count(Address.id).label("numaddresses")).join(
        Address
    ).group_by(User.name)
    

    任何多列/实体查询返回的元组都是命名元组:

    for row in (
        session.query(User.name, func.count(Address.id).label("numaddresses"))
        .join(Address)
        .group_by(User.name)
    ):
        print("name", row.name, "number", row.numaddresses)
    

    Query具有statement访问器,以及一个subquery()方法,允许Query用于创建更复杂的组合:

    subq = (
        session.query(Keyword.id.label("keyword_id"))
        .filter(Keyword.name.in_(["beans", "carrots"]))
        .subquery()
    )
    recipes = session.query(Recipe).filter(
        exists()
        .where(Recipe.id == recipe_keywords.c.recipe_id)
        .where(recipe_keywords.c.keyword_id == subq.c.keyword_id)
    )
    
  • 建议使用显式 ORM 别名进行别名连接 - aliased()函数生成一个类的“别名”,允许在 ORM 查询中与别名进行细粒度控制。虽然仍然可以使用表级别的别名(即table.alias()),但 ORM 级别的别名保留了 ORM 映射对象的语义,这对于继承映射、选项和其他场景非常重要。例如:

    Friend = aliased(Person)
    session.query(Person, Friend).join((Friend, Person.friends)).all()
    
  • query.join()功能大大增强。 - 您现在可以通过多种方式指定连接的目标和 ON 子句。可以仅提供目标类,SQLA 将尝试通过相同的外键形式连接到它,就像table.join(someothertable)一样。还可以提供目标和显式的 ON 条件,其中 ON 条件可以是relation()名称,实际类描述符或 SQL 表达式。或者也可以像以前那样只提供relation()名称或类描述符。请参阅 ORM 教程,其中有几个示例。

  • 建议使用声明性用于不需要(且不喜欢)表和映射器之间抽象的应用程序 - [/docs/05/reference/ext/declarative.html 声明性]模块用于将Tablemapper()和用户定义的类对象的表达结合在一起,强烈建议使用它,因为它简化了应用程序配置,确保了“每个类一个映射器”的模式,并允许对不同的mapper()调用提供完整的配置范围。将mapper()Table的使用分开现在被称为“经典 SQLAlchemy 使用方式”,当然可以与声明性混合使用。

  • 已从类中删除了.c.属性(即MyClass.c.somecolumn)。与 0.4 版本一样,类级别的属性可用作查询元素,即Class.c.propname现在被Class.propname所取代,并且c属性仍然保留在Table对象上,其中它们指示存在于表上的Column对象的命名空间。

    要获取映射类的表(如果您之前没有保留它):

    table = class_mapper(someclass).mapped_table
    

    迭代遍历列:

    for col in table.c:
        print(col)
    

    使用特定列进行操作:

    table.c.somecolumn
    

    类绑定描述符支持完整的 Column 运算符集,以及文档化的与关系有关的运算符,如has()any()contains()等。

    删除.c.的原因是,在 0.5 版本中,类绑定描述符可能具有不同的含义,以及关于类映射的信息,与普通的Column对象不同-并且存在一些情况,您会特别想要使用其中之一。通常,使用类绑定描述符会调用一组映射/多态感知的转换,而使用表绑定列则不会。在 0.4 版本中,这些转换适用于所有表达式,但是 0.5 版本完全区分列和映射描述符,仅将转换应用于后者。因此,在许多情况下,特别是在处理连接的表继承配置以及使用query(<columns>)时,Class.propnametable.c.colname不可互换。

    例如,session.query(users.c.id, users.c.name)session.query(User.id, User.name)是不同的;在后一种情况下,Query知道正在使用的映射器,并且可以使用进一步的映射器特定操作,如query.join(<propname>)query.with_parent()等,但在前一种情况下不行。此外,在多态继承场景中,类绑定描述符指的是多态可选择使用的列,而不一定是直接对应描述符的表列。例如,一组类通过连接表继承与person表相关联,每个表的person_id列都将其Class.person_id属性映射到person中的person_id列,而不是其子类表。版本 0.4 会自动将此行为映射到表绑定的Column对象上。在 0.5 中,已移除了此自动转换,因此实际上可以使用表绑定列来覆盖多态查询时发生的转换;这使得Query能够在连接表或具体表继承设置中创建优化的选择,以及可移植的子查询等。

  • 会话现在与事务自动同步。 会话现在默认情况下自动与事务同步,包括自动刷新和自动过期。除非使用autocommit选项禁用,否则始终存在事务。当所有三个标志都设置为默认值时,会话在回滚后能够优雅地恢复,并且很难将过时数据导入会话中。详细信息请参阅新的会话文档。

  • 隐式排序已移除。这将影响依赖于 SA 的“隐式排序”行为的 ORM 用户,该行为规定所有没有order_by()的 Query 对象将按照主映射表的“id”或“oid”列进行排序,并且所有延迟/急切加载的集合都应用类似的排序。在 0.5 中,必须显式配置mapper()relation()对象上的自动排序(如果需要),或者在使用Query时。

    要将 0.4 映射转换为 0.5,使其排序行为与 0.4 或之前的版本极为相似,请在mapper()relation()上使用order_by设置:

    mapper(
        User,
        users,
        properties={"addresses": relation(Address, order_by=addresses.c.id)},
        order_by=users.c.id,
    )
    

    要在 backref 上设置排序,请使用backref()函数:

    "keywords": relation(
        Keyword,
        secondary=item_keywords,
        order_by=keywords.c.name,
        backref=backref("items", order_by=items.c.id),
    )
    

    使用声明式?为了帮助满足新的order_by要求,现在可以使用稍后在 Python 中评估的字符串来设置order_by和相关内容(这仅适用于声明式,而不是普通的映射器):

    class MyClass(MyDeclarativeBase):
        ...
        "addresses": relation("Address", order_by="Address.id")
    

    通常在加载基于列表的项目集合的relation()上设置order_by是一个好主意,因为否则无法影响排序。除此之外,最佳实践是使用Query.order_by()来控制加载的主要实体的排序。

  • Session 现在是 autoflush=True/autoexpire=True/autocommit=False。 - 要设置它,只需调用sessionmaker()而不带任何参数。现在transactional=True的名称是autocommit=False。刷新发生在每次查询时(可通过autoflush=False禁用),在每次commit()之前(一如既往),以及在每次begin_nested()之前(因此回滚到 SAVEPOINT 是有意义的)。所有对象在每次commit()和每次rollback()后都会过期。回滚后,待定对象被清除,删除的对象移回持久状态。这些默认设置非常好地协同工作,实际上不再需要像clear()这样的旧技术(也已重命名为expunge_all())。

    P.S.: 在rollback()后,会话现在是可重用的。标量和集合属性的更改、添加和删除都会被回滚。

  • session.add()取代了 session.save()、session.update()、session.save_or_update()。 - session.add(someitem)session.add_all([list of items])方法取代了save()update()save_or_update()。这些方法将在整个 0.5 版本中继续被弃用。

  • backref 配置更简洁。 - backref()函数现在在未明确声明时使用前向relation()primaryjoinsecondaryjoin参数。在两个方向上分别指定primaryjoin/secondaryjoin不再必要。

  • 简化的多态选项。 - ORM 的“多态加载”行为已经简化。在 0.4 版本中,mapper()有一个名为polymorphic_fetch的参数,可以配置为selectdeferred。此选项已被移除;现在映射器将仅推迟未包含在 SELECT 语句中的任何列。实际使用的 SELECT 语句由with_polymorphic映射器参数控制(在 0.4 中也有,替代了select_table),以及Query上的with_polymorphic()方法(同样在 0.4 中)。

    对继承类的延迟加载进行了改进,现在映射器在所有情况下都会生成“优化”版本的 SELECT 语句;也就是说,如果类 B 继承自 A,并且类 B 上的几个属性已过期,刷新操作将只包括 B 的表在 SELECT 语句中,不会 JOIN 到 A。

  • Session上的execute()方法将普通字符串转换为text()构造,以便所有绑定参数都可以指定为“:bindname”而无需显式调用text()。如果需要“原始”SQL,请使用session.connection().execute("raw text")

  • session.Query().iterate_instances()已重命名为instances()。旧的返回列表而不是迭代器的instances()方法已不复存在。如果你依赖于该行为,应该使用list(your_query.instances())

扩展 ORM

在 0.5 版本中,我们将继续提供更多修改和扩展 ORM 的方法。以下是摘要:

  • MapperExtension. - 这是经典的扩展类,仍然存在。很少需要的方法是create_instance()populate_instance()。要控制从数据库加载对象时的初始化,使用reconstruct_instance()方法,或者更容易地使用文档中描述的@reconstructor装饰器。

  • SessionExtension. - 这是一个易于使用的会话事件扩展类。特别是,它提供了before_flush()after_flush()after_flush_postexec()方法。在许多情况下,推荐使用这种用法,而不是MapperExtension.before_XXX,因为在before_flush()中,您可以自由修改会话的刷新计划,这是无法从MapperExtension中完成的。

  • AttributeExtension. - 这个类现在是公共 API 的一部分,允许拦截属性上的用户事件,包括属性设置和删除操作,以及集合追加和删除。它还允许修改要设置或追加的值。文档中描述的@validates装饰器提供了一种快速的方式,将任何映射属性标记为特定类方法“验证”。

  • Attribute Instrumentation Customization. - 提供了一个 API,用于雄心勃勃地完全替换 SQLAlchemy 的属性检测,或者仅在某些情况下进行增强。这个 API 是为 Trellis 工具包而制作的,但作为公共 API 可用。在分发的/examples/custom_attributes目录中提供了一些示例。

模式/类型

  • String with no length no longer generates TEXT, it generates VARCHAR - 当未指定长度时,String类型不再神奇地转换为Text类型。这只在发出 CREATE TABLE 时才会生效,因为它将发出没有长度参数的VARCHAR,这在许多(但不是所有)数据库上是无效的。要创建 TEXT(或 CLOB,即无界字符串)列,请使用Text类型。

  • PickleType() with mutable=True requires an eq() method - 当PickleType类型的mutable=True时,需要比较值。比较pickle.dumps()的方法效率低下且不可靠。如果传入对象没有实现__eq__(),并且也不是None,则使用dumps()进行比较,但会发出警告。对于实现__eq__()的类型,包括所有字典、列表等,比较将使用==,默认情况下是可靠的。

  • TypeEngine/TypeDecorator 的 convert_bind_param()convert_result_value() 方法已移除。 - 不幸的是,O’Reilly 书籍在 0.3 之后弃用了这些方法,但仍然对其进行了文档记录。对于一个子类化 TypeEngine 的用户定义类型,应该使用 bind_processor()result_processor() 方法进行绑定/结果处理。任何用户定义类型,无论是扩展 TypeEngine 还是 TypeDecorator,只要使用旧的 0.3 风格,都可以通过以下适配器轻松地调整为新风格:

    class AdaptOldConvertMethods(object):
      """A mixin which adapts 0.3-style convert_bind_param and
     convert_result_value methods
    
     """
    
        def bind_processor(self, dialect):
            def convert(value):
                return self.convert_bind_param(value, dialect)
    
            return convert
    
        def result_processor(self, dialect):
            def convert(value):
                return self.convert_result_value(value, dialect)
    
            return convert
    
        def convert_result_value(self, value, dialect):
            return value
    
        def convert_bind_param(self, value, dialect):
            return value
    

    要使用上述混合项:

    class MyType(AdaptOldConvertMethods, TypeEngine): ...
    
  • ColumnTable 上的 quote 标志以及 Table 上的 quote_schema 标志现在控制引用方式,包括正面和负面。默认值为 None,表示让常规的引用规则生效。当为 True 时,强制引用。当为 False 时,强制不引用。

  • 现在可以更方便地使用 Column(..., server_default='val') 指定列 DEFAULT 值的 DDL,废弃了 Column(..., PassiveDefault('val'))default= 现在仅用于 Python 初始化的默认值,并且可以与 server_default 共存。新的 server_default=FetchedValue() 取代了标记列受外部触发器影响的 PassiveDefault('') 习惯用法,没有 DDL 的副作用。

  • SQLite 的 DateTimeTimeDate 类型现在仅接受 datetime 对象,而不接受字符串作为绑定参数输入。如果想要创建自己的“混合”类型,它接受字符串并将结果返回为日期对象(可以是任何格式),则创建一个基于 StringTypeDecorator。如果只想要基于字符串的日期,只需使用 String

  • 此外,当与 SQLite 一起使用时,DateTimeTime 类型现在以与 str(datetime) 相同的方式表示 Python datetime.datetime 对象的 “微秒” 字段,即作为小数秒,而不是微秒的计数。也就是说:

    dt = datetime.datetime(2008, 6, 27, 12, 0, 0, 125)  # 125 usec
    
    # old way
    "2008-06-27 12:00:00.125"
    
    # new way
    "2008-06-27 12:00:00.000125"
    

    因此,如果现有的基于文件的 SQLite 数据库打算在 0.4 和 0.5 之间使用,您必须将 datetime 列升级为存储新格式(注意:请测试此功能,我相信它是正确的):

    UPDATE  mytable  SET  somedatecol  =
      substr(somedatecol,  0,  19)  ||  '.'  ||  substr((substr(somedatecol,  21,  -1)  /  1000000),  3,  -1);
    

    或者,可以按以下方式启用“传统”模式:

    from sqlalchemy.databases.sqlite import DateTimeMixin
    
    DateTimeMixin.__legacy_microseconds__ = True
    

连接池默认不再是线程本地的

0.4 版本不幸默认设置为 pool_threadlocal=True,导致在单个线程中使用多个 Sessions 时出现意外行为。此标志在 0.5 版本中默认关闭。要重新启用 0.4 版本的行为,请在 create_engine() 中指定 pool_threadlocal=True,或者通过 strategy="threadlocal" 使用 “threadlocal” 策略。

args 被接受,args 不再被接受

method(\*args)method([args]) 的策略是,如果方法接受一个表示固定结构的可变长度项集合,则采用 \*args。如果方法接受一个数据驱动的可变长度项集合,则采用 [args]

  • 各种 Query.options() 函数 eagerload(), eagerload_all(), lazyload(), contains_eager(), defer(), undefer() 现在都接受可变长度的 \*keys 作为参数,这允许使用描述符来制定路径,例如:

    query.options(eagerload_all(User.orders, Order.items, Item.keywords))
    

    为了向后兼容性,仍然接受单个数组参数。

  • 类似地,Query.join()Query.outerjoin() 方法现在接受可变长度的 *args,为了向后兼容性,现在只接受单个数组:

    query.join("orders", "items")
    query.join(User.orders, Order.items)
    
  • 列上的 in_() 方法和类似方法现在只接受列表参数。不再接受 \*args

已移除

  • entity_name - 这个特性一直存在问题,很少被使用。0.5 版本更深入地揭示了 entity_name 的问题,导致其被移除。如果需要为单个类使用不同的映射,将类拆分为单独的子类并分别映射它们。一个示例在 [wiki:UsageRecipes/EntityName] 中。有关背景的更多信息请参阅 https://groups.google.c om/group/sqlalchemy/browse_thread/thread/9e23a0641a88b96d? hl=en 。

  • get()/load() 清理

    load() 方法已被移除。它的功能有点随意,基本上是从 Hibernate 复制过来的,在那里也不是一个特别有意义的方法。

    要获得等效功能:

    x = session.query(SomeClass).populate_existing().get(7)
    

    Session.get(cls, id)Session.load(cls, id) 已被移除。Session.get()session.query(cls).get(id) 是多余的。

    MapperExtension.get() 也已被移除(MapperExtension.load() 也是)。要覆盖 Query.get() 的功能,使用子类:

    class MyQuery(Query):
        def get(self, ident): ...
    
    session = sessionmaker(query_cls=MyQuery)()
    
    ad1 = session.query(Address).get(1)
    
  • sqlalchemy.orm.relation()

    下列已弃用的关键字参数已被移除:

    foreignkey, association, private, attributeext, is_backref

    特别地,attributeext 被替换为 extension - AttributeExtension 类现在在公共 API 中。

  • session.Query()

    下列已弃用的函数已被移除:

    list, scalar, count_by, select_whereclause, get_by, select_by, join_by, selectfirst, selectone, select, execute, select_statement, select_text, join_to, join_via, selectfirst_by, selectone_by, apply_max, apply_min, apply_avg, apply_sum

    另外,join()outerjoin()add_entity()add_column()id 关键字参数已被移除。要将 Query 中的表别名定位到结果列,使用 aliased 构造:

    from sqlalchemy.orm import aliased
    
    address_alias = aliased(Address)
    print(session.query(User, address_alias).join((address_alias, User.addresses)).all())
    
  • sqlalchemy.orm.Mapper

    • instances()

    • get_session() - 这个方法并不是很显著,但会将延迟加载与特定会话关联起来,即使父对象完全分离,当使用 scoped_session() 或旧的 SessionContextExt 等扩展时。有些应用程序可能依赖于这种行为,但现在可能不再按预期工作;但更好的编程实践是始终确保对象在会话中存在,如果需要从它们的属性访问数据库。

  • mapper(MyClass, mytable)

    映射类不再使用“c”类属性进行仪器化; 例如MyClass.c

  • sqlalchemy.orm.collections

    _prepare_instrumentation 别名 prepare_instrumentation 已被移除。

  • sqlalchemy.orm

    移除了EXT_PASS别名EXT_CONTINUE

  • sqlalchemy.engine

    DefaultDialect.preexecute_sequences.preexecute_pk_sequences的别名已被移除。

    已移除了弃用的 engine_descriptors()函数。

  • sqlalchemy.ext.activemapper

    模块已移除。

  • sqlalchemy.ext.assignmapper

    模块已移除。

  • sqlalchemy.ext.associationproxy

    代理的.append(item, \**kw)上的关键字参数传递已被移除,现在只是.append(item)

  • sqlalchemy.ext.selectresultssqlalchemy.mods.selectresults

    模块已移除。

  • sqlalchemy.ext.declarative

    declared_synonym()已移除。

  • sqlalchemy.ext.sessioncontext

    模块已移除。

  • sqlalchemy.log

    SADeprecationWarning别名sqlalchemy.exc.SADeprecationWarning已被移除。

  • sqlalchemy.exc

    exc.AssertionError已移除,使用被 Python 内置的同名替换。

  • sqlalchemy.databases.mysql

    已弃用的get_version_info方言方法已被移除。

重命名或移动

  • sqlalchemy.exceptions现在是sqlalchemy.exc

    该模块在 0.6 之前仍可使用旧名称导入。

  • FlushErrorConcurrentModificationErrorUnmappedColumnError -> sqlalchemy.orm.exc

    这些异常已移至 orm 包。导入sqlalchemy.orm将在 0.6 之前为兼容性安装 sqlalchemy.exc 的别名。

  • sqlalchemy.logging -> sqlalchemy.log

    此内部模块已重命名。在使用 py2app 等扫描导入的工具打包 SA 时,不再需要特殊处理。

  • session.Query().iterate_instances() -> session.Query().instances().

已弃用

  • Session.save()Session.update()Session.save_or_update()

    所有三个被Session.add()替换

  • sqlalchemy.PassiveDefault

    使用Column(server_default=...)在底层转换为sqlalchemy.DefaultClause()

  • session.Query().iterate_instances()。已重命名为instances().

主要文档更改

文档的一些部分已被完全重写,可以作为新 ORM 功能的介绍。特别是QuerySession对象在 API 和行为上有一些明显的差异,这些差异从根本上改变了许多基本操作的方式,特别是构建高度定制的 ORM 查询和处理过时的会话状态、提交和回滚。

弃用来源

另一个信息来源在一系列单元测试中记录了一些常见Query模式的最新用法;此文件可在[source:sqlalchemy/trunk/test/orm/test_deprecations.py]中查看。

要求更改

  • Python 2.4 或更高版本是必需的。SQLAlchemy 0.4 系列是最后一个支持 Python 2.3 的版本。

对象关系映射

  • Query 中的列级表达式。 - 如 教程 中详细说明的,Query 具有创建特定 SELECT 语句的能力,而不仅仅是针对完整行的语句:

    session.query(User.name, func.count(Address.id).label("numaddresses")).join(
        Address
    ).group_by(User.name)
    

    任何多列/实体查询返回的元组都是命名元组:

    for row in (
        session.query(User.name, func.count(Address.id).label("numaddresses"))
        .join(Address)
        .group_by(User.name)
    ):
        print("name", row.name, "number", row.numaddresses)
    

    Query 有一个 statement 访问器,以及一个 subquery() 方法,允许 Query 用于创建更复杂的组合:

    subq = (
        session.query(Keyword.id.label("keyword_id"))
        .filter(Keyword.name.in_(["beans", "carrots"]))
        .subquery()
    )
    recipes = session.query(Recipe).filter(
        exists()
        .where(Recipe.id == recipe_keywords.c.recipe_id)
        .where(recipe_keywords.c.keyword_id == subq.c.keyword_id)
    )
    
  • 推荐使用显式 ORM 别名进行别名连接 - aliased() 函数生成一个类的“别名”,允许与 ORM 查询一起对别名进行精细控制。虽然仍然可以使用表级别的别名(即 table.alias()),但 ORM 级别的别名保留了 ORM 映射对象的语义,这对于继承映射、选项和其他场景非常重要。例如:

    Friend = aliased(Person)
    session.query(Person, Friend).join((Friend, Person.friends)).all()
    
  • query.join() 大大增强。 - 现在可以以多种方式指定连接的目标和 ON 子句。可以仅提供目标类,SQLA 将尝试通过外键以与 table.join(someothertable) 相同的方式与其形成连接。也可以提供目标和显式的 ON 条件,其中 ON 条件可以是 relation() 名称、实际类描述符或 SQL 表达式。或者仍然可以使用旧的仅 relation() 名称或类描述符的方式。请参阅 ORM 教程,其中有几个示例。

  • 推荐为不需要(也不偏好)表和映射器之间抽象的应用程序使用声明式 - [/docs/05/reference/ext/declarative.html 声明式] 模块,用于将 Tablemapper() 和用户定义的类对象的表达式结合在一起,强烈推荐,因为它简化了应用程序配置,确保了“每个类一个映射器”的模式,并允许对不同的 mapper() 调用可用的完整配置范围。现在将单独使用 mapper()Table 称为“经典 SQLAlchemy 使用”,当然可以与声明式自由混合使用。

  • 已从类中删除 .c. 属性(即 MyClass.c.somecolumn)。与 0.4 版本一样,类级别的属性可用作查询元素,即 Class.c.propname 现在被 Class.propname 取代,c 属性仍然保留在表对象上,其中它们指示表上存在的 Column 对象的命名空间。

    要获取映射类的表(如果您之前没有保留它):

    table = class_mapper(someclass).mapped_table
    

    通过列进行迭代:

    for col in table.c:
        print(col)
    

    使用特定列:

    table.c.somecolumn
    

    类绑定描述符支持完整的列运算符以及文档化的关系导向运算符,如 has()any()contains() 等。

    移除.c.的原因是在 0.5 中,类绑定的描述符可能具有不同的含义,以及关于类映射的信息,与普通的Column对象不同-并且存在一些情况下,您可能希望明确使用其中之一。通常,使用类绑定的描述符会调用一组映射/多态感知的转换,而使用表绑定的列则不会。在 0.4 中,这些转换适用于所有表达式,但是 0.5 完全区分列和映射描述符,仅对后者应用转换。因此,在许多情况下,特别是在处理连接表继承配置以及使用query(<columns>)时,Class.propnametable.c.colname是不可互换的。

    例如,session.query(users.c.id, users.c.name)session.query(User.id, User.name)是不同的;在后一种情况下,Query知道正在使用的映射器,并且可以使用进一步的映射器特定操作,如query.join(<propname>)query.with_parent()等,但在前一种情况下则不行。此外,在多态继承场景中,类绑定的描述符指的是多态可选择的列,而不一定是直接对应描述符的表列。例如,一组通过连接表继承到person表的类,每个表的person_id列都将其Class.person_id属性映射到person中的person_id列,而不是其子类表。版本 0.4 会自动将此行为映射到表绑定的Column对象上。在 0.5 中,这种自动转换已被移除,因此实际上可以使用表绑定的列来覆盖多态查询时发生的转换;这使得Query能够在连接表或具体表继承设置中创建优化的选择,以及可移植的子查询等。

  • 会话现在与事务自动同步。 会话现在默认自动与事务同步,包括自动刷新和自动过期。除非使用autocommit选项禁用,否则始终存在事务。当所有三个标志都设置为默认值时,会话在回滚后能够优雅地恢复,并且很难将过时数据输入会话。有关详细信息,请参阅新的会话文档。

  • 隐式排序已移除。这将影响依赖 SA 的“隐式排序”行为的 ORM 用户,该行为规定所有没有order_by()的 Query 对象将按照主映射表的“id”或“oid”列进行排序,并且所有延迟/急切加载的集合都会应用类似的排序。在 0.5 版本中,必须在mapper()relation()对象上明确配置自动排序(如果需要),或者在使用Query时进行配置。

    要将 0.4 映射转换为 0.5,使其排序行为与 0.4 或之前的版本极为相似,请在mapper()relation()上使用order_by设置:

    mapper(
        User,
        users,
        properties={"addresses": relation(Address, order_by=addresses.c.id)},
        order_by=users.c.id,
    )
    

    要在backref上设置排序,使用backref()函数:

    "keywords": relation(
        Keyword,
        secondary=item_keywords,
        order_by=keywords.c.name,
        backref=backref("items", order_by=items.c.id),
    )
    

    使用声明式?为了帮助新的order_by要求,现在可以使用稍后在 Python 中评估的字符串来设置order_by和相关内容(这仅适用于声明式,而不是普通映射器):

    class MyClass(MyDeclarativeBase):
        ...
        "addresses": relation("Address", order_by="Address.id")
    

    通常在加载基于列表的项目集合的relation()上设置order_by是个好主意,因为否则无法影响排序。除此之外,最佳实践是使用Query.order_by()来控制加载的主要实体的排序。

  • 会话现在是 autoflush=True/autoexpire=True/autocommit=False。 - 要设置它,只需调用sessionmaker()而不带参数。现在transactional=True的名称是autocommit=False。刷新发生在每个查询发出时(使用autoflush=False禁用),在每个commit()之前(一如既往),以及在每个begin_nested()之前(因此回滚到 SAVEPOINT 是有意义的)。在每个commit()和每个rollback()之后,所有对象都会过期。回滚后,待处理对象被清除,删除的对象移回持久状态。这些默认设置非常好地配合在一起,实际上不再需要像clear()这样的旧技术(也更名为expunge_all())。

    注:在rollback()后,会话现在是可重用的。标量和集合属性的更改、添加和删除都会被回滚。

  • session.add()取代 session.save()、session.update()、session.save_or_update()。 - session.add(someitem)session.add_all([list of items])方法取代了save()update()save_or_update()。这些方法将在整个 0.5 版本中继续被弃用。

  • backref 配置更简洁。 - 当前backref()函数在未明确说明时,现在使用前向relation()primaryjoinsecondaryjoin参数。不再需要在两个方向分别指定primaryjoin/secondaryjoin

  • 简化多态选项。 - ORM 的“多态加载”行为已经简化。在 0.4 版本中,mapper()有一个名为polymorphic_fetch的参数,可以配置为selectdeferred。此选项已被移除;现在映射器将仅推迟未在 SELECT 语句中出现的任何列。实际使用的 SELECT 语句由with_polymorphic映射器参数控制(在 0.4 中也有,替换了select_table),以及Query上的with_polymorphic()方法(在 0.4 中也有)。

    对于继承类的延迟加载的改进是,映射器现在在所有情况下都生成“优化”版本的 SELECT 语句;也就是说,如果类 B 从 A 继承,并且在类 B 上已经过期了几个属性,则刷新操作将仅在 SELECT 语句中包含 B 的表,并且不会 JOIN 到 A。

  • Session 上的 execute() 方法将普通字符串转换为 text() 结构,以便可以将绑定参数全部指定为“:bindname”而不需要显式调用 text()。如果需要“原始”SQL,请使用 session.connection().execute("raw text")

  • session.Query().iterate_instances() 已重命名为 instances()。旧的 instances() 方法不再返回列表而是返回迭代器。如果您依赖该行为,则应使用 list(your_query.instances())

扩展 ORM

在 0.5 版本中,我们正在以更多的方式修改和扩展 ORM。以下是摘要:

  • MapperExtension. - 这是经典的扩展类,仍然存在。很少需要的方法是 create_instance()populate_instance()。要控制从数据库加载对象时的初始化,请使用 reconstruct_instance() 方法,或者更容易地使用文档中描述的 @reconstructor 装饰器。

  • SessionExtension. - 这是一个易于使用的会话事件扩展类。特别是,它提供了 before_flush()after_flush()after_flush_postexec() 方法。在许多情况下,推荐使用这种用法而不是 MapperExtension.before_XXX,因为在 before_flush() 中,您可以自由地修改会话的刷新计划,而这在 MapperExtension 中无法做到。

  • AttributeExtension. - 此类现在是公共 API 的一部分,并允许拦截属性上的用户事件,包括属性设置和删除操作以及集合附加和删除操作。它还允许修改要设置或附加的值。在文档中描述的 @validates 装饰器提供了一种快速的方式,可以将任何映射属性标记为特定类方法“验证”的方法。

  • 属性仪器定制。 - 为了完全替换 SQLAlchemy 的属性仪器,或者仅在某些情况下对其进行增强,提供了一个 API。此 API 是为了 Trellis 工具包而制作的,但作为公共 API 提供。在分发目录中的 /examples/custom_attributes 中提供了一些示例。

架构/类型

  • 没有长度的字符串不再生成 TEXT,而是生成 VARCHAR - 当未指定长度时,String 类型不再神奇地转换为 Text 类型。这仅在发出 CREATE TABLE 时才会生效,因为它将发出不带长度参数的 VARCHAR,这在许多(但不是所有)数据库上都是无效的。要创建 TEXT(或 CLOB,即无界限的字符串)列,请使用 Text 类型。

  • PickleType() with mutable=True 需要一个 eq()方法 - PickleType类型在 mutable=True 时需要比较值。使用pickle.dumps()进行比较的方法效率低下且不可靠。如果传入对象没有实现__eq__()并且也不是None,则使用dumps()比较,但会发出警告。对于实现__eq__()的类型,包括所有字典、列表等,比较将使用==,现在默认情况下是可靠的。

  • TypeEngine/TypeDecorator 的 convert_bind_param()和 convert_result_value()方法已移除。 - O’Reilly 书籍不幸地记录了这些方法,尽管它们在 0.3 版本后已被弃用。对于一个继承TypeEngine的用户定义类型,应该使用bind_processor()result_processor()方法进行绑定/结果处理。任何用户定义的类型,无论是扩展TypeEngine还是TypeDecorator,只要使用旧的 0.3 风格,都可以通过以下适配器轻松地适应新风格:

    class AdaptOldConvertMethods(object):
      """A mixin which adapts 0.3-style convert_bind_param and
     convert_result_value methods
    
     """
    
        def bind_processor(self, dialect):
            def convert(value):
                return self.convert_bind_param(value, dialect)
    
            return convert
    
        def result_processor(self, dialect):
            def convert(value):
                return self.convert_result_value(value, dialect)
    
            return convert
    
        def convert_result_value(self, value, dialect):
            return value
    
        def convert_bind_param(self, value, dialect):
            return value
    

    要使用上述 mixin:

    class MyType(AdaptOldConvertMethods, TypeEngine): ...
    
  • ColumnTable上的quote标志以及Table上的quote_schema标志现在控制引号的正面和负面。默认值为None,表示让常规引号规则生效。当为True时,强制引号。当为False时,强制不引号。

  • DEFAULT值 DDL 现在可以更方便地使用Column(..., server_default='val')来指定,废弃了Column(..., PassiveDefault('val'))default=现在专门用于 Python 发起的默认值,并且可以与 server_default 共存。一个新的server_default=FetchedValue()取代了PassiveDefault('')的习惯用法,用于标记受外部触发器影响的列,并且没有 DDL 副作用。

  • SQLite 的DateTimeTimeDate类型现在只接受 datetime 对象,而不是字符串作为绑定参数输入。如果您想创建自己的“混合”类型,接受字符串并将结果返回为日期对象(以您喜欢的任何格式),请创建一个基于StringTypeDecorator。如果您只想要基于字符串的日期,只需使用String

  • 此外,当与 SQLite 一起使用时,DateTimeTime类型现在以与str(datetime)相同的方式表示 Python datetime.datetime对象的“微秒”字段 - 作为分数秒,而不是微秒计数。也就是说:

    dt = datetime.datetime(2008, 6, 27, 12, 0, 0, 125)  # 125 usec
    
    # old way
    "2008-06-27 12:00:00.125"
    
    # new way
    "2008-06-27 12:00:00.000125"
    

    因此,如果现有的基于文件的 SQLite 数据库打算在 0.4 和 0.5 之间使用,您必须将 datetime 列升级为存储新格式(注意:请测试这一点,我相当确定是正确的):

    UPDATE  mytable  SET  somedatecol  =
      substr(somedatecol,  0,  19)  ||  '.'  ||  substr((substr(somedatecol,  21,  -1)  /  1000000),  3,  -1);
    

    或者,按如下方式启用“legacy”模式:

    from sqlalchemy.databases.sqlite import DateTimeMixin
    
    DateTimeMixin.__legacy_microseconds__ = True
    

连接池默认不再是线程本地的。

0.4 版本的默认设置pool_threadlocal=True导致意外行为,例如在单个线程中使用多个会话时。在 0.5 中,此标志已关闭。要重新启用 0.4 的行为,请在create_engine()中指定pool_threadlocal=True,或者通过strategy="threadlocal"使用“threadlocal”策略。

args 被接受,args 不再被接受

method(\*args) vs. method([args]) 的策略是,如果方法接受代表固定结构的可变长度项目集合,则使用\*args。如果方法接受数据驱动的可变长度项目集合,则使用[args]

  • 各种 Query.options()函数eagerload()eagerload_all()lazyload()contains_eager()defer()undefer()现在都接受可变长度的\*keys作为参数,这允许使用描述符制定路径,例如:

    query.options(eagerload_all(User.orders, Order.items, Item.keywords))
    

    为了向后兼容,仍然接受单个数组参数。

  • 类似地,Query.join()Query.outerjoin() 方法接受可变长度的*args,向后兼容仍然接受单个数组:

    query.join("orders", "items")
    query.join(User.orders, Order.items)
    
  • in_() 方法现在只接受列表参数,不再接受\*args

已移除

  • entity_name - 这个特性一直存在问题,很少被使用。0.5 更深入地揭示了entity_name存在的问题,因此被移除。如果需要为单个类使用不同的映射,将类拆分为单独的子类并分别映射它们。一个示例在[wiki:UsageRecipes/EntityName]中。有关原因的更多信息请参见 https://groups.google.c om/group/sqlalchemy/browse_thread/thread/9e23a0641a88b96d? hl=en 。

  • get()/load() 清理

    load() 方法已被移除。其功能有点随意,基本上是从 Hibernate 复制过来的,在那里也不是一个特别有意义的方法。

    要获得等效功能:

    x = session.query(SomeClass).populate_existing().get(7)
    

    Session.get(cls, id)Session.load(cls, id) 已被移除。Session.get()session.query(cls).get(id) 是冗余的。

    MapperExtension.get() 也被移除(MapperExtension.load()也是)。要重写Query.get()的功能,使用一个子类:

    class MyQuery(Query):
        def get(self, ident): ...
    
    session = sessionmaker(query_cls=MyQuery)()
    
    ad1 = session.query(Address).get(1)
    
  • sqlalchemy.orm.relation()

    下列已弃用的关键字参数已被移除:

    外键,关联,私有,属性扩展,is_backref

    特别是,attributeext 被替换为 extension - AttributeExtension 类现在在公共 API 中。

  • session.Query()

    下列已弃用的函数已被移除:

    列表,标量,count_by,select_whereclause,get_by,select_by,join_by,selectfirst,selectone,select,execute,select_statement,select_text,join_to,join_via,selectfirst_by,selectone_by,apply_max,apply_min,apply_avg,apply_sum

    另外,已移除了join()outerjoin()add_entity()add_column()中的id关键字参数。要将Query中的目标表别名指向结果列,请使用aliased构造:

    from sqlalchemy.orm import aliased
    
    address_alias = aliased(Address)
    print(session.query(User, address_alias).join((address_alias, User.addresses)).all())
    
  • sqlalchemy.orm.Mapper

    • instances()

    • get_session() - 此方法可能不太显著,但其效果是将延迟加载与特定会话关联,即使父对象完全分离,当使用scoped_session()或旧的SessionContextExt等扩展时。一些依赖于此行为的应用程序可能不再按预期工作;但���好的编程实践是始终确保对象存在于会话中,如果需要从其属性访问数据库。

  • mapper(MyClass, mytable)

    映射类不再使用“c”类属性进行检测;例如MyClass.c

  • sqlalchemy.orm.collections

    _prepare_instrumentation别名已移除。

  • sqlalchemy.orm

    已移除EXT_PASS别名的EXT_CONTINUE

  • sqlalchemy.engine

    DefaultDialect.preexecute_sequences.preexecute_pk_sequences的别名已移除。

    已移除不推荐使用的engine_descriptors()函数。

  • sqlalchemy.ext.activemapper

    模块已移除。

  • sqlalchemy.ext.assignmapper

    模块已移除。

  • sqlalchemy.ext.associationproxy

    在代理的.append(item, \**kw)上的关键字参数传递已移除,现在简单地是.append(item)

  • sqlalchemy.ext.selectresultssqlalchemy.mods.selectresults

    模块已移除。

  • sqlalchemy.ext.declarative

    declared_synonym()已移除。

  • sqlalchemy.ext.sessioncontext

    模块已移除。

  • sqlalchemy.log

    SADeprecationWarning别名到sqlalchemy.exc.SADeprecationWarning的移除。

  • sqlalchemy.exc

    exc.AssertionError已移除,使用替代名称相同的 Python 内置函数。

  • sqlalchemy.databases.mysql

    已移除不推荐使用的get_version_info方言方法。

已更名或移动

  • sqlalchemy.exceptions现在是sqlalchemy.exc

    该模块仍可在 0.6 版本之前使用旧名称导入。

  • FlushErrorConcurrentModificationErrorUnmappedColumnError -> sqlalchemy.orm.exc

    这些异常已移至 orm 包。导入‘sqlalchemy.orm’将在 0.6 版本之前为兼容性安装 sqlalchemy.exc 的别名。

  • sqlalchemy.logging -> sqlalchemy.log

    此内部模块已更名。在打包 SA 与 py2app 等扫描导入工具时不再需要特殊处理。

  • session.Query().iterate_instances() -> session.Query().instances().

已弃用

  • Session.save()Session.update()Session.save_or_update()

    以上三个被Session.add()替代

  • sqlalchemy.PassiveDefault

    使用Column(server_default=...)在底层转换为 sqlalchemy.DefaultClause()。

  • session.Query().iterate_instances()。已更名为instances().

SQLAlchemy 0.4 有什么新东西?

原文:docs.sqlalchemy.org/en/20/changelog/migration_04.html

关于本文档

本文档描述了 SQLAlchemy 版本 0.3(2007 年 10 月 14 日最后发布)与 SQLAlchemy 版本 0.4(2008 年 10 月 12 日最后发布)之间的变化。

文档日期:2008 年 3 月 21 日

先说最重要的事情

如果您使用了任何 ORM 特性,请确保从 sqlalchemy.orm 中导入:

from sqlalchemy import *
from sqlalchemy.orm import *

其次,您曾经在任何地方使用 engine=connectable=bind_to=something.enginemetadata.connect(),现在使用 bind

myengine = create_engine("sqlite://")

meta = MetaData(myengine)

meta2 = MetaData()
meta2.bind = myengine

session = create_session(bind=myengine)

statement = select([table], bind=myengine)

明白了吗?好!你现在(95%)兼容了 0.4 版本。如果你在使用 0.3.10,你可以立即做出这些更改;它们也会在那里起作用。

模块导入

在 0.3 版本中,“from sqlalchemy import *”会将所有 sqlalchemy 的子模块导入到您的命名空间中。版本 0.4 不再将子模块导入到命名空间中。这可能意味着您需要在您的代码中添加额外的导入。

在 0.3 版本中,这段代码可以工作:

from sqlalchemy import *

class UTCDateTime(types.TypeDecorator):
    pass

在 0.4 版本中,必须这样做:

from sqlalchemy import *
from sqlalchemy import types

class UTCDateTime(types.TypeDecorator):
    pass

对象关系映射

查询

新的查询 API

查询已标准化为生成接口(旧接口仍然存在,只是已弃用)。虽然大多数生成接口在 0.3 版本中都可用,但 0.4 版本的查询具有与生成外部相匹配的内部实质,而且有更多的技巧。所有结果的缩小都通过filter()filter_by()进行,限制/偏移要么通过数组切片,要么通过limit()/offset()进行,连接是通过join()outerjoin()(或者更手动地,通过select_from()以及手动形成的条件)。

为了避免弃用警告,您必须对您的 03 代码进行一些更改

User.query.get_by( **kwargs )

User.query.filter_by(**kwargs).first()

User.query.select_by( **kwargs )

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()

新的基于属性的表达式构造

在 ORM 中最明显的区别是,您现在可以直接使用基于类的属性构造您的查询条件。在使用映射类时,不再需要“.c.”前缀:

session.query(User).filter(and_(User.name == "fred", User.id > 17))

虽然简单的基于列的比较不是什么大问题,但类属性有一些新的“更高级”的构造可用,包括以前只在 filter_by() 中可用的内容:

# comparison of scalar relations to an instance
filter(Address.user == user)

# return all users who contain a particular address
filter(User.addresses.contains(address))

# return all users who *dont* contain the address
filter(~User.address.contains(address))

# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))

# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))

# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))

# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

映射类上的.c属性中仍然可用 Column 集合。请注意,基于属性的表达式仅在映射类的映射属性中可用。.c仍然用于访问常规表中的列和从 SQL 表达式产生的可选择对象中的列。

自动连接别名

我们已经有了 join() 和 outerjoin() 一段时间了:

session.query(Order).join("items")

现在你可以给它们起别名了:

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

以上代码将创建两个别名从 orders->items 的连接。每个连接后的filter()调用将其表条件调整为别名的条件。要获取Item对象,请使用add_entity(),并使用id目标每个连接:

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

返回的元组形式为:(Order,Item,Item)

自引用查询

因此,query.join() 现在可以创建别名。这给了我们什么?自引用查询!可以在没有任何 Alias 对象的情况下进行连接:

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)

# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

要为沿途每个表添加条件在别名连接中,您可以使用 from_joinpoint 继续针对相同别名行进行连接:

# search for the treenode along the path "n1/n12/n122"

# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")

# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")

# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")

node = q.first()

query.populate_existing()

query.load() 的贪婪版本(或 session.refresh())。从查询加载的每个实例,包括所有贪婪加载的项目,如果已经存在于会话中,则立即刷新:

session.query(Blah).populate_existing().all()

关系

嵌入在更新/插入中的 SQL 子句

用于内联执行 SQL 子句,直接嵌入在 flush() 期间的 UPDATE 或 INSERT 中:

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

order.hash = text("select hash from hashing_table")

在操作之后,列属性设置为延迟加载器,以便在下次访问时发出加载新值的 SQL。

自引用和循环贪婪加载

由于我们的别名技术已经改进,relation() 可以沿着相同的表进行任意次数的连接;您告诉它您想要深入多深。让我们更清楚地展示自引用的 TreeNode

nodes = Table(
    "nodes",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("parent_id", Integer, ForeignKey("nodes.id")),
    Column("name", String(30)),
)

class TreeNode(object):
    pass

mapper(
    TreeNode,
    nodes,
    properties={"children": relation(TreeNode, lazy=False, join_depth=3)},
)

那么当我们说:

create_session().query(TreeNode).all()

? 沿着别名进行连接,从父级开始深入三级:

SELECT
nodes_3.id  AS  nodes_3_id,  nodes_3.parent_id  AS  nodes_3_parent_id,  nodes_3.name  AS  nodes_3_name,
nodes_2.id  AS  nodes_2_id,  nodes_2.parent_id  AS  nodes_2_parent_id,  nodes_2.name  AS  nodes_2_name,
nodes_1.id  AS  nodes_1_id,  nodes_1.parent_id  AS  nodes_1_parent_id,  nodes_1.name  AS  nodes_1_name,
nodes.id  AS  nodes_id,  nodes.parent_id  AS  nodes_parent_id,  nodes.name  AS  nodes_name
FROM  nodes  LEFT  OUTER  JOIN  nodes  AS  nodes_1  ON  nodes.id  =  nodes_1.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_2  ON  nodes_1.id  =  nodes_2.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_3  ON  nodes_2.id  =  nodes_3.parent_id
ORDER  BY  nodes.oid,  nodes_1.oid,  nodes_2.oid,  nodes_3.oid

注意这些漂亮干净的别名。连接不在乎是针对同一个立即表还是一些其他对象,然后再循环回开始。当指定了 join_depth 时,任何类型的贪婪加载链都可以在自身上循环。当不存在时,贪婪加载在遇到循环时会自动停止。

复合类型

这是来自 Hibernate 阵营的一个例子。复合类型允许您定义一个由多个列(或一个列,如果您愿意的话)组成的自定义数据类型。让我们定义一个新类型,Point。存储一个 x/y 坐标:

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __eq__(self, other):
        return other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

Point 对象的定义方式是特定于自定义类型的;构造函数接受一个参数列表,并且 __composite_values__() 方法生成这些参数的序列。顺序将与我们的映射器匹配,我们马上就会看到。

让我们创建一个存储每行两个点的顶点表:

vertices = Table(
    "vertices",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x1", Integer),
    Column("y1", Integer),
    Column("x2", Integer),
    Column("y2", Integer),
)

然后,映射它!我们将创建一个存储两个 Point 对象的 Vertex 对象:

class Vertex(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end

mapper(
    Vertex,
    vertices,
    properties={
        "start": composite(Point, vertices.c.x1, vertices.c.y1),
        "end": composite(Point, vertices.c.x2, vertices.c.y2),
    },
)

一旦设置了您的复合类型,它就可以像任何其他类型一样使用:

v = Vertex(Point(3, 4), Point(26, 15))
session.save(v)
session.flush()

# works in queries too
q = session.query(Vertex).filter(Vertex.start == Point(3, 4))

如果您想定义映射属性在表达式中生成 SQL 子句的方式,请创建自己的 sqlalchemy.orm.PropComparator 子类,定义任何常见操作符(如 __eq__()__le__() 等),并将其发送到 composite()。复合类型也可以作为主键,并且可以在 query.get() 中使用:

# a Document class which uses a composite Version
# object as primary key
document = query.get(Version(1, "a"))

dynamic_loader() 关系

一个返回所有读操作的实时 Query 对象的 relation()。写操作仅限于 append()remove(),对集合的更改在会话刷新之前不可见。此功能在“自动刷新”会话中特别方便,该会话会在每次查询之前刷新。

mapper(
    Foo,
    foo_table,
    properties={
        "bars": dynamic_loader(
            Bar,
            backref="foo",
            # <other relation() opts>
        )
    },
)

session = create_session(autoflush=True)
foo = session.query(Foo).first()

foo.bars.append(Bar(name="lala"))

for bar in foo.bars.filter(Bar.name == "lala"):
    print(bar)

session.commit()

新选项:undefer_group()eagerload_all()

一些方便的查询选项。undefer_group()将整个“延迟”列组标记为未延迟:

mapper(
    Class,
    table,
    properties={
        "foo": deferred(table.c.foo, group="group1"),
        "bar": deferred(table.c.bar, group="group1"),
        "bat": deferred(table.c.bat, group="group1"),
    },
)

session.query(Class).options(undefer_group("group1")).filter(...).all()

eagerload_all()设置一系列属性为一次性急切加载:

mapper(Foo, foo_table, properties={"bar": relation(Bar)})
mapper(Bar, bar_table, properties={"bat": relation(Bat)})
mapper(Bat, bat_table)

# eager load bar and bat
session.query(Foo).options(eagerload_all("bar.bat")).filter(...).all()

新的集合 API

集合不再由{{{InstrumentedList}}}代理代理,对成员、方法和属性的访问是直接的。装饰器现在拦截进入和离开集合的对象,并且现在可以轻松编写一个自定义集合类来管理自己的成员。灵活的装饰器还取代了 0.3 中自定义集合的命名方法接口,允许任何类轻松适应用作集合容器。

基于字典的集合现在更容易使用,完全类似于dict。不再需要更改__iter__以用于dict,新的内置dict类型涵盖了许多需求:

# use a dictionary relation keyed by a column
relation(Item, collection_class=column_mapped_collection(items.c.keyword))
# or named attribute
relation(Item, collection_class=attribute_mapped_collection("keyword"))
# or any function you like
relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))

现有的 0.3 类似dict和自由形式对象派生的集合类需要更新以适应新的 API。在大多数情况下,这只是在类定义中添加一些装饰器的问题。

从外部表/子查询映射的关系

这个功能在 0.3 中悄悄出现,但在 0.4 中得到改进,这要归功于更好地能够将针对表的子查询转换为该表的别名的子查询;这对于急切加载、查询中的别名连接等非常重要。当您只需要添加一些额外列或子查询时,它减少了对选择语句创建映射器的需求:

mapper(
    User,
    users,
    properties={
        "fullname": column_property(
            (users.c.firstname + users.c.lastname).label("fullname")
        ),
        "numposts": column_property(
            select([func.count(1)], users.c.id == posts.c.user_id)
            .correlate(users)
            .label("posts")
        ),
    },
)

典型的查询如下:

SELECT  (SELECT  count(1)  FROM  posts  WHERE  users.id  =  posts.user_id)  AS  count,
users.firstname  ||  users.lastname  AS  fullname,
users.id  AS  users_id,  users.firstname  AS  users_firstname,  users.lastname  AS  users_lastname
FROM  users  ORDER  BY  users.oid

水平扩展(分片)API

[browser:/sqlalchemy/trunk/examples/sharding/attribute_shard .py]

会话

新的会话创建范式;SessionContext,assignmapper 已弃用

是的,整个事情都被两个配置函数替换了。同时使用两者将产生自 0.1 以来最接近的感觉(即,输入最少)。

在定义engine(或任何地方)的地方配置自己的Session类:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("myengine://")
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

如果您需要后配置您的会话,比如使用引擎,稍后使用configure()添加:

Session.configure(bind=create_engine(...))

所有SessionContext的行为以及assignmapperquery__init__方法都移动到新的scoped_session()函数中,该函数与sessionmakercreate_session()兼容:

from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)

u = User(name="wendy")

sess = Session()
sess.save(u)
sess.commit()

# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

使用线程本地Session时,返回的类已实现了所有Session的接口作为类方法,并且使用mapper类方法可以使用“assignmapper”的功能。就像旧的objectstore时代一样...。

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)

u = User(name="wendy")

Session.commit()

会话再次默认使用弱引用

默认情况下,在 Session 上将 weak_identity_map 标志设置为 True。外部解除引用并超出范围的实例将自动从会话中移除。但是,具有“脏”更改的项目将保持强引用,直到这些更改被刷新,此时对象将恢复为弱引用(这适用于像可选属性这样的‘可变’类型)。将 weak_identity_map 设置为 False 将为那些像缓存一样使用会话的人恢复旧的强引用行为。

自动事务会话

正如您可能已经注意到的,我们在 Session 上调用了 commit()。标志 transactional=True 意味着 Session 总是处于事务中,commit() 永久保存。

自动刷新会话

此外,autoflush=True 意味着 Session 在每次 query 之前都会执行 flush(),以及在调用 flush()commit() 时。所以现在这将起作用:

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

u = User(name="wendy")

sess = Session()
sess.save(u)

# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name="wendy").one()

事务方法转移到了会话中

commit()rollback(),以及 begin() 现在直接在 Session 上。不再需要为任何事情使用 SessionTransaction(它仍然在后台运行)。

Session = sessionmaker(autoflush=True, transactional=False)

sess = Session()
sess.begin()

# use the session

sess.commit()  # commit transaction

与封闭引擎级(即非 ORM)事务共享 Session 很容易:

Session = sessionmaker(autoflush=True, transactional=False)

conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)

# ... session is transactional

# commit the outermost transaction
trans.commit()

带有 SAVEPOINT 的嵌套会话事务

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

两阶段提交会话

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

继承

无连接或联合的多态继承

继承的新文档:www.sqlalchemy.org/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined

使用 get() 时更好的多态行为

加入表继承层次结构中的所有类都使用基类获得 _instance_key,即 (BaseClass, (1, ), None)。这样,当您针对基类调用 get() 时,它可以在当前标识映射中定位子类实例,而无需查询数据库。

类型

sqlalchemy.types.TypeDecorator 的自定义子类

有一个新 API用于子类化 TypeDecorator。在某些情况下,使用 0.3 API 会导致编译错误。

SQL 表达式

全新的、确定性的标签/别名生成

所有“匿名”标签和别名现在都使用简单的 _ 格式。SQL 更容易阅读,并且与计划优化器缓存兼容。只需查看一些教程中的示例:www.sqlalchemy.org/docs/04/ormtutorial.html www.sqlalchemy.org/docs/04/sqlexpression.html

生成式 select() 构造

这绝对是使用select()的方法。请参阅 htt p://www.sqlalchemy.org/docs/04/sqlexpression.html#sql_transf orm 。

新的运算符系统

SQL 运算符和几乎每个 SQL 关键字现在都被抽象为编译器层。它们现在表现智能,并且具有类型/后端感知性,参见:www.sqlalchemy.org/docs/04/sqlexpression.html#sql_operators

所有type关键字参数重命名为type_

就像它说的那样:

b = bindparam("foo", type_=String)

in_函数更改为接受序列或可选择的

in_函数现在接受一个值序列或可选择的可选参数。仍然可以使用以前将值作为位置参数传递的 API,但现在已被弃用。这意味着

my_table.select(my_table.c.id.in_(1, 2, 3))
my_table.select(my_table.c.id.in_(*listOfIds))

应更改为

my_table.select(my_table.c.id.in_([1, 2, 3]))
my_table.select(my_table.c.id.in_(listOfIds))

模式和反射

MetaDataBoundMetaDataDynamicMetaData

在 0.3.x 系列中,BoundMetaDataDynamicMetaData已被弃用,取而代之的是MetaDataThreadLocalMetaData。旧名称已在 0.4 版本中移除。更新很简单:

+-------------------------------------+-------------------------+
|If You Had                           | Now Use                 |
+=====================================+=========================+
| ``MetaData``                        | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``BoundMetaData``                   | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``DynamicMetaData`` (with one       | ``MetaData``            |
| engine or threadlocal=False)        |                         |
+-------------------------------------+-------------------------+
| ``DynamicMetaData``                 | ``ThreadLocalMetaData`` |
| (with different engines per thread) |                         |
+-------------------------------------+-------------------------+

很少使用的name参数已从MetaData类型中删除。ThreadLocalMetaData构造函数现在不再接受参数。这两种类型现在都可以绑定到一个Engine或一个单独的Connection

一步多表反射

现在您可以加载表定义,并在一个步骤中自动创建整个数据库或模式的Table对象:

>>> metadata = MetaData(myengine, reflect=True)
>>> metadata.tables.keys()
['table_a', 'table_b', 'table_c', '...']

MetaData还增加了一个.reflect()方法,可以更精细地控制加载过程,包括指定要加载的可用表的子集。

SQL 执行

engineconnectablebind_to现在都是bind

TransactionsNestedTransactionsTwoPhaseTransactions

连接池事件

连接池现在在创建新的 DB-API 连接、检出和检入池时触发事件。您可以使用这些事件在新连接上执行会话范围的 SQL 设置语句,例如。

修复了 Oracle Engine

在 0.3.11 版本中,Oracle Engine 在处理主键时存在错误。这些错误可能导致在使用 Oracle Engine 时,其他引擎(如 sqlite)正常工作的程序失败。在 0.4 版本中,Oracle Engine 已经重新设计,修复了这些主键问题。

Oracle 的输出参数

result = engine.execute(
    text(
        "begin foo(:x, :y, :z); end;",
        bindparams=[
            bindparam("x", Numeric),
            outparam("y", Numeric),
            outparam("z", Numeric),
        ],
    ),
    x=5,
)
assert result.out_parameters == {"y": 10, "z": 75}

连接绑定的MetaDataSessions

MetaDataSession可以显式绑定到连接:

conn = engine.connect()
sess = create_session(bind=conn)

更快、更可靠的ResultProxy对象

首要事项

如果您正在使用任何 ORM 功能,请确保从sqlalchemy.orm导入:

from sqlalchemy import *
from sqlalchemy.orm import *

其次,无论您以前使用engine=connectable=bind_to=something.enginemetadata.connect(),现在都使用bind

myengine = create_engine("sqlite://")

meta = MetaData(myengine)

meta2 = MetaData()
meta2.bind = myengine

session = create_session(bind=myengine)

statement = select([table], bind=myengine)

明白了吗?很好!您现在(95%)兼容 0.4 版本。如果您正在使用 0.3.10 版本,您可以立即进行这些更改;它们也适用于那里。

模块导入

在 0.3 版本中,“from sqlalchemy import *” 将所有 sqlalchemy 的子模块导入到您的命名空间中。0.4 版本不再将子模块导入到命名空间中。这可能意味着您需要在代码中添加额外的导入。

在 0.3 版本中,这段代码有效:

from sqlalchemy import *

class UTCDateTime(types.TypeDecorator):
    pass

在 0.4 版本中,必须执行:

from sqlalchemy import *
from sqlalchemy import types

class UTCDateTime(types.TypeDecorator):
    pass

对象关系映射

查询

新的查询 API

查询标准化为生成式接口(旧接口仍然存在,只是已弃用)。虽然大部分生成式接口在 0.3 版本中可用,但 0.4 版本的 Query 具有与生成式外部匹配的内部实现,并且有更多技巧。所有结果缩小都通过 filter()filter_by() 进行,限制/偏移要么通过数组切片要么通过 limit()/offset() 进行,连接通过 join()outerjoin() 进行(或者更手动地,通过 select_from() 以及手动形成的条件)。

为了避免弃用警告,您必须对您的 03 代码进行一些更改

User.query.get_by( **kwargs )

User.query.filter_by(**kwargs).first()

User.query.select_by( **kwargs )

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()

新的基于属性的表达式构造

在 ORM 中最明显的区别是,现在您可以直接使用基于类的属性构建查询条件。在处理映射类时不再需要“.c.”前缀:

session.query(User).filter(and_(User.name == "fred", User.id > 17))

虽然简单的基于列的比较不是什么大问题,但类属性具有一些新的“更高级”构造可用,包括以前仅在 filter_by() 中可用的内容:

# comparison of scalar relations to an instance
filter(Address.user == user)

# return all users who contain a particular address
filter(User.addresses.contains(address))

# return all users who *dont* contain the address
filter(~User.address.contains(address))

# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))

# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))

# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))

# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

Column 集合仍然可以在 .c 属性中的映射类中使用。请注意,基于属性的表达式仅适用于映射类的映射属性。.c 仍然用于访问常规表中的列以及从 SQL 表达式生成的可选择对象。

自动连接别名

我们已经有了 join() 和 outerjoin() 一段时间了:

session.query(Order).join("items")

现在您可以为它们创建别名:

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

以上将使用别名从 orders->items 创建两个连接。每个连接后续的 filter() 调用将调整其表条件以符合别名。要访问 Item 对象,请使用 add_entity() 并将每个连接的 id 作为目标:

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

返回元组形式:(Order, Item, Item)

自引用查询

因此 query.join() 现在可以创建别名。这给我们带来了什么?自引用查询!可以在没有任何 Alias 对象的情况下进行连接:

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)

# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

要为每个表添加条件,可以使用 from_joinpoint 在别名连接中保持针对相同别名行的连接:

# search for the treenode along the path "n1/n12/n122"

# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")

# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")

# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")

node = q.first()

query.populate_existing()

query.load() 的贪婪版本(或 session.refresh())。从查询加载的每个实例,包括所有贪婪加载的项目,如果已经存在于会话中,则立即刷新:

session.query(Blah).populate_existing().all()

关系

嵌入在更新/插入中的 SQL 子句

对于内联执行 SQL 子句,嵌入在 flush() 中的 UPDATE 或 INSERT 中:

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

order.hash = text("select hash from hashing_table")

在操作后,列属性设置为延迟加载器,因此当您下次访问时,它会发出 SQL 来加载新值。

自引用和循环贪婪加载

由于我们的别名技术已经改进,relation()可以沿着相同的表任意次数连接;你告诉它你想要多深。让我们更清楚地展示自引用的TreeNode

nodes = Table(
    "nodes",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("parent_id", Integer, ForeignKey("nodes.id")),
    Column("name", String(30)),
)

class TreeNode(object):
    pass

mapper(
    TreeNode,
    nodes,
    properties={"children": relation(TreeNode, lazy=False, join_depth=3)},
)

那么当我们说:

create_session().query(TreeNode).all()

? 通过别名连接,从父级开始深入三级:

SELECT
nodes_3.id  AS  nodes_3_id,  nodes_3.parent_id  AS  nodes_3_parent_id,  nodes_3.name  AS  nodes_3_name,
nodes_2.id  AS  nodes_2_id,  nodes_2.parent_id  AS  nodes_2_parent_id,  nodes_2.name  AS  nodes_2_name,
nodes_1.id  AS  nodes_1_id,  nodes_1.parent_id  AS  nodes_1_parent_id,  nodes_1.name  AS  nodes_1_name,
nodes.id  AS  nodes_id,  nodes.parent_id  AS  nodes_parent_id,  nodes.name  AS  nodes_name
FROM  nodes  LEFT  OUTER  JOIN  nodes  AS  nodes_1  ON  nodes.id  =  nodes_1.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_2  ON  nodes_1.id  =  nodes_2.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_3  ON  nodes_2.id  =  nodes_3.parent_id
ORDER  BY  nodes.oid,  nodes_1.oid,  nodes_2.oid,  nodes_3.oid

注意漂亮干净的别名名称。连接不在乎是否针对同一个直接表或一些其他对象,然后循环回开始。当指定join_depth时,任何类型的链式急切加载可以循环回自身。当不存在时,急切加载在遇到循环时会自动停止。

复合类型

这是来自 Hibernate 阵营的一个。复合类型让你定义一个由多个列(或一个列,如果你愿意)组成的自定义数据类型。让我们定义一个新类型,Point。存储一个 x/y 坐标:

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __eq__(self, other):
        return other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

Point对象的定义方式是特定于自定义类型的;构造函数接受一个参数列表,__composite_values__()方法生成这些参数的序列。顺序将与我们的映射器匹配,我们马上就会看到。

让我们创建一个存储每行两个点的顶点表:

vertices = Table(
    "vertices",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x1", Integer),
    Column("y1", Integer),
    Column("x2", Integer),
    Column("y2", Integer),
)

然后,映射它!我们将创建一个存储两个Point对象的Vertex对象:

class Vertex(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end

mapper(
    Vertex,
    vertices,
    properties={
        "start": composite(Point, vertices.c.x1, vertices.c.y1),
        "end": composite(Point, vertices.c.x2, vertices.c.y2),
    },
)

一旦设置了复合类型,它就可以像任何其他类型一样使用:

v = Vertex(Point(3, 4), Point(26, 15))
session.save(v)
session.flush()

# works in queries too
q = session.query(Vertex).filter(Vertex.start == Point(3, 4))

如果你想定义映射属性在表达式中生成 SQL 子句的方式,创建自己的sqlalchemy.orm.PropComparator子类,定义任何常见操作符(如__eq__()__le__()等),并将其发送到composite()。复合类型也可以作为主键,并且可用于query.get()

# a Document class which uses a composite Version
# object as primary key
document = query.get(Version(1, "a"))

dynamic_loader()关系

一个返回所有读操作的实时Query对象的relation()。写操作仅限于append()remove(),对集合的更改在会话刷新之前不可见。这个特性在“自动刷新”会话中特别方便,它会在每次查询之前刷新。

mapper(
    Foo,
    foo_table,
    properties={
        "bars": dynamic_loader(
            Bar,
            backref="foo",
            # <other relation() opts>
        )
    },
)

session = create_session(autoflush=True)
foo = session.query(Foo).first()

foo.bars.append(Bar(name="lala"))

for bar in foo.bars.filter(Bar.name == "lala"):
    print(bar)

session.commit()

新选项:undefer_group()eagerload_all()

一些方便的查询选项。undefer_group()将整个“延迟”列组标记为未延迟:

mapper(
    Class,
    table,
    properties={
        "foo": deferred(table.c.foo, group="group1"),
        "bar": deferred(table.c.bar, group="group1"),
        "bat": deferred(table.c.bat, group="group1"),
    },
)

session.query(Class).options(undefer_group("group1")).filter(...).all()

eagerload_all()设置一系列属性在一次遍历中急切加载:

mapper(Foo, foo_table, properties={"bar": relation(Bar)})
mapper(Bar, bar_table, properties={"bat": relation(Bat)})
mapper(Bat, bat_table)

# eager load bar and bat
session.query(Foo).options(eagerload_all("bar.bat")).filter(...).all()

新的集合 API

集合不再由{{{InstrumentedList}}}代理进行代理,对成员、方法和属性的访问是直接的。装饰器现在拦截进入和离开集合的对象,现在可以轻松编写一个自定义集合类来管理自己的成员。灵活的装饰器还取代了 0.3 版本中自定义集合的命名方法接口,允许任何类轻松适应用作集合容器。

基于字典的集合现在更容易使用,完全类似于dict。不再需要更改dict__iter__,新的内置dict类型涵盖了许多需求:

# use a dictionary relation keyed by a column
relation(Item, collection_class=column_mapped_collection(items.c.keyword))
# or named attribute
relation(Item, collection_class=attribute_mapped_collection("keyword"))
# or any function you like
relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))

现有的 0.3 版本的类似 dict 和自由形式对象派生的集合类需要更新到新的 API。在大多数情况下,这只是在类定义中添加几个装饰器的问题。

从外部表/子查询映射关系

这个功能在 0.3 版本中悄然出现,但在 0.4 版本中得到改进,这要归功于更好地将针对表的子查询转换为针对该表的别名的能力;这对于急加载、查询中的别名连接等非常重要。这减少了在只需要添加一些额外列或子查询时对选择语句创建映射器的需求:

mapper(
    User,
    users,
    properties={
        "fullname": column_property(
            (users.c.firstname + users.c.lastname).label("fullname")
        ),
        "numposts": column_property(
            select([func.count(1)], users.c.id == posts.c.user_id)
            .correlate(users)
            .label("posts")
        ),
    },
)

典型的查询如下:

SELECT  (SELECT  count(1)  FROM  posts  WHERE  users.id  =  posts.user_id)  AS  count,
users.firstname  ||  users.lastname  AS  fullname,
users.id  AS  users_id,  users.firstname  AS  users_firstname,  users.lastname  AS  users_lastname
FROM  users  ORDER  BY  users.oid

水平扩展(分片)API

[browser:/sqlalchemy/trunk/examples/sharding/attribute_shard .py]

会话

新的会话创建范式;SessionContext,assignmapper 已弃用

是的,整个事情都被两个配置函数替换了。同时使用两者将产生自 0.1 版本以来最接近 0.1 版本的感觉(即,输入最少)。

在定义引擎(或任何地方)的地方配置您自己的 Session 类��

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("myengine://")
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

如果您需要在之后使用 configure() 来后置配置您的会话,比如添加引擎:

Session.configure(bind=create_engine(...))

SessionContext 的所有行为以及 assignmapperquery__init__ 方法都移动到了新的 scoped_session() 函数中,该函数与 sessionmakercreate_session() 兼容:

from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)

u = User(name="wendy")

sess = Session()
sess.save(u)
sess.commit()

# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

当使用线程本地的 Session 时,返回的类已经实现了所有 Session 的接口作为类方法,并且可以使用 mapper 类方法来使用 “assignmapper” 的功能。就像旧的 objectstore 时代一样……。

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)

u = User(name="wendy")

Session.commit()

会话再次默认为弱引用

weak_identity_map 标志现在默认设置为 True 在 Session 上。外部解除引用并且超出范围的实例会自动从会话中移除。但是,具有“脏”更改的项目将保持强引用,直到这些更改被刷新,此时对象将恢复为弱引用(这适用于像可选属性这样的“可变”类型)。将 weak_identity_map 设置为 False 将为那些像缓存一样使用会话的人恢复旧的强引用行为。

自动事务会话

正如您在上面所注意到的,我们在 Session 上调用 commit()。标志 transactional=True 意味着 Session 总是处于事务中,commit() 永久持久化。

自动刷新会话

此外,autoflush=True 意味着 Session 将在每次 query 之前刷新,以及在调用 flush()commit() 时。所以现在这将起作用:

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

u = User(name="wendy")

sess = Session()
sess.save(u)

# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name="wendy").one()

事务方法移动到会话上

commit()rollback(),以及 begin() 现在直接在 Session 上。不再需要为任何事情使用 SessionTransaction(它仍然在后台运行)。

Session = sessionmaker(autoflush=True, transactional=False)

sess = Session()
sess.begin()

# use the session

sess.commit()  # commit transaction

与封闭的引擎级(即非 ORM)事务共享 Session 很容易:

Session = sessionmaker(autoflush=True, transactional=False)

conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)

# ... session is transactional

# commit the outermost transaction
trans.commit()

使用 SAVEPOINT 的嵌套会话事务

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

两阶段提交会话

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

继承

无联接或联合的多态继承

继承的新文档:www.sqlalchemy.org/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined

使用get()实现更好的多态行为

在联接表继承层次结构中,所有类都使用基类获得_instance_key,即(BaseClass, (1, ), None)。这样,当您针对基类调用get()时,它可以在当前标识映射中定位子类实例,而无需查询数据库。

类型

自定义sqlalchemy.types.TypeDecorator的子类

有一个新的 API用于子类化 TypeDecorator。在某些情况下,使用 0.3 API 会导致编译错误。

查询

新的查询 API

查询标准化为生成式接口(旧接口仍然存在,只是已弃用)。虽然大部分生成式接口在 0.3 中可用,但 0.4 查询具有与生成式外部匹配的内部实现,并且有更多技巧。所有结果缩小都通过filter()filter_by()进行,限制/偏移要么通过数组切片要么通过limit()/offset()进行,连接通过join()outerjoin()进行(或更手动地,通过select_from()以及手动形成的条件)。

为避免弃用警告,您必须对您的 03 代码进行一些更改

User.query.get_by( **kwargs )

User.query.filter_by(**kwargs).first()

User.query.select_by( **kwargs )

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()

新的基于属性的表达式构造

在 ORM 中最明显的区别是,现在你可以直接使用基于类的属性构建查询条件。在使用映射类时不再需要“.c.”前缀:

session.query(User).filter(and_(User.name == "fred", User.id > 17))

虽然简单的基于列的比较不是什么大问题,但类属性有一些新的“更高级”的构造可用,包括以前仅在filter_by()中可用的内容:

# comparison of scalar relations to an instance
filter(Address.user == user)

# return all users who contain a particular address
filter(User.addresses.contains(address))

# return all users who *dont* contain the address
filter(~User.address.contains(address))

# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))

# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))

# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))

# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

Column集合仍然可以在映射类的.c属性中使用。请注意,基于属性的表达式仅适用于映射类的映射属性。在正常表和从 SQL 表达式生成的可选择对象中,仍然使用.c来访问列。

自动连接别名

我们现在已经有了 join()和 outerjoin():

session.query(Order).join("items")

现在你可以给它们起别名:

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

上述将从 orders->items 创建两个连接,使用别名。每个连接后续的filter()调用将调整其表条件为别名的条件。要获取Item对象,请使用add_entity()并将每个连接的id作为目标:

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

返回形式为的元组:(Order,Item,Item)

自引用查询

因此,query.join()现在可以创建别名。那给了我们什么?自引用查询!连接可以在没有任何Alias对象的情况下完成:

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)

# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

要为沿途的每个表添加条件以进行别名连接,您可以使用from_joinpoint来继续针对相同行别名进行连接:

# search for the treenode along the path "n1/n12/n122"

# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")

# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")

# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")

node = q.first()

query.populate_existing()

query.load()(或session.refresh())的急切版本。如果查询中加载的每个实例,包括所有急切加载的项,已经存在于会话中,则立即刷新它们:

session.query(Blah).populate_existing().all()

新的查询 API

查询标准化为生成接口(旧接口仍在,只是已弃用)。虽然大多数生成接口在 0.3 中可用,但 0.4 版本的查询具有匹配生成外部的内部要领,并且有更多技巧。所有结果缩小都通过filter()filter_by()进行,限制/偏移要么通过数组切片,要么通过limit()/offset()进行,连接是通过join()outerjoin()进行的(或者更手动地,通过select_from()以及手动形成的条件)。

为了避免弃用警告,您必须对您的 03 代码进行一些更改

User.query.get_by(**kwargs)

User.query.filter_by(**kwargs).first()

User.query.select_by(**kwargs)

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()

新的基于属性的表达式构造

在 ORM 中最明显的区别是,现在您可以直接使用基于类的属性构造查询条件。当使用映射类时,不再需要“ .c.”前缀:

session.query(User).filter(and_(User.name == "fred", User.id > 17))

虽然简单的基于列的比较不是什么大不了的事,但类属性具有一些新的“更高级别”的构造可用,包括以前仅在filter_by()中可用的内容:

# comparison of scalar relations to an instance
filter(Address.user == user)

# return all users who contain a particular address
filter(User.addresses.contains(address))

# return all users who *dont* contain the address
filter(~User.address.contains(address))

# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))

# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))

# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))

# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

.c属性上的Column集合仍然可用于映射类中。请注意,基于属性的表达式仅适用于映射类的映射属性。.c仍然用于访问常规表中的列以及从 SQL 表达式生成的可选择对象。

自动连接别名

我们已经有了一段时间的 join()和 outerjoin():

session.query(Order).join("items")

现在你可以给它们取别名:

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

以上将使用别名从订单->项目创建两个连接。每个之后的filter()调用将其表条件调整为别名的条件。要访问Item对象,请使用add_entity()并将每个连接目标化为id

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

返回形式为的元组:(Order,Item,Item)

自引用查询

因此,query.join()现在可以创建别名。那给了我们什么?自引用查询!连接可以在没有任何Alias对象的情况下完成:

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)

# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

要为沿途的每个表添加条件以进行别名连接,您可以使用from_joinpoint来继续针对相同行别名进行连接:

# search for the treenode along the path "n1/n12/n122"

# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")

# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")

# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")

node = q.first()

query.populate_existing()

query.load()(或session.refresh())的急切版本。如果查询中加载的每个实例,包括所有急切加载的项,已经存在于会话中,则立即刷新它们:

session.query(Blah).populate_existing().all()

关系

嵌入在更新/插入中的 SQL 子句

对于内联执行 SQL 子句,嵌入在flush()期间的 UPDATE 或 INSERT 中:

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

order.hash = text("select hash from hashing_table")

操作后使用延迟加载器设置列属性,以便在下次访问时发出加载新值的 SQL。

自引用和循环急加载

由于我们的别名技术已经提高,relation()可以在同一张表上任意次进行连接;您告诉它您想要深入多少层。让我们更清楚地展示自引用的TreeNode

nodes = Table(
    "nodes",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("parent_id", Integer, ForeignKey("nodes.id")),
    Column("name", String(30)),
)

class TreeNode(object):
    pass

mapper(
    TreeNode,
    nodes,
    properties={"children": relation(TreeNode, lazy=False, join_depth=3)},
)

当我们说:

create_session().query(TreeNode).all()

? 通过别名进行连接,从父级深入三层:

SELECT
nodes_3.id  AS  nodes_3_id,  nodes_3.parent_id  AS  nodes_3_parent_id,  nodes_3.name  AS  nodes_3_name,
nodes_2.id  AS  nodes_2_id,  nodes_2.parent_id  AS  nodes_2_parent_id,  nodes_2.name  AS  nodes_2_name,
nodes_1.id  AS  nodes_1_id,  nodes_1.parent_id  AS  nodes_1_parent_id,  nodes_1.name  AS  nodes_1_name,
nodes.id  AS  nodes_id,  nodes.parent_id  AS  nodes_parent_id,  nodes.name  AS  nodes_name
FROM  nodes  LEFT  OUTER  JOIN  nodes  AS  nodes_1  ON  nodes.id  =  nodes_1.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_2  ON  nodes_1.id  =  nodes_2.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_3  ON  nodes_2.id  =  nodes_3.parent_id
ORDER  BY  nodes.oid,  nodes_1.oid,  nodes_2.oid,  nodes_3.oid

注意漂亮干净的别名名称。连接不在乎是否针对同一立即表或一些其他对象,然后循环回开始。当指定join_depth时,任何类型的急加载链都可以循环回自身。当不存在时,急加载在遇到循环时会自动停止。

复合类型

这是来自 Hibernate 阵营的一个例子。复合类型允许您定义一个由多个列(或一个列,如果您愿意)组成的自定义数据类型。让我们定义一个新类型,Point。存储一个 x/y 坐标:

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __eq__(self, other):
        return other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

Point对象的定义方式是特定于自定义类型的;构造函数接受参数列表,并且__composite_values__()方法生成这些参数的序列。顺序将与我们的映射器匹配,我们马上就会看到。

让我们创建一个存储每行两个点的顶点表:

vertices = Table(
    "vertices",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x1", Integer),
    Column("y1", Integer),
    Column("x2", Integer),
    Column("y2", Integer),
)

然后,映射它!我们将创建一个存储两个Point对象的Vertex对象:

class Vertex(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end

mapper(
    Vertex,
    vertices,
    properties={
        "start": composite(Point, vertices.c.x1, vertices.c.y1),
        "end": composite(Point, vertices.c.x2, vertices.c.y2),
    },
)

一旦设置了您的复合类型,它就可以像任何其他类型一样使用:

v = Vertex(Point(3, 4), Point(26, 15))
session.save(v)
session.flush()

# works in queries too
q = session.query(Vertex).filter(Vertex.start == Point(3, 4))

如果您想定义映射属性在表达式中生成 SQL 子句的方式,请创建自己的sqlalchemy.orm.PropComparator子类,定义任何常见操作符(如__eq__()__le__()等),并将其发送到composite()。复合类型也可以作为主键,并且可在query.get()中使用:

# a Document class which uses a composite Version
# object as primary key
document = query.get(Version(1, "a"))

dynamic_loader()关系

一个返回所有读操作的实时Query对象的relation()。写操作仅限于append()remove(),对集合的更改在会话刷新之前不可见。此功能在“自动刷新”会话中特别方便,该会话会在每次查询之前刷新。

mapper(
    Foo,
    foo_table,
    properties={
        "bars": dynamic_loader(
            Bar,
            backref="foo",
            # <other relation() opts>
        )
    },
)

session = create_session(autoflush=True)
foo = session.query(Foo).first()

foo.bars.append(Bar(name="lala"))

for bar in foo.bars.filter(Bar.name == "lala"):
    print(bar)

session.commit()

新选项:undefer_group()eagerload_all()

一些方便的查询选项。undefer_group()将整个“延迟”列组标记为未延迟:

mapper(
    Class,
    table,
    properties={
        "foo": deferred(table.c.foo, group="group1"),
        "bar": deferred(table.c.bar, group="group1"),
        "bat": deferred(table.c.bat, group="group1"),
    },
)

session.query(Class).options(undefer_group("group1")).filter(...).all()

eagerload_all()设置一系列属性在一次遍历中急加载:

mapper(Foo, foo_table, properties={"bar": relation(Bar)})
mapper(Bar, bar_table, properties={"bat": relation(Bat)})
mapper(Bat, bat_table)

# eager load bar and bat
session.query(Foo).options(eagerload_all("bar.bat")).filter(...).all()

新的集合 API

集合不再由 {{{InstrumentedList}}} 代理进行代理,并且对成员、方法和属性的访问是直接的。装饰器现在拦截进入和离开集合的对象,并且现在可以轻松地编写一个自定义集合类来管理自己的成员。灵活的装饰器还取代了 0.3 版本中自定义集合的命名方法接口,允许任何类轻松地适应用作集合容器。

基于字典的集合现在更易于使用,并且完全类似于dict。对dict来说,不再需要更改__iter__,新的内置dict类型涵盖了许多需求:

# use a dictionary relation keyed by a column
relation(Item, collection_class=column_mapped_collection(items.c.keyword))
# or named attribute
relation(Item, collection_class=attribute_mapped_collection("keyword"))
# or any function you like
relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))

现有的 0.3 版本类似dict和自由对象衍生的集合类将需要更新到新的 API。在大多数情况下,这只是在类定义中添加几个装饰器的问题。

来自外部表/子查询的映射关系

该功能在 0.3 版本中悄悄出现,但由于更好地能够将针对表的子查询转换为针对该表的别名的子查询而得到改进,在 0.4 版本中得到改进;这对于贪婪加载、查询中的别名连接等非常重要。它减少了在只需要添加一些额外列或子查询时创建针对选择语句的映射器的需求:

mapper(
    User,
    users,
    properties={
        "fullname": column_property(
            (users.c.firstname + users.c.lastname).label("fullname")
        ),
        "numposts": column_property(
            select([func.count(1)], users.c.id == posts.c.user_id)
            .correlate(users)
            .label("posts")
        ),
    },
)

典型的查询看起来像:

SELECT  (SELECT  count(1)  FROM  posts  WHERE  users.id  =  posts.user_id)  AS  count,
users.firstname  ||  users.lastname  AS  fullname,
users.id  AS  users_id,  users.firstname  AS  users_firstname,  users.lastname  AS  users_lastname
FROM  users  ORDER  BY  users.oid

更新/插入中嵌入的 SQL 子句

对于嵌入在flush()期间的 UPDATE 或 INSERT 中的 SQL 子句的内联执行:

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

order.hash = text("select hash from hashing_table")

在操作后设置了延迟加载器的列属性,以便在下次访问时发出加载新值的 SQL。

自引用和循环贪婪加载

由于我们的别名技术已经提高,relation()可以沿着同一张表任意次数进行连接;您告诉它您想要多深。让我们更清楚地展示自引用的TreeNode

nodes = Table(
    "nodes",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("parent_id", Integer, ForeignKey("nodes.id")),
    Column("name", String(30)),
)

class TreeNode(object):
    pass

mapper(
    TreeNode,
    nodes,
    properties={"children": relation(TreeNode, lazy=False, join_depth=3)},
)

那么,当我们说:

create_session().query(TreeNode).all()

? 在父级的别名上三级深度的连接:

SELECT
nodes_3.id  AS  nodes_3_id,  nodes_3.parent_id  AS  nodes_3_parent_id,  nodes_3.name  AS  nodes_3_name,
nodes_2.id  AS  nodes_2_id,  nodes_2.parent_id  AS  nodes_2_parent_id,  nodes_2.name  AS  nodes_2_name,
nodes_1.id  AS  nodes_1_id,  nodes_1.parent_id  AS  nodes_1_parent_id,  nodes_1.name  AS  nodes_1_name,
nodes.id  AS  nodes_id,  nodes.parent_id  AS  nodes_parent_id,  nodes.name  AS  nodes_name
FROM  nodes  LEFT  OUTER  JOIN  nodes  AS  nodes_1  ON  nodes.id  =  nodes_1.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_2  ON  nodes_1.id  =  nodes_2.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_3  ON  nodes_2.id  =  nodes_3.parent_id
ORDER  BY  nodes.oid,  nodes_1.oid,  nodes_2.oid,  nodes_3.oid

注意这些漂亮干净的别名。连接不在乎它是针对同一即时表还是针对某个其他对象,然后又回到开头。当指定了join_depth时,任何类型的贪婪加载都可以在自身上循环回来。当不存在时,贪婪加载在碰到循环时会自动停止。

复合类型

这是 Hibernate 阵营中的一个特点。复合类型允许您定义一个由多个列(或一个列,如果您想要的话)组成的自定义数据类型。让我们定义一个新类型,Point。存储一个 x/y 坐标:

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __eq__(self, other):
        return other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

Point 对象的定义方式是特定于自定义类型的;构造函数接受一个参数列表,并且__composite_values__()方法生成这些参数的序列。稍后我们将看到,顺序将与我们的映射器相匹配。

让我们创建一个存储每行两个点的顶点表:

vertices = Table(
    "vertices",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x1", Integer),
    Column("y1", Integer),
    Column("x2", Integer),
    Column("y2", Integer),
)

然后,映射它!我们将创建一个存储两个Point对象的Vertex对象:

class Vertex(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end

mapper(
    Vertex,
    vertices,
    properties={
        "start": composite(Point, vertices.c.x1, vertices.c.y1),
        "end": composite(Point, vertices.c.x2, vertices.c.y2),
    },
)

一旦设置了复合类型,它就可以像任何其他类型一样使用:

v = Vertex(Point(3, 4), Point(26, 15))
session.save(v)
session.flush()

# works in queries too
q = session.query(Vertex).filter(Vertex.start == Point(3, 4))

如果您想要定义映射属性在表达式中生成 SQL 子句的方式,请创建自己的 sqlalchemy.orm.PropComparator 子类,定义任何常见操作符(如 __eq__()__le__() 等),并将其发送到 composite()。组合类型也可以作为主键,并且可在 query.get() 中使用:

# a Document class which uses a composite Version
# object as primary key
document = query.get(Version(1, "a"))

dynamic_loader() 关系

relation() 返回一个用于所有读取操作的实时 Query 对象。写操作仅限于 append()remove(),对集合的更改在会话刷新之前不可见。这个特性在“自动刷新”会话中特别方便,在每次查询之前都会刷新。

mapper(
    Foo,
    foo_table,
    properties={
        "bars": dynamic_loader(
            Bar,
            backref="foo",
            # <other relation() opts>
        )
    },
)

session = create_session(autoflush=True)
foo = session.query(Foo).first()

foo.bars.append(Bar(name="lala"))

for bar in foo.bars.filter(Bar.name == "lala"):
    print(bar)

session.commit()

新选项:undefer_group()eagerload_all()

一些方便的查询选项。undefer_group() 将整个“延迟”列组标记为未延迟:

mapper(
    Class,
    table,
    properties={
        "foo": deferred(table.c.foo, group="group1"),
        "bar": deferred(table.c.bar, group="group1"),
        "bat": deferred(table.c.bat, group="group1"),
    },
)

session.query(Class).options(undefer_group("group1")).filter(...).all()

eagerload_all() 一次性设置一系列属性为急加载:

mapper(Foo, foo_table, properties={"bar": relation(Bar)})
mapper(Bar, bar_table, properties={"bat": relation(Bat)})
mapper(Bat, bat_table)

# eager load bar and bat
session.query(Foo).options(eagerload_all("bar.bat")).filter(...).all()

新的集合 API

集合不再由 InstrumentedList 代理代理,并且对成员、方法和属性的访问是直接的。装饰器现在拦截进入和离开集合的对象,并且现在可以轻松编写一个自定义的集合类来管理其自己的成员资格。灵活的装饰器也替代了 0.3 版本中自定义集合的命名方法接口,允许任何类容易地被调整为用作集合容器。

基于字典的集合现在更易于使用,并完全类似于 dict。不再需要更改 __iter__ 来使用 dict,并且新的内置 dict 类型满足了许多需求:

# use a dictionary relation keyed by a column
relation(Item, collection_class=column_mapped_collection(items.c.keyword))
# or named attribute
relation(Item, collection_class=attribute_mapped_collection("keyword"))
# or any function you like
relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))

现有的 0.3 版本类似于 dict 和自由形式对象派生的集合类需要更新到新的 API。在大多数情况下,这只是简单地向类定义中添加几个装饰器。

来自外部表/子查询的映射关系

这个特性在 0.3 中悄然出现,但在 0.4 中得到了改进,这要归功于更好地将针对表的子查询转换为针对该表的别名的子查询的能力;这对于急加载、查询中的别名连接等非常重要。它减少了在只需要添加一些额外列或子查询时创建针对选择语句的映射器的必要性:

mapper(
    User,
    users,
    properties={
        "fullname": column_property(
            (users.c.firstname + users.c.lastname).label("fullname")
        ),
        "numposts": column_property(
            select([func.count(1)], users.c.id == posts.c.user_id)
            .correlate(users)
            .label("posts")
        ),
    },
)

典型的查询看起来像这样:

SELECT  (SELECT  count(1)  FROM  posts  WHERE  users.id  =  posts.user_id)  AS  count,
users.firstname  ||  users.lastname  AS  fullname,
users.id  AS  users_id,  users.firstname  AS  users_firstname,  users.lastname  AS  users_lastname
FROM  users  ORDER  BY  users.oid

水平扩展(分片)API

[browser:/sqlalchemy/trunk/examples/sharding/attribute_shard .py]

会话

新的会话创建范例;SessionContext,assignmapper 弃用

是的,整个流程正在用两个配置函数替换。同时使用两者将产生自 0.1 版以来最接近 0.1 版感觉的体验(即,输入最少)。

在定义您的 engine(或任何位置)的地方配置您自己的 Session 类:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("myengine://")
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

如果需要在会话后配置您的会话,比如说使用引擎,请稍后使用 configure() 添加:

Session.configure(bind=create_engine(...))

所有SessionContext的行为以及assignmapperquery__init__方法都移至新的scoped_session()函数中,该函数与sessionmakercreate_session()兼容:

from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)

u = User(name="wendy")

sess = Session()
sess.save(u)
sess.commit()

# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

当使用线程本地Session时,返回的类已实现了所有Session的接口作为类方法,并且可以使用mapper类方法来使用“assignmapper”的功能。就像旧的objectstore时代一样……。

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)

u = User(name="wendy")

Session.commit()

会话再次默认使用弱引用

weak_identity_map标志现在默认设置为TrueSession上。外部解除引用并超出范围的实例将自动从会话中移除。但是,具有“脏”更改的项目将保持强引用,直到这些更改被刷新,此时对象将恢复为弱引用(这适用于“可变”类型,如可选属性)。将weak_identity_map设置为False将为那些像缓存一样使用会话的人恢复旧的强引用行为。

自动事务会话

正如您可能已经注意到的,我们在Session上调用commit()。标志transactional=True意味着Session始终处于事务中,commit()会永久保存。

自动刷新会话

此外,autoflush=True意味着Session将在每次query之前flush(),以及在调用flush()commit()时。因此,现在这将起作用:

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

u = User(name="wendy")

sess = Session()
sess.save(u)

# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name="wendy").one()

事务方法移至会话

commit()rollback(),以及begin()现在直接在Session上。不再需要为任何事情使用SessionTransaction(它仍然在后台运行)。

Session = sessionmaker(autoflush=True, transactional=False)

sess = Session()
sess.begin()

# use the session

sess.commit()  # commit transaction

与包含引擎级(即非 ORM)事务共享Session很容易:

Session = sessionmaker(autoflush=True, transactional=False)

conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)

# ... session is transactional

# commit the outermost transaction
trans.commit()

使用 SAVEPOINT 的嵌套会话事务

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

两阶段提交会话

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

新的会话创建范式;SessionContext,assignmapper 已弃用

是的,整个设置正在被两个配置函数替换。同时使用两者将产生自 0.1 版本以来最接近 0.1 版本的感觉(即,键入的数量最少)。

在定义您的engine(或任何地方)时配置自己的Session类:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("myengine://")
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

如果您需要对会话进行后期配置,比如添加引擎,请稍后使用configure()进行添加:

Session.configure(bind=create_engine(...))

所有SessionContext的行为以及assignmapperquery__init__方法都移至新的scoped_session()函数中,该函数与sessionmakercreate_session()兼容:

from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)

u = User(name="wendy")

sess = Session()
sess.save(u)
sess.commit()

# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

使用线程本地 Session 时,返回的类实现了所有 Session 的接口作为类方法,并且可以使用 mapper 类方法来使用 “assignmapper” 的功能。就像旧的 objectstore 时代一样……。

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)

u = User(name="wendy")

Session.commit()

会话再次默认为弱引用

weak_identity_map 标志现在默认设置为 True 在 Session 上。外部解除引用并超出范围的实例会自动从会话中移除。但是,具有“脏”更改的项目将保持强引用,直到这些更改被刷新,此时对象将恢复为弱引用(这适用于‘可变’类型,如可选属性)。将 weak_identity_map 设置为 False 可以为那些像缓存一样使用会话的人恢复旧的强引用行为。

自动事务会话

正如您可能已经注意到的,我们在 Session 上调用 commit()。标志 transactional=True 意味着 Session 总是处于事务中,commit() 永久保存。

自动刷新会话

此外,autoflush=True 意味着 Session 在每次 query 之前都会执行 flush(),以及在调用 flush()commit() 时也会执行。所以现在这将起作用:

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

u = User(name="wendy")

sess = Session()
sess.save(u)

# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name="wendy").one()

事务方法移至会话

commit()rollback(),以及 begin() 现在直接在 Session 上。不再需要为任何事情使用 SessionTransaction(它仍然在后台运行)。

Session = sessionmaker(autoflush=True, transactional=False)

sess = Session()
sess.begin()

# use the session

sess.commit()  # commit transaction

与封闭的引擎级(即非 ORM)事务共享 Session 很容易:

Session = sessionmaker(autoflush=True, transactional=False)

conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)

# ... session is transactional

# commit the outermost transaction
trans.commit()

使用 SAVEPOINT 的嵌套会话事务

在引擎和 ORM 层面都可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

两阶段提交会话

在引擎和 ORM 层面都可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

继承

无连接或联合的多态继承

继承的新文档:www.sqlalchemy.org/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined

使用 get() 时更好的多态行为

在加入表继承层次结构中,所有类都使用基类获得 _instance_key,即 (BaseClass, (1, ), None)。这样,当您对基类进行 get() 查询时,它可以在当前标识映射中定位子类实例,而无需查询数据库。

无连接或联合的多态继承

继承的新文档:www.sqlalchemy.org/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined

使用 get() 时更好的多态行为

在连接表继承层次结构中,所有类都使用基类获取_instance_key,即(BaseClass, (1, ), None)。这样当你对基类调用get()时,它可以在当前标识映射中定位子类实例,而无需查询数据库。

类型

sqlalchemy.types.TypeDecorator的自定义子类

有一个新 API用于子类化 TypeDecorator。在某些情况下使用 0.3 API 会导致编译错误。

sqlalchemy.types.TypeDecorator的自定义子类

有一个新 API用于子类化 TypeDecorator。在某些情况下使用 0.3 API 会导致编译错误。

SQL 表达式

所有新的、确定性的标签/别名生成

所有“匿名”标签和别名现在都使用简单的<name>_<number>格式。SQL 更容易阅读,并且与计划优化器缓存兼容。只需查看一些教程中的示例:www.sqlalchemy.org/docs/04/ormtutorial.html www.sqlalchemy.org/docs/04/sqlexpression.html

生成式select()构造

这绝对是使用select()的正确方法。查看 htt p://www.sqlalchemy.org/docs/04/sqlexpression.html#sql_transf orm。

新操作系统

SQL 运算符和几乎每个 SQL 关键字现在都被抽象为编译器层。它们现在具有智能行为,并且具有类型/后端感知能力,请参阅:www.sqlalchemy.org/docs/04/sqlexpression.html#sql_operators

所有type关键字参数重命名为type_

就像它所说的:

b = bindparam("foo", type_=String)

in_函数更改为接受序列或可选择项

in_函数现在以其唯一参数接受值序列或可选择的。以前的 API 仍然支持传递值作为位置参数,但现在已过时。这意味着

my_table.select(my_table.c.id.in_(1, 2, 3))
my_table.select(my_table.c.id.in_(*listOfIds))

应更改为

my_table.select(my_table.c.id.in_([1, 2, 3]))
my_table.select(my_table.c.id.in_(listOfIds))

所有新的、确定性的标签/别名生成

所有“匿名”标签和别名现在都使用简单的<name>_<number>格式。SQL 更容易阅读,并且与计划优化器缓存兼容。只需查看一些教程中的示例:www.sqlalchemy.org/docs/04/ormtutorial.html www.sqlalchemy.org/docs/04/sqlexpression.html

生成式select()构造

这绝对是使用select()的正确方法。查看 htt p://www.sqlalchemy.org/docs/04/sqlexpression.html#sql_transf orm。

新操作系统

SQL 操作符以及几乎每个 SQL 关键字都现在抽象成了编译器层。它们现在具有智能行为,并且具有类型/后端感知性,请参阅:www.sqlalchemy.org/docs/04/sqlexpression.html#sql_operators

所有 type 关键字参数重命名为 type_

就像它说的那样:

b = bindparam("foo", type_=String)

in_ 函数更改为接受序列或可选择项

in_ 函数现在接受一个值序列或可选择项作为其唯一参数。之前的传递值作为位置参数的 API 仍然有效,但现在已被弃用。这意味着

my_table.select(my_table.c.id.in_(1, 2, 3))
my_table.select(my_table.c.id.in_(*listOfIds))

应更改为

my_table.select(my_table.c.id.in_([1, 2, 3]))
my_table.select(my_table.c.id.in_(listOfIds))

模式和反射

MetaDataBoundMetaDataDynamicMetaData

在 0.3.x 系列中,BoundMetaDataDynamicMetaData 已被弃用,而不是 MetaDataThreadLocalMetaData。旧名称已在 0.4 版本中移除。更新很简单:

+-------------------------------------+-------------------------+
|If You Had                           | Now Use                 |
+=====================================+=========================+
| ``MetaData``                        | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``BoundMetaData``                   | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``DynamicMetaData`` (with one       | ``MetaData``            |
| engine or threadlocal=False)        |                         |
+-------------------------------------+-------------------------+
| ``DynamicMetaData``                 | ``ThreadLocalMetaData`` |
| (with different engines per thread) |                         |
+-------------------------------------+-------------------------+

MetaData 类型中不常用的 name 参数已被移除。ThreadLocalMetaData 构造函数现在不接受任何参数。这两种类型现在可以绑定到一个 Engine 或单个 Connection

一步多表反射

现在您可以在一次通行中从整个数据库或模式加载表定义并自动创建 Table 对象:

>>> metadata = MetaData(myengine, reflect=True)
>>> metadata.tables.keys()
['table_a', 'table_b', 'table_c', '...']

MetaData 还增加了一个 .reflect() 方法,可以更精细地控制加载过程,包括指定要加载的可用表的子集。

MetaDataBoundMetaDataDynamicMetaData

在 0.3.x 系列中,BoundMetaDataDynamicMetaData 已被弃用,而不是 MetaDataThreadLocalMetaData。旧名称已在 0.4 版本中移除。更新很简单:

+-------------------------------------+-------------------------+
|If You Had                           | Now Use                 |
+=====================================+=========================+
| ``MetaData``                        | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``BoundMetaData``                   | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``DynamicMetaData`` (with one       | ``MetaData``            |
| engine or threadlocal=False)        |                         |
+-------------------------------------+-------------------------+
| ``DynamicMetaData``                 | ``ThreadLocalMetaData`` |
| (with different engines per thread) |                         |
+-------------------------------------+-------------------------+

MetaData 类型中不常用的 name 参数已经被移除。ThreadLocalMetaData 构造函数现在不接受任何参数。这两种类型现在可以绑定到一个 Engine 或单个 Connection

一步多表反射

现在您可以在一次通行中从整个数据库或模式加载表定义并自动创建 Table 对象:

>>> metadata = MetaData(myengine, reflect=True)
>>> metadata.tables.keys()
['table_a', 'table_b', 'table_c', '...']

MetaData 还增加了一个 .reflect() 方法,可以更精细地控制加载过程,包括指定要加载的可用表的子集。

SQL 执行

engineconnectablebind_to 现在都是 bind

TransactionsNestedTransactionsTwoPhaseTransactions

连接池事件

当新的 DB-API 连接被创建、检出和重新放回到池中时,连接池现在会触发事件。您可以使用这些事件在新连接上执行会话范围的 SQL 设置语句,例如。

Oracle Engine 修复

在 0.3.11 版本中,Oracle Engine 处理主键的方式存在 bug。这些 bug 可能导致在使用 Oracle Engine 时,那些在其他引擎(如 sqlite)上运行良好的程序失败。在 0.4 版本中,Oracle Engine 已经重做,修复了这些主键问题。

Oracle 的输出参数

result = engine.execute(
    text(
        "begin foo(:x, :y, :z); end;",
        bindparams=[
            bindparam("x", Numeric),
            outparam("y", Numeric),
            outparam("z", Numeric),
        ],
    ),
    x=5,
)
assert result.out_parameters == {"y": 10, "z": 75}

连接绑定的 MetaDataSessions

MetaDataSession 可以明确绑定到一个连接:

conn = engine.connect()
sess = create_session(bind=conn)

更快、更可靠的 ResultProxy 对象

engineconnectablebind_to 现在都改为 bind

TransactionsNestedTransactionsTwoPhaseTransactions

连接池事件

现在连接池在创建新的 DB-API 连接、检出和检入连接池时会触发事件。您可以利用这些事件在新连接上执行会话范围的 SQL 设置语句,例如。

Oracle 引擎已修复

在 0.3.11 版本中,Oracle 引擎在处理主键时存在 bug。这些 bug 可能导致在使用 Oracle 引擎时,那些在其他引擎(如 sqlite)上正常运行的程序失败。在 0.4 版本中,Oracle 引擎已经重新设计,修复了这些主键问题。

用于 Oracle 的输出参数

result = engine.execute(
    text(
        "begin foo(:x, :y, :z); end;",
        bindparams=[
            bindparam("x", Numeric),
            outparam("y", Numeric),
            outparam("z", Numeric),
        ],
    ),
    x=5,
)
assert result.out_parameters == {"y": 10, "z": 75}

连接绑定的 MetaDataSessions

MetaDataSession 可以显式绑定到连接:

conn = engine.connect()
sess = create_session(bind=conn)

更快、更可靠的 ResultProxy 对象

posted @ 2024-06-22 11:43  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报