SqlAlchemy 2.0 中文文档(七十九)(4)https://developer.aliyun.com/article/1561107
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
对象的策略,用于对所有具有针对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中的先前更改有些相关。在映射到连接时,同名列必须明确链接到映射属性,即如映射一个类到多个表中所述。
给定两个表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]})
映射器要求在映射的可选择项中存在多态列
这在 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)的 DBAPI,这是不一致的,与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。
关于此情况的反射,具有服务器默认值的 int PK 列的反射将 “autoincrement” 标志设置为 False,但在检测到 PG SERIAL 列的序列默认值的情况下除外。
sqlalchemy.exceptions
在 sys.modules 中的别名已被移除。
几年来,我们已经将字符串 sqlalchemy.exceptions
添加到 sys.modules
中,以便像 “import sqlalchemy.exceptions
” 这样的语句可以工作。 核心异常模块的名称已经很久以来是 exc
,因此建议导入此模块的方式是:
from sqlalchemy import
exceptions
名称仍然存在于“sqlalchemy
”中,供可能已经使用 from sqlalchemy import exceptions
的应用程序使用,但他们也应该开始使用 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
取代。
对表的useexisting
标志已被新的一对标志keep_existing
和extend_existing
取代。extend_existing
等同于useexisting
- 返回现有表,并添加额外的构造元素。使用keep_existing
,返回现有表,但不添加额外的构造元素 - 这些元素仅在表新建时应用。
类的默认构造函数不接受参数。
核心类型模块中的简单类型,如Integer
、Date
等,不接受参数。接受/忽略通用参数 \*args, \**kwargs
的默认构造函数在 0.7b4/0.7.0 版本中已恢复,但会发出弃用警告。
如果在核心类型如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()
传递给 bindparam()
的可调用对象不会被评估 - 影响 Beaker 示例
请注意,这会影响 Beaker 缓存示例,其中 _params_from_query()
函数的工作需要进行轻微调整。如果你正在使用 Beaker 示例中的代码,应用这一变更。
types.type_map 现在是私有的,types._type_map
我们注意到一些用户在 sqlalchemy.types
中利用这个字典作为将 Python 类型与 SQL 类型关联的快捷方式。我们无法保证这个字典的内容或格式,此外,将 Python 类型与 SQL 类型一对一关联的业务有一些灰色地带,最好由各个应用程序自行决定,因此我们已经将这个属性标记为下划线。
将独立 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()
之前已弃用,现在已移除
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]。
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]。