SqlAlchemy 2.0 中文文档(七十八)(3)

简介: SqlAlchemy 2.0 中文文档(七十八)

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_idfolder_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() 的最新技术进行了全面修订,详细说明了自定义相关属性和集合访问的最新技术。

#1401 #610 ### 新的类/对象检查系统

许多 SQLAlchemy 用户正在编写需要检查映射类的属性的系统,包括能够访问主键列、对象关系、普通属性等,通常是为了构建数据编组系统,如 JSON/XML 转换方案和各种表单库。

最初,TableColumn 模型是最初的检查点,拥有一个完全文档化的系统。虽然 SQLAlchemy ORM 模型也是完全可内省的,但这从未是一个完全稳定和受支持的特性,用户往往不清楚如何获取这些信息。

0.8 现在为此提供了一致、稳定且完全文档化的 API,包括适用于映射类、实例、属性和其他核心和 ORM 结构的检查系统。此系统的入口是核心级别的 inspect() 函数。在大多数情况下,被检查的对象已经是 SQLAlchemy 系统的一部分,比如 MapperInstanceStateInspector 等。在某些情况下,已经添加了新对象,用于在某些情境中提供检查 API,比如 AliasedInspAttributeState

以下是一些关键功能的介绍:

>>> 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

#2208

新的 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() - 用于多态加载控制的新更新文档。

#2333

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()实体

#2438 #1106

事件可以应用于未映射的超类

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"
    # ...

#2585

Declarative 区分模块/包

Declarative 的一个关键特性是能够通过它们的字符串名称引用其他映射类。类名注册表现在对给定类的所属模块和包敏感。可以通过点名在表达式中引用这些类:

class Snack(Base):
    # ...
    peanuts = relationship(
        "nuts.Peanut", primaryjoin="nuts.Peanut.snack_id == Snack.id"
    )

解析允许使用任何完整或部分消歧义的包名称。如果到特定类的路径仍然模糊不清,则会引发错误。

#2338

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

#2485

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

#2245

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'

#2365

rollback()仅会回滚从 begin_nested()开始的“脏”对象

通过Session.begin_nested()使用 SAVEPOINT 的用户,应该改变行为以提高效率 - 在rollback()时,只有自上次刷新以来被标记为脏的对象将会过期,其余的Session保持不变。这是因为对 SAVEPOINT 的 ROLLBACK 不会终止包含事务的隔离,因此除了当前事务中未刷新的更改外,不需要过期。

#2452

缓存示例现在使用 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 缓存

#2589

重写的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()的部分,详细介绍了定制相关属性和集合访问的最新技术。

#1401 #610

新的类/对象检查系统

许多 SQLAlchemy 用户正在编写需要检查映射类属性的系统,包括能够访问主键列、对象关系、普通属性等,通常用于构建数据编组系统,如 JSON/XML 转换方案和各种表单库。

最初,TableColumn模型是最初的检查点,具有良好记录的系统。虽然 SQLAlchemy ORM 模型也是完全可自省的,但这从未是一个完全稳定和受支持的功能,用户往往不清楚如何获取这些信息。

现在,0.8 版本为此提供了一致、稳定且完全文档化的 API,包括一个检查系统,可用于映射类、实例、属性和其他核心和 ORM 构造。 这个系统的入口是核心级别的inspect()函数。 在大多数情况下,被检查的对象已经是 SQLAlchemy 系统的一部分,比如MapperInstanceStateInspector等。 在某些情况下,已添加了新对象,用于在某些上下文中提供检查 API,比如AliasedInspAttributeState

以下是一些关键功能的演示:

>>> 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

#2208

新的 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() - 用于多态加载控制的新更新文档。

#2333

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() 实体

#2438 #1106

事件可以应用于未映射的超类

现在可以将 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"
    # ...

#2585

声明式区分模块/包

声明式的一个关键特性是能够使用其字符串名称引用其他映射类。现在,类名的注册表对给定类的拥有模块和包是敏感的。可以通过表达式中的点名引用这些类:

class Snack(Base):
    # ...
    peanuts = relationship(
        "nuts.Peanut", primaryjoin="nuts.Peanut.snack_id == Snack.id"
    )

解析允许使用任何全名或部分消除歧义的包名称。如果对特定类的路径仍然不明确,将会引发错误。

#2338

声明式中的新延迟反射功能

“延迟反射”示例已移至声明式中的支持功能。此功能允许仅使用占位符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

#2485

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类将扩展为TableUser映射到其中的表。

#2245

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'

#2365

rollback()将仅回滚从 begin_nested()开始的“脏”对象

一项行为变更应该提高那些通过Session.begin_nested()使用 SAVEPOINT 的用户的效率 - 在rollback()时,只有自上次刷新以来被标记为脏的对象将被过期,其余的Session保持不变。这是因为对 SAVEPOINT 的 ROLLBACK 不会终止包含事务的隔离,因此除了当前事务中未刷新的更改外,不需要过期。

#2452

缓存示例现在使用 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

#2589


SqlAlchemy 2.0 中文文档(七十八)(4)https://developer.aliyun.com/article/1560967

相关文章
|
6月前
|
网络协议 NoSQL Redis
Docker Compose--模板文件
Docker Compose--模板文件
386 29
|
11月前
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
962 69
利用Spring Cloud Gateway Predicate优化微服务路由策略
|
11月前
|
存储 监控 供应链
微服务拆分的 “坑”:实战复盘与避坑指南
本文回顾了从2~3人初创团队到百人技术团队的成长历程,重点讨论了从传统JSP到前后端分离+SpringCloud微服务架构的演变。通过实际案例,总结了微服务拆分过程中常见的两个问题:服务拆分边界不清晰和拆分粒度过细,并提出了优化方案,将11个微服务优化为6个,提高了系统的可维护性和扩展性。
252 0
|
10月前
|
Java 关系型数据库 数据库
微服务SpringCloud分布式事务之Seata
SpringCloud+SpringCloudAlibaba的Seata实现分布式事务,步骤超详细,附带视频教程
782 1
|
NoSQL Linux MongoDB
MongoDB配置用户名和密码
MongoDB配置用户名和密码
2394 0
|
开发者 Python
新手困扰?该如何了解github的热门趋势和star排行?
新手困扰?该如何了解github的热门趋势和star排行?
894 0
|
分布式计算 Hadoop Shell
使用Shell命令操作HDFS
【8月更文挑战第5天】使用Shell命令操作HDFS。
365 5
|
数据采集 Python
|
SQL 关系型数据库 API
SQLAlchemy 2.0 中文文档翻译完成
SQLAlchemy 2.0 中文文档翻译完成
437 1
|
安全 Linux Shell
Linux服务器 /etc/pam.d目录下的文件都是什么作用?
【8月更文挑战第2天】Linux服务器 /etc/pam.d目录下的文件都是什么作用?
1741 2