SqlAlchemy 2.0 中文文档(七十四)(1)https://developer.aliyun.com/article/1562361
介绍
本指南介绍了 SQLAlchemy 版本 1.2 中的新功能,并记录了影响从 SQLAlchemy 1.1 系列迁移其应用程序的用户的更改。
请仔细查看行为变化部分,可能会对行为产生不兼容的变化。
平台支持
针对 Python 2.7 及更高版本
SQLAlchemy 1.2 现在将最低 Python 版本提升至 2.7,不再支持 2.6。预计将合并到 1.2 系列中的新语言特性在 Python 2.6 中不受支持。对于 Python 3 的支持,SQLAlchemy 目前在 3.5 和 3.6 版本上进行测试。
针对 Python 2.7 及更高版本
SQLAlchemy 1.2 现在将最低 Python 版本提升至 2.7,不再支持 2.6。预计将合并到 1.2 系列中的新语言特性在 Python 2.6 中不受支持。对于 Python 3 的支持,SQLAlchemy 目前在 3.5 和 3.6 版本上进行测试。
新功能和改进 - ORM
“Baked” 加载现在是懒加载的默认选项
sqlalchemy.ext.baked
扩展首次引入于 1.0 系列,允许构建所谓的BakedQuery
对象,该对象与表示查询结构的缓存键一起生成Query
对象;然后将此缓存键链接到生成的字符串 SQL 语句,以便后续使用具有相同结构的另一个BakedQuery
将绕过构建Query
对象、构建其中的核心select()
对象,以及将select()
编译为字符串的所有开销,从而削减通常与构建和发出 ORM Query
对象相关的大部分函数调用开销。
BakedQuery
现在在 ORM 默认情况下用于生成“延迟”查询,用于懒加载relationship()
构造,例如默认的lazy="select"
关系加载策略。这将显著减少应用程序在使用懒加载查询加载集合和相关对象时的函数调用。此功能以前在 1.0 和 1.1 中通过使用全局 API 方法或使用baked_select
策略可用,现在是此行为的唯一实现。该功能还得到改进,使得对于具有懒加载后生效的其他加载器选项的对象仍然可以进行缓存。
可以使用 relationship.bake_queries
标志在每个关系基础上禁用缓存行为,这对于非常罕见的情况非常有用,例如使用不兼容缓存的自定义 Query
实现的关系。
#3954 ### 新的“selectin”急加载,一次性使用 IN 加载所有集合
添加了一个名为“selectin”加载的新急加载器,这在许多方面类似于“子查询”加载,但生成的 SQL 语句更简单,可缓存且更高效。
给定以下查询:
q = ( session.query(User) .filter(User.name.like("%ed%")) .options(subqueryload(User.addresses)) )
生成的 SQL 将是针对 User
的查询,然后是 User.addresses
的 subqueryload(请注意还列出了参数):
SELECT users.id AS users_id, users.name AS users_name FROM users WHERE users.name LIKE ? ('%ed%',) SELECT addresses.id AS addresses_id, addresses.user_id AS addresses_user_id, addresses.email_address AS addresses_email_address, anon_1.users_id AS anon_1_users_id FROM (SELECT users.id AS users_id FROM users WHERE users.name LIKE ?) AS anon_1 JOIN addresses ON anon_1.users_id = addresses.user_id ORDER BY anon_1.users_id ('%ed%',)
使用“selectin”加载,我们实际上得到了一个引用父查询中加载的实际主键值的 SELECT:
q = ( session.query(User) .filter(User.name.like("%ed%")) .options(selectinload(User.addresses)) )
产生:
SELECT users.id AS users_id, users.name AS users_name FROM users WHERE users.name LIKE ? ('%ed%',) SELECT users_1.id AS users_1_id, addresses.id AS addresses_id, addresses.user_id AS addresses_user_id, addresses.email_address AS addresses_email_address FROM users AS users_1 JOIN addresses ON users_1.id = addresses.user_id WHERE users_1.id IN (?, ?) ORDER BY users_1.id (1, 3)
上述 SELECT 语句包括以下优点:
- 它不使用子查询,只是一个 INNER JOIN,这意味着在像 MySQL 这样不喜欢子查询的数据库上性能会更好
- 其结构独立于原始查询;与新的 扩展 IN 参数系统 结合使用,我们在大多数情况下可以使用“烘焙”查询来缓存字符串 SQL,从而显著减少每个查询的开销。
- 因为查询仅获取给定主键标识符列表,“selectin”加载可能与
Query.yield_per()
兼容,以便一次处理 SELECT 结果的块,前提是数据库驱动程序允许多个同时游标(SQLite、PostgreSQL;不是 MySQL 驱动程序或 SQL Server ODBC 驱动程序)。联接急加载和子查询急加载都不兼容Query.yield_per()
。
选择急加载的缺点可能是潜在的大型 SQL 查询,带有大量的 IN 参数列表。 IN 参数列表本身被分组为 500 个一组,因此超过 500 个结果对象的结果集将有更多额外的“SELECT IN”查询。此外,对复合主键的支持取决于数据库是否能够使用带有 IN 的元组,例如 (table.column_one, table_column_two) IN ((?, ?), (?, ?) (?, ?))
。目前,已知 PostgreSQL 和 MySQL 兼容此语法,而 SQLite 不兼容。
另请参见
Select IN 加载
#3944 ### “selectin” 多态加载,使用单独的 IN 查询加载子类
与刚刚在新“selectin” eager loading, loads all collections at once using IN 中描述的“selectin”关系加载功能类似的是“selectin”多态加载。这是一种专为连接式的急加载定制的多态加载功能,允许基本实体的加载通过简单的 SELECT 语句进行,但是额外的子类属性使用额外的 SELECT 语句加载:
>>> from sqlalchemy.orm import selectin_polymorphic >>> query = session.query(Employee).options( ... selectin_polymorphic(Employee, [Manager, Engineer]) ... ) >>> query.all() SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type FROM employee () SELECT engineer.id AS engineer_id, employee.id AS employee_id, employee.type AS employee_type, engineer.engineer_name AS engineer_engineer_name FROM employee JOIN engineer ON employee.id = engineer.id WHERE employee.id IN (?, ?) ORDER BY employee.id (1, 2) SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name FROM employee JOIN manager ON employee.id = manager.id WHERE employee.id IN (?) ORDER BY employee.id (3,)
另请参阅
使用 selectin_polymorphic()
#3948 ### 可接收临时 SQL 表达式的 ORM 属性
新增了一个 ORM 属性类型query_expression()
,与deferred()
类似,但其 SQL 表达式在查询时确定,使用新选项with_expression()
;如果未指定,属性默认为None
:
from sqlalchemy.orm import query_expression from sqlalchemy.orm import with_expression class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) x = Column(Integer) y = Column(Integer) # will be None normally... expr = query_expression() # but let's give it x + y a1 = session.query(A).options(with_expression(A.expr, A.x + A.y)).first() print(a1.expr)
另请参阅
查询时的 SQL 表达式作为映射属性
#3058 ### ORM 支持多表删除
ORM Query.delete()
方法支持多表条件的删除,如多表条件支持的删除中所介绍的。该功能的工作方式与更新的多表条件相同,最初在 0.8 版本中引入,并在 Query.update()支持 UPDATE…FROM 中描述。
下面,我们对SomeEntity
发出一个 DELETE 请求,添加一个 FROM 子句(或者等效的,根据后端不同)对SomeOtherEntity
进行操作:
query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter( SomeOtherEntity.foo == "bar" ).delete()
另请参阅
多表条件支持的删除
#959 ### 支持混合、复合的批量更新
现在,混合属性(例如sqlalchemy.ext.hybrid
)以及复合属性(复合列类型)在使用Query.update()
更新语句的 SET 子句中均得到支持。
对于混合属性,可以直接使用简单的表达式,或者可以使用新的装饰器hybrid_property.update_expression()
将值拆分为多个列/表达式:
class Person(Base): # ... first_name = Column(String(10)) last_name = Column(String(10)) @hybrid.hybrid_property def name(self): return self.first_name + " " + self.last_name @name.expression def name(cls): return func.concat(cls.first_name, " ", cls.last_name) @name.update_expression def name(cls, value): f, l = value.split(" ", 1) return [(cls.first_name, f), (cls.last_name, l)]
如上所述,可以使用以下方式渲染 UPDATE:
session.query(Person).filter(Person.id == 5).update({Person.name: "Dr. No"})
类似的功能也适用于复合属性,其中复合值将被拆分为其各个列以进行批量更新:
session.query(Vertex).update({Edge.start: Point(3, 4)})
另请参阅
允许批量 ORM 更新 ### 混合属性支持在子类之间重用,重新定义@getter
sqlalchemy.ext.hybrid.hybrid_property
类现在支持在子类之间多次调用像@setter
、@expression
等的变异器,并且现在提供了一个@getter
变异器,以便特定的混合属性可以在子类或其他类之间重新使用。这与标准 Python 中@property
的行为类似:
class FirstNameOnly(Base): # ... first_name = Column(String) @hybrid_property def name(self): return self.first_name @name.setter def name(self, value): self.first_name = value class FirstNameLastName(FirstNameOnly): # ... last_name = Column(String) @FirstNameOnly.name.getter def name(self): return self.first_name + " " + self.last_name @name.setter def name(self, value): self.first_name, self.last_name = value.split(" ", maxsplit=1) @name.expression def name(cls): return func.concat(cls.first_name, " ", cls.last_name)
在上面的示例中,FirstNameOnly.name
混合属性被FirstNameLastName
子类引用,以便将其专门用于新子类。这是通过在每次调用@getter
、@setter
以及所有其他变异器方法像@expression
中复制混合对象到新对象来实现的,从而保持先前混合属性的定义不变。以前,像@setter
这样的方法会直接修改现有的混合属性,干扰了超类上的定义。
注意
请务必阅读在子类之间重用混合属性的文档,了解如何覆盖hybrid_property.expression()
和hybrid_property.comparator()
的重要注意事项,因为在某些情况下,可能需要使用特殊限定符hybrid_property.overrides
来避免与QueryableAttribute
发生名称冲突。
注意
@hybrid_property
中的这种变化意味着,当向@hybrid_property
添加 setter 和其他状态时,方法必须保留原始混合属性的名称,否则具有附加状态的新混合属性将以不匹配的名称存在于类中。这与标准 Python 中的@property
构造的行为相同:
class FirstNameOnly(Base): @hybrid_property def name(self): return self.first_name # WRONG - will raise AttributeError: can't set attribute when # assigning to .name @name.setter def _set_name(self, value): self.first_name = value class FirstNameOnly(Base): @hybrid_property def name(self): return self.first_name # CORRECT - note regular Python @property works the same way @name.setter def name(self, value): self.first_name = value
#3912 ### 新的 bulk_replace 事件
为了适应 A @validates method receives all values on bulk-collection set before comparison 中描述的验证用例,添加了一个新的AttributeEvents.bulk_replace()
方法,该方法与AttributeEvents.append()
和AttributeEvents.remove()
事件一起调用。在比较现有集合之前调用“bulk_replace”,以便可以修改集合。之后,单个项目将附加到新的目标集合,触发为集合中的新项目触发的“append”事件,这与以前的行为相同。下面同时说明了“bulk_replace”和“append”,包括如果使用集合赋值,“append”将接收到已由“bulk_replace”处理的对象。新符号attributes.OP_BULK_REPLACE
可以用于确定此“append”事件是否是批量替换的第二部分:
from sqlalchemy.orm.attributes import OP_BULK_REPLACE @event.listens_for(SomeObject.collection, "bulk_replace") def process_collection(target, values, initiator): values[:] = [_make_value(value) for value in values] @event.listens_for(SomeObject.collection, "append", retval=True) def process_collection(target, value, initiator): # make sure bulk_replace didn't already do it if initiator is None or initiator.op is not OP_BULK_REPLACE: return _make_value(value) else: return value
#3896 ### 为 sqlalchemy.ext.mutable 添加了新的“modified”事件处理程序
添加了一个新的事件处理程序AttributeEvents.modified()
,该处理程序在对flag_modified()
方法的调用时触发,通常是从sqlalchemy.ext.mutable
扩展调用的。
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.mutable import MutableDict from sqlalchemy import event Base = declarative_base() class MyDataClass(Base): __tablename__ = "my_data" id = Column(Integer, primary_key=True) data = Column(MutableDict.as_mutable(JSONEncodedDict)) @event.listens_for(MyDataClass.data, "modified") def modified_json(instance): print("json value modified:", instance.data)
上述事件处理程序将在对.data
字典进行就地更改时触发。
#3303 ### 添加了对 Session.refresh 的“for update”参数
添加了新参数Session.refresh.with_for_update
到Session.refresh()
方法。当Query.with_lockmode()
方法被弃用,而倾向于Query.with_for_update()
时,Session.refresh()
方法从未更新以反映新选项:
session.refresh(some_object, with_for_update=True)
Session.refresh.with_for_update
参数现在接受一个选项字典,该字典将作为发送给Query.with_for_update()
的相同参数:
session.refresh(some_objects, with_for_update={"read": True})
新参数取代了Session.refresh.lockmode
参数。
#3991 ### 就地变异操作符适用于 MutableSet、MutableList
对于MutableSet
,我们实现了就地变异操作符__ior__
、__iand__
、__ixor__
和__isub__
,以及对于MutableList
的__iadd__
。虽然这些方法以前可以成功地更新集合,但它们不会正确地触发更改事件。这些操作符像以前一样改变集合,但额外地发出了正确的更改事件,以便更改成为下一个刷新进程的一部分:
model = session.query(MyModel).first() model.json_set &= {1, 3}
#3853 ### AssociationProxy any()、has()、contains()可以与链式关联代理一起使用
AssociationProxy.any()
、AssociationProxy.has()
和AssociationProxy.contains()
比较方法现在支持链接到一个属性,该属性本身也是一个AssociationProxy
,递归地。在下面的示例中,A.b_values
是一个关联到AtoB.bvalue
的关联代理,它本身是一个关联到B
的关联代理:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) b_values = association_proxy("atob", "b_value") c_values = association_proxy("atob", "c_value") class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) value = Column(String) c = relationship("C") class C(Base): __tablename__ = "c" id = Column(Integer, primary_key=True) b_id = Column(ForeignKey("b.id")) value = Column(String) class AtoB(Base): __tablename__ = "atob" a_id = Column(ForeignKey("a.id"), primary_key=True) b_id = Column(ForeignKey("b.id"), primary_key=True) a = relationship("A", backref="atob") b = relationship("B", backref="atob") b_value = association_proxy("b", "value") c_value = association_proxy("b", "c")
我们可以使用AssociationProxy.contains()
在A.b_values
上进行查询,以跨越两个代理A.b_values
,AtoB.b_value
:
>>> s.query(A).filter(A.b_values.contains("hi")).all() SELECT a.id AS a_id FROM a WHERE EXISTS (SELECT 1 FROM atob WHERE a.id = atob.a_id AND (EXISTS (SELECT 1 FROM b WHERE b.id = atob.b_id AND b.value = :value_1)))
类似地,我们可以使用AssociationProxy.any()
在A.c_values
上进行查询,以跨越两个代理A.c_values
,AtoB.c_value
:
>>> s.query(A).filter(A.c_values.any(value="x")).all() SELECT a.id AS a_id FROM a WHERE EXISTS (SELECT 1 FROM atob WHERE a.id = atob.a_id AND (EXISTS (SELECT 1 FROM b WHERE b.id = atob.b_id AND (EXISTS (SELECT 1 FROM c WHERE b.id = c.b_id AND c.value = :value_1)))))
#3769 ### 标识键增强以支持分片
现在,ORM 使用的标识键结构包含了一个额外的成员,以便来自不同上下文的两个相同的主键可以共存于同一个标识映射中。
水平分片 中的示例已更新以说明此行为。该示例显示了一个分片类 WeatherLocation
,引用一个依赖的 WeatherReport
对象,其中 WeatherReport
类映射到一个存储简单整数主键的表。来自不同数据库的两个 WeatherReport
对象可能具有相同的主键值。该示例现在说明了一个新的 identity_token
字段跟踪此差异,以便这两个对象可以共存于同一标识映射中:
tokyo = WeatherLocation("Asia", "Tokyo") newyork = WeatherLocation("North America", "New York") tokyo.reports.append(Report(80.0)) newyork.reports.append(Report(75)) sess = create_session() sess.add_all([tokyo, newyork, quito]) sess.commit() # the Report class uses a simple integer primary key. So across two # databases, a primary key will be repeated. The "identity_token" tracks # in memory that these two identical primary keys are local to different # databases. newyork_report = newyork.reports[0] tokyo_report = tokyo.reports[0] assert inspect(newyork_report).identity_key == (Report, (1,), "north_america") assert inspect(tokyo_report).identity_key == (Report, (1,), "asia") # the token representing the originating shard is also available directly assert inspect(newyork_report).identity_token == "north_america" assert inspect(tokyo_report).identity_token == "asia"
#4137 ### “烘焙”加载现在是延迟加载的默认设置
sqlalchemy.ext.baked
扩展首次在 1.0 系列中引入,允许构建所谓的 BakedQuery
对象,该对象生成一个与表示查询结构的缓存键相关联的 Query
对象;然后将此缓存键链接到生成的字符串 SQL 语句,以便后续使用具有相同结构的另一个 BakedQuery
将绕过构建 Query
对象的所有开销,构建其中的核心 select()
对象,以及将 select()
编译为字符串,从而削减通常与构建和发出 ORM Query
对象相关的大部分函数调用开销。
当 ORM 生成“延迟”查询以懒加载 relationship()
构造时,默认现在使用 BakedQuery
,例如默认的 lazy="select"
关系加载器策略。这将显著减少应用程序使用延迟加载查询加载集合和相关对象时的函数调用数量。以前,此功能在 1.0 和 1.1 中通过使用全局 API 方法或使用 baked_select
策略可用,现在是此行为的唯一实现。该功能还得到改进,使得对于具有延迟加载后生效的其他加载器选项的对象仍然可以进行缓存。
可以使用 relationship.bake_queries
标志在每个关系基础上禁用缓存行为,这对于非常不寻常的情况非常有用,例如使用不兼容缓存的自定义 Query
实现的关系。
新的 “selectin” 急切加载,一次加载所有集合使用 IN
添加了一个名为 “selectin” 加载的新急切加载器,从许多方面来看,它类似于 “subquery” 加载,但是生成了一个更简单的可缓存的 SQL 语句,而且更有效率。
给定如下查询:
q = ( session.query(User) .filter(User.name.like("%ed%")) .options(subqueryload(User.addresses)) )
生成的 SQL 将是针对 User
的查询,然后是 User.addresses
的 subqueryload(注意还列出了参数):
SELECT users.id AS users_id, users.name AS users_name FROM users WHERE users.name LIKE ? ('%ed%',) SELECT addresses.id AS addresses_id, addresses.user_id AS addresses_user_id, addresses.email_address AS addresses_email_address, anon_1.users_id AS anon_1_users_id FROM (SELECT users.id AS users_id FROM users WHERE users.name LIKE ?) AS anon_1 JOIN addresses ON anon_1.users_id = addresses.user_id ORDER BY anon_1.users_id ('%ed%',)
使用 “selectin” 加载,我们得到���是一个 SELECT,它引用了在父查询中加载的实际主键值:
q = ( session.query(User) .filter(User.name.like("%ed%")) .options(selectinload(User.addresses)) )
产生:
SELECT users.id AS users_id, users.name AS users_name FROM users WHERE users.name LIKE ? ('%ed%',) SELECT users_1.id AS users_1_id, addresses.id AS addresses_id, addresses.user_id AS addresses_user_id, addresses.email_address AS addresses_email_address FROM users AS users_1 JOIN addresses ON users_1.id = addresses.user_id WHERE users_1.id IN (?, ?) ORDER BY users_1.id (1, 3)
上述 SELECT 语句包括以下优点:
- 它不使用子查询,只是一个 INNER JOIN,这意味着在像 MySQL 这样不喜欢子查询的数据库上性能会更好
- 其结构独立于原始查询;与新的 扩展 IN 参数系统 结合,我们在大多数情况下可以使用 “baked” 查询来缓存字符串 SQL,显著减少每个查询的开销
- 由于查询仅为给定的主键标识符列表获取数据,“selectin” 加载可能与
Query.yield_per()
兼容,以便一次操作 SELECT 结果的一部分,前提是数据库驱动程序允许多个同时游标(SQLite,PostgreSQL;不是 MySQL 驱动程序或 SQL Server ODBC 驱动程序)。联接式急切加载和子查询急切加载都不兼容Query.yield_per()
。
selectin 急切加载的缺点是潜在的大型 SQL 查询,具有大量的 IN 参数列表。 IN 参数列表本身被分组为 500 个一组,因此超过 500 个 lead 对象的结果集将有更多的附加 “SELECT IN” 查询。此外,对复合主键的支持取决于数据库是否能够使用带有 IN 的元组,例如 (table.column_one, table_column_two) IN ((?, ?), (?, ?) (?, ?))
。目前,已知 PostgreSQL 和 MySQL 兼容此语法,SQLite 不兼容。
另请参见
选择 IN 加载
“selectin” 多态加载,使用单独的 IN 查询加载子类
与刚刚在新的“selectin”急加载,使用 IN 一次加载所有集合中描述的“selectin”关系加载功能类似的是“selectin”多态加载。这是一个主要针对连接式急加载的多态加载功能,允许基本实体的加载通过简单的 SELECT 语句进行,然后额外子类的属性通过额外的 SELECT 语句进行加载:
>>> from sqlalchemy.orm import selectin_polymorphic >>> query = session.query(Employee).options( ... selectin_polymorphic(Employee, [Manager, Engineer]) ... ) >>> query.all() SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type FROM employee () SELECT engineer.id AS engineer_id, employee.id AS employee_id, employee.type AS employee_type, engineer.engineer_name AS engineer_engineer_name FROM employee JOIN engineer ON employee.id = engineer.id WHERE employee.id IN (?, ?) ORDER BY employee.id (1, 2) SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name FROM employee JOIN manager ON employee.id = manager.id WHERE employee.id IN (?) ORDER BY employee.id (3,)
另请参阅
使用 selectin_polymorphic()
可接收临时 SQL 表达式的 ORM 属性
新的 ORM 属性类型query_expression()
被添加,类似于deferred()
,不同之处在于其 SQL 表达式在查询时使用新选项with_expression()
确定;如果未指定,则属性默认为None
:
from sqlalchemy.orm import query_expression from sqlalchemy.orm import with_expression class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) x = Column(Integer) y = Column(Integer) # will be None normally... expr = query_expression() # but let's give it x + y a1 = session.query(A).options(with_expression(A.expr, A.x + A.y)).first() print(a1.expr)
另请参阅
查询时 SQL 表达式作为映射属性
ORM 支持多表删除
ORM Query.delete()
方法支持多表条件的 DELETE,就像在支持多表条件的 DELETE 中介绍的那样。该功能与 0.8 中首次引入的 UPDATE 的多表条件相同,详细描述在 Query.update()支持 UPDATE…FROM 中。
下面,我们对SomeEntity
执行一个 DELETE 操作,添加一个 FROM 子句(或等效的,取决于后端)对SomeOtherEntity
:
query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter( SomeOtherEntity.foo == "bar" ).delete()
另请参阅
支持多表条件的 DELETE
支持混合属性,复合属性的批量更新
混合属性(例如sqlalchemy.ext.hybrid
)以及复合属性(复合列类型)现在都支持在使用Query.update()
时用于 UPDATE 语句的 SET 子句中。
对于混合属性,可以直接使用简单表达式,或者可以使用新的装饰器hybrid_property.update_expression()
将一个值分解为多个列/表达式:
class Person(Base): # ... first_name = Column(String(10)) last_name = Column(String(10)) @hybrid.hybrid_property def name(self): return self.first_name + " " + self.last_name @name.expression def name(cls): return func.concat(cls.first_name, " ", cls.last_name) @name.update_expression def name(cls, value): f, l = value.split(" ", 1) return [(cls.first_name, f), (cls.last_name, l)]
上面,一个 UPDATE 可以使用以下方式呈现:
session.query(Person).filter(Person.id == 5).update({Person.name: "Dr. No"})
类似的功能也适用于复合类型,其中复合值将被拆分为其各个列以进行批量更新:
session.query(Vertex).update({Edge.start: Point(3, 4)})
另请参阅
允许批量 ORM 更新
混合属性支持在子类之间重用,重新定义 @getter
sqlalchemy.ext.hybrid.hybrid_property
类现在支持在子类之间多次调用修改器,如 @setter
、@expression
等,并且现在提供了一个 @getter
修改器,以便可以在子类或其他类之间重新用特定的混合属性。这与标准 Python 中 @property
的行为类似:
class FirstNameOnly(Base): # ... first_name = Column(String) @hybrid_property def name(self): return self.first_name @name.setter def name(self, value): self.first_name = value class FirstNameLastName(FirstNameOnly): # ... last_name = Column(String) @FirstNameOnly.name.getter def name(self): return self.first_name + " " + self.last_name @name.setter def name(self, value): self.first_name, self.last_name = value.split(" ", maxsplit=1) @name.expression def name(cls): return func.concat(cls.first_name, " ", cls.last_name)
在上面的例子中,FirstNameOnly.name
混合属性被 FirstNameLastName
子类引用,以便将其专门重新用于新的子类。这是通过在每次调用 @getter
、@setter
以及所有其他修改器方法(如 @expression
)中将混合对象复制到一个新对象中来实现的,从而保持先前混合属性的定义不变。以前,像 @setter
这样的方法会就地修改现有的混合属性,干扰了超类上的定义。
注意
请务必阅读在子类之间重用混合属性的文档,了解如何覆盖hybrid_property.expression()
和 hybrid_property.comparator()
的重要注意事项,因为在某些情况下可能需要一个特殊的限定符 hybrid_property.overrides
来避免与 QueryableAttribute
的名称冲突。
注意
这种对 @hybrid_property
的更改意味着,当向 @hybrid_property
添加 setter 和其他状态时,方法必须保留原始混合属性的名称,否则新的带有额外状态的混合属性将以不匹配的名称存在于类中。这与标准 Python 中 @property
的行为相同:
class FirstNameOnly(Base): @hybrid_property def name(self): return self.first_name # WRONG - will raise AttributeError: can't set attribute when # assigning to .name @name.setter def _set_name(self, value): self.first_name = value class FirstNameOnly(Base): @hybrid_property def name(self): return self.first_name # CORRECT - note regular Python @property works the same way @name.setter def name(self, value): self.first_name = value
新的 bulk_replace 事件
为了适应 在批量集合设置之前比较时,@validates 方法接收所有值 中描述的验证用例,添加了一个新的 AttributeEvents.bulk_replace()
方法,它与 AttributeEvents.append()
和 AttributeEvents.remove()
事件一起调用。“bulk_replace” 在 “append” 和 “remove” 之前调用,以便在比较现有集合之前修改集合。之后,单个项目将被附加到新的目标集合,触发针对集合中新项目的 “append” 事件,就像以前的行为一样。下面同时说明了 “bulk_replace” 和 “append”,包括如果使用集合赋值,“append” 将接收到已由 “bulk_replace” 处理过的对象的情况。新的符号 attributes.OP_BULK_REPLACE
可以用于确定此 “append” 事件是否是批量替换的第二部分:
from sqlalchemy.orm.attributes import OP_BULK_REPLACE @event.listens_for(SomeObject.collection, "bulk_replace") def process_collection(target, values, initiator): values[:] = [_make_value(value) for value in values] @event.listens_for(SomeObject.collection, "append", retval=True) def process_collection(target, value, initiator): # make sure bulk_replace didn't already do it if initiator is None or initiator.op is not OP_BULK_REPLACE: return _make_value(value) else: return value
新的 sqlalchemy.ext.mutable 的“modified”事件处理器
新的事件处理器 AttributeEvents.modified()
被添加了,它会在调用 flag_modified()
方法时触发,通常这个方法是从 sqlalchemy.ext.mutable
扩展中调用的:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.mutable import MutableDict from sqlalchemy import event Base = declarative_base() class MyDataClass(Base): __tablename__ = "my_data" id = Column(Integer, primary_key=True) data = Column(MutableDict.as_mutable(JSONEncodedDict)) @event.listens_for(MyDataClass.data, "modified") def modified_json(instance): print("json value modified:", instance.data)
上面的事件处理程序将在对 .data
字典进行原位更改时触发。
添加了“for update”参数到 Session.refresh
向 Session.refresh()
方法添加了新参数 Session.refresh.with_for_update
。当 Query.with_lockmode()
方法被弃用,改用 Query.with_for_update()
后,Session.refresh()
方法从未更新以反映新选项:
session.refresh(some_object, with_for_update=True)
Session.refresh.with_for_update
参数接受一个选项字典,这些选项将作为传递给 Query.with_for_update()
的相同参数:
session.refresh(some_objects, with_for_update={"read": True})
新参数取代了 Session.refresh.lockmode
参数。
可变集合 MutableSet
和 可变列表 MutableList
支持原地变异操作符
为 MutableSet
实现了原地变异操作符 __ior__
、__iand__
、__ixor__
和 __isub__
,以及为 MutableList
实现了 __iadd__
。虽然这些方法以前可以成功更新集合,但它们不会正确触发更改事件。这些操作符像以前一样改变集合,但另外会发出正确的更改事件,以便更改成为下一个刷新过程的一部分:
model = session.query(MyModel).first() model.json_set &= {1, 3}
AssociationProxy
的 any()
、has()
和 contains()
方法可以与链式关联代理一起使用
AssociationProxy.any()
、AssociationProxy.has()
和 AssociationProxy.contains()
比较方法现在支持链接到一个属性,该属性本身也是一个 AssociationProxy
,递归地。下面,A.b_values
是一个关联代理,链接到 AtoB.bvalue
,而 AtoB.bvalue
本身是一个关联代理,链接到 B
:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) b_values = association_proxy("atob", "b_value") c_values = association_proxy("atob", "c_value") class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) value = Column(String) c = relationship("C") class C(Base): __tablename__ = "c" id = Column(Integer, primary_key=True) b_id = Column(ForeignKey("b.id")) value = Column(String) class AtoB(Base): __tablename__ = "atob" a_id = Column(ForeignKey("a.id"), primary_key=True) b_id = Column(ForeignKey("b.id"), primary_key=True) a = relationship("A", backref="atob") b = relationship("B", backref="atob") b_value = association_proxy("b", "value") c_value = association_proxy("b", "c")
我们可以使用 AssociationProxy.contains()
在 A.b_values
上进行查询,以跨越两个代理 A.b_values
、AtoB.b_value
:
>>> s.query(A).filter(A.b_values.contains("hi")).all() SELECT a.id AS a_id FROM a WHERE EXISTS (SELECT 1 FROM atob WHERE a.id = atob.a_id AND (EXISTS (SELECT 1 FROM b WHERE b.id = atob.b_id AND b.value = :value_1)))
类似地,我们可以使用 AssociationProxy.any()
在 A.c_values
上进行查询,以跨越两个代理 A.c_values
、AtoB.c_value
:
>>> s.query(A).filter(A.c_values.any(value="x")).all() SELECT a.id AS a_id FROM a WHERE EXISTS (SELECT 1 FROM atob WHERE a.id = atob.a_id AND (EXISTS (SELECT 1 FROM b WHERE b.id = atob.b_id AND (EXISTS (SELECT 1 FROM c WHERE b.id = c.b_id AND c.value = :value_1)))))
身份键增强以支持分片
ORM 现在使用的身份键结构包含一个额外成员,因此来自不同上下文的两个相同主键可以共存于同一身份映射中。
水平分片的示例已更新以说明这种行为。示例展示了一个分片类WeatherLocation
,它引用一个依赖的WeatherReport
对象,其中WeatherReport
类映射到一个存储简单整数主键的表。来自不同数据库的两个WeatherReport
对象可能具有相同的主键值。现在的示例说明了一个新的identity_token
字段跟踪这种差异,以便这两个对象可以共存于同一个标识映射中:
tokyo = WeatherLocation("Asia", "Tokyo") newyork = WeatherLocation("North America", "New York") tokyo.reports.append(Report(80.0)) newyork.reports.append(Report(75)) sess = create_session() sess.add_all([tokyo, newyork, quito]) sess.commit() # the Report class uses a simple integer primary key. So across two # databases, a primary key will be repeated. The "identity_token" tracks # in memory that these two identical primary keys are local to different # databases. newyork_report = newyork.reports[0] tokyo_report = tokyo.reports[0] assert inspect(newyork_report).identity_key == (Report, (1,), "north_america") assert inspect(tokyo_report).identity_key == (Report, (1,), "asia") # the token representing the originating shard is also available directly assert inspect(newyork_report).identity_token == "north_america" assert inspect(tokyo_report).identity_token == "asia"
SqlAlchemy 2.0 中文文档(七十四)(3)https://developer.aliyun.com/article/1562363