SqlAlchemy 2.0 中文文档(七十八)(2)https://developer.aliyun.com/article/1560957
新的 ORM 特性
重写的 relationship() 机制
0.8 版本中关于 relationship() 如何确定如何在两个实体之间连接的能力得到了大大改进和增强。新系统包括以下功能:
- 当构建针对具有多个外键路径指向目标的类的
relationship()时,不再需要primaryjoin参数。只需要使用foreign_keys参数来指定应包含的列即可:
class Parent(Base): __tablename__ = "parent" id = Column(Integer, primary_key=True) child_id_one = Column(Integer, ForeignKey("child.id")) child_id_two = Column(Integer, ForeignKey("child.id")) child_one = relationship("Child", foreign_keys=child_id_one) child_two = relationship("Child", foreign_keys=child_id_two) class Child(Base): __tablename__ = "child" id = Column(Integer, primary_key=True)
- 对于自引用、复合外键的关系,在其中一个列指向自身的情况下,现在已经得到支持。典型案例如下:
class Folder(Base): __tablename__ = "folder" __table_args__ = ( ForeignKeyConstraint( ["account_id", "parent_id"], ["folder.account_id", "folder.folder_id"] ), ) account_id = Column(Integer, primary_key=True) folder_id = Column(Integer, primary_key=True) parent_id = Column(Integer) name = Column(String) parent_folder = relationship( "Folder", backref="child_folders", remote_side=[account_id, folder_id] )
- 上面的示例中,
Folder引用了其父Folder,从account_id到自身的连接,并从parent_id到folder_id。当 SQLAlchemy 构造自动连接时,不再假设“远程”一侧的所有列都被别名化,并且“本地”一侧的所有列都没有被别名化 -account_id列在两侧都存在。因此,内部关系机制被完全重写,以支持一种完全不同的系统,其中生成了两个account_id的副本,每个副本包含不同的注释以确定它们在语句中的角色。注意基本急加载中的连接条件:
SELECT folder.account_id AS folder_account_id, folder.folder_id AS folder_folder_id, folder.parent_id AS folder_parent_id, folder.name AS folder_name, folder_1.account_id AS folder_1_account_id, folder_1.folder_id AS folder_1_folder_id, folder_1.parent_id AS folder_1_parent_id, folder_1.name AS folder_1_name FROM folder LEFT OUTER JOIN folder AS folder_1 ON folder_1.account_id = folder.account_id AND folder.folder_id = folder_1.parent_id WHERE folder.folder_id = ? AND folder.account_id = ?
- 以前难以处理的自定义连接条件,比如涉及函数和/或类型转换的条件,现在在大多数情况下将按预期运行:
class HostEntry(Base): __tablename__ = "host_entry" id = Column(Integer, primary_key=True) ip_address = Column(INET) content = Column(String(50)) # relationship() using explicit foreign_keys, remote_side parent_host = relationship( "HostEntry", primaryjoin=ip_address == cast(content, INET), foreign_keys=content, remote_side=ip_address, )
- 新的
relationship()机制利用了 SQLAlchemy 中称为 annotations 的概念。这些注释也可以通过foreign()和remote()函数显式地提供给应用程序代码,作为改善高级配置的可读性的手段,或者直接注入精确配置,绕过通常的连接检查启发式方法:
from sqlalchemy.orm import foreign, remote class HostEntry(Base): __tablename__ = "host_entry" id = Column(Integer, primary_key=True) ip_address = Column(INET) content = Column(String(50)) # relationship() using explicit foreign() and remote() annotations # in lieu of separate arguments parent_host = relationship( "HostEntry", primaryjoin=remote(ip_address) == cast(foreign(content), INET), )
另请参阅
配置关系连接方式 - 对于 relationship() 的最新技术进行了全面修订,详细说明了自定义相关属性和集合访问的最新技术。
许多 SQLAlchemy 用户正在编写需要检查映射类的属性的系统,包括能够访问主键列、对象关系、普通属性等,通常是为了构建数据编组系统,如 JSON/XML 转换方案和各种表单库。
最初,Table 和 Column 模型是最初的检查点,拥有一个完全文档化的系统。虽然 SQLAlchemy ORM 模型也是完全可内省的,但这从未是一个完全稳定和受支持的特性,用户往往不清楚如何获取这些信息。
0.8 现在为此提供了一致、稳定且完全文档化的 API,包括适用于映射类、实例、属性和其他核心和 ORM 结构的检查系统。此系统的入口是核心级别的 inspect() 函数。在大多数情况下,被检查的对象已经是 SQLAlchemy 系统的一部分,比如 Mapper、InstanceState、Inspector 等。在某些情况下,已经添加了新对象,用于在某些情境中提供检查 API,比如 AliasedInsp 和 AttributeState。
以下是一些关键功能的介绍:
>>> class User(Base): ... __tablename__ = "user" ... id = Column(Integer, primary_key=True) ... name = Column(String) ... name_syn = synonym(name) ... addresses = relationship("Address") >>> # universal entry point is inspect() >>> b = inspect(User) >>> # b in this case is the Mapper >>> b <Mapper at 0x101521950; User> >>> # Column namespace >>> b.columns.id Column('id', Integer(), table=<user>, primary_key=True, nullable=False) >>> # mapper's perspective of the primary key >>> b.primary_key (Column('id', Integer(), table=<user>, primary_key=True, nullable=False),) >>> # MapperProperties available from .attrs >>> b.attrs.keys() ['name_syn', 'addresses', 'id', 'name'] >>> # .column_attrs, .relationships, etc. filter this collection >>> b.column_attrs.keys() ['id', 'name'] >>> list(b.relationships) [<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>] >>> # they are also namespaces >>> b.column_attrs.id <sqlalchemy.orm.properties.ColumnProperty object at 0x101525090> >>> b.relationships.addresses <sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0> >>> # point inspect() at a mapped, class level attribute, >>> # returns the attribute itself >>> b = inspect(User.addresses) >>> b <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0> >>> # From here we can get the mapper: >>> b.mapper <Mapper at 0x101525810; Address> >>> # the parent inspector, in this case a mapper >>> b.parent <Mapper at 0x101521950; User> >>> # an expression >>> print(b.expression) "user".id = address.user_id >>> # inspect works on instances >>> u1 = User(id=3, name="x") >>> b = inspect(u1) >>> # it returns the InstanceState >>> b <sqlalchemy.orm.state.InstanceState object at 0x10152bed0> >>> # similar attrs accessor refers to the >>> b.attrs.keys() ['id', 'name_syn', 'addresses', 'name'] >>> # attribute interface - from attrs, you get a state object >>> b.attrs.id <sqlalchemy.orm.state.AttributeState object at 0x10152bf90> >>> # this object can give you, current value... >>> b.attrs.id.value 3 >>> # ... current history >>> b.attrs.id.history History(added=[3], unchanged=(), deleted=()) >>> # InstanceState can also provide session state information >>> # lets assume the object is persistent >>> s = Session() >>> s.add(u1) >>> s.commit() >>> # now we can get primary key identity, always >>> # works in query.get() >>> b.identity (3,) >>> # the mapper level key >>> b.identity_key (<class '__main__.User'>, (3,)) >>> # state within the session >>> b.persistent, b.transient, b.deleted, b.detached (True, False, False, False) >>> # owning session >>> b.session <sqlalchemy.orm.session.Session object at 0x101701150>
另请参阅
运行时检查 API
新的 with_polymorphic() 功能,可在任何地方使用
Query.with_polymorphic() 方法允许用户指定在针对连接表实体进行查询时应该存在哪些表。不幸的是,该方法很笨拙,仅适用于列表中的第一个实体,并且在使用和内部方面都有令人困扰的行为。已添加了一个名为 with_polymorphic() 的新增强功能,可以将任何实体“别名化”为其自身的“多态”版本,可在任何地方自由使用:
from sqlalchemy.orm import with_polymorphic palias = with_polymorphic(Person, [Engineer, Manager]) session.query(Company).join(palias, Company.employees).filter( or_(Engineer.language == "java", Manager.hair == "pointy") )
另请参阅
使用 with_polymorphic() - 用于多态加载控制的新更新文档。
of_type() 与 alias()、with_polymorphic()、any()、has()、joinedload()、subqueryload()、contains_eager() 一起使用
PropComparator.of_type()方法用于在构建 SQL 表达式时指定要使用的特定子类型,该子类型作为relationship()的目标具有多态映射。现在可以通过与新的with_polymorphic()函数结合使用该方法来定位任意数量的目标子类型:
# use eager loading in conjunction with with_polymorphic targets Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True) q = ( s.query(DataContainer) .join(DataContainer.jobs.of_type(Job_P)) .options(contains_eager(DataContainer.jobs.of_type(Job_P))) )
该方法现在在大多数接受常规关系属性的地方同样有效,包括与加载器函数一起使用,如joinedload()、subqueryload()、contains_eager()以及比较方法,如PropComparator.any()和PropComparator.has():
# use eager loading in conjunction with with_polymorphic targets Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True) q = ( s.query(DataContainer) .join(DataContainer.jobs.of_type(Job_P)) .options(contains_eager(DataContainer.jobs.of_type(Job_P))) ) # pass subclasses to eager loads (implicitly applies with_polymorphic) q = s.query(ParentThing).options( joinedload_all(ParentThing.container, DataContainer.jobs.of_type(SubJob)) ) # control self-referential aliasing with any()/has() Job_A = aliased(Job) q = ( s.query(Job) .join(DataContainer.jobs) .filter( DataContainer.jobs.of_type(Job_A).any( and_(Job_A.id < Job.id, Job_A.type == "fred") ) ) )
另请参阅
连接到特定子类型或 with_polymorphic()实体
事件可以应用于未映射的超类
Mapper 和实例事件现在可以与未映射的超类关联,这些事件将随着子类被映射而传播。应该使用propagate=True标志。此功能允许将事件与声明基类关联:
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @event.listens_for("load", Base, propagate=True) def on_load(target, context): print("New instance loaded:", target) # on_load() will be applied to SomeClass class SomeClass(Base): __tablename__ = "sometable" # ...
Declarative 区分模块/包
Declarative 的一个关键特性是能够通过它们的字符串名称引用其他映射类。类名注册表现在对给定类的所属模块和包敏感。可以通过点名在表达式中引用这些类:
class Snack(Base): # ... peanuts = relationship( "nuts.Peanut", primaryjoin="nuts.Peanut.snack_id == Snack.id" )
解析允许使用任何完整或部分消歧义的包名称。如果到特定类的路径仍然模糊不清,则会引发错误。
Declarative 中的新 DeferredReflection 功能
“延迟反射”示例已移至 Declarative 中的一个支持功能。该功能允许仅使用占位符Table元数据构建声明性映射类,直到调用prepare()步骤,给定一个Engine以完全反映所有表并建立实际映射。该系统支持列的覆盖,单一和联合继承,以及每个引擎的不同基础。现在可以在一个步骤中在引擎创建时针对现有表创建完整的声明性配置:
class ReflectedOne(DeferredReflection, Base): __abstract__ = True class ReflectedTwo(DeferredReflection, Base): __abstract__ = True class MyClass(ReflectedOne): __tablename__ = "mytable" class MyOtherClass(ReflectedOne): __tablename__ = "myothertable" class YetAnotherClass(ReflectedTwo): __tablename__ = "yetanothertable" ReflectedOne.prepare(engine_one) ReflectedTwo.prepare(engine_two)
另请参见
DeferredReflection
ORM 类现在被核心构造所接受
虽然与Query.filter()一起使用的 SQL 表达式,例如User.id == 5,一直与核心构造(如select())兼容,但传递给select()、Select.select_from()或Select.correlate()时,映射类本身将不被识别。一个新的 SQL 注册系统允许一个映射类作为核心中的 FROM 子句被接受:
from sqlalchemy import select stmt = select([User]).where(User.id == 5)
上面,映射的User类将扩展为User映射到的Table。
Query.update()支持 UPDATE…FROM
新的 UPDATE…FROM 机制在 query.update()中起作用。下面,我们对SomeEntity发出一个 UPDATE,添加一个 FROM 子句(或等效的,取决于后端)对SomeOtherEntity:
query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter( SomeOtherEntity.foo == "bar" ).update({"data": "x"})
特别是,支持对联合继承实体的更新,前提是 UPDATE 的目标是本地表上的,或者如果父表和子表混合,则它们在查询中明确连接。下面,以Engineer作为Person的联合子类:
query(Engineer).filter(Person.id == Engineer.id).filter( Person.name == "dilbert" ).update({"engineer_data": "java"})
会产生:
UPDATE engineer SET engineer_data='java' FROM person WHERE person.id=engineer.id AND person.name='dilbert'
rollback()仅会回滚从 begin_nested()开始的“脏”对象
通过Session.begin_nested()使用 SAVEPOINT 的用户,应该改变行为以提高效率 - 在rollback()时,只有自上次刷新以来被标记为脏的对象将会过期,其余的Session保持不变。这是因为对 SAVEPOINT 的 ROLLBACK 不会终止包含事务的隔离,因此除了当前事务中未刷新的更改外,不需要过期。
缓存示例现在使用 dogpile.cache
缓存示例现在使用dogpile.cache。Dogpile.cache 是 Beaker 缓存部分的重写,具有更简单和更快的操作,以及对分布式锁定的支持。
请注意,Dogpile 示例以及之前的 Beaker 示例中使用的 SQLAlchemy API 略有变化,特别是在 Beaker 示例中所示的这种变化是必要的:
--- examples/beaker_caching/caching_query.py +++ examples/beaker_caching/caching_query.py @@ -222,7 +222,8 @@ """ if query._current_path: - mapper, key = query._current_path[-2:] + mapper, prop = query._current_path[-2:] + key = prop.key for cls in mapper.class_.__mro__: if (cls, key) in self._relationship_options:
另请参阅
Dogpile 缓存
重写的relationship()机制
0.8 版本在relationship()确定如何在两个实体之间连接方面具有更加改进和强大的系统。新系统包括以下功能:
- 当针对具有多个到目标的外键路径的类构建
relationship()时,不再需要primaryjoin参数。只需要使用foreign_keys参数来指定应包含的列:
class Parent(Base): __tablename__ = "parent" id = Column(Integer, primary_key=True) child_id_one = Column(Integer, ForeignKey("child.id")) child_id_two = Column(Integer, ForeignKey("child.id")) child_one = relationship("Child", foreign_keys=child_id_one) child_two = relationship("Child", foreign_keys=child_id_two) class Child(Base): __tablename__ = "child" id = Column(Integer, primary_key=True)
- 支持自引用、复合外键的关系,其中一列指向自身。典型案例如下:
class Folder(Base): __tablename__ = "folder" __table_args__ = ( ForeignKeyConstraint( ["account_id", "parent_id"], ["folder.account_id", "folder.folder_id"] ), ) account_id = Column(Integer, primary_key=True) folder_id = Column(Integer, primary_key=True) parent_id = Column(Integer) name = Column(String) parent_folder = relationship( "Folder", backref="child_folders", remote_side=[account_id, folder_id] )
- 在上面的示例中,
Folder指向其父Folder,从account_id连接到自身,并且从parent_id连接到folder_id。当 SQLAlchemy 构建自动连接时,不能再假定“远程”一侧的所有列都被别名化,而“本地”一侧的所有列都没有被别名化 -account_id列在两侧都存在。因此,内部关系机制被完全重写以支持一个完全不同的系统,其中生成了两个account_id的副本,每个包含不同的注释以确定它们在语句中的角色。请注意基本贪婪加载中的连接条件:
SELECT folder.account_id AS folder_account_id, folder.folder_id AS folder_folder_id, folder.parent_id AS folder_parent_id, folder.name AS folder_name, folder_1.account_id AS folder_1_account_id, folder_1.folder_id AS folder_1_folder_id, folder_1.parent_id AS folder_1_parent_id, folder_1.name AS folder_1_name FROM folder LEFT OUTER JOIN folder AS folder_1 ON folder_1.account_id = folder.account_id AND folder.folder_id = folder_1.parent_id WHERE folder.folder_id = ? AND folder.account_id = ?
- 以前难以处理的自定义连接条件,例如涉及函数和/或类型转换的情况,现在在大多数情况下将按预期运行:
class HostEntry(Base): __tablename__ = "host_entry" id = Column(Integer, primary_key=True) ip_address = Column(INET) content = Column(String(50)) # relationship() using explicit foreign_keys, remote_side parent_host = relationship( "HostEntry", primaryjoin=ip_address == cast(content, INET), foreign_keys=content, remote_side=ip_address, )
- 新的
relationship()机制利用了 SQLAlchemy 中称为 annotations 的概念。这些注释也可以通过foreign()和remote()函数显式地提供给应用程序代码,作为改进高级配置的手段或直接注入精确配置的方式,绕过通常的联接检查启发式算法:
from sqlalchemy.orm import foreign, remote class HostEntry(Base): __tablename__ = "host_entry" id = Column(Integer, primary_key=True) ip_address = Column(INET) content = Column(String(50)) # relationship() using explicit foreign() and remote() annotations # in lieu of separate arguments parent_host = relationship( "HostEntry", primaryjoin=remote(ip_address) == cast(foreign(content), INET), )
另请参阅
配置关系连接方式 - 一个新修订的关于relationship()的部分,详细介绍了定制相关属性和集合访问的最新技术。
新的类/对象检查系统
许多 SQLAlchemy 用户正在编写需要检查映射类属性的系统,包括能够访问主键列、对象关系、普通属性等,通常用于构建数据编组系统,如 JSON/XML 转换方案和各种表单库。
最初,Table和Column模型是最初的检查点,具有良好记录的系统。虽然 SQLAlchemy ORM 模型也是完全可自省的,但这从未是一个完全稳定和受支持的功能,用户往往不清楚如何获取这些信息。
现在,0.8 版本为此提供了一致、稳定且完全文档化的 API,包括一个检查系统,可用于映射类、实例、属性和其他核心和 ORM 构造。 这个系统的入口是核心级别的inspect()函数。 在大多数情况下,被检查的对象已经是 SQLAlchemy 系统的一部分,比如Mapper、InstanceState、Inspector等。 在某些情况下,已添加了新对象,用于在某些上下文中提供检查 API,比如AliasedInsp和AttributeState。
以下是一些关键功能的演示:
>>> class User(Base): ... __tablename__ = "user" ... id = Column(Integer, primary_key=True) ... name = Column(String) ... name_syn = synonym(name) ... addresses = relationship("Address") >>> # universal entry point is inspect() >>> b = inspect(User) >>> # b in this case is the Mapper >>> b <Mapper at 0x101521950; User> >>> # Column namespace >>> b.columns.id Column('id', Integer(), table=<user>, primary_key=True, nullable=False) >>> # mapper's perspective of the primary key >>> b.primary_key (Column('id', Integer(), table=<user>, primary_key=True, nullable=False),) >>> # MapperProperties available from .attrs >>> b.attrs.keys() ['name_syn', 'addresses', 'id', 'name'] >>> # .column_attrs, .relationships, etc. filter this collection >>> b.column_attrs.keys() ['id', 'name'] >>> list(b.relationships) [<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>] >>> # they are also namespaces >>> b.column_attrs.id <sqlalchemy.orm.properties.ColumnProperty object at 0x101525090> >>> b.relationships.addresses <sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0> >>> # point inspect() at a mapped, class level attribute, >>> # returns the attribute itself >>> b = inspect(User.addresses) >>> b <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0> >>> # From here we can get the mapper: >>> b.mapper <Mapper at 0x101525810; Address> >>> # the parent inspector, in this case a mapper >>> b.parent <Mapper at 0x101521950; User> >>> # an expression >>> print(b.expression) "user".id = address.user_id >>> # inspect works on instances >>> u1 = User(id=3, name="x") >>> b = inspect(u1) >>> # it returns the InstanceState >>> b <sqlalchemy.orm.state.InstanceState object at 0x10152bed0> >>> # similar attrs accessor refers to the >>> b.attrs.keys() ['id', 'name_syn', 'addresses', 'name'] >>> # attribute interface - from attrs, you get a state object >>> b.attrs.id <sqlalchemy.orm.state.AttributeState object at 0x10152bf90> >>> # this object can give you, current value... >>> b.attrs.id.value 3 >>> # ... current history >>> b.attrs.id.history History(added=[3], unchanged=(), deleted=()) >>> # InstanceState can also provide session state information >>> # lets assume the object is persistent >>> s = Session() >>> s.add(u1) >>> s.commit() >>> # now we can get primary key identity, always >>> # works in query.get() >>> b.identity (3,) >>> # the mapper level key >>> b.identity_key (<class '__main__.User'>, (3,)) >>> # state within the session >>> b.persistent, b.transient, b.deleted, b.detached (True, False, False, False) >>> # owning session >>> b.session <sqlalchemy.orm.session.Session object at 0x101701150>
另请参阅
运行时检查 API
新的 with_polymorphic()功能,可在任何地方使用
Query.with_polymorphic()方法允许用户指定在针对联接表实体进行查询时应该存在哪些表。 不幸的是,该方法很笨拙,只适用于列表中的第一个实体,否则在使用和内部方面都有一些尴尬的行为。 已添加了一个名为with_polymorphic()的新增强功能,它允许任何实体“别名”为其自身的“多态”版本,可自由在任何地方使用:
from sqlalchemy.orm import with_polymorphic palias = with_polymorphic(Person, [Engineer, Manager]) session.query(Company).join(palias, Company.employees).filter( or_(Engineer.language == "java", Manager.hair == "pointy") )
另请参阅
使用 with_polymorphic() - 用于多态加载控制的新更新文档。
of_type() 与 alias()、with_polymorphic()、any()、has()、joinedload()、subqueryload()、contains_eager()一起使用
PropComparator.of_type()方法用于在构建 SQL 表达式时指定要使用的特定子类型,该表达式沿着具有多态映射作为目标的relationship()。 现在,可以通过与新的with_polymorphic()函数结合使用,来指定任意数量的目标子类型:
# use eager loading in conjunction with with_polymorphic targets Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True) q = ( s.query(DataContainer) .join(DataContainer.jobs.of_type(Job_P)) .options(contains_eager(DataContainer.jobs.of_type(Job_P))) )
此方法现在在大多数常规关系属性接受的地方同样有效,包括与加载器函数一起使用,如joinedload()、subqueryload()、contains_eager(),以及比较方法如PropComparator.any() 和 PropComparator.has():
# use eager loading in conjunction with with_polymorphic targets Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True) q = ( s.query(DataContainer) .join(DataContainer.jobs.of_type(Job_P)) .options(contains_eager(DataContainer.jobs.of_type(Job_P))) ) # pass subclasses to eager loads (implicitly applies with_polymorphic) q = s.query(ParentThing).options( joinedload_all(ParentThing.container, DataContainer.jobs.of_type(SubJob)) ) # control self-referential aliasing with any()/has() Job_A = aliased(Job) q = ( s.query(Job) .join(DataContainer.jobs) .filter( DataContainer.jobs.of_type(Job_A).any( and_(Job_A.id < Job.id, Job_A.type == "fred") ) ) )
另请参阅
连接到特定子类型或 with_polymorphic() 实体
事件可以应用于未映射的超类
现在可以将 Mapper 和实例事件与未映射的超类关联,这些事件将传播到子类中,当这些子类被映射时。应该使用propagate=True标志。此功能允许将事件与声明式基类关联起来:
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @event.listens_for("load", Base, propagate=True) def on_load(target, context): print("New instance loaded:", target) # on_load() will be applied to SomeClass class SomeClass(Base): __tablename__ = "sometable" # ...
声明式区分模块/包
声明式的一个关键特性是能够使用其字符串名称引用其他映射类。现在,类名的注册表对给定类的拥有模块和包是敏感的。可以通过表达式中的点名引用这些类:
class Snack(Base): # ... peanuts = relationship( "nuts.Peanut", primaryjoin="nuts.Peanut.snack_id == Snack.id" )
解析允许使用任何全名或部分消除歧义的包名称。如果对特定类的路径仍然不明确,将会引发错误。
声明式中的新延迟反射功能
“延迟反射”示例已移至声明式中的支持功能。此功能允许仅使用占位符Table元数据构建声明式映射类,直到调用prepare()步骤,并提供一个Engine以完全反射所有表并建立实际映射为止。该系统支持列的重写、单一和连接继承,以及每个引擎的不同基类。现在可以一次性在引擎创建时针对现有表创建完整的声明式配置:
class ReflectedOne(DeferredReflection, Base): __abstract__ = True class ReflectedTwo(DeferredReflection, Base): __abstract__ = True class MyClass(ReflectedOne): __tablename__ = "mytable" class MyOtherClass(ReflectedOne): __tablename__ = "myothertable" class YetAnotherClass(ReflectedTwo): __tablename__ = "yetanothertable" ReflectedOne.prepare(engine_one) ReflectedTwo.prepare(engine_two)
另请参阅
DeferredReflection
ORM 类现在被核心构造所接受
虽然与Query.filter()一起使用的 SQL 表达式,例如User.id == 5,一直与核心构造兼容,例如select(),但当传递给select(),Select.select_from()或Select.correlate()时,映射类本身将不被识别。一个新的 SQL 注册系统允许映射类作为核心中的 FROM 子句被接受:
from sqlalchemy import select stmt = select([User]).where(User.id == 5)
上面,映射的User类将扩展为Table,User映射到其中的表。
Query.update()支持 UPDATE…FROM
新的 UPDATE…FROM 机制适用于 query.update()。下面,我们对SomeEntity发出一个带有 FROM 子句(或等效的,取决于后端)的 UPDATE:
query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter( SomeOtherEntity.foo == "bar" ).update({"data": "x"})
特别是,支持对连接继承实体的更新,前提是 UPDATE 的目标是过滤表上的本地表,或者如果父表和子表混合,它们在查询中明确连接。下面,给定Engineer作为Person的连接子类:
query(Engineer).filter(Person.id == Engineer.id).filter( Person.name == "dilbert" ).update({"engineer_data": "java"})
会产生:
UPDATE engineer SET engineer_data='java' FROM person WHERE person.id=engineer.id AND person.name='dilbert'
rollback()将仅回滚从 begin_nested()开始的“脏”对象
一项行为变更应该提高那些通过Session.begin_nested()使用 SAVEPOINT 的用户的效率 - 在rollback()时,只有自上次刷新以来被标记为脏的对象将被过期,其余的Session保持不变。这是因为对 SAVEPOINT 的 ROLLBACK 不会终止包含事务的隔离,因此除了当前事务中未刷新的更改外,不需要过期。
缓存示例现在使用 dogpile.cache
缓存示例现在使用dogpile.cache。Dogpile.cache 是 Beaker 缓存部分的重写,具有更简单和更快的操作,以及对分布式锁定的支持。
注意,Dogpile 示例以及之前的 Beaker 示例中使用的 SQLAlchemy API 略有变化,特别是正如 Beaker 示例中所示,这种变化是必要的:
--- examples/beaker_caching/caching_query.py +++ examples/beaker_caching/caching_query.py @@ -222,7 +222,8 @@ """ if query._current_path: - mapper, key = query._current_path[-2:] + mapper, prop = query._current_path[-2:] + key = prop.key for cls in mapper.class_.__mro__: if (cls, key) in self._relationship_options:
另请参见
Dogpile Caching
SqlAlchemy 2.0 中文文档(七十八)(4)https://developer.aliyun.com/article/1560967