SqlAlchemy 2.0 中文文档(七十九)(3)https://developer.aliyun.com/article/1561105
日志增强
Vinay Sajip 提供了一个补丁,使我们的日志系统中不再需要“十六进制字符串”嵌入到引擎和池的日志语句中,以使echo
标志能够正确工作。使用过滤日志对象的新系统使我们能够保持我们当前的echo
行为,即echo
局限于各个引擎,而无需额外的标识字符串局限于这些引擎。
简化的多态 _on 赋值
当在继承场景中使用时,polymorphic_on
列映射属性的人口现在在对象构造时发生,即调用其__init__
方法,使用 init 事件。然后,该属性的行为与任何其他列映射属性相同。以前,特殊逻辑会在刷新时触发以填充此列,这会阻止任何用户代码修改其行为。新方法在三个方面改进了这一点:1. 多态标识现在在对象构造时立即存在;2. 多态标识可以被用户代码更改,而不会与任何其他列映射属性的行为有任何区别;3. 刷新期间映射器的内部简化,不再需要对此列进行特殊检查。
包含跨多个路径(即“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))
允许刷新没有父级的孤立对象
我们长期以来一直有一个行为,即在执行 flush 操作时检查所谓的“孤立对象”,即,与一个指定了“delete-orphan”级联的 relationship()
相关联的对象已经被新增到了会话中以进行 INSERT 操作,但尚未建立父关系。多年前,为了满足一些测试用例对孤立对象行为的一致性进行测试,添加了此检查。在现代 SQLA 中,不再需要在 Python 端进行此检查。通过将对象的外键引用设置为对象的父行的 NOT NULL,数据库会在确立数据一致性方面发挥作用,SQLA 允许大多数其他操作以相同的方式完成。如果对象的父外键可为空,则可以插入行。当对象被持久化到特定父对象,并且然后与该父对象取消关联时,“孤立”行为会运行,导致为其发出 DELETE 语句。
在刷新时生成的警告,集合成员,不是刷新的一部分的标量引用
当通过父对象上标记为“脏”的加载的 relationship()
引用的相关对象在当前 Session
中不存在时,会发出警告。
当对象被添加到 Session
中,或者当对象首次与父对象关联时,save-update
级联会生效,以便对象及其相关内容通常都存在于同一个 Session
中。但是,如果对特定的 relationship()
禁用了 save-update
级联,则此行为不会发生,并且刷新过程不会尝试进行纠正,而是保持一致性以配置的级联行为。以前,在刷新期间检测到此类对象时,它们会被静默跳过。新行为是发出警告,以便提醒通常是意外行为的根源。
设置不再安装 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 运行测试的唯一方法。
非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])
aliased()接受FromClause
元素
这是一个方便的辅助工具,以便在传递一个普通的FromClause
,比如一个select
,Table
或join
到orm.aliased()
构造时,它会通过到该 from 构造的.alias()
方法,而不是构造一个 ORM 级别的AliasedClass
。
Session.connection(),Session.execute()接受‘bind’
这是为了允许 execute/connection 操作明确参与引擎的打开事务。它还允许Session
的自定义子类实现自己的get_bind()
方法和参数,以便在execute()
和connection()
方法中同等使用这些自定义参数。
Session.connection Session.execute
列子句中的独立绑定参数自动标记。
在 select 的“columns clause”中存在的绑定参数现在会像其他“匿名”子句一样自动标记,这样在获取行时它们的“类型”就会有意义,就像结果行处理器一样。
SQLite - 相对文件路径通过 os.path.abspath()进行标准化
这样,一个改变当前目录的脚本将继续以后续建立的 SQLite 连接的相同位置为目标。
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 映射具有PickleType
或postgresql.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()
不是自动的,历史跟踪系统也不像现在这样复杂。
现有应用程序使用PickleType
、postgresql.ARRAY
或其他MutableType
子类,并且需要就地变异检测的应用程序,应该迁移到新的变异跟踪系统,因为mutable=True
可能会在未来被弃用。
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 中的说明。
Session.merge()
检查版本化映射器的版本 id
Session.merge()将检查传入状态的版本 id 与数据库的版本 id 是否匹配,假设映射使用版本 id 并且传入状态已分配版本 id,并且如果它们不匹配,则引发 StaleDataError。这是正确的行为,因为如果传入状态包含过时的版本 id,则应假定状态是过时的。
如果将数据合并到版本化状态中,则可以将版本 id 属性未定义,并且不会进行版本检查。
通过检查 Hibernate 的操作来确认了这个检查 - merge()
和版本化功能最初都是从 Hibernate 中适应过来的。
查询中的元组标签名称改进
这个改进对于依赖于旧行为的应用程序可能会有一点向后不兼容。
给定两个映射类Foo
和Bar
,每个类都有一个列spam
:
qa = session.query(Foo.spam) qb = session.query(Bar.spam) qu = qa.union(qb)
由qu
产生的单个列的名称将是spam
。以前会是类似foo_spam
的东西,这是由于union
组合事物的方式,这与非联合查询的情况下的名称spam
不一致。
映射列属性首先引用最具体的列
这是在映射列属性引用多个列时涉及的行为更改,特别是在处理具有与超类属性相同名称的连接表子类上的属性时。
使用声明性,情景是这样的:
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.id
和child.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中的更改有些相关。在映射到连接时,同名列必须明确链接到映射的属性,即如在多个表上映射一个类中所述。
给定两个表foo
和bar
,每个表都有一个主键列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]})
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
。还有一些“多态联合”场景,类似的错误有时会发生。
这样的配置错误一直是“错误的”,上述映射不像规定的那样工作 - 该列将被忽略。然而,如果某个应用程序不知情地依赖于此行为,则这可能是潜在的向后不兼容的情况。
DDL()
构造现在转义百分号
以前,在DDL()
字符串中的百分号必须转义,即 %%
取决于 DBAPI,对于那些接受pyformat
或format
绑定(即 psycopg2,mysql-python),这与text()
构造函数自动执行的操作不一致。现在对于DDL()
与text()
,进行了相同的转义。
Table.c
/ MetaData.tables
稍微改进,不允许直接突变
另一个区域是,一些用户以一种实际上不按预期工作的方式进行尝试,但仍然留下了极小的可能性,即某些应用程序正在依赖于此行为,Table
的.c
属性返回的构造和MetaData
的.tables
属性是明确不可变的。构造的“可变”版本现在是私有的。向.c
添加列涉及使用Table
的append_column()
方法,该方法确保事物以适当的方式与父Table
关联; 同样,MetaData.tables
与此字典中存储的Table
对象有一个协议,以及一些新的簿记,即跟踪所有模式名称的set()
,仅使用公共Table
构造函数以及Table.tometadata()
才能满足。
当然,ColumnCollection
和dict
这些属性所查询的集合可能会在所有突变方法上实现事件,以便在直接突变集合时发生适当的簿记,但是在有人有动力实现所有这些以及数十个新单元测试之前,缩小这些集合突变路径将确保没有应用程序试图依赖于当前不受支持的用法。
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 列的情况下。
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 在映射具有PickleType
或postgresql.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()
不是自动的,历史跟踪系统也没有现在这么复杂。
使用PickleType
、postgresql.ARRAY
或其他MutableType
子类,并且需要原地变异检测的现有应用程序,应该迁移到新的变异跟踪系统,因为mutable=True
可能会在未来被弃用。
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 中的说明。
SqlAlchemy 2.0 中文文档(七十九)(5)https://developer.aliyun.com/article/1561109