SqlAlchemy 2.0 中文文档(七十九)(1)https://developer.aliyun.com/article/1561101
映射器要求多态性的列在映射的可选择项中存在
在 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
绑定的 DBAPI(例如 psycopg2,mysql-python),这与自动执行此操作的 text()
构造不一致。现在,DDL()
与 text()
一样进行相同的转义。
Table.c
/ MetaData.tables
稍微精炼了一下,不允许直接变异。
另一个领域,一些用户在进行某种方式的尝试时实际上并不按预期工作,但仍然留下了极小的机会,即某些应用程序依赖于这种行为,.c
属性在 Table
上返回的构造和 MetaData
上的 .tables
属性明确是不可变的。构造的“可变”版本现在是私有的。向 .c
添加列涉及使用 Table
的 append_column()
方法,这确保了事物以适当的方式与父 Table
关联;同样,MetaData.tables
与存储在此字典中的 Table
对象有一个合同,以及一些新的簿记,跟踪所有模式名称的 set()
,只有通过使用公共 Table
构造函数以及 Table.tometadata()
才能满足。
当然,ColumnCollection
和 dict
集合可能会在某一天实现对其所有变异方法的事件,以便在直接变异集合时发生适当的簿记,但在有人有动力实现所有这些以及数十个新单元测试之前,缩小这些集合的变异路径将确保没有应用程序试图依赖当前不支持的用法。
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 列,我们检测到一个序列默认值。
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
类型上的默认构造函数不会接受参数
核心类型模块中的简单类型如 Integer
、Date
等不接受参数。从 0.7b4/0.7.0 开始,接受/忽略 catchall \*args, \**kwargs
的默认构造函数已经恢复,但会发出弃用警告。
如果在核心类型如 Integer
中使用参数,可能是你打算使用特定于方言的类型,比如 sqlalchemy.dialects.mysql.INTEGER
,它接受一个“display_width”参数。
compile_mappers() 重命名为 configure_mappers(),简化配置内部
这个系统从最初是一个小型的、实现在单个映射器本地的东西,命名不当,逐渐演变成了更像是一个全局“注册表”级别的功能,命名不当,因此我们通过将实现移出 Mapper
并将其重命名为 configure_mappers()
来修复这两个问题。当然,通常情况下应用程序不需要调用 configure_mappers()
,因为这个过程是根据需要的,一旦通过属性或查询访问需要映射时就会发生。
核心监听器/代理被事件监听器取代
PoolListener
、ConnectionProxy
、DDLElement.execute_at
被分别替代为 event.listen()
,使用 PoolEvents
、EngineEvents
、DDLEvents
分发目标。
ORM 扩展被事件监听��取代
MapperExtension
、AttributeExtension
、SessionExtension
被分别替代为 event.listen()
,使用 MapperEvents
/InstanceEvents
、AttributeEvents
、SessionEvents
分发目标。
在 MySQL 中,将字符串发送到 select()
的 ‘distinct’ 应该通过前缀来完成
这个晦涩的特性允许在 MySQL 后端中使用这种模式:
select([mytable], distinct="ALL", prefixes=["HIGH_PRIORITY"])
对于非标准或不寻常的前缀,应该使用 prefixes
关键字或 prefix_with()
方法:
select([mytable]).prefix_with("HIGH_PRIORITY", "ALL")
useexisting
被 extend_existing
和 keep_existing
取代
Table 上的 useexisting
标志已被新的一对标志 keep_existing
和 extend_existing
取代。extend_existing
等同于 useexisting
- 返回现有的 Table,并添加额外的构造元素。使用 keep_existing
,返回现有的 Table,但不添加额外的构造元素 - 这些元素仅在新创建 Table 时应用。
不兼容的后向 API 更改
传递给 bindparam()
的可调用对象不会被评估 - 影响 Beaker 示例
请注意,这影响了 Beaker 缓存示例,其中 _params_from_query()
函数的工作需要进行轻微调整。如果您正在使用 Beaker 示例中的代码,则应用此更改。
types.type_map 现在是私有的,types._type_map
我们注意到一些用户在 sqlalchemy.types
内部利用这个字典作为将 Python 类型与 SQL 类型关联的快捷方式。我们无法保证这个字典的内容或格式,并且将 Python 类型一对一关联的业务有一些灰色地带,最好由各个应用程序自行决定,因此我们已经强调了这个属性。
将独立 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()
以前弃用,现在已移除
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 中,像synonym
和comparable_property
以及所有Extension
和其他事件类等功能已被取代。 但是这些“取代”功能已被重新实现,使得它们的实现主要存在于核心 ORM 代码之外,因此它们的持续“挂在”不会影响 SQLAlchemy 进一步简化和完善其内部的能力,并且我们预计它们将在可预见的未来保留在 API 中。
新功能
新事件系统
SQLAlchemy 早期开始使用 MapperExtension
类,该类提供了对映射器持久性周期的钩子。随着 SQLAlchemy 快速变得更加组件化,将映射器推入更专注的配置角色,许多更多的“扩展”、“监听器”和“代理”类出现,以以一种临时的方式解决各种活动拦截用例。其中一部分是由活动的分歧驱动的;ConnectionProxy
对象希望提供一个重写语句和参数的系统;AttributeExtension
提供了一个替换传入值的系统,而 DDL
对象具有可以切换到方言敏感可调用对象的事件。
0.7 重新实现了几乎所有这些插件点,采用了一种新的、统一的方法,保留了不同系统的所有功能,提供了更多的灵活性和更少的样板代码,性能更好,并消除了需要为每个事件子系统学习根本不同的 API 的必要性。现有的类 MapperExtension
、SessionExtension
、AttributeExtension
、ConnectionProxy
、PoolListener
以及 DDLElement.execute_at
方法已被弃用,现在根据新系统实现 - 这些 API 仍然完全可用,并预计将在可预见的未来保持不变。
新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。API 的外观和感觉受到了 JQuery、Blinker 和 Hibernate 等多样化来源的驱动,并且在与数十位用户进行的 Twitter 会议期间进行了多次修改,这似乎比邮件列表对这类问题的回应率要高得多。
它还具有一个开放式的目标规范系统,允许将事件与 API 类关联,例如所有 Session
或 Engine
对象,以及与 API 类的特定实例关联,例如特定的 Pool
或 Mapper
,以及与用户定义的类相关的对象,例如映射的类,或者特定子类的实例上的某个属性。个别监听器子系统可以将包装器应用于传入的用户定义的监听器函数,从而修改它们的调用方式 - 映射器事件可以接收被操作对象的实例,或者其底层的 InstanceState
对象。属性事件可以选择是否要负责返回一个新值。
几个系统现在基于新的事件 API 构建,包括新的“可变属性” API 以及复合属性。对事件的更大强调还导致引入了一些新事件,包括属性过期和刷新操作,pickle 加载/转储操作,完成的映射器构造操作。
另请参见
事件
混合属性,实现/取代了 synonym()、comparable_property()
“派生属性”示例现在已经转变为官方扩展。synonym()
的典型用例是为映射列提供描述符访问;comparable_property()
的用例是能够从任何描述符返回PropComparator
。实际上,“派生”的方法更易于使用,更具可扩展性,用几十行纯 Python 实现几乎不需要导入,甚至不需要 ORM 核心意识到它。该功能现在被称为“混合属性”扩展。
synonym()
和comparable_property()
仍然是 ORM 的一部分,尽管它们的实现已经移出,建立在类似于混合扩展的方法之上,以便核心 ORM 映射器/查询/属性模块在其他情况下并不真正意识到它们。
另请参阅
混合属性
速度增强
与所有主要 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
更简洁的查询.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
突变事件扩展,取代“mutable=True”
一个新的扩展,Mutation Tracking,提供了一种机制,用户定义的数据类型可以向拥有的父级或父级提供更改事件。该扩展包括一种用于标量数据库值的方法,例如由PickleType
、postgresql.ARRAY
或其他自定义MutableType
类管理的值,以及一种用于 ORM “组合”配置的方法,这些配置使用composite()
。
另请参阅
Mutation Tracking
NULLS FIRST / NULLS LAST 操作符
这些操作符作为asc()
和desc()
操作符的扩展实现,称为nullsfirst()
和nullslast()
。
另请参阅
nullsfirst()
nullslast()
select.distinct()、query.distinct()接受 PostgreSQL DISTINCT ON 的*args
通过将表达式列表传递给select()
的distinct
关键字参数,现在select()
和Query
的distinct()
方法接受位置参数,当使用 PostgreSQL 后端时,这些参数将被渲染为 DISTINCT ON。
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)
窗口函数 SQL 构造
“窗口函数”向语句提供有关生成的结果集的信息。这允许根据诸如“行号”、“排名”等各种条件进行筛选。它们至少被 PostgreSQL、SQL Server 和 Oracle 支持,可能还有其他数据库。
关于窗口函数的最佳介绍在 PostgreSQL 的网站上,自从 8.4 版本以来就支持窗口函数:
www.postgresql.org/docs/current/static/tutorial-window.html
SQLAlchemy 提供了一个简单的构造,通常通过现有的函数子句调用,使用over()
方法,接受order_by
和partition_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
Connection 上的 execution_options()接受“isolation_level”参数
这将为单个Connection
设置事务隔离级别,直到该Connection
关闭并其底层 DBAPI 资源返回到连接池,此时隔离级别将重置为默认值。默认的隔离级别是使用create_engine()
的isolation_level
参数设置的。
事务隔离支持目前仅由 PostgreSQL 和 SQLite 后端支持。
TypeDecorator
与整数主键列一起使用
一个TypeDecorator
,它扩展了Integer
的行为,可以与主键列一起使用。Column
的“autoincrement”特性现在将识别到底层数据库列仍然是整数,以便lastrowid
机制继续正常工作。TypeDecorator
本身将其结果值处理器应用于新生成的主键,包括通过 DBAPI cursor.lastrowid
访问器接收到的主键。
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 的必要性。预先存在的类MapperExtension
、SessionExtension
、AttributeExtension
、ConnectionProxy
、PoolListener
以及DDLElement.execute_at
方法已被弃用,现在根据新系统实现 - 这些 API 仍然完全可用,并且预计将在可预见的未来保持不变。
新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。API 的外观和感觉受到了 JQuery、Blinder 和 Hibernate 等多样化来源的驱动,并且在与数十名用户进行的会议期间多次进行了修改,这些会议在 Twitter 上的响应率似乎比邮件列表更高。
它还具有一种开放式的目标规范系统,允许将事件与 API 类关联,例如所有的Session
或Engine
对象,以及特定 API 类的实例,例如特定的Pool
或Mapper
,以及相关对象,如映射的用户定义类,或者特定子类的映射父类实例的特定属性。各个监听器子系统可以对传入的用户定义监听器函数应用包装器,修改它们的调用方式 - 映射事件可以接收被操作对象的实例,或者其底层的InstanceState
对象。属性事件可以选择是否要负责返回一个新值。
几个系统现在建立在新的事件 API 之上,包括新的“可变属性”API 以及复合属性。对事件的更大强调还导致引入了一些新事件,包括属性过期和刷新操作,pickle 加载/转储操作,完成的映射器构造操作。
另请参阅
事件
混合属性,实现/取代了 synonym()、comparable_property()
“派生属性”示例现在已成为官方扩展。synonym()
的典型用例是为映射列提供描述符访问;comparable_property()
的用例是能够从任何描述符返回PropComparator
。实际上,“派生”的方法更容易使用,更具可扩展性,用几十行纯 Python 实现,几乎不需要导入,甚至不需要 ORM 核心知道它。该功能现在被称为“混合属性”扩展。
synonym()
和comparable_property()
仍然是 ORM 的一部分,尽管它们的实现已经移出,建立在类似于混合扩展的方法上,因此核心 ORM 映射器/查询/属性模块在其他情况下并不真正意识到它们。
另请参见
混合属性
SqlAlchemy 2.0 中文文档(七十九)(3)https://developer.aliyun.com/article/1561105