首页 > 数据库 >SqlAlchemy-2-0-中文文档-十六-

SqlAlchemy-2-0-中文文档-十六-

时间:2024-06-22 11:42:50浏览次数:36  
标签:__ 中文 SqlAlchemy name 映射 Column Base 文档 id

SqlAlchemy 2.0 中文文档(十六)

原文:docs.sqlalchemy.org/en/20/contents.html

Automap

原文:docs.sqlalchemy.org/en/20/orm/extensions/automap.html

定义一个扩展到sqlalchemy.ext.declarative系统的系统,自动生成从数据库模式到映射类和关系,通常而不一定是一个反射的数据库模式。

希望AutomapBase系统提供了一个快速和现代化的解决方案,解决了非常著名的SQLSoup也试图解决的问题,即从现有数据库动态生成快速和基本的对象模型。通过严格在映射器配置级别解决该问题,并与现有的声明类技术完全集成,AutomapBase试图提供一个与问题紧密集成的方法,以迅速自动生成临时映射。

提示

Automap 扩展针对“零声明”方法,其中可以从数据库模式动态生成包括类和预命名关系在内的完整 ORM 模型。对于仍希望使用显式类声明以及与表反射结合使用的显式关系定义的应用程序,描述在使用 DeferredReflection 中的DeferredReflection类是更好的选择。

基本用法

最简单的用法是将现有数据库反映到一个新模型中。我们创建一个新的AutomapBase类,方式类似于我们创建声明性基类,使用automap_base()。然后,我们调用AutomapBase.prepare()在生成的基类上,要求它反映模式并生成映射:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy import create_engine

Base = automap_base()

# engine, suppose it has two tables 'user' and 'address' set up
engine = create_engine("sqlite:///mydatabase.db")

# reflect the tables
Base.prepare(autoload_with=engine)

# mapped classes are now created with names by default
# matching that of the table name.
User = Base.classes.user
Address = Base.classes.address

session = Session(engine)

# rudimentary relationships are produced
session.add(Address(email_address="[email protected]", user=User(name="foo")))
session.commit()

# collection-based relationships are by default named
# "<classname>_collection"
u1 = session.query(User).first()
print(u1.address_collection)

在上面,调用AutomapBase.prepare()并传递AutomapBase.prepare.reflect参数,表示将在此声明基类的MetaData集合上调用MetaData.reflect()方法; 然后,MetaData中的每个** viable **Table都将自动生成一个新的映射类。将连接各个表的ForeignKeyConstraint对象将用于在类之间生成新的双向relationship()对象。类和关系遵循一个默认命名方案,我们可以自定义。在这一点上,我们基本的映射包含了相关的UserAddress类,可以以传统方式使用。

注意

通过** viable **,我们指的是表必须指定主键才能进行映射。此外,如果检测到表是两个其他表之间的纯关联表,则不会直接映射该表,而是将其配置为两个引用表的映射之间的多对多表。

从现有元数据生成映射

我们可以将预先声明的MetaData对象传递给automap_base()。该对象可以以任何方式构造,包括以编程方式、从序列化文件或从使用MetaData.reflect()反映的自身构造。下面我们演示了反射和显式表声明的组合:

from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey
from sqlalchemy.ext.automap import automap_base

engine = create_engine("sqlite:///mydatabase.db")

# produce our own MetaData object
metadata = MetaData()

# we can reflect it ourselves from a database, using options
# such as 'only' to limit what tables we look at...
metadata.reflect(engine, only=["user", "address"])

# ... or just define our own Table objects with it (or combine both)
Table(
    "user_order",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("user_id", ForeignKey("user.id")),
)

# we can then produce a set of mappings from this MetaData.
Base = automap_base(metadata=metadata)

# calling prepare() just sets up mapped classes and relationships.
Base.prepare()

# mapped classes are ready
User = Base.classes.user
Address = Base.classes.address
Order = Base.classes.user_order

从多个模式生成映射

当使用反射时,AutomapBase.prepare() 方法一次最多只能从一个模式中反射表,使用 AutomapBase.prepare.schema 参数来指示要从中反射的模式的名称。为了从多个模式中填充 AutomapBase 中的表,可以多次调用 AutomapBase.prepare(),每次将不同的名称传递给 AutomapBase.prepare.schema 参数。AutomapBase.prepare() 方法会保持一个内部列表,其中包含已经映射过的 Table 对象,并且只会为自上次运行 AutomapBase.prepare() 以来新增的那些 Table 对象添加新的映射:

e = create_engine("postgresql://scott:tiger@localhost/test")

Base.metadata.create_all(e)

Base = automap_base()

Base.prepare(e)
Base.prepare(e, schema="test_schema")
Base.prepare(e, schema="test_schema_2")

新版本 2.0 中新增了 AutomapBase.prepare() 方法,可以任意调用;每次运行时只会映射新增的表。在 1.4 版本及之前的版本中,多次调用会导致错误,因为它会尝试重新映射已经映射过的类。之前的解决方法是直接调用 MetaData.reflect(),该方法仍然可用。

跨多个模式自动映射同名表

对于多个模式可能有同名表的常见情况,因此可能生成同名类,可以通过使用 AutomapBase.prepare.classname_for_table 钩子在每个模式基础上应用不同的类名来解决冲突,或者通过使用 AutomapBase.prepare.modulename_for_table 钩子来解决同名类的歧义,该钩子允许通过更改它们的有效 __module__ 属性来区分同名类。在下面的示例中,此钩子用于为所有类创建一个 __module__ 属性,其形式为 mymodule.<schemaname>,如果没有模式,则使用模式名称 default

e = create_engine("postgresql://scott:tiger@localhost/test")

Base.metadata.create_all(e)

def module_name_for_table(cls, tablename, table):
    if table.schema is not None:
        return f"mymodule.{table.schema}"
    else:
        return f"mymodule.default"

Base = automap_base()

Base.prepare(e, modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema", modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema_2", modulename_for_table=module_name_for_table)

同名类被组织成可在 AutomapBase.by_module 中使用的分层集合。使用特定包/模块的点分隔名称向下遍历到所需的类名。

注意

当使用 AutomapBase.prepare.modulename_for_table 钩子来返回一个不是 None 的新 __module__ 时,类不会被放入 AutomapBase.classes 集合中;只有那些没有给定显式模块名的类才会放在这里,因为该集合不能单独表示同名类。

在上面的示例中,如果数据库中包含了三个默认模式、test_schema 模式和 test_schema_2 模式中都命名为 accounts 的表,将会有三个单独的类可用,分别是:

Base.by_module.mymodule.default.accounts
Base.by_module.mymodule.test_schema.accounts
Base.by_module.mymodule.test_schema_2.accounts

对于所有 AutomapBase 类生成的默认模块命名空间是 sqlalchemy.ext.automap。如果没有使用 AutomapBase.prepare.modulename_for_table 钩子,AutomapBase.by_module 的内容将完全在 sqlalchemy.ext.automap 命名空间内(例如 MyBase.by_module.sqlalchemy.ext.automap.<classname>),其中将包含与 AutomapBase.classes 中看到的相同系列的类。因此,只有在存在显式 __module__ 约定时才通常需要使用 AutomapBase.by_module

显式指定类

提示

如果在应用程序中期望显式类占据主要地位,请考虑改用 DeferredReflection

automap 扩展允许类被明确定义,类似于DeferredReflection类的方式。从AutomapBase继承的类表现得像常规的声明类,但在构建后不会立即映射,而是在调用AutomapBase.prepare()时映射。AutomapBase.prepare()方法将利用我们根据使用的表名建立的类。如果我们的模式包含表useraddress,我们可以定义要使用的一个或两个类:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine

# automap base
Base = automap_base()

# pre-declare User for the 'user' table
class User(Base):
    __tablename__ = "user"

    # override schema elements like Columns
    user_name = Column("name", String)

    # override relationships too, if desired.
    # we must use the same name that automap would use for the
    # relationship, and also must refer to the class name that automap will
    # generate for "address"
    address_collection = relationship("address", collection_class=set)

# reflect
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine)

# we still have Address generated from the tablename "address",
# but User is the same as Base.classes.User now

Address = Base.classes.address

u1 = session.query(User).first()
print(u1.address_collection)

# the backref is still there:
a1 = session.query(Address).first()
print(a1.user)

在上面,更复杂的细节之一是,我们展示了覆盖relationship()对象的过程,这是 automap 会创建的。为了做到这一点,我们需要确保名称与 automap 通常生成的名称匹配,即关系名称将是User.address_collection,而从 automap 的角度来看,所指的类的名称被称为address,尽管我们在使用这个类时将其称为Address

覆盖命名方案

automap 负责根据模式生成映射类和关系名称,这意味着它在确定这些名称时有决策点。这三个决策点是使用函数提供的,这些函数可以传递给AutomapBase.prepare()方法,并被称为classname_for_table()name_for_scalar_relationship()name_for_collection_relationship()。以下示例中提供了任意或所有这些函数,我们使用“驼峰命名法”为类名和使用 Inflect 包的“复数形式”为集合名:

import re
import inflect

def camelize_classname(base, tablename, table):
    "Produce a 'camelized' class name, e.g."
    "'words_and_underscores' -> 'WordsAndUnderscores'"

    return str(
        tablename[0].upper()
        + re.sub(
            r"_([a-z])",
            lambda m: m.group(1).upper(),
            tablename[1:],
        )
    )

_pluralizer = inflect.engine()

def pluralize_collection(base, local_cls, referred_cls, constraint):
    "Produce an 'uncamelized', 'pluralized' class name, e.g."
    "'SomeTerm' -> 'some_terms'"

    referred_name = referred_cls.__name__
    uncamelized = re.sub(
        r"[A-Z]",
        lambda m: "_%s" % m.group(0).lower(),
        referred_name,
    )[1:]
    pluralized = _pluralizer.plural(uncamelized)
    return pluralized

from sqlalchemy.ext.automap import automap_base

Base = automap_base()

engine = create_engine("sqlite:///mydatabase.db")

Base.prepare(
    autoload_with=engine,
    classname_for_table=camelize_classname,
    name_for_collection_relationship=pluralize_collection,
)

根据上述映射,我们现在将拥有UserAddress两个类,其中从UserAddress的集合被称为User.addresses

User, Address = Base.classes.User, Base.classes.Address

u1 = User(addresses=[Address(email="[email protected]")])

关系检测

自动映射所实现的绝大部分是基于外键生成 relationship() 结构。其工作原理如下:

  1. 检查已知映射到特定类的给定 Table 是否存在ForeignKeyConstraint 对象。

  2. 对于每个 ForeignKeyConstraint,将匹配到的远程Table对象与其应映射到的类相匹配,如果有的话,否则将跳过。

  3. 由于我们正在检查的 ForeignKeyConstraint 对应于来自直接映射类的引用,因此关系将被设置为指向引用类的多对一关系;在引用类上将创建相应的一个对多反向引用,引用此类。

  4. 如果属于ForeignKeyConstraint 的任何列不可为空(例如 nullable=False),则将在要传递给关系或反向引用的关键字参数中添加一个 relationship.cascade 关键字参数,其值为 all, delete-orphan。如果ForeignKeyConstraint 报告对于一组非空列设置了 ForeignKeyConstraint.ondeleteCASCADE,或者对于可为空列设置了 SET NULL,则在关系关键字参数集合中将选项relationship.passive_deletes标志设置为 True。请注意,并非所有后端都支持对 ON DELETE 的反射。

  5. 关系的名称是使用AutomapBase.prepare.name_for_scalar_relationshipAutomapBase.prepare.name_for_collection_relationship可调用函数确定的。重要的是要注意,默认关系命名是从实际类名派生的。如果您通过声明给出了特定类的显式名称,或者指定了备用类命名方案,那么关系名称将从该名称派生。

  6. 对于这些名称,类被检查是否存在匹配的已映射属性。如果在一侧检测到一个,但在另一侧没有,则AutomapBase尝试在缺失的一侧创建一个关系,然后使用relationship.back_populates参数将新关系指向另一侧。

  7. 在通常情况下,如果任一侧都没有关系,则AutomapBase.prepare()会在“多对一”一侧生成一个relationship(),并使用relationship.backref参数将其与另一侧匹配。

  8. relationship()的生成以及可选地backref()的生成由AutomapBase.prepare.generate_relationship函数处理,该函数可以由最终用户提供,以增强传递给relationship()backref()的参数或者使用这些函数的自定义实现。

自定义关系参数

AutomapBase.prepare.generate_relationship 钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的 generate_relationship() 函数,在使用我们自己的参数扩充给定的关键字字典后,返回对象。

下面是如何将 relationship.cascaderelationship.passive_deletes 选项传递给所有一对多关系的示例:

from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.orm import interfaces

def _gen_relationship(
    base, direction, return_fn, attrname, local_cls, referred_cls, **kw
):
    if direction is interfaces.ONETOMANY:
        kw["cascade"] = "all, delete-orphan"
        kw["passive_deletes"] = True
    # make use of the built-in function to actually return
    # the result.
    return generate_relationship(
        base, direction, return_fn, attrname, local_cls, referred_cls, **kw
    )

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine

# automap base
Base = automap_base()

engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)

多对多关系

automap 将生成多对多关系,例如包含 secondary 参数的关系。生成这些关系的过程如下:

  1. 在任何映射类被分配给它之前,给定的 Table 将被检查是否包含 ForeignKeyConstraint 对象。

  2. 如果表包含两个且仅两个 ForeignKeyConstraint 对象,并且此表中的所有列都是这两个 ForeignKeyConstraint 对象的成员,则假定该表是“secondary”表,并且不会直接映射

  3. Table 所指向的两个(或一个,用于自引用)外部表将与它们将要映射到的类进行匹配,如果有的话。

  4. 如果双方的映射类位于同一位置,则在两个类之间创建一个双向的多对多 relationship() / backref() 对。

  5. 多对多的覆盖逻辑与一对多/多对一的相同;在调用 generate_relationship() 函数生成结构后,现有属性将被保留。

具有继承关系的关系

automap 不会在处于继承关系的两个类之间生成任何关系。也就是说,对于以下给定的两个类:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

EngineerEmployee 的外键不是用于建立关系,而是用于在两个类之间建立连接的继承关系。

请注意,这意味着自动映射将不会为从子类到父类的外键生成 任何 关系。如果一个映射还具有从子类到父类的实际关系,那么这些关系需要是显式的。在下面的例子中,由于 EngineerEmployee 有两个单独的外键,我们需要设置我们想要的关系以及 inherit_condition,因为这些都不是 SQLAlchemy 可以猜测的:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    favorite_employee_id = Column(Integer, ForeignKey("employee.id"))

    favorite_employee = relationship(Employee, foreign_keys=favorite_employee_id)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "inherit_condition": id == Employee.id,
    }

处理简单的命名冲突

在映射过程中如果出现命名冲突的情况,根据需要覆盖 classname_for_table()name_for_scalar_relationship()name_for_collection_relationship() 中的任何一个。例如,如果自动映射尝试将一个多对一关系命名为一个现有列相同的名称,可以有条件地选择替代约定。给定一个模式:

CREATE  TABLE  table_a  (
  id  INTEGER  PRIMARY  KEY
);

CREATE  TABLE  table_b  (
  id  INTEGER  PRIMARY  KEY,
  table_a  INTEGER,
  FOREIGN  KEY(table_a)  REFERENCES  table_a(id)
);

上述模式将首先将 table_a 表自动映射为名为 table_a 的类;然后将在 table_b 的类上自动映射一个与此相关类相同名称的关系,例如 table_a。这个关系名称与映射列 table_b.table_a 冲突,并且将在映射时发出错误。

我们可以通过以下方式使用下划线解决这个冲突:

def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
    name = referred_cls.__name__.lower()
    local_table = local_cls.__table__
    if name in local_table.columns:
        newname = name + "_"
        warnings.warn("Already detected name %s present.  using %s" % (name, newname))
        return newname
    return name

Base.prepare(
    autoload_with=engine,
    name_for_scalar_relationship=name_for_scalar_relationship,
)

或者,我们可以在列的一侧更改名称。可以使用在 Naming Declarative Mapped Columns Explicitly 中描述的技术修改映射的列,通过将列显式地分配给一个新名称:

Base = automap_base()

class TableB(Base):
    __tablename__ = "table_b"
    _table_a = Column("table_a", ForeignKey("table_a.id"))

Base.prepare(autoload_with=engine)

使用明确声明的自动映射

正如前面所述,自动映射不依赖于反射,并且可以利用MetaData 集合内的任何 Table 对象集合。由此可见,自动映射也可以在完全定义了表元数据的完整模型中生成丢失的关系:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import Column, Integer, String, ForeignKey

Base = automap_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(ForeignKey("user.id"))

# produce relationships
Base.prepare()

# mapping is complete, with "address_collection" and
# "user" relationships
a1 = Address(email="u1")
a2 = Address(email="u2")
u1 = User(address_collection=[a1, a2])
assert a1.user is u1

在上面的例子中,对于大部分完成的 UserAddress 映射,我们在 Address.user_id 上定义的 ForeignKey 允许在映射的类上生成一个双向关系对 Address.userUser.address_collection

注意,当子类化AutomapBase时,需要调用AutomapBase.prepare()方法;如果不调用,我们声明的类处于未映射状态。

拦截列定义

MetaDataTable 对象支持一个事件钩子DDLEvents.column_reflect(),可用于拦截关于数据库列反射的信息,在构建Column对象之前。例如,如果我们想要使用类似"attr_<columnname>"的命名约定来映射列,可以应用该事件:

@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()

# run reflection
Base.prepare(autoload_with=engine)

从版本 1.4.0b2 开始:DDLEvents.column_reflect()事件可以应用于MetaData对象。

另请参阅

DDLEvents.column_reflect()

自动从反射表中命名列 - 在 ORM 映射文档中

API 参考

对象名称 描述
automap_base([declarative_base], **kw) 生成一个声明式自动映射基类。
AutomapBase 用于“自动映射”模式的基类。
classname_for_table(base, tablename, table) 返回应用于给定表名的类名。
generate_relationship(base, direction, return_fn, attrname, ..., **kw) 代表两个映射类生成一个relationship()backref()
name_for_collection_relationship(base, local_cls, referred_cls, constraint) 返回应用于从一个类到另一个类的集合引用的属性名称。
name_for_scalar_relationship(base, local_cls, referred_cls, constraint) 返回应用于标量对象引用的一个类到另一个类的属性名称。
function sqlalchemy.ext.automap.automap_base(declarative_base: Type[Any] | None = None, **kw: Any) → Any

生成一个声明式自动映射基类。

此函数生成一个新的基类,它是 AutomapBase 类的产品,以及由 declarative_base() 生成的一个声明基类。

除了 declarative_base 外,所有参数都是直接传递给 declarative_base() 函数的关键字参数。

参数:

  • declarative_base – 由 declarative_base() 生成的现有类。当传递了这个参数时,函数不再调用 declarative_base() 本身,所有其他关键字参数都会被忽略。

  • **kw – 关键字参数被传递给 declarative_base()

class sqlalchemy.ext.automap.AutomapBase

用于“自动映射”模式的基类。

AutomapBase 类可以与由 declarative_base() 函数生成的“声明基类”类相比较。实际上,AutomapBase 类总是与实际的声明基类一起使用作为一个 mixin。

一个新的可子类化的 AutomapBase 通常使用 automap_base() 函数实例化。

成员

by_module, classes, metadata, prepare()

另请参阅

自动映射

attribute by_module: ClassVar[ByModuleProperties]

一个包含点分隔的模块名称层次结构链接到类的 Properties 实例。

这个集合是一个替代 AutomapBase.classes 集合的选择,当使用 AutomapBase.prepare.modulename_for_table 参数时,这个参数将为生成的类应用不同的 __module__ 属性。

自动映射生成的类的默认 __module__sqlalchemy.ext.automap;使用 AutomapBase.by_module 访问这个命名空间会像这样:

User = Base.by_module.sqlalchemy.ext.automap.User

如果一个类的 __module__mymodule.account,访问这个命名空间会像这样:

MyClass = Base.by_module.mymodule.account.MyClass

新特性在版本 2.0 中添加。

另请参阅

从多个模式生成映射

attribute classes: ClassVar[Properties[Type[Any]]]

包含类的 Properties 实例。

这个对象的行为类似于表上的 .c 集合。类以它们被赋予的名称呈现,例如:

Base = automap_base()
Base.prepare(autoload_with=some_engine)

User, Address = Base.classes.User, Base.classes.Address

对于类名与 Properties 方法名重叠的情况,比如 items(),也支持获取项的形式:

Item = Base.classes["items"]
attribute metadata: ClassVar[MetaData]

指的是将用于新 Table 对象的 MetaData 集合。

另请参见

访问表和元数据

classmethod prepare(autoload_with: Engine | None = None, engine: Any | None = None, reflect: bool = False, schema: str | None = None, classname_for_table: PythonNameForTableType | None = None, modulename_for_table: PythonNameForTableType | None = None, collection_class: Any | None = None, name_for_scalar_relationship: NameForScalarRelationshipType | None = None, name_for_collection_relationship: NameForCollectionRelationshipType | None = None, generate_relationship: GenerateRelationshipType | None = None, reflection_options: Dict[_KT, _VT] | immutabledict[_KT, _VT] = {}) → None

MetaData 中提取映射类和关系,并执行映射。

有关完整文档和示例,请参阅 基本用法。

参数:

  • autoload_with – 用于执行模式反射的 EngineConnection;当指定时,MetaData.reflect() 方法将在此方法的范围内调用。

  • engine

    旧版;如果 AutomapBase.reflect 为 True,则用于指示反映表的 EngineConnection

    自 1.4 版开始弃用:AutomapBase.prepare.engine 参数已弃用,并将在未来版本中移除。请使用 AutomapBase.prepare.autoload_with 参数。

  • reflect

    旧版;如果 MetaData.reflect() 应被调用,则使用 AutomapBase.autoload_with

    自 1.4 版开始弃用:AutomapBase.prepare.reflect 参数已弃用,并将在未来版本中移除。当传递 AutomapBase.prepare.autoload_with 时,将启用反射。

  • classname_for_table – 可调用函数,用于根据表名生成新类名。默认为 classname_for_table()

  • modulename_for_table

    __module__ 的有效值将由可调用函数产生,用于为内部生成的类生成模块名,以允许在单个自动映射基类中具有相同名称的多个类,这些类可能位于不同的“模块”中。

    默认为 None,表示 __module__ 不会被显式设置;Python 运行时将使用值 sqlalchemy.ext.automap 用于这些类。

    当为生成的类分配 __module__ 时,可以使用 AutomapBase.by_module 集合基于点分隔的模块名称进行访问。使用此钩子分配了显式 __module_ 的类会被放置到 AutomapBase.classes 集合中,只会放置到 AutomapBase.by_module 中。

    版本 2.0 中的新内容。

    另请参阅

    从多个模式生成映射

  • name_for_scalar_relationship – 用于生成标量关系的关系名称的可调用函数。默认为 name_for_scalar_relationship()

  • name_for_collection_relationship – 用于为面向集合的关系生成关系名称的可调用函数。默认为 name_for_collection_relationship()

  • generate_relationship – 实际生成 relationship()backref() 构造的可调用函数。默认为 generate_relationship()

  • collection_class – 当创建表示集合的新 relationship() 对象时将使用的 Python 集合类。默认为 list

  • schema

    在使用 AutomapBase.prepare.autoload_with 参数反射表时要反射的模式名称。名称传递给 MetaData.reflect.schema 参数的 MetaData.reflect()。当省略时,数据库连接使用的默认模式将被使用。

    注意

    AutomapBase.prepare.schema 参数支持一次反射单个模式。为了包含来自多个模式的表,请多次调用 AutomapBase.prepare()

    对于多模式自动映射的概述,包括使用额外命名约定解决表名冲突,请参见 从多个模式生成映射 部分。

    版本 2.0 中的新功能:AutomapBase.prepare() 支持直接调用任意次数,跟踪已经处理过的表,以避免第二次处理它们。

  • reflection_options

    当存在时,此选项字典将传递给 MetaData.reflect(),以提供一般的反射特定选项,如 only 和/或特定于方言的选项,如 oracle_resolve_synonyms

    版本 1.4 中的新功能。

function sqlalchemy.ext.automap.classname_for_table(base: Type[Any], tablename: str, table: Table) → str

返回给定表名时应该使用的类名。

默认实现是:

return str(tablename)

可以使用 AutomapBase.prepare.classname_for_table 参数指定备用实现。

参数:

  • base – 执行准备工作的 AutomapBase 类。

  • tablenameTable 的字符串名称。

  • tableTable 对象本身。

返回:

一个字符串类名。

注意

在 Python 2 中,用于类名的字符串必须是非 Unicode 对象,例如 str() 对象。Table.name 属性通常是 Python 的 unicode 子类,因此应该在考虑任何非 ASCII 字符后,对此名称应用 str() 函数。

function sqlalchemy.ext.automap.name_for_scalar_relationship(base: Type[Any], local_cls: Type[Any], referred_cls: Type[Any], constraint: ForeignKeyConstraint) → str

返回应该用于从一个类到另一个类引用的属性名称,用于标量对象引用。

默认实现是:

return referred_cls.__name__.lower()

可以使用 AutomapBase.prepare.name_for_scalar_relationship 参数指定备用实现。

参数:

  • base – 执行准备工作的 AutomapBase 类。

  • local_cls – 要映射到本地端的类。

  • referred_cls – 要映射到引用方的类。

  • constraint – 正在检查以产生此关系的ForeignKeyConstraint

function sqlalchemy.ext.automap.name_for_collection_relationship(base: Type[Any], local_cls: Type[Any], referred_cls: Type[Any], constraint: ForeignKeyConstraint) → str

返回应该用于从一个类引用到另一个类的属性名称,用于集合引用。

默认实现如下:

return referred_cls.__name__.lower() + "_collection"

可以使用AutomapBase.prepare.name_for_collection_relationship参数指定备用实现。

参数:

  • base – 进行准备工作的AutomapBase类。

  • local_cls – 在本地端映射的类。

  • referred_cls – 在引用方的类。

  • constraint – 正在检查以产生此关系的ForeignKeyConstraint

function sqlalchemy.ext.automap.generate_relationship(base: Type[Any], direction: RelationshipDirection, return_fn: Callable[..., Relationship[Any]] | Callable[..., ORMBackrefArgument], attrname: str, local_cls: Type[Any], referred_cls: Type[Any], **kw: Any) → Relationship[Any] | ORMBackrefArgument

代表两个映射类生成relationship()backref()

可以使用AutomapBase.prepare.generate_relationship参数指定备用实现。

此函数的默认实现如下:

if return_fn is backref:
    return return_fn(attrname, **kw)
elif return_fn is relationship:
    return return_fn(referred_cls, **kw)
else:
    raise TypeError("Unknown relationship function: %s" % return_fn)

参数:

  • base – 进行准备工作的AutomapBase类。

  • direction – 表示关系的“方向”; 这将是ONETOMANYMANYTOONEMANYTOMANY之一。

  • return_fn – 默认用于创建关系的函数。这将是relationship()backref()中的一个。backref()函数的结果将用于在第二步产生一个新的relationship(),因此如果正在使用自定义关系函数,则用户定义的实现正确区分这两个函数非常关键。

  • attrname – 正在分配此关系的属性名称。如果generate_relationship.return_fn的值是backref()函数,则此名称是分配给反向引用的名称。

  • local_cls – 此关系或反向引用将在本地存在的“本地”类。

  • referred_cls – 关系或反向引用所指向的“被引用”类。

  • **kw – 所有额外的关键字参数都将传递给函数。

返回:

一个由 generate_relationship.return_fn 参数指定的 relationship()backref() 结构。

基本用法

最简单的用法是将现有数据库反映到新模型中。我们以与创建声明性基类相似的方式创建一个新的 AutomapBase 类,使用 automap_base()。然后,我们调用 AutomapBase.prepare() 在生成的基类上,要求它反映架构并生成映射:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy import create_engine

Base = automap_base()

# engine, suppose it has two tables 'user' and 'address' set up
engine = create_engine("sqlite:///mydatabase.db")

# reflect the tables
Base.prepare(autoload_with=engine)

# mapped classes are now created with names by default
# matching that of the table name.
User = Base.classes.user
Address = Base.classes.address

session = Session(engine)

# rudimentary relationships are produced
session.add(Address(email_address="[email protected]", user=User(name="foo")))
session.commit()

# collection-based relationships are by default named
# "<classname>_collection"
u1 = session.query(User).first()
print(u1.address_collection)

上面,在传递 AutomapBase.prepare.reflect 参数时调用 AutomapBase.prepare() 表示将在此声明基类的 MetaData 集合上调用 MetaData.reflect() 方法;然后,每个 viable TableMetaData 内将自动生成一个新的映射类。将连接各个表的 ForeignKeyConstraint 对象用于在类之间生成新的双向 relationship() 对象。类和关系遵循默认命名方案,我们可以自定义。在此时,我们的基本映射由相关的 UserAddress 类组成,可以像传统方式一样使用。

注意

这里的 viable 意味着要将表映射,必须指定主键。此外,如果检测到表是两个其他表之间的纯关联表,则不会直接映射,而是将其配置为两个引用表的映射之间的多对多表。

从现有的元数据生成映射

我们可以将预先声明的MetaData对象传递给automap_base()。这个对象可以以任何方式构建,包括以编程方式、从序列化文件中或者通过MetaData.reflect()自身进行反射。下面我们展示了反射和显式表声明的结合使用:

from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey
from sqlalchemy.ext.automap import automap_base

engine = create_engine("sqlite:///mydatabase.db")

# produce our own MetaData object
metadata = MetaData()

# we can reflect it ourselves from a database, using options
# such as 'only' to limit what tables we look at...
metadata.reflect(engine, only=["user", "address"])

# ... or just define our own Table objects with it (or combine both)
Table(
    "user_order",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("user_id", ForeignKey("user.id")),
)

# we can then produce a set of mappings from this MetaData.
Base = automap_base(metadata=metadata)

# calling prepare() just sets up mapped classes and relationships.
Base.prepare()

# mapped classes are ready
User = Base.classes.user
Address = Base.classes.address
Order = Base.classes.user_order

从多个模式生成映射

当使用反射时,AutomapBase.prepare()方法最多一次只能从一个模式中反射表,使用AutomapBase.prepare.schema参数来指示要反射的模式的名称。为了将AutomapBase填充到来自多个模式的表中,可以多次调用AutomapBase.prepare(),每次传递不同的名称给AutomapBase.prepare.schema参数。AutomapBase.prepare()方法会保留一个已经映射过的Table对象的内部列表,并且只会为那些自上次运行AutomapBase.prepare()以来新的Table对象添加新的映射:

e = create_engine("postgresql://scott:tiger@localhost/test")

Base.metadata.create_all(e)

Base = automap_base()

Base.prepare(e)
Base.prepare(e, schema="test_schema")
Base.prepare(e, schema="test_schema_2")

2.0 版本新增功能:AutomapBase.prepare()方法可以被任意次数调用;每次运行只会映射新添加的表。在 1.4 版本及更早版本中,多次调用会导致错误,因为它会尝试重新映射已经映射的类。直接调用MetaData.reflect()的先前解决方法仍然可用。

在多个模式中自动映射同名表

对于常见情况,即多个模式可能具有相同命名的表,因此可能生成相同命名的类,可以通过使用AutomapBase.prepare.classname_for_table挂钩来在每个模式基础上应用不同的类名来解决冲突,或者使用AutomapBase.prepare.modulename_for_table挂钩,通过更改它们的有效__module__属性来消除同名类的歧义。在下面的示例中,此挂钩用于为所有类创建一个__module__属性,其形式为mymodule.<schemaname>,其中如果没有模式,则使用模式名为default

e = create_engine("postgresql://scott:tiger@localhost/test")

Base.metadata.create_all(e)

def module_name_for_table(cls, tablename, table):
    if table.schema is not None:
        return f"mymodule.{table.schema}"
    else:
        return f"mymodule.default"

Base = automap_base()

Base.prepare(e, modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema", modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema_2", modulename_for_table=module_name_for_table)

相同命名的类被组织成一个层次化的集合,可在AutomapBase.by_module中使用。该集合使用特定包/模块的点分隔名称进行遍历,直到所需的类名。

注意

当使用AutomapBase.prepare.modulename_for_table挂钩返回一个不是None的新__module__时,类不会放置到AutomapBase.classes集合中;只有没有给定显式模块名称的类才会放在这里,因为该集合无法表示同名类。

在上面的示例中,如果数据库中包含默认模式,test_schema模式和test_schema_2模式中的一个名为accounts的表,那么将会有三个不同的类可用:

Base.by_module.mymodule.default.accounts
Base.by_module.mymodule.test_schema.accounts
Base.by_module.mymodule.test_schema_2.accounts

对于所有AutomapBase类生成的默认模块命名空间是sqlalchemy.ext.automap。如果没有使用AutomapBase.prepare.modulename_for_table挂钩,则AutomapBase.by_module的内容将完全在sqlalchemy.ext.automap命名空间内(例如MyBase.by_module.sqlalchemy.ext.automap.<classname>),其中包含与AutomapBase.classes中看到的相同的一系列类。因此,通常只有在存在显式__module__约定时才需要使用AutomapBase.by_module

在跨多个模式自动映射同名表时

对于常见情况,即多个模式可能具有相同命名的表,因此会生成相同命名的类,可以通过使用AutomapBase.prepare.classname_for_table钩子来根据每个模式应用不同的类名来解决冲突,或者通过使用AutomapBase.prepare.modulename_for_table钩子来解决相同命名类的歧义问题,该钩子允许通过更改它们的有效__module__属性来区分相同命名的类。在下面的示例中,该钩子用于创建一个形式为mymodule.<schemaname>__module__属性,其中如果不存在模式,则使用模式名称default

e = create_engine("postgresql://scott:tiger@localhost/test")

Base.metadata.create_all(e)

def module_name_for_table(cls, tablename, table):
    if table.schema is not None:
        return f"mymodule.{table.schema}"
    else:
        return f"mymodule.default"

Base = automap_base()

Base.prepare(e, modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema", modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema_2", modulename_for_table=module_name_for_table)

相同命名的类被组织成一个层次结构集合,可在AutomapBase.by_module中使用。该集合通过特定包/模块的点分隔名称向下遍历到所需的类名。

注意

当使用AutomapBase.prepare.modulename_for_table钩子返回一个不是None的新__module__时,该类不会被放置到AutomapBase.classes集合中;只有那些没有给定显式模块名的类会被放置在此处,因为集合不能单独表示同名类。

在上述示例中,如果数据库中包含了三个默认模式、test_schema模式和test_schema_2模式中都命名为accounts的表,则会分别获得三个单独的类:

Base.by_module.mymodule.default.accounts
Base.by_module.mymodule.test_schema.accounts
Base.by_module.mymodule.test_schema_2.accounts

为所有AutomapBase类生成的默认模块命名空间是sqlalchemy.ext.automap。 如果未使用AutomapBase.prepare.modulename_for_table挂钩,则AutomapBase.by_module的内容将完全在sqlalchemy.ext.automap命名空间内(例如,MyBase.by_module.sqlalchemy.ext.automap.<classname>),其中包含与AutomapBase.classes中看到的相同系列的类。 因此,仅当存在显式的__module__约定时才通常需要使用AutomapBase.by_module

明确指定类

提示

如果明确的类在应用程序中占主导地位,请考虑改用DeferredReflection

automap扩展允许以与DeferredReflection类相似的方式明确定义类。 从AutomapBase继承的类表现得像常规的声明性类一样,但在构造后不会立即映射,而是在调用AutomapBase.prepare()时映射。 AutomapBase.prepare()方法将利用我们基于所使用的表名建立的类。 如果我们的模式包含表useraddress,我们可以定义要使用的一个或两个类:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine

# automap base
Base = automap_base()

# pre-declare User for the 'user' table
class User(Base):
    __tablename__ = "user"

    # override schema elements like Columns
    user_name = Column("name", String)

    # override relationships too, if desired.
    # we must use the same name that automap would use for the
    # relationship, and also must refer to the class name that automap will
    # generate for "address"
    address_collection = relationship("address", collection_class=set)

# reflect
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine)

# we still have Address generated from the tablename "address",
# but User is the same as Base.classes.User now

Address = Base.classes.address

u1 = session.query(User).first()
print(u1.address_collection)

# the backref is still there:
a1 = session.query(Address).first()
print(a1.user)

上面,更复杂的细节之一是,我们说明了如何覆盖relationship()对象之一,该对象 automap 将会创建。 为此,我们需要确保名称与 automap 通常生成的名称相匹配,即关系名称将为User.address_collection,并且从 automap 的角度来看,所引用的类的名称称为address,即使我们在对此类的使用中将其称为Address

覆盖命名方案

automap 被要求根据模式生成映射类和关系名称,这意味着它在确定这些名称的方式上有决策点。这三个决策点通过可以传递给AutomapBase.prepare()方法的函数来提供,分别称为classname_for_table()name_for_scalar_relationship()name_for_collection_relationship()。以下示例中提供了任意或全部这些函数,我们使用了“驼峰命名法”作为类名,并使用了 Inflect 包来对集合名称进行“复数化”:

import re
import inflect

def camelize_classname(base, tablename, table):
    "Produce a 'camelized' class name, e.g."
    "'words_and_underscores' -> 'WordsAndUnderscores'"

    return str(
        tablename[0].upper()
        + re.sub(
            r"_([a-z])",
            lambda m: m.group(1).upper(),
            tablename[1:],
        )
    )

_pluralizer = inflect.engine()

def pluralize_collection(base, local_cls, referred_cls, constraint):
    "Produce an 'uncamelized', 'pluralized' class name, e.g."
    "'SomeTerm' -> 'some_terms'"

    referred_name = referred_cls.__name__
    uncamelized = re.sub(
        r"[A-Z]",
        lambda m: "_%s" % m.group(0).lower(),
        referred_name,
    )[1:]
    pluralized = _pluralizer.plural(uncamelized)
    return pluralized

from sqlalchemy.ext.automap import automap_base

Base = automap_base()

engine = create_engine("sqlite:///mydatabase.db")

Base.prepare(
    autoload_with=engine,
    classname_for_table=camelize_classname,
    name_for_collection_relationship=pluralize_collection,
)

从上面的映射中,我们现在会有 UserAddress 两个类,其中从 UserAddress 的集合被称为 User.addresses

User, Address = Base.classes.User, Base.classes.Address

u1 = User(addresses=[Address(email="[email protected]")])

关系检测

automap 的绝大部分工作是根据外键生成relationship()结构。它对于多对一和一对多关系的工作机制如下:

  1. 已知映射到特定类的给定Table,会被检查其是否存在ForeignKeyConstraint对象。

  2. 对于每一个ForeignKeyConstraint,远程的Table对象被匹配到其要映射的类,如果有的话,否则将被跳过。

  3. 由于我们正在检查的ForeignKeyConstraint对应于从直接映射类的引用,该关系将被设置为指向被引用类的多对一关系;在被引用的类上将创建一个相应的一对多反向引用,指向该类。

  4. 如果ForeignKeyConstraint的任何一列不可为空(例如,nullable=False),将会将all, delete-orphanrelationship.cascade关键字参数添加到要传递给关联或反向引用的关键字参数中。如果ForeignKeyConstraint报告对于一组非空列设置了CASCADE或对于可为空列设置了SET NULLForeignKeyConstraint.ondelete,则将在关系关键字参数集合中将选项relationship.passive_deletes标志设置为True。请注意,并非所有后端都支持删除操作的反射。

  5. 关联的名称是使用AutomapBase.prepare.name_for_scalar_relationshipAutomapBase.prepare.name_for_collection_relationship可调用函数确定的。重要的是要注意,默认的关联命名从实际类名派生名称。如果您通过声明为特定类指定了显式名称,或指定了替代类命名方案,则关系名称将从该名称派生。

  6. 检查类以查找与这些名称匹配的现有映射属性。如果在一侧检测到一个属性,但在另一侧没有,则AutomapBase尝试在缺失的一侧创建一个关联,然后使用relationship.back_populates参数指向新关联到另一侧。

  7. 在通常情况下,如果任何一侧都没有关联,则AutomapBase.prepare()会在“多对一”一侧产生一个relationship(),并使用relationship.backref参数将其与另一侧匹配。

  8. relationship()的生成以及可选的backref()的生成被交由AutomapBase.prepare.generate_relationship函数处理,该函数可以由最终用户提供,以增强传递给relationship()backref()的参数,或者利用这些函数的自定义实现。

自定义关系参数

AutomapBase.prepare.generate_relationship钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的generate_relationship()函数,在用自己的参数扩充给定关键字字典后返回对象。

下面是如何向所有一对多关系发送relationship.cascaderelationship.passive_deletes选项的示例:

from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.orm import interfaces

def _gen_relationship(
    base, direction, return_fn, attrname, local_cls, referred_cls, **kw
):
    if direction is interfaces.ONETOMANY:
        kw["cascade"] = "all, delete-orphan"
        kw["passive_deletes"] = True
    # make use of the built-in function to actually return
    # the result.
    return generate_relationship(
        base, direction, return_fn, attrname, local_cls, referred_cls, **kw
    )

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine

# automap base
Base = automap_base()

engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)

多对多关系

automap将生成多对多关系,例如包含secondary参数的关系。生成这些关系的过程如下:

  1. 给定的Table在分配任何映射类之前将被检查其ForeignKeyConstraint对象。

  2. 如果表包含两个且仅两个ForeignKeyConstraint对象,并且此表中的所有列都是这两个ForeignKeyConstraint对象的成员,则假定该表是“次要”表,并且不会直接映射

  3. Table引用的两个(对于自引用的情况则为一个)外部表会与它们将要映射到的类匹配,如果有的话。

  4. 如果两边的映射类被定位,那么在两个类之间将创建一个多对多的双向 relationship() / backref() 对。

  5. 对于多对多的覆盖逻辑与一对多/多对一的逻辑相同;调用generate_relationship() 函数来生成结构,已存在的属性将被保留。

具有继承关系的关系

automap 不会在处于继承关系的两个类之间生成任何关系。 也就是说,给定以下两个类:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

EngineerEmployee的外键不是用于关系,而是用于在两个类之间建立联合继承。

请注意,这意味着 automap 将不会为从子类到超类的外键生成 任何 关系。 如果映射还具有从子类到超类的实际关系,那么这些关系需要显式说明。 如下,由于从EngineerEmployee有两个单独的外键,我们需要设置我们想要的关系以及inherit_condition,因为这些不是 SQLAlchemy 可以猜测的事情:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    favorite_employee_id = Column(Integer, ForeignKey("employee.id"))

    favorite_employee = relationship(Employee, foreign_keys=favorite_employee_id)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "inherit_condition": id == Employee.id,
    }

处理简单的命名冲突

在映射过程中出现命名冲突的情况下,根据需要覆盖 classname_for_table()name_for_scalar_relationship()name_for_collection_relationship() 中的任何一个。 例如,如果 automap 正试图将多对一关系命名为现有列相同的名称,可以条件地选择替代约定。 给定一个模式:

CREATE  TABLE  table_a  (
  id  INTEGER  PRIMARY  KEY
);

CREATE  TABLE  table_b  (
  id  INTEGER  PRIMARY  KEY,
  table_a  INTEGER,
  FOREIGN  KEY(table_a)  REFERENCES  table_a(id)
);

上述模式首先将table_a表自动映射为一个名为table_a的类;然后将关系自动映射到table_b的类上,该关系的名称与此相关类的名称相同,例如table_a。 此关系名称与映射列table_b.table_a冲突,并且在映射时会发出错误。

我们可以通过以下方式使用下划线来解决此冲突:

def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
    name = referred_cls.__name__.lower()
    local_table = local_cls.__table__
    if name in local_table.columns:
        newname = name + "_"
        warnings.warn("Already detected name %s present.  using %s" % (name, newname))
        return newname
    return name

Base.prepare(
    autoload_with=engine,
    name_for_scalar_relationship=name_for_scalar_relationship,
)

或者,我们可以在列方面更改名称。 可以使用在 Naming Declarative Mapped Columns Explicitly 中描述的技术来修改映射的列,通过将列显式地分配给新名称:

Base = automap_base()

class TableB(Base):
    __tablename__ = "table_b"
    _table_a = Column("table_a", ForeignKey("table_a.id"))

Base.prepare(autoload_with=engine)

自定义关系参数

AutomapBase.prepare.generate_relationship 钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的 generate_relationship() 函数,在使用我们自己的参数扩充给定的关键字字典后返回对象。

下面是如何将 relationship.cascaderelationship.passive_deletes 选项传递给所有一对多关系的示例:

from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.orm import interfaces

def _gen_relationship(
    base, direction, return_fn, attrname, local_cls, referred_cls, **kw
):
    if direction is interfaces.ONETOMANY:
        kw["cascade"] = "all, delete-orphan"
        kw["passive_deletes"] = True
    # make use of the built-in function to actually return
    # the result.
    return generate_relationship(
        base, direction, return_fn, attrname, local_cls, referred_cls, **kw
    )

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine

# automap base
Base = automap_base()

engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)

多对多关系

automap 将生成多对多关系,例如那些包含 secondary 参数的关系。生成这些关系的过程如下:

  1. 在为其分配任何映射类之前,将检查给定的 Table 是否包含 ForeignKeyConstraint 对象。

  2. 如果表包含两个并且仅有两个 ForeignKeyConstraint 对象,并且此表中的所有列都是这两个 ForeignKeyConstraint 对象的成员,则假定该表是一个“次要”表,并且不会直接映射

  3. Table 所引用的两个(或一个,用于自引用)外部表将与它们将被映射到的类匹配,如果有的话。

  4. 如果两侧的映射类位于同一处,则在两个类之间创建一个双向的多对多 relationship() / backref() 对。

  5. 对于多对多的覆盖逻辑与一对多/多对一的逻辑相同;调用 generate_relationship() 函数来生成结构,并将保留现有属性。

继承关系

automap 将不会在处于继承关系的两个类之间生成任何关系。也就是说,对于以下两个给定的类:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

EngineerEmployee 的外键不是用于关系,而是用于在两个类之间建立联合继承。

请注意,这意味着 automap 不会为从子类到超类的外键生成任何关系。如果映射实际上还有从子类到超类的关系,那么这些关系需要是显式的。在下面的例子中,由于从 EngineerEmployee 有两个单独的外键,我们需要设置我们想要的关系以及 inherit_condition,因为这些是 SQLAlchemy 无法猜测的事情:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    favorite_employee_id = Column(Integer, ForeignKey("employee.id"))

    favorite_employee = relationship(Employee, foreign_keys=favorite_employee_id)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "inherit_condition": id == Employee.id,
    }

处理简单的命名冲突

在映射过程中出现命名冲突的情况下,根据需要覆盖任何 classname_for_table()name_for_scalar_relationship()name_for_collection_relationship()。例如,如果 automap 尝试将一个多对一关系命名为现有列的名称,可以有条件地选择替代约定。给定一个模式:

CREATE  TABLE  table_a  (
  id  INTEGER  PRIMARY  KEY
);

CREATE  TABLE  table_b  (
  id  INTEGER  PRIMARY  KEY,
  table_a  INTEGER,
  FOREIGN  KEY(table_a)  REFERENCES  table_a(id)
);

上述模式将首先将 table_a 表自动映射为名为 table_a 的类;然后将在 table_b 类上自动映射一个与此相关类相同名称的关系,例如 table_a。这个关系名称与映射列 table_b.table_a 冲突,并且在映射时会发出错误。

通过使用下划线,我们可以解决这个冲突:

def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
    name = referred_cls.__name__.lower()
    local_table = local_cls.__table__
    if name in local_table.columns:
        newname = name + "_"
        warnings.warn("Already detected name %s present.  using %s" % (name, newname))
        return newname
    return name

Base.prepare(
    autoload_with=engine,
    name_for_scalar_relationship=name_for_scalar_relationship,
)

或者,我们可以在列的一侧更改名称。可以使用在 显式命名声明性映射列 中描述的技术修改映射的列,通过将列显式分配给一个新名称:

Base = automap_base()

class TableB(Base):
    __tablename__ = "table_b"
    _table_a = Column("table_a", ForeignKey("table_a.id"))

Base.prepare(autoload_with=engine)

使用具有显式声明的 Automap

正如之前所指出的,automap 不依赖于反射,并且可以利用 Table 对象集合中的任何对象在 MetaData 集合中。由此可见,automap 也可以用于生成缺失的关系,只要有一个完全定义了表元数据的完整模型:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import Column, Integer, String, ForeignKey

Base = automap_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(ForeignKey("user.id"))

# produce relationships
Base.prepare()

# mapping is complete, with "address_collection" and
# "user" relationships
a1 = Address(email="u1")
a2 = Address(email="u2")
u1 = User(address_collection=[a1, a2])
assert a1.user is u1

在上面的例子中,给定了大部分完整的 UserAddress 映射,我们在 Address.user_id 上定义的 ForeignKey 允许在映射类上生成一个双向关系对 Address.userUser.address_collection

请注意,当子类化AutomapBase时,需要调用AutomapBase.prepare()方法;如果未调用,则我们声明的类处于未映射状态。

拦截列定义

MetaDataTable对象支持一个事件钩子DDLEvents.column_reflect(),可用于在构建Column对象之前拦截有关数据库列的反射信息。例如,如果我们想要使用命名约定来映射列,例如"attr_<columnname>",则可以应用该事件如下:

@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()

# run reflection
Base.prepare(autoload_with=engine)

版本 1.4.0b2 中的新内容:DDLEvents.column_reflect()事件可以应用于一个MetaData对象。

另请参阅

DDLEvents.column_reflect()

从反射表自动命名方案 - 在 ORM 映射文档中

API 参考

对象名称 描述
automap_base([declarative_base], **kw) 生成一个声明式自动映射基类。
AutomapBase 用于“自动映射”模式的基类。
classname_for_table(base, tablename, table) 给定表名,返回应该使用的类名。
generate_relationship(base, direction, return_fn, attrname, ..., **kw) 代表两个映射类生成一个relationship()或者backref()
name_for_collection_relationship(base, local_cls, referred_cls, constraint) 返回用于从一个类引用另一个类的属性名称,用于集合引用。
name_for_scalar_relationship(base, local_cls, referred_cls, constraint) 返回用于从一个类引用另一个类的属性名称,用于标量对象引用。
function sqlalchemy.ext.automap.automap_base(declarative_base: Type[Any] | None = None, **kw: Any) → Any

生成一个声明式自动映射基类。

此函数生成一个新的基类,该基类是由 AutomapBase 类和由 declarative_base() 产生的声明性基类的产品。

除了 declarative_base 外的所有参数都是直接传递给 declarative_base() 函数的关键字参数。

参数:

  • declarative_base – 由 declarative_base() 产生的现有类。当传递此参数时,函数不再调用 declarative_base() 自身,并且所有其他关键字参数都将被忽略。

  • **kw – 关键字参数会传递给 declarative_base()

class sqlalchemy.ext.automap.AutomapBase

用于“automap”模式的基类。

AutomapBase 类可以与由 declarative_base() 函数产生的“声明性基类”类进行比较。在实践中,AutomapBase 类始终与实际的声明性基类一起使用作为混入。

一个新的可子类化的 AutomapBase 通常是使用 automap_base() 函数实例化的。

成员

by_module, classes, metadata, prepare()

另请参阅

Automap

attribute by_module: ClassVar[ByModuleProperties]

包含点分隔的模块名称的层次结构,链接到类的 Properties 实例。

这个集合是 AutomapBase.classes 集合的一种替代方法,当利用 AutomapBase.prepare.modulename_for_table 参数时,该参数将为生成的类应用不同的 __module__ 属性。

automap 生成类的默认 __module__sqlalchemy.ext.automap;要使用 AutomapBase.by_module 访问此命名空间,看起来像这样:

User = Base.by_module.sqlalchemy.ext.automap.User

如果一个类的 __module__mymodule.account,访问此命名空间看起来像这样:

MyClass = Base.by_module.mymodule.account.MyClass

2.0 版中的新功能。

另请参阅

从多个模式生成映射

attribute classes: ClassVar[Properties[Type[Any]]]

包含类的 Properties 实例。

此对象的行为类似于表上的 .c 集合。类以其给定的名称存在,例如:

Base = automap_base()
Base.prepare(autoload_with=some_engine)

User, Address = Base.classes.User, Base.classes.Address

对于与 Properties 方法名重叠的类名,例如 items(),也支持使用 getitem 形式:

Item = Base.classes["items"]
attribute metadata: ClassVar[MetaData]

指的是将用于新 Table 对象的 MetaData 集合。

另请参见

访问表和元数据

classmethod prepare(autoload_with: Engine | None = None, engine: Any | None = None, reflect: bool = False, schema: str | None = None, classname_for_table: PythonNameForTableType | None = None, modulename_for_table: PythonNameForTableType | None = None, collection_class: Any | None = None, name_for_scalar_relationship: NameForScalarRelationshipType | None = None, name_for_collection_relationship: NameForCollectionRelationshipType | None = None, generate_relationship: GenerateRelationshipType | None = None, reflection_options: Dict[_KT, _VT] | immutabledict[_KT, _VT] = {}) → None

MetaData 中提取映射类和关系,并执行映射。

有关完整文档和示例,请参见 基本使用。

参数:

  • autoload_with – 使用与其执行模式反射的 EngineConnection;当指定时,MetaData.reflect() 方法将在此方法的范围内调用。

  • engine

    已弃用;使用 AutomapBase.autoload_with。用于指示在反映表时使用的 EngineConnection,如果 AutomapBase.reflect 为 True。

    自版本 1.4 起已弃用:AutomapBase.prepare.engine 参数已弃用,并将在未来版本中删除。请使用 AutomapBase.prepare.autoload_with 参数。

  • reflect

    已弃用;使用 AutomapBase.autoload_with。指示是否应调用 MetaData.reflect()

    自版本 1.4 起已弃用:AutomapBase.prepare.reflect 参数已弃用,并将在未来版本中删除。当传递了 AutomapBase.prepare.autoload_with 时启用反射。

  • classname_for_table – 一个可调用的函数,将根据表名生成新类名。默认为 classname_for_table()

  • modulename_for_table

    可调用函数,用于为内部生成的类生成有效的__module__,以允许在单个自动映射基类中具有相同名称的多个类,这些类将位于不同的“模块”中。

    默认为None,表示__module__不会被显式设置;Python 运行时将为这些类使用值sqlalchemy.ext.automap

    在为生成的类分配__module__时,可以基于点分隔的模块名称使用AutomapBase.by_module集合访问它们。使用此钩子分配了显式__module_的类不会放入AutomapBase.classes集合中,而只会放入AutomapBase.by_module中。

    2.0 版中的新功能。

    另请参见

    从多个模式生成映射

  • name_for_scalar_relationship – 可调用函数,用于为标量关系生成关系名称。默认为name_for_scalar_relationship()

  • name_for_collection_relationship – 可调用函数,用于为面向集合的关系生成关系名称。默认为name_for_collection_relationship()

  • generate_relationship – 可调用函数,用于实际生成relationship()backref()构造。默认为generate_relationship()

  • collection_class – 当创建代表集合的新relationship()对象时将使用的 Python 集合类。默认为list

  • schema

    反映表时要反映的模式名称,使用AutomapBase.prepare.autoload_with参数。该名称传递给MetaData.reflect()MetaData.reflect.schema参数。当省略时,将使用数据库连接使用的默认模式。

    注意

    AutomapBase.prepare.schema 参数支持一次反射单个模式。要包含来自多个模式的表,请多次调用 AutomapBase.prepare()

    有关多模式自动映射的概述,包括使用附加命名约定解决表名冲突的方法,请参阅从多个模式生成映射 部分。

    新版本 2.0 中:AutomapBase.prepare() 可以直接调用任意次数,并跟踪已处理的表,以避免再次处理它们。

  • reflection_options

    当存在时,此选项字典将传递给 MetaData.reflect() 以提供通用的反射特定选项,如 only 和/或特定于方言的选项,如 oracle_resolve_synonyms

    新版本 1.4 中。

function sqlalchemy.ext.automap.classname_for_table(base: Type[Any], tablename: str, table: Table) → str

返回应使用的类名,给定表的名称。

默认实现为:

return str(tablename)

可以使用 AutomapBase.prepare.classname_for_table 参数指定替代实现。

参数:

  • base – 进行准备的 AutomapBase 类。

  • tablenameTable 的字符串名称。

  • tableTable 对象本身。

返回:

一个字符串类名。

注意

在 Python 2 中,用于类名的字符串 必须 是非 Unicode 对象,例如 str() 对象。Table.name 属性通常是 Python unicode 子类,因此应在考虑任何非 ASCII 字符后,应用 str() 函数到此名称。

function sqlalchemy.ext.automap.name_for_scalar_relationship(base: Type[Any], local_cls: Type[Any], referred_cls: Type[Any], constraint: ForeignKeyConstraint) → str

返回应用于从一个类到另一个类的引用的属性名称,用于标量对象引用。

默认实现为:

return referred_cls.__name__.lower()

可以使用 AutomapBase.prepare.name_for_scalar_relationship 参数指定替代实现。

参数:

  • base – 进行准备的 AutomapBase 类。

  • local_cls – 映射到本地方的类。

  • referred_cls – 映射到引用方的类。

  • constraint – 正在检查以生成此关系的ForeignKeyConstraint

function sqlalchemy.ext.automap.name_for_collection_relationship(base: Type[Any], local_cls: Type[Any], referred_cls: Type[Any], constraint: ForeignKeyConstraint) → str

返回应用于从一个类到另一个类的引用的属性名称,用于集合引用。

默认实现如下:

return referred_cls.__name__.lower() + "_collection"

可以使用AutomapBase.prepare.name_for_collection_relationship参数指定替代实现。

参数:

  • base – 执行准备工作的AutomapBase类。

  • local_cls – 要映射到本地方的类。

  • referred_cls – 要映射到引用方的类。

  • constraint – 正在检查以生成此关系的ForeignKeyConstraint

function sqlalchemy.ext.automap.generate_relationship(base: Type[Any], direction: RelationshipDirection, return_fn: Callable[..., Relationship[Any]] | Callable[..., ORMBackrefArgument], attrname: str, local_cls: Type[Any], referred_cls: Type[Any], **kw: Any) → Relationship[Any] | ORMBackrefArgument

代表两个映射类生成一个relationship()backref()

可以使用AutomapBase.prepare.generate_relationship参数指定此函数的替代实现。

此函数的默认实现如下:

if return_fn is backref:
    return return_fn(attrname, **kw)
elif return_fn is relationship:
    return return_fn(referred_cls, **kw)
else:
    raise TypeError("Unknown relationship function: %s" % return_fn)

参数:

  • base – 执行准备工作的AutomapBase类。

  • direction – 指示关系的“方向”; 这将是ONETOMANYMANYTOONEMANYTOMANY之一。

  • return_fn – 默认用于创建关系的函数。这将是relationship()backref()之一。backref()函数的结果将用于在第二步生成新的relationship(),因此如果使用自定义关系函数,则用户定义的实现必须正确区分这两个函数。

  • attrname – 正在分配此关系的属性名称。如果generate_relationship.return_fn的值是backref()函数,则此名称是分配给反向引用的名称。

  • local_cls – 此关系或反向引用将在本地存在的“本地”类。

  • referred_cls – 此关系或反向引用所指向的“引用”类。

  • **kw – 所有附加的关键字参数都将传递给该函数。

返回值:

relationship()backref() 构造,由 generate_relationship.return_fn 参数所指定。

烘焙查询

原文:docs.sqlalchemy.org/en/20/orm/extensions/baked.html

bakedQuery对象提供了一种替代的创建模式,允许缓存对象的构建和字符串编译步骤。这意味着对于一个特定的Query构建场景,如果该场景被多次使用,那么从初始构建查询到生成 SQL 字符串所涉及的所有 Python 函数调用将只会发生一次,而不是每次构建和执行查询时都会发生。

这个系统的理念是极大地减少 Python 解释器在发出 SQL 之前发生的一切的开销。 “baked”系统的缓存不会以任何方式减少 SQL 调用或缓存来自数据库的返回结果。一个展示 SQL 调用和结果集本身缓存的技术在 Dogpile Caching 中可用。

从版本 1.4 开始弃用:SQLAlchemy 1.4 和 2.0 具有全新的直接查询缓存系统,不再需要BakedQuery系统。现在,对于所有 Core 和 ORM 查询,缓存现在是透明激活的,用户不需要采取任何操作,使用在 SQL Compilation Caching 中描述的系统。

深度炼金术

sqlalchemy.ext.baked扩展不适合初学者。正确使用它需要对 SQLAlchemy、数据库驱动程序以及后端数据库之间的交互有很好的高级理解。这个扩展提供了一种非常特定的优化,通常是不需要的。如上所述,它不会缓存查询,只会缓存 SQL 本身的字符串形式。

概要

使用 baked 系统的开始是生成所谓的“面包店”,它代表了一系列特定查询对象的存储:

from sqlalchemy.ext import baked

bakery = baked.bakery()

上述的“面包店”将缓存数据存储在一个默认为 200 个元素的 LRU 缓存中,需要注意的是 ORM 查询通常会包含一个用于调用 ORM 查询的条目,以及每个数据库方言的 SQL 字符串的一个条目。

该面包店允许我们通过指定其构造方式为一系列 Python 可调用对象(通常为 lambda 函数)来构建一个Query对象。为了简洁使用,它重写了+=运算符,使得典型的查询构建看起来像下面这样:

from sqlalchemy import bindparam

def search_for_user(session, username, email=None):
    baked_query = bakery(lambda session: session.query(User))
    baked_query += lambda q: q.filter(User.name == bindparam("username"))

    baked_query += lambda q: q.order_by(User.id)

    if email:
        baked_query += lambda q: q.filter(User.email == bindparam("email"))

    result = baked_query(session).params(username=username, email=email).all()

    return result

以下是关于上述代码的一些观察:

  1. baked_query 对象是 BakedQuery 的一个实例。该对象本质上是一个真正的 orm Query 对象的“构建者”,但它本身并不是实际的 Query 对象。

  2. 实际的 Query 对象根本没有构建,直到在函数的最后调用 Result.all() 时。

  3. 添加到 baked_query 对象的步骤都表示为 Python 函数,通常是 lambda。传递给 bakery() 函数的第一个 lambda 接收一个 Session 作为其参数。其余的 lambda 每个接收一个 Query 作为其参数。

  4. 在上述代码中,即使我们的应用程序可能多次调用 search_for_user(),即使在每次调用中我们都建立一个全新的 BakedQuery 对象,所有的 lambda 只调用一次。只要此查询在烘培中被缓存,每个 lambda 在此期间都不会被第二次调用。

  5. 缓存是通过存储lambda 对象本身的引用来实现的,以便构建缓存键;也就是说,Python 解释器将这些函数分配为 Python 标识,这决定了如何在后续运行中识别查询。对于那些指定了 email 参数的 search_for_user() 调用,可调用的 lambda q: q.filter(User.email == bindparam('email')) 将成为被检索到的缓存键的一部分;当 emailNone 时,这个可调用函数不会成为缓存键的一部分。

  6. 由于 lambda 都只调用一次,因此在 lambda 内部不得引用可能跨调用改变的变量;相反,假设这些是要绑定到 SQL 字符串中的值,我们使用 bindparam() 构建命名参数,稍后使用 Result.params() 应用它们的实际值。

性能

烘焙查询可能看起来有些奇怪、有些笨拙、有些冗长。然而,对于在应用程序中多次调用的查询,Python 性能的节约非常显著。在 性能 中演示的示例套件 short_selects 说明了查询的比较,每个查询仅返回一行,如下所示的常规查询:

session = Session(bind=engine)
for id_ in random.sample(ids, n):
    session.query(Customer).filter(Customer.id == id_).one()

相比于等效的“烘焙”查询:

bakery = baked.bakery()
s = Session(bind=engine)
for id_ in random.sample(ids, n):
    q = bakery(lambda s: s.query(Customer))
    q += lambda q: q.filter(Customer.id == bindparam("id"))
    q(s).params(id=id_).one()

对于每个块的 10000 次调用的 Python 函数调用计数的差异为:

test_baked_query : test a baked query of the full entity.
                   (10000 iterations); total fn calls 1951294

test_orm_query :   test a straight ORM query of the full entity.
                   (10000 iterations); total fn calls 7900535

以强大的笔记本电脑上的秒数来看,这是这样的:

test_baked_query : test a baked query of the full entity.
                   (10000 iterations); total time 2.174126 sec

test_orm_query :   test a straight ORM query of the full entity.
                   (10000 iterations); total time 7.958516 sec

请注意,此测试非常有意地包含了仅返回一行的查询。对于返回许多行的查询,烘焙查询的性能优势将越来越小,与获取行所花费的时间成比例。必须牢记的是,烘焙查询功能仅适用于构建查询本身,而不适用于获取结果。使用烘焙特性绝不是对更快应用程序的担保;它只是一种潜在有用的功能,适用于那些已经被证明受到这种特定形式的开销影响的应用程序。

理由

上述“lambda”方法是更传统的“参数化”方法的一个超集。假设我们希望构建一个简单的系统,在该系统中我们仅构建一次Query,然后将其存储在字典中以供重复使用。现在就可以通过简单地构建查询并通过调用my_cached_query = query.with_session(None)来移除其Session来实现这一点:

my_simple_cache = {}

def lookup(session, id_argument):
    if "my_key" not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        my_simple_cache["my_key"] = query.with_session(None)
    else:
        query = my_simple_cache["my_key"].with_session(session)

    return query.params(id=id_argument).all()

上述方法为我们带来了非常小的性能优势。通过重用Query,我们节省了session.query(Model)构造函数内部的 Python 工作以及调用filter(Model.id == bindparam('id')),这将跳过为我们构建核心表达式以及将其发送到Query.filter()的过程。然而,该方法仍然每次调用Query.all()时重新生成完整的Select对象,并且每次都将此全新的Select发送到字符串编译步骤,对于像上面这样的简单情况,这可能约占开销的 70%。

为了减少额外的开销,我们需要一些更专门的逻辑,一些记忆构造选择对象和 SQL 构造的方法。在维基百科的 BakedQuery 部分有一个示例,这是这个特性的前身,但在那个系统中,我们没有缓存查询的构造。为了去除所有开销,我们需要缓存查询的构造以及 SQL 编译。假设我们按照这种方式调整了配方,并制作了一个 .bake() 方法,用于预编译查询的 SQL,生成一个可以以最小开销调用的新对象。我们的例子变成了:

my_simple_cache = {}

def lookup(session, id_argument):
    if "my_key" not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        my_simple_cache["my_key"] = query.with_session(None).bake()
    else:
        query = my_simple_cache["my_key"].with_session(session)

    return query.params(id=id_argument).all()

在上述例子中,我们已经解决了性能问题,但我们仍然需要处理这个字符串缓存键。

我们可以使用“面包店”方法来重新构建上面的内容,使其看起来不像“逐步建立 lambda”方法那样不寻常,而更像是对简单“重用查询”方法的简单改进:

bakery = baked.bakery()

def lookup(session, id_argument):
    def create_model_query(session):
        return session.query(Model).filter(Model.id == bindparam("id"))

    parameterized_query = bakery.bake(create_model_query)
    return parameterized_query(session).params(id=id_argument).all()

在上述示例中,我们使用“烘焙”系统的方式与简单的“缓存查询”系统非常相似。但是,它使用了两行代码少,不需要制造一个“my_key”的缓存键,还包括与我们自定义的“烘焙”函数相同的功能,该函数从查询的构造函数到过滤器调用再到Select对象的生成,再到字符串编译步骤,都缓存了 100% 的 Python 调用工作。

从上面的内容,如果我们问自己,“如果查找需要根据查询结构做条件决策怎么办?”,这就是为什么“烘焙”是这样的方式的地方。我们可以从任意数量的函数构建参数化查询,而不是从一个函数(这是我们最初认为烘焙可能的工作方式)开始。考虑我们的简单例子,如果我们需要在条件基础上在查询中添加一个附加子句:

my_simple_cache = {}

def lookup(session, id_argument, include_frobnizzle=False):
    if include_frobnizzle:
        cache_key = "my_key_with_frobnizzle"
    else:
        cache_key = "my_key_without_frobnizzle"

    if cache_key not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        if include_frobnizzle:
            query = query.filter(Model.frobnizzle == True)

        my_simple_cache[cache_key] = query.with_session(None).bake()
    else:
        query = my_simple_cache[cache_key].with_session(session)

    return query.params(id=id_argument).all()

我们的“简单”参数化系统现在必须负责生成考虑到“include_frobnizzle”标志是否已传递的缓存键,因为此标志的存在意味着生成的 SQL 将完全不同。很明显,随着查询构建复杂性的提高,缓存这些查询的任务会非常快地变得繁重。我们可以将上述示例转换为以下对“面包店”直接使用:

bakery = baked.bakery()

def lookup(session, id_argument, include_frobnizzle=False):
    def create_model_query(session):
        return session.query(Model).filter(Model.id == bindparam("id"))

    parameterized_query = bakery.bake(create_model_query)

    if include_frobnizzle:

        def include_frobnizzle_in_query(query):
            return query.filter(Model.frobnizzle == True)

        parameterized_query = parameterized_query.with_criteria(
            include_frobnizzle_in_query
        )

    return parameterized_query(session).params(id=id_argument).all()

在上述示例中,我们再次缓存的不仅是查询对象,还有它需要执行的所有工作以生成 SQL。我们也不再需要处理确保生成准确考虑到我们所做的所有结构修改的缓存键;这现在是自动处理的,而且没有错误的机会。

此代码示例比朴素示例少了几行代码,消除了处理缓存键的需求,并且具有完整的所谓“已烘焙”功能的巨大性能优势。但仍然有点啰嗦!因此,我们将像BakedQuery.add_criteria()BakedQuery.with_criteria()这样的方法简化为操作符,并鼓励(尽管绝对不是必需的!)使用简单的 lambda 函数,仅作为减少冗长性的手段:

bakery = baked.bakery()

def lookup(session, id_argument, include_frobnizzle=False):
    parameterized_query = bakery.bake(
        lambda s: s.query(Model).filter(Model.id == bindparam("id"))
    )

    if include_frobnizzle:
        parameterized_query += lambda q: q.filter(Model.frobnizzle == True)

    return parameterized_query(session).params(id=id_argument).all()

在上述情况中,该方法更容易实现,并且在代码流程上更类似于非缓存查询函数的情况,因此使代码更易于移植。

上述描述本质上是对到达当前“已烘焙”方法的设计过程的总结。从“正常”方法开始,还需要解决缓存键的构建和管理、移除所有冗余的 Python 执行以及需要使用条件构建的查询等附加问题,从而导致了最终的方法。

特殊查询技术

本节将描述一些特定查询情况下的技术。

使用 IN 表达式

SQLAlchemy 中的ColumnOperators.in_()方法在历史上基于传递给方法的项目列表呈现一个可变的绑定参数集。对于已烘焙的查询,这不起作用,因为该列表的长度可能在不同的调用中发生变化。为了解决这个问题,bindparam.expanding参数支持一个延迟呈现的 IN 表达式,在烘焙查询内安全地进行缓存。实际元素列表在语句执行时呈现,而不是在语句编译时:

bakery = baked.bakery()

baked_query = bakery(lambda session: session.query(User))
baked_query += lambda q: q.filter(User.name.in_(bindparam("username", expanding=True)))

result = baked_query.with_session(session).params(username=["ed", "fred"]).all()

请参阅下文

bindparam.expanding

ColumnOperators.in_()

使用子查询

当使用Query对象时,通常需要一个Query对象用于在另一个查询中生成子查询。在Query目前处于烘焙形式的情况下,可以使用一个临时方法来检索Query对象,使用BakedQuery.to_query()方法。此方法传递给生成烘焙查询特定步骤的 lambda 可调用参数的SessionQuery

bakery = baked.bakery()

# a baked query that will end up being used as a subquery
my_subq = bakery(lambda s: s.query(User.id))
my_subq += lambda q: q.filter(User.id == Address.user_id)

# select a correlated subquery in the top columns list,
# we have the "session" argument, pass that
my_q = bakery(lambda s: s.query(Address.id, my_subq.to_query(s).as_scalar()))

# use a correlated subquery in some of the criteria, we have
# the "query" argument, pass that.
my_q += lambda q: q.filter(my_subq.to_query(q).exists())

版本 1.3 中的新功能。

使用 before_compile 事件

从 SQLAlchemy 1.3.11 开始,针对特定Query使用QueryEvents.before_compile()事件将禁止烘焙查询系统缓存查询,如果事件挂钩返回一个与传入的不同的新Query对象。这样,QueryEvents.before_compile()挂钩可以在每次使用特定Query时被调用,以适应每次以不同方式修改查询的挂钩。要允许QueryEvents.before_compile()修改sqlalchemy.orm.Query()对象,但仍然允许结果被缓存,可以注册传递bake_ok=True标志的事件:

@event.listens_for(Query, "before_compile", retval=True, bake_ok=True)
def my_event(query):
    for desc in query.column_descriptions:
        if desc["type"] is User:
            entity = desc["entity"]
            query = query.filter(entity.deleted == False)
    return query

上述策略适用于每次都以完全相同方式修改给定Query的事件,不依赖于特定参数或外部状态的更改。

版本 1.3.11 中的新功能:- 在QueryEvents.before_compile()事件中添加了“bake_ok”标志,并且如果此标志未设置,则不允许通过“baked”扩展进行缓存以发生对返回新Query对象的事件处理程序。

禁用全局会话烘焙查询

标志Session.enable_baked_queries可以设置为 False,导致所有烘焙查询在针对该Session使用时不使用缓存:

session = Session(engine, enable_baked_queries=False)

像所有会话标志一样,它也被工厂对象(如sessionmaker)和方法(如sessionmaker.configure())接受。

此标志的直接理由是,一个应用程序如果出现问题,可能是由于用户定义的烘焙查询或其他烘焙查询问题导致的缓存键冲突,可以关闭该行为,以确定或排除烘焙查询作为问题原因。

版本 1.2 中的新功能。

惰性加载集成

从版本 1.4 开始更改:从 SQLAlchemy 1.4 开始,“烘焙查询”系统不再是关系加载系统的一部分。而是改用本地缓存系统。

API 文档

对象名称 描述
BakedQuery 用于构建Query对象的构建器对象。
bakery 构建一个新的烘焙坊。
Bakery 返回一个BakedQuery的可调用对象。
function sqlalchemy.ext.baked.bakery(size=200, _size_alert=None)

构建一个新的烘焙坊。

返回:

一个Bakery的实例。

class sqlalchemy.ext.baked.BakedQuery

成员

add_criteria(), bakery(), for_session(), spoil(), to_query(), with_criteria()

用于构建Query对象的构建器对象。

method add_criteria(fn, *args)

向这个BakedQuery添加一个条件函数。

这相当于使用+=运算符就地修改BakedQuery

classmethod bakery(size=200, _size_alert=None)

构建一个新的烘焙坊。

返回:

一个Bakery的实例。

method for_session(session)

为这个BakedQuery返回一个Result对象。

这相当于将BakedQuery作��Python 可调用对象调用,例如result = my_baked_query(session)

method spoil(full=False)

取消在此BakedQuery对象上发生的任何查询缓存。

BakedQuery可以继续正常使用,但是附加的创建函数不会被缓存;它们将在每次调用时被调用。

这是为了支持在构建烘焙查询的特定步骤中,某些使查询无法缓存的情况,例如依赖于某些不可缓存值的变体。

参数:

full – 如果为 False,则仅在破坏步骤之后添加到此BakedQuery对象的函数将不被缓存;直到此时为止的BakedQuery的状态将从缓存中拉取。如果为 True,则每次完全从头构建整个Query对象,每次调用都会调用所有创建函数。

method to_query(query_or_session)

返回作为子查询使用的Query对象。

此方法应在用于生成封闭的BakedQuery步骤的 lambda 可调用对象内部使用。参数通常应该是传递给 lambda 的Query对象:

sub_bq = self.bakery(lambda s: s.query(User.name))
sub_bq += lambda q: q.filter(
    User.id == Address.user_id).correlate(Address)

main_bq = self.bakery(lambda s: s.query(Address))
main_bq += lambda q: q.filter(
    sub_bq.to_query(q).exists())

在子查询用于第一个针对Session的可调用对象时,也接受Session

sub_bq = self.bakery(lambda s: s.query(User.name))
sub_bq += lambda q: q.filter(
    User.id == Address.user_id).correlate(Address)

main_bq = self.bakery(
    lambda s: s.query(
    Address.id, sub_bq.to_query(q).scalar_subquery())
)

参数:

query_or_session

一个Query对象或一个类Session对象,假设它在封闭的BakedQuery可调用对象的上下文中。

版本 1.3 中的新功能。

method with_criteria(fn, *args)

向从此克隆的BakedQuery添加一个条件函数。

这相当于使用+运算符产生一个具有修改的新BakedQuery

class sqlalchemy.ext.baked.Bakery

返回一个返回BakedQuery的可调用对象。

此对象由类方法BakedQuery.bakery()返回。它存在为了便于检查“缓存”。

版本 1.2 中的新功能。

class sqlalchemy.ext.baked.Result

对一个Session发起一个BakedQuery的调用。

Result对象是实际创建或从缓存中检索的针对目标SessionQuery对象,并且然后为结果调用。

method all()

返回所有行。

等效于Query.all()

method count()

返回‘count’。

等效于Query.count()

请注意,这使用子查询确保准确计算,而不管原始语句的结构如何。

method first()

返回第一行。

等效于Query.first()

method get(ident)

根据标识检索对象。

等效于Query.get()

method one()

返回确切的一个结果或引发异常。

等效于Query.one()

method one_or_none()

返回一个或零个结果,或者对于多行引发异常。

等效于Query.one_or_none()

method params(*args, **kw)

指定要替换为字符串 SQL 语句的参数。

method scalar()

返回第一个结果的第一个元素,如果没有行,则返回 None。如果返回多行,则引发 MultipleResultsFound 异常。

等效于Query.scalar()

method with_post_criteria(fn)

添加一个将在缓存后应用的条件函数。

这将添加一个函数,该函数将针对从缓存中检索的Query对象运行。目前仅包括Query.params()Query.execution_options()方法。

警告

Result.with_post_criteria()函数应用于Query对象之后查询的 SQL 语句对象已从缓存中检索。只应使用Query.params()Query.execution_options()方法。

在版本 1.2 中新增。

概要

使用烘焙系统的方法是首先生成所谓的“烘焙坊”,该坊代表一系列特定的查询对象的存储:

from sqlalchemy.ext import baked

bakery = baked.bakery()

上述“面包店”将在默认为 200 个元素的 LRU 缓存中存储缓存数据,需要注意的是,ORM 查询通常会包含一个为调用的 ORM 查询条目,以及每个数据库方言的 SQL 字符串的条目。

面包店允许我们通过指定其构造方式为一系列 Python 可调用对象来构建一个Query对象,这些对象通常是 lambda 表达式。为了简洁使用,它重写了+=运算符,使得典型的查询构建看起来像下面这样:

from sqlalchemy import bindparam

def search_for_user(session, username, email=None):
    baked_query = bakery(lambda session: session.query(User))
    baked_query += lambda q: q.filter(User.name == bindparam("username"))

    baked_query += lambda q: q.order_by(User.id)

    if email:
        baked_query += lambda q: q.filter(User.email == bindparam("email"))

    result = baked_query(session).params(username=username, email=email).all()

    return result

以下是关于上述代码的一些观察:

  1. baked_query对象是BakedQuery的一个实例。这个对象本质上是一个真正的 orm Query对象的“构造器”,但它本身并不是真正的 Query对象。

  2. 实际的Query对象根本没有构建,直到函数的最后一刻调用Result.all()时。

  3. 添加到baked_query对象的步骤都表示为 Python 函数,通常是 lambda 函数。给bakery()函数的第一个 lambda 函数以Session作为其参数。其余的 lambda 函数每个都以Query作为其参数。

  4. 在上述代码中,即使我们的应用程序可能多次调用search_for_user(),即使在每次调用中我们都会构建一个全新的BakedQuery对象,所有的 lambda 函数只会被调用一次。只要此查询被缓存在面包店中,每个 lambda 函数永远不会再次被调用。

  5. 缓存是通过存储lambda 对象本身的引用来实现的,以形成一个缓存键;也就是说,Python 解释器将这些函数分配给 Python 标识符,这决定了如何在后续运行中识别查询。对于那些指定了email参数的search_for_user()调用,可调用对象lambda q: q.filter(User.email == bindparam('email'))将成为检索到的缓存键的一部分;当emailNone时,此可调用对象不会成为缓存键的一部分。

  6. 因为 lambda 函数只被调用一次,所以至关重要的是在 lambda 函数内部不引用可能在调用之间更改的变量;相反,假设这些是要绑定到 SQL 字符串中的值,我们使用 bindparam() 来构造命名参数,在稍后使用 Result.params() 应用其实际值。

性能

烘焙查询可能看起来有点奇怪,有点笨拙,有点啰嗦。然而,在应用程序中调用多次的查询中,Python 性能的节约非常显著。在 Performance 中演示的示例套件 short_selects 对比了每个仅返回一行的查询,例如以下常规查询:

session = Session(bind=engine)
for id_ in random.sample(ids, n):
    session.query(Customer).filter(Customer.id == id_).one()

与等效的“烘焙”查询相比:

bakery = baked.bakery()
s = Session(bind=engine)
for id_ in random.sample(ids, n):
    q = bakery(lambda s: s.query(Customer))
    q += lambda q: q.filter(Customer.id == bindparam("id"))
    q(s).params(id=id_).one()

对于对每个块进行 10000 次调用的 Python 函数调用次数的差异为:

test_baked_query : test a baked query of the full entity.
                   (10000 iterations); total fn calls 1951294

test_orm_query :   test a straight ORM query of the full entity.
                   (10000 iterations); total fn calls 7900535

在一台性能强大的笔记本电脑上,这在秒数上表现如下:

test_baked_query : test a baked query of the full entity.
                   (10000 iterations); total time 2.174126 sec

test_orm_query :   test a straight ORM query of the full entity.
                   (10000 iterations); total time 7.958516 sec

请注意,这个测试非常有意地包含了只返回一行的查询。对于返回许多行的查询,烘焙查询的性能优势将逐渐减少,与获取行所花费的时间成比例。必须牢记的是,烘焙查询功能仅适用于构建查询本身,而不适用于获取结果。使用烘焙功能绝不是使应用程序更快的保证;它只是一个可能有用的功能,适用于那些已经被测量为受到这种特定形式的开销影响的应用程序。

理念

上面的“lambda”方法是更传统的“参数化”方法的超集。假设我们希望构建一个简单的系统,在这个系统中我们只需构建一个Query,然后将其存储在字典中以便重复使用。现在,我们可以通过构建查询,然后通过调用 my_cached_query = query.with_session(None) 来移除其Session来实现这一点:

my_simple_cache = {}

def lookup(session, id_argument):
    if "my_key" not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        my_simple_cache["my_key"] = query.with_session(None)
    else:
        query = my_simple_cache["my_key"].with_session(session)

    return query.params(id=id_argument).all()

上述方法只能带来非常微小的性能提升。通过重用Query,我们可以节省在session.query(Model)构造函数内部的 Python 工作以及调用filter(Model.id == bindparam('id'))时所需的工作,这将为我们跳过 Core 表达式的构建以及将其发送到Query.filter()。然而,该方法仍然在每次调用Query.all()时重新生成完整的Select对象,并且每次还会将这个全新的Select对象发送到字符串编译步骤中,对于像上面这样的简单情况,这可能占据了大约 70% 的开销。

为了减少额外的开销,我们需要一些更专门的逻辑,一种记忆构建选择对象和构建 SQL 的方法。在维基中的BakedQuery部分有一个例子,这是该功能的前身,但在那个系统中,我们没有缓存查询的构建。为了消除所有开销,我们需要缓存查询的构建以及 SQL 编译。假设我们按照这种方式调整了配方,并制作了一个.bake()方法,用于预先编译查询的 SQL,生成一个可以以最小开销调用的新对象。我们的例子变成了:

my_simple_cache = {}

def lookup(session, id_argument):
    if "my_key" not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        my_simple_cache["my_key"] = query.with_session(None).bake()
    else:
        query = my_simple_cache["my_key"].with_session(session)

    return query.params(id=id_argument).all()

上面,我们已经解决了性能问题,但我们仍然需要处理这个字符串缓存键。

我们可以使用“面包房”方法来重新构建上述方法,使其看起来不像“构建 lambda”方法那样不寻常,而更像是对简单“重用查询”的简单改进:

bakery = baked.bakery()

def lookup(session, id_argument):
    def create_model_query(session):
        return session.query(Model).filter(Model.id == bindparam("id"))

    parameterized_query = bakery.bake(create_model_query)
    return parameterized_query(session).params(id=id_argument).all()

上面,我们使用“baked”系统的方式与简单的“缓存查询”系统非常相似。但是,它使用了两行更少的代码,不需要制造一个“my_key”的缓存键,而且还包含了与我们自定义的“bake”函数相同的功能,该函数缓存了查询构造函数,筛选调用,生成Select对象以及字符串编译步骤的 100% Python 调用工作。

从上面的内容中,如果我们问自己,“如果查找需要根据查询结构做条件决策,会怎样?”,这就是为什么“烘焙”是这样的方式的原因。我们可以从任意数量的函数构建参数化查询,而不是从一个函数构建(这是我们最初认为烘焙可能起作用的方式)。考虑我们的简单示例,如果我们需要在查询中有一个额外的条件子句:

my_simple_cache = {}

def lookup(session, id_argument, include_frobnizzle=False):
    if include_frobnizzle:
        cache_key = "my_key_with_frobnizzle"
    else:
        cache_key = "my_key_without_frobnizzle"

    if cache_key not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        if include_frobnizzle:
            query = query.filter(Model.frobnizzle == True)

        my_simple_cache[cache_key] = query.with_session(None).bake()
    else:
        query = my_simple_cache[cache_key].with_session(session)

    return query.params(id=id_argument).all()

我们的“简单”参数化系统现在必须负责生成缓存键,考虑到是否传递了“include_frobnizzle”标志,因为该标志的存在意味着生成的 SQL 将完全不同。很明显,随着查询构建的复杂性增加,缓存这些查询的任务会很快变得繁重。我们可以将上面的示例转换为直接使用“bakery”如下:

bakery = baked.bakery()

def lookup(session, id_argument, include_frobnizzle=False):
    def create_model_query(session):
        return session.query(Model).filter(Model.id == bindparam("id"))

    parameterized_query = bakery.bake(create_model_query)

    if include_frobnizzle:

        def include_frobnizzle_in_query(query):
            return query.filter(Model.frobnizzle == True)

        parameterized_query = parameterized_query.with_criteria(
            include_frobnizzle_in_query
        )

    return parameterized_query(session).params(id=id_argument).all()

在上面的情况下,我们不仅缓存查询对象,还缓存生成 SQL 所需的所有工作。我们也不再需要处理确保生成准确考虑到我们所做的所有结构修改的缓存键;这现在是自动处理的,而且没有错误的机会。

这段代码示例比简单示例少了几行,消除了处理缓存键的需要,并具有完整的所谓“烘焙”功能的巨大性能优势。但仍然有点冗长!因此,我们将像BakedQuery.add_criteria()BakedQuery.with_criteria()这样的方法缩短为运算符,并鼓励(尽管当然不是必须!)使用简单的 lambda 表达式,只是为了减少冗长:

bakery = baked.bakery()

def lookup(session, id_argument, include_frobnizzle=False):
    parameterized_query = bakery.bake(
        lambda s: s.query(Model).filter(Model.id == bindparam("id"))
    )

    if include_frobnizzle:
        parameterized_query += lambda q: q.filter(Model.frobnizzle == True)

    return parameterized_query(session).params(id=id_argument).all()

在上面的情况下,这种方法更容易实现,并且在代码流程上更类似于非缓存查询函数的代码,因此使得代码更容易移植。

上述描述基本上是到达当前“烘焙”方法的设计过程的总结。从“正常”方法开始,缓存键构建和管理的额外问题,消除所有多余的 Python 执行以及需要处理条件构建的查询都需要解决,最终导致了最终方法。

特殊查询技术

这一部分将描述一些特定查询情况下的技术。

使用 IN 表达式

在 SQLAlchemy 中,ColumnOperators.in_() 方法在历史上基于传递给方法的项目列表渲染一组变量绑定参数。这对于烘焙查询不起作用,因为该列表的长度可能在不同调用时发生变化。为了解决这个问题,bindparam.expanding 参数支持一个延迟渲染的 IN 表达式,可以安全地缓存在烘焙查询内部。实际元素列表在语句执行时渲染,而不是在语句编译时:

bakery = baked.bakery()

baked_query = bakery(lambda session: session.query(User))
baked_query += lambda q: q.filter(User.name.in_(bindparam("username", expanding=True)))

result = baked_query.with_session(session).params(username=["ed", "fred"]).all()

另请参阅

bindparam.expanding

ColumnOperators.in_()

使用子查询

在使用Query对象时,通常需要一个Query对象用于在另一个内部生成子查询。在Query当前处于烘焙形式的情况下,可以使用一个中间方法来检索Query对象,使用BakedQuery.to_query()方法。该方法传递给生成烘焙查询特定步骤的 lambda 可调用参数的SessionQuery

bakery = baked.bakery()

# a baked query that will end up being used as a subquery
my_subq = bakery(lambda s: s.query(User.id))
my_subq += lambda q: q.filter(User.id == Address.user_id)

# select a correlated subquery in the top columns list,
# we have the "session" argument, pass that
my_q = bakery(lambda s: s.query(Address.id, my_subq.to_query(s).as_scalar()))

# use a correlated subquery in some of the criteria, we have
# the "query" argument, pass that.
my_q += lambda q: q.filter(my_subq.to_query(q).exists())

版本 1.3 中新增。

使用 before_compile 事件

自 SQLAlchemy 1.3.11 起,针对特定的 Query 使用 QueryEvents.before_compile() 事件将禁止烘焙查询系统缓存查询,如果事件挂钩返回一个与传入的不同的新 Query 对象。这样,每次使用特定的 Query 都可以调用 QueryEvents.before_compile() 钩子,以适应每次更改查询的钩子。要允许 QueryEvents.before_compile() 修改 sqlalchemy.orm.Query() 对象,但仍然允许结果被缓存,可以注册事件并传递 bake_ok=True 标志:

@event.listens_for(Query, "before_compile", retval=True, bake_ok=True)
def my_event(query):
    for desc in query.column_descriptions:
        if desc["type"] is User:
            entity = desc["entity"]
            query = query.filter(entity.deleted == False)
    return query

上述策略适用于每次以完全相同方式修改给定的 Query 的事件,不依赖于特定参数或更改的外部状态。

新版本 1.3.11 中添加了 bake_ok 标志到 QueryEvents.before_compile() 事件,并且如果此标志未设置,则不允许为返回新 Query 对象的事件处理程序通过“烘焙”扩展进行缓存。 ### 使用 IN 表达式

SQLAlchemy 中的 ColumnOperators.in_() 方法在历史上根据传递给方法的项目列表呈现一组变量绑定参数。对于烘焙查询,这不起作用,因为该列表的长度可以在不同的调用中改变。为解决此问题,bindparam.expanding 参数支持在烘焙查询中安全缓存的延迟呈现 IN 表达式。实际元素列表在语句执行时呈现,而不是在语句编译时:

bakery = baked.bakery()

baked_query = bakery(lambda session: session.query(User))
baked_query += lambda q: q.filter(User.name.in_(bindparam("username", expanding=True)))

result = baked_query.with_session(session).params(username=["ed", "fred"]).all()

另请参阅

bindparam.expanding

ColumnOperators.in_()

使用子查询

当使用Query对象时,通常需要一个Query对象用于在另一个查询中生成子查询。在当前Query处于烘焙形式时,可能需要使用一个临时方法来检索Query对象,该方法使用BakedQuery.to_query()方法。此方法传递给用于生成烘焙查询特定步骤的 lambda 可调用的SessionQuery参数:

bakery = baked.bakery()

# a baked query that will end up being used as a subquery
my_subq = bakery(lambda s: s.query(User.id))
my_subq += lambda q: q.filter(User.id == Address.user_id)

# select a correlated subquery in the top columns list,
# we have the "session" argument, pass that
my_q = bakery(lambda s: s.query(Address.id, my_subq.to_query(s).as_scalar()))

# use a correlated subquery in some of the criteria, we have
# the "query" argument, pass that.
my_q += lambda q: q.filter(my_subq.to_query(q).exists())

新版本 1.3。

使用 before_compile 事件

从 SQLAlchemy 1.3.11 开始,针对特定Query使用QueryEvents.before_compile()事件将阻止烘焙查询系统缓存查询,如果事件钩子返回一个与传入的不同的新Query对象。这是为了每次使用时都可以调用特定QueryEvents.before_compile()钩子,以适应每次都以不同方式修改查询的钩子。要允许QueryEvents.before_compile()修改sqlalchemy.orm.Query()对象,但仍然允许结果被缓存,可以注册事件并传递bake_ok=True标志:

@event.listens_for(Query, "before_compile", retval=True, bake_ok=True)
def my_event(query):
    for desc in query.column_descriptions:
        if desc["type"] is User:
            entity = desc["entity"]
            query = query.filter(entity.deleted == False)
    return query

以上策略适用于每次都会以完全相同方式修改给定的Query的事件,不依赖于特定参数或会发生变化的外部状态。

新版本 1.3.11 中新增:- 在QueryEvents.before_compile()事件中添加了“bake_ok”标志,并且如果未设置此标志,则禁止通过“baked”扩展对返回新的Query对象的事件处理程序进行缓存。

禁用全局 Baked 查询

标志Session.enable_baked_queries可以设置为 False,导致所有烘焙查询在用于该Session时不使用缓存:

session = Session(engine, enable_baked_queries=False)

与所有会话标志一样,它也被工厂对象如sessionmaker和方法如sessionmaker.configure()所接受。

此标志的直接理由是,应用程序可能由于用户定义的烘焙查询或其他烘焙查询问题而看到问题,可以将行为关闭,以识别或排除烘焙查询作为问题的原因。

版本 1.2 中的新功能。

惰性加载集成

从版本 1.4 起更改:自 SQLAlchemy 1.4 起,“烘焙查询”系统不再是关系加载系统的一部分。取而代之的是使用本地缓存系统。

API 文档

对象名称 描述
BakedQuery 用于Query对象的构建器对象。
bakery 构建一个新的面包店。
Bakery 返回一个BakedQuery的可调用对象。
function sqlalchemy.ext.baked.bakery(size=200, _size_alert=None)

构建一个新的面包店。

返回:

一个Bakery的实例

class sqlalchemy.ext.baked.BakedQuery

成员

add_criteria(), bakery(), for_session(), spoil(), to_query(), with_criteria()

用于Query对象的构建器对象。

method add_criteria(fn, *args)

为此BakedQuery添加一个条件函数。

这相当于使用 += 运算符就地修改BakedQuery

classmethod bakery(size=200, _size_alert=None)

构建一个新的面包店。

返回:

一个Bakery的实例

method for_session(session)

为此BakedQuery返回一个Result对象。

这相当于将BakedQuery作为 Python 可调用对象调用,例如 result = my_baked_query(session)

method spoil(full=False)

取消此BakedQuery对象上将发生的任何查询缓存。

BakedQuery仍然可以正常使用,但是额外的创建函数不会被缓存;它们将在每次调用时被调用。

这是为了支持构建烘焙查询的特定步骤使查询无法缓存的情况,例如依赖于某些不可缓存值的变体。

参数:

full – 如果为 False,则仅在 spoil 步骤之后添加到此BakedQuery对象的函数将不被缓存;直到此点为止的BakedQuery状态将从缓存中提取。如果为 True,则每次都会从头开始构建整个Query对象,每次调用都会调用所有创建函数。

method to_query(query_or_session)

返回用作子查询的Query对象。

此方法应在用于生成封闭BakedQuery步骤的 lambda 可调用内使用。参数通常应为传递给 lambda 的Query对象:

sub_bq = self.bakery(lambda s: s.query(User.name))
sub_bq += lambda q: q.filter(
    User.id == Address.user_id).correlate(Address)

main_bq = self.bakery(lambda s: s.query(Address))
main_bq += lambda q: q.filter(
    sub_bq.to_query(q).exists())

在第一个可调用中使用子查询针对Session的情况下,也接受Session

sub_bq = self.bakery(lambda s: s.query(User.name))
sub_bq += lambda q: q.filter(
    User.id == Address.user_id).correlate(Address)

main_bq = self.bakery(
    lambda s: s.query(
    Address.id, sub_bq.to_query(q).scalar_subquery())
)

参数:

query_or_session

一个Query对象或一个类Session对象,假定在封闭BakedQuery可调用的上下文中。

版本 1.3 中新增。

method with_criteria(fn, *args)

向从此克隆的BakedQuery添加一个条件函数。

这相当于使用+运算符生成具有修改的新BakedQuery

class sqlalchemy.ext.baked.Bakery

返回一个返回BakedQuery的可调用对象。

此对象由类方法BakedQuery.bakery()返回。它作为一个对象存在,以便可以轻松检查“缓存”。

版本 1.2 中新增。

class sqlalchemy.ext.baked.Result

针对Session调用BakedQuery

Result 对象是实际创建或从缓存中检索到的Query对象,针对目标Session进行调用以获取结果。

method all()

返回所有行。

等同于Query.all()

method count()

返回‘count’。

等同于Query.count()

请注意,这使用子查询来确保准确计数,而不考虑原始语句的结构。

method first()

返回第一行。

等同于Query.first()

method get(ident)

根据标识检索对象。

等同于Query.get()

method one()

返回确切的一个结果或引发异常。

等同于Query.one()

method one_or_none()

返回一个或零个结果,或者对于多行会引发异常。

等同于Query.one_or_none()

method params(*args, **kw)

指定要替换到字符串 SQL 语句中的参数。

method scalar()

返回第一个结果的第一个元素,如果没有行则返回 None。如果返回多行,则引发 MultipleResultsFound 异常。

等同于Query.scalar()

method with_post_criteria(fn)

添加一个将在缓存后应用的条件函数。

这添加了一个将在从缓存中检索到的Query对象上运行的函数。目前仅包括Query.params()Query.execution_options()方法。

警告

Result.with_post_criteria() 函数应用于查询的Query对象之后查询的 SQL 语句对象已从缓存中检索。只应使用Query.params()Query.execution_options()方法。

新版本 1.2 中新增。

声明式扩展

原文:docs.sqlalchemy.org/en/20/orm/extensions/declarative/index.html

声明式映射 API 特定的扩展。

1.4 版本更改:绝大部分声明式扩展现在已整合到 SQLAlchemy ORM 中,并可从 sqlalchemy.orm 命名空间导入。请参阅声明式映射的文档以获取新文档。有关更改的概述,请参阅声明式现已与 ORM 整合,并带有新功能。

对象名称 描述
AbstractConcreteBase 一个用于“具体”声明式映射的辅助类。
ConcreteBase 一个用于“具体”声明式映射的辅助类。
DeferredReflection 一个用于基于延迟反射步骤构建映射的辅助类。
class sqlalchemy.ext.declarative.AbstractConcreteBase

一个用于“具体”声明式映射的辅助类。

AbstractConcreteBase 将自动使用 polymorphic_union() 函数,对所有作为此类的子类映射的表执行。该函数通过 __declare_first__() 函数调用,这实际上是一个 before_configured() 事件的钩子。

AbstractConcreteBase 应用 Mapper 到其直接继承的类,就像对任何其他声明式映射的类一样。然而,Mapper 没有映射到任何特定的 Table 对象。相反,它直接映射到由 polymorphic_union() 产生的“多态”可选择的对象,并且不执行自己的持久化操作。与 ConcreteBase 相比,后者将其直接继承的类映射到直接存储行的实际 Table

注意

AbstractConcreteBase延迟了基类的映射器创建,直到所有子类都已定义,因为它需要创建一个针对包含所有子类表的可选择项的映射。为了实现这一点,它等待映射器配置事件发生,然后扫描所有配置的子类,并设置一个将一次性查询所有子类的映射。

虽然此事件通常会自动调用,但在AbstractConcreteBase的情况下,如果第一个操作是针对此基类的查询,则可能需要在定义所有子类映射之后显式调用它。为此,一旦所有期望的类都已配置,可以调用正在使用的registry上的registry.configure()方法,该方法可在特定声明基类的关系中使用:

Base.registry.configure()

示例:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.ext.declarative import AbstractConcreteBase

class Base(DeclarativeBase):
    pass

class Employee(AbstractConcreteBase, Base):
    pass

class Manager(Employee):
    __tablename__ = 'manager'
    employee_id = Column(Integer, primary_key=True)
    name = Column(String(50))
    manager_data = Column(String(40))

    __mapper_args__ = {
        'polymorphic_identity':'manager',
        'concrete':True
    }

Base.registry.configure()

抽象基类在声明时以一种特殊的方式处理;在类配置时,它的行为类似于声明式的混入或__abstract__基类。一旦类被配置并生成映射,它会被映射自身,但在其所有子类之后。这是在任何其他 SQLAlchemy API 功能中都找不到的非常独特的映射系统。

使用这种方法,我们可以指定将在映射的子类上发生的列和属性,就像我们通常在 Mixin 和自定义基类中所做的那样:

from sqlalchemy.ext.declarative import AbstractConcreteBase

class Company(Base):
    __tablename__ = 'company'
    id = Column(Integer, primary_key=True)

class Employee(AbstractConcreteBase, Base):
    strict_attrs = True

    employee_id = Column(Integer, primary_key=True)

    @declared_attr
    def company_id(cls):
        return Column(ForeignKey('company.id'))

    @declared_attr
    def company(cls):
        return relationship("Company")

class Manager(Employee):
    __tablename__ = 'manager'

    name = Column(String(50))
    manager_data = Column(String(40))

    __mapper_args__ = {
        'polymorphic_identity':'manager',
        'concrete':True
    }

Base.registry.configure()

然而,当我们使用我们的映射时,ManagerEmployee都将拥有一个可独立使用的.company属性:

session.execute(
    select(Employee).filter(Employee.company.has(id=5))
)

参数:

strict_attrs

当在基类上指定时,“严格”属性模式被启用,试图将基类上的 ORM 映射属性限制为仅当下立即存在的属性,同时仍保留“多态”加载行为。

2.0 版中新增。

另请参阅

ConcreteBase

具体表继承

抽象具体类

类签名

sqlalchemy.ext.declarative.AbstractConcreteBase (sqlalchemy.ext.declarative.extensions.ConcreteBase)

class sqlalchemy.ext.declarative.ConcreteBase

用于‘具体’声明映射的辅助类。

ConcreteBase 会自动使用 polymorphic_union() 函数,针对所有映射为该类的子类的表。该函数通过 __declare_last__() 函数调用,这实质上是 after_configured() 事件的钩子。

ConcreteBase 为类本身生成一个映射表。与 AbstractConcreteBase 相比,后者不会。

示例:

from sqlalchemy.ext.declarative import ConcreteBase

class Employee(ConcreteBase, Base):
    __tablename__ = 'employee'
    employee_id = Column(Integer, primary_key=True)
    name = Column(String(50))
    __mapper_args__ = {
                    'polymorphic_identity':'employee',
                    'concrete':True}

class Manager(Employee):
    __tablename__ = 'manager'
    employee_id = Column(Integer, primary_key=True)
    name = Column(String(50))
    manager_data = Column(String(40))
    __mapper_args__ = {
                    'polymorphic_identity':'manager',
                    'concrete':True}

polymorphic_union() 使用的鉴别器列的默认名称为 type。为了适应映射的用例,其中映射表中的实际列已命名为 type,可以通过设置 _concrete_discriminator_name 属性来配置鉴别器名称:

class Employee(ConcreteBase, Base):
    _concrete_discriminator_name = '_concrete_discriminator'

自版本 1.3.19 中新增:为 ConcreteBase 添加了 _concrete_discriminator_name 属性,以便自定义虚拟鉴别器列名称。

自版本 1.4.2 中更改:只需将 _concrete_discriminator_name 属性放置在最基类上即可使所有子类正确生效。如果映射列名称与鉴别器名称冲突,则现在会显示显式错误消息,而在 1.3.x 系列中会有一些警告,然后生成一个无用的查询。

另请参阅

AbstractConcreteBase

具体表继承

class sqlalchemy.ext.declarative.DeferredReflection

一个用于基于延迟反射步骤构建映射的辅助类。

通常情况下,通过将一个 Table 对象设置为具有 autoload_with=engine 的 __table__ 属性,可以使用反射来使用声明。一个声明性类。需要注意的是,在构建普通声明性映射的时候,Table 必须是完全反映的,或者至少有一个主键列,这意味着在类声明时必须可用 Engine

DeferredReflection mixin 将映射器的构建移动到稍后的时间点,在调用首先反射到目前为止创建的所有 Table 对象的特定方法之后。类可以定义如下:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import DeferredReflection
Base = declarative_base()

class MyClass(DeferredReflection, Base):
    __tablename__ = 'mytable'

在上面,MyClass 还没有映射。在上述方式定义了一系列类之后,可以使用 prepare() 反射所有表并创建映射:

engine = create_engine("someengine://...")
DeferredReflection.prepare(engine)

DeferredReflection mixin 可以应用于单个类,用作声明基类本身,或用于自定义抽象类。使用抽象基类允许仅为特定准备步骤准备一部分类,这对于使用多个引擎的应用程序是必要的。例如,如果一个应用程序有两个引擎,您可能会使用两个基类,并分别准备每个基类,例如:

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'

# ... etc.

在上面,ReflectedOneReflectedTwo 的类层次结构可以分别配置:

ReflectedOne.prepare(engine_one)
ReflectedTwo.prepare(engine_two)

成员

prepare()

另请参阅

使用 DeferredReflection - 在 使用声明式配置表 部分。

classmethod prepare(bind: Engine | Connection, **reflect_kw: Any) → None

反射所有当前 DeferredReflection 子类的所有 Table 对象

参数:

  • bind

    EngineConnection 实例

    ..versionchanged:: 2.0.16 现在也接受 Connection

  • **reflect_kw

    传递给 MetaData.reflect() 的其他关键字参数,例如 MetaData.reflect.views

    新版本 2.0.16 中的内容。

Mypy / Pep-484 对 ORM 映射的支持

原文:docs.sqlalchemy.org/en/20/orm/extensions/mypy.html

当使用直接引用 Column 对象而不是 SQLAlchemy 2.0 中引入的 mapped_column() 构造时,支持 PEP 484 类型注释以及 MyPy 类型检查工具。

自 2.0 版开始已被弃用:SQLAlchemy Mypy 插件已弃用,并且可能在 SQLAlchemy 2.1 发布时被移除。我们建议用户尽快迁移。

无法跨不断变化的 mypy 发布维护此插件,未来的稳定性不能保证。

现代 SQLAlchemy 现在提供了 完全符合 pep-484 的映射语法;请参阅链接的部分以获取迁移详情。

安装

仅适用于 SQLAlchemy 2.0:不应安装存根,并且应完全卸载诸如 sqlalchemy-stubssqlalchemy2-stubs 等软件包。

Mypy 包本身是一个依赖项。

可以使用 pip 使用“mypy”额外钩子安装 Mypy:

pip install sqlalchemy[mypy]

插件本身如 Configuring mypy to use Plugins 中描述的那样配置,使用 sqlalchemy.ext.mypy.plugin 模块名,例如在 setup.cfg 中:

[mypy]
plugins = sqlalchemy.ext.mypy.plugin

插件功能

Mypy 插件的主要目的是拦截并修改 SQLAlchemy 声明性映射 的静态定义,使其与它们在被其 Mapper 对象 instrumented 后的结构相匹配。这允许类结构本身以及使用类的代码对 Mypy 工具有意义,否则基于当前声明性映射的功能,这是不可能的。该插件类似于需要为类似 dataclasses 这样的库修改类的动态插件。

为了涵盖这种情况经常发生的主要区域,考虑以下 ORM 映射,使用 User 类的典型示例:

from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import declarative_base

# "Base" is a class that is created dynamically from the
# declarative_base() function
Base = declarative_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

# "some_user" is an instance of the User class, which
# accepts "id" and "name" kwargs based on the mapping
some_user = User(id=5, name="user")

# it has an attribute called .name that's a string
print(f"Username: {some_user.name}")

# a select() construct makes use of SQL expressions derived from the
# User class itself
select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))

上述,Mypy 扩展可以执行的步骤包括:

  • 解释由 declarative_base() 生成的 Base 动态类,以便从中继承的类被认为是映射的。它还可以适应在使用装饰器进行声明式映射(无声明式基类)中描述的类装饰器方法。

  • 对在声明式“内联”样式中定义的 ORM 映射属性进行类型推断,例如上面示例中 User 类的 idname 属性。这包括 User 的实例将使用 int 类型的 idstr 类型的 name。还包括当访问 User.idUser.name 类级属性时,如上面的 select() 语句中所示,它们与 SQL 表达式行为兼容,这是从 InstrumentedAttribute 属性描述符类派生的。

  • __init__() 方法应用于尚未包含显式构造函数的映射类,该构造函数接受检测到的所有映射属性的特定类型的关键字参数。

当 Mypy 插件处理上述文件时,传递给 Mypy 工具的结果静态类定义和 Python 代码等效于以下内容:

from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import Mapped
from sqlalchemy.orm.decl_api import DeclarativeMeta

class Base(metaclass=DeclarativeMeta):
    __abstract__ = True

class User(Base):
    __tablename__ = "user"

    id: Mapped[Optional[int]] = Mapped._special_method(
        Column(Integer, primary_key=True)
    )
    name: Mapped[Optional[str]] = Mapped._special_method(Column(String))

    def __init__(self, id: Optional[int] = ..., name: Optional[str] = ...) -> None: ...

some_user = User(id=5, name="user")

print(f"Username: {some_user.name}")

select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))

上述已经采取的关键步骤包括:

  • Base 类现在明确地是基于 DeclarativeMeta 类定义的,而不再是一个动态类。

  • idname 属性是基于 Mapped 类定义的,该类代表一个在类和实例级别表现出不同行为的 Python 描述符。Mapped 类现在是用于所有 ORM 映射属性的 InstrumentedAttribute 类的基类。

    Mapped 被定义为一个针对任意 Python 类型的通用类,这意味着特定的 Mapped 实例与特定的 Python 类型相关联,例如上面的 Mapped[Optional[int]]Mapped[Optional[str]

  • 声明性映射属性赋值的右侧被移除,因为这类似于Mapper类通常要执行的操作,即它将用InstrumentedAttribute](../internals.html#sqlalchemy.orm.InstrumentedAttribute "sqlalchemy.orm.InstrumentedAttribute")的特定实例替换这些属性。原始表达式移动到一个函数调用中,这样可以仍然进行类型检查而不与表达式的左侧冲突。对于 Mypy 来说,左侧的类型注释足以理解属性的行为。

  • 添加了User.__init__()方法的类型存根,其中包括了正确的关键字和数据类型。

用法

以下各小节将讨论到目前为止已经考虑到的符合 PEP-484 的各种使用情况。

基于 TypeEngine 的列的内省

对于包含显式数据类型的映射列,当它们被映射为内联属性时,映射类型将被自动内省:

class MyClass(Base):
    # ...

    id = Column(Integer, primary_key=True)
    name = Column("employee_name", String(50), nullable=False)
    other_name = Column(String(50))

上述,idnameother_name的最终类级数据类型将被内省为Mapped[Optional[int]]Mapped[Optional[str]]Mapped[Optional[str]]。这些类型默认始终被认为是Optional,即使对于主键和非空列也是如此。原因是因为虽然数据库列idname不能为 NULL,但 Python 属性idname很可能是None,而不需要显式的构造函数:

>>> m1 = MyClass()
>>> m1.id
None

上述列的类型可以被显式地声明,提供了更清晰的自我文档化以及能够控制哪些类型是可选的两个优点:

class MyClass(Base):
    # ...

    id: int = Column(Integer, primary_key=True)
    name: str = Column("employee_name", String(50), nullable=False)
    other_name: Optional[str] = Column(String(50))

Mypy 插件将接受上述intstrOptional[str]并将它们转换为包含在其周围的Mapped[]类型。Mapped[]构造也可以被显式使用:

from sqlalchemy.orm import Mapped

class MyClass(Base):
    # ...

    id: Mapped[int] = Column(Integer, primary_key=True)
    name: Mapped[str] = Column("employee_name", String(50), nullable=False)
    other_name: Mapped[Optional[str]] = Column(String(50))

当类型是非可选时,这意味着从MyClass的实例中访问的属性将被认为是非None的:

mc = MyClass(...)

# will pass mypy --strict
name: str = mc.name

对于可选属性,Mypy 认为类型必须包含 None,否则就是Optional

mc = MyClass(...)

# will pass mypy --strict
other_name: Optional[str] = mc.name

无论映射的属性是否被标记为Optional__init__()方法的生成都仍然认为所有关键字都是可选的。这再次与 SQLAlchemy ORM 在创建构造函数时实际执行的操作相匹配,不应与诸如 Python dataclasses之类的验证系统的行为混淆,后者将生成一个根据注释匹配的构造函数,包括可选和必需的属性。

没有明确类型的列

包含ForeignKey修饰符的列在 SQLAlchemy 声明映射中不需要指定数据类型。对于这种类型的属性,Mypy 插件将通知用户需要发送明确的类型:

# .. other imports
from sqlalchemy.sql.schema import ForeignKey

Base = declarative_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id = Column(ForeignKey("user.id"))

插件将按以下方式传递消息:

$ mypy test3.py --strict
test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
ORM mapped expression assigned to attribute 'user_id'; please specify a
Python type or Mapped[<python type>] on the left hand side.
Found 1 error in 1 file (checked 1 source file)

要解决问题,请对Address.user_id列应用明确的类型注释:

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

使用命令式表映射列

在命令式表样式中,Column定义位于与映射属性本身分开的Table构造内。Mypy 插件不考虑这个Table,而是支持可以明确声明属性,并且必须使用Mapped类将其标识为映射属性:

class MyClass(Base):
    __table__ = Table(
        "mytable",
        Base.metadata,
        Column(Integer, primary_key=True),
        Column("employee_name", String(50), nullable=False),
        Column(String(50)),
    )

    id: Mapped[int]
    name: Mapped[str]
    other_name: Mapped[Optional[str]]

上述Mapped注释被视为映射列,并将包含在默认构造函数中,同时为MyClass在类级别和实例级别提供正确的类型配置文件。

映射关系

该插件对使用类型推断来检测关系类型有限支持。对于所有无法检测类型的情况,它将发出信息丰富的错误消息,并且在所有情况下,可以明确提供适当的类型,要么使用Mapped类,要么选择在内联声明中省略它。插件还需要确定关系是指向集合还是标量,并且为此依赖于relationship.uselist和/或relationship.collection_class参数的显式值。如果这些参数都不存在,则需要明确的类型,以及如果relationship()的目标类型是字符串或可调用对象,而不是类:

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user = relationship(User)

上述映射将产生以下错误:

test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)

可以通过使用relationship(User, uselist=False)或提供类型来解决错误,在这种情况下是标量User对象:

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user: User = relationship(User)

对于集合,类似的模式也适用,即在没有uselist=Truerelationship.collection_class的情况下,可以使用诸如List之类的集合注释。还可以完全适当地使用类的字符串名称进行注释,如 pep-484 所支持,确保根据需要在TYPE_CHECKING 块中导入类:

from typing import TYPE_CHECKING, List

from .mymodel import Base

if TYPE_CHECKING:
    # if the target of the relationship is in another module
    # that cannot normally be imported at runtime
    from .myaddressmodel import Address

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    addresses: List["Address"] = relationship("Address")

与列一样,Mapped 类也可以显式应用:

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user: Mapped[User] = relationship(User, back_populates="addresses")

使用 @declared_attr 和声明性混合类

declared_attr 类允许在类级别函数中声明声明性映射的属性,并且在使用声明性混合类时特别有用。对于这些函数,函数的返回类型应使用Mapped[]构造或指示函数返回的确切对象类型进行注释。此外,“mixin”类(即不以declarative_base()类扩展,也不使用诸如registry.mapped()之类的方法映射的类)应该被装饰上 declarative_mixin() 装饰器,这为 Mypy 插件提供了一个提示,指明特定的类意图充当声明性混合类:

from sqlalchemy.orm import declarative_mixin, declared_attr

@declarative_mixin
class HasUpdatedAt:
    @declared_attr
    def updated_at(cls) -> Column[DateTime]:  # uses Column
        return Column(DateTime)

@declarative_mixin
class HasCompany:
    @declared_attr
    def company_id(cls) -> Mapped[int]:  # uses Mapped
        return Column(ForeignKey("company.id"))

    @declared_attr
    def company(cls) -> Mapped["Company"]:
        return relationship("Company")

class Employee(HasUpdatedAt, HasCompany, Base):
    __tablename__ = "employee"

    id = Column(Integer, primary_key=True)
    name = Column(String)

注意方法HasCompany.company的实际返回类型与注释之间的不匹配。Mypy 插件将所有@declared_attr函数转换为简单的带注释的属性,以避免这种复杂性:

# what Mypy sees
class HasCompany:
    company_id: Mapped[int]
    company: Mapped["Company"]

与数据类或其他类型敏感的属性系统相结合

在 将 ORM 映射应用到现有数据类(遗留数据类用法) 中的 Python 数据类集成示例存在一个问题;Python 数据类期望明确的类型,它将用于构建类,并且每个赋值语句中给定的值都是重要的。也就是说,必须确切地声明以下类才能被数据类接受:

mapper_registry: registry = registry()

@mapper_registry.mapped
@dataclass
class User:
    __table__ = Table(
        "user",
        mapper_registry.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("fullname", String(50)),
        Column("nickname", String(12)),
    )
    id: int = field(init=False)
    name: Optional[str] = None
    fullname: Optional[str] = None
    nickname: Optional[str] = None
    addresses: List[Address] = field(default_factory=list)

    __mapper_args__ = {  # type: ignore
        "properties": {"addresses": relationship("Address")}
    }

我们不能将我们的Mapped[]类型应用于属性idname等,因为它们会被@dataclass装饰器拒绝。此外,Mypy 还有另一个专门用于数据类的插件,这也可能妨碍我们的操作。

上述类实际上会顺利通过 Mypy 的类型检查;我们唯一缺少的是在User上的属性可用于 SQL 表达式,例如:

stmt = select(User.name).where(User.id.in_([1, 2, 3]))

为了提供一个解决方法,Mypy 插件具有一个额外的功能,我们可以指定一个额外的属性 _mypy_mapped_attrs,它是一个包含类级对象或它们的字符串名称的列表。这个属性可以在 TYPE_CHECKING 变量内部是条件性的:

@mapper_registry.mapped
@dataclass
class User:
    __table__ = Table(
        "user",
        mapper_registry.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("fullname", String(50)),
        Column("nickname", String(12)),
    )
    id: int = field(init=False)
    name: Optional[str] = None
    fullname: Optional[str]
    nickname: Optional[str]
    addresses: List[Address] = field(default_factory=list)

    if TYPE_CHECKING:
        _mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]

    __mapper_args__ = {  # type: ignore
        "properties": {"addresses": relationship("Address")}
    }

使用上述方法,列在 _mypy_mapped_attrs 中的属性将应用 Mapped 类型信息,以便在类绑定上下文中使用 User 类时,它将表现为一个 SQLAlchemy 映射类。

安装

对于 仅适用于 SQLAlchemy 2.0:不应安装存根,而应完全卸载像 sqlalchemy-stubssqlalchemy2-stubs 这样的包。

Mypy 包本身是一个依赖项。

可以使用 pip 使用 “mypy” extras 钩子安装 Mypy:

pip install sqlalchemy[mypy]

插件本身配置如 配置 mypy 使用插件 中所述,使用 sqlalchemy.ext.mypy.plugin 模块名称,例如在 setup.cfg 中:

[mypy]
plugins = sqlalchemy.ext.mypy.plugin

插件的功能

Mypy 插件的主要目的是拦截和修改 SQLAlchemy 声明式映射 的静态定义,以使其与它们在被 Mapper 对象 仪器化 后的结构相匹配。这使得类结构本身以及使用类的代码对 Mypy 工具有意义,否则根据当前声明式映射的功能,这将不是情况。该插件类似于为像 dataclasses 这样的库所需的类似插件,这些插件在运行时动态地修改类。

要涵盖此类情况经常发生的主要领域,请考虑以下 ORM 映射,使用 User 类的典型示例:

from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import declarative_base

# "Base" is a class that is created dynamically from the
# declarative_base() function
Base = declarative_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

# "some_user" is an instance of the User class, which
# accepts "id" and "name" kwargs based on the mapping
some_user = User(id=5, name="user")

# it has an attribute called .name that's a string
print(f"Username: {some_user.name}")

# a select() construct makes use of SQL expressions derived from the
# User class itself
select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))

上面,Mypy 扩展可以执行的步骤包括:

  • 对由 declarative_base() 生成的 Base 动态类进行解释,以便继承它的类被知道是已映射的。它还可以适应 使用装饰器进行声明式映射(无声明式基类) 中描述的类装饰器方法。

  • 对于在声明式“内联”样式中定义的 ORM 映射属性的类型推断,例如上面示例中 User 类的 idname 属性。这包括 User 实例将使用 int 类型的 idstr 类型的 name。它还包括当访问 User.idUser.name 类级属性时,正如它们在上面的 select() 语句中那样,它们与 SQL 表达式行为兼容,这是从 InstrumentedAttribute 属性描述符类派生的。

  • __init__() 方法应用于尚未包含显式构造函数的映射类,该构造函数接受特定类型的关键字参数,用于检测到的所有映射属性。

当 Mypy 插件处理上述文件时,结果的静态类定义和传递给 Mypy 工具的 Python 代码等效于以下内容:

from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import Mapped
from sqlalchemy.orm.decl_api import DeclarativeMeta

class Base(metaclass=DeclarativeMeta):
    __abstract__ = True

class User(Base):
    __tablename__ = "user"

    id: Mapped[Optional[int]] = Mapped._special_method(
        Column(Integer, primary_key=True)
    )
    name: Mapped[Optional[str]] = Mapped._special_method(Column(String))

    def __init__(self, id: Optional[int] = ..., name: Optional[str] = ...) -> None: ...

some_user = User(id=5, name="user")

print(f"Username: {some_user.name}")

select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))

以上已经采取的关键步骤包括:

  • Base 类现在明确地以 DeclarativeMeta 类的形式定义,而不是动态类。

  • idname 属性是以 Mapped 类的术语定义的,该类表示在类与实例级别上表现出不同行为的 Python 描述符。Mapped 类现在是用于所有 ORM 映射属性的 InstrumentedAttribute 类的基类。

    Mapped 被定义为针对任意 Python 类型的通用类,这意味着 Mapped 的特定出现与特定的 Python 类型相关联,例如上面的 Mapped[Optional[int]]Mapped[Optional[str]]

  • 声明式映射属性分配的右侧 已移除,因为这类似于 Mapper 类通常会执行的操作,即它将这些属性替换为 InstrumentedAttribute 的具体实例。原始表达式移到一个函数调用中,这将允许它仍然被类型检查而不与表达式的左侧发生冲突。对于 Mypy 来说,左侧的类型注释足以理解属性的行为。

  • User.__init__() 方法添加了类型存根,其中包括正确的关键字和数据类型。

使用方法

以下各小节将讨论迄今为止已考虑到的个别用例的 pep-484 符合性。

基于 TypeEngine 的列的自省

对于包含显式数据类型的映射列,当它们作为内联属性映射时,映射类型将被自动解析:

class MyClass(Base):
    # ...

    id = Column(Integer, primary_key=True)
    name = Column("employee_name", String(50), nullable=False)
    other_name = Column(String(50))

在上面,idnameother_name 这些最终的类级别数据类型将被解析为 Mapped[Optional[int]]Mapped[Optional[str]]Mapped[Optional[str]]。这些类型默认情况下总是被认为是 Optional 的,即使对于主键和非空列也是如此。原因是因为虽然数据库列idname不能为 NULL,但 Python 属性 idname 在没有显式构造函数的情况下肯定可以是 None

>>> m1 = MyClass()
>>> m1.id
None

上述列的类型可以被显式地声明,提供了更清晰的自我文档说明以及能够控制哪些类型是可选的两个优点:

class MyClass(Base):
    # ...

    id: int = Column(Integer, primary_key=True)
    name: str = Column("employee_name", String(50), nullable=False)
    other_name: Optional[str] = Column(String(50))

Mypy 插件将接受上述的 intstrOptional[str],并将它们转换为包含在 Mapped[] 类型周围的类型。Mapped[] 结构也可以被显式使用:

from sqlalchemy.orm import Mapped

class MyClass(Base):
    # ...

    id: Mapped[int] = Column(Integer, primary_key=True)
    name: Mapped[str] = Column("employee_name", String(50), nullable=False)
    other_name: Mapped[Optional[str]] = Column(String(50))

当类型是非可选时,这意味着从 MyClass 实例访问的属性将被视为非 None:

mc = MyClass(...)

# will pass mypy --strict
name: str = mc.name

对于可选属性,Mypy 认为类型必须包括 None,否则为 Optional

mc = MyClass(...)

# will pass mypy --strict
other_name: Optional[str] = mc.name

无论映射属性是否被标记为 Optional,生成的 __init__() 方法仍然将所有关键字视为可选的。这再次与 SQLAlchemy ORM 实际创建构造函数时的行为相匹配,不应与诸如 Python dataclasses 之类的验证系统的行为混淆,后者将生成一个与注释匹配的构造函数,以确定可选 vs. 必需属性的注解。

没有明确类型的列

包含 ForeignKey 修改器的列在 SQLAlchemy 声明式映射中不需要指定数据类型。对于这种类型的属性,Mypy 插件将通知用户需要发送一个显式类型:

# .. other imports
from sqlalchemy.sql.schema import ForeignKey

Base = declarative_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id = Column(ForeignKey("user.id"))

插件将如下传递消息:

$ mypy test3.py --strict
test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
ORM mapped expression assigned to attribute 'user_id'; please specify a
Python type or Mapped[<python type>] on the left hand side.
Found 1 error in 1 file (checked 1 source file)

要解决此问题,请为 Address.user_id 列应用显式类型注释:

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

使用命令式表格映射列

在命令式表格风格中,Column 定义位于一个与映射属性本身分离的 Table 结构内。Mypy 插件不考虑这个 Table,而是支持可以显式声明属性,必须使用 Mapped 类来标识它们为映射属性:

class MyClass(Base):
    __table__ = Table(
        "mytable",
        Base.metadata,
        Column(Integer, primary_key=True),
        Column("employee_name", String(50), nullable=False),
        Column(String(50)),
    )

    id: Mapped[int]
    name: Mapped[str]
    other_name: Mapped[Optional[str]]

上述Mapped注释被视为映射列,并将包含在默认构造函数中,以及在类级别和实例级别为MyClass提供正确的类型配置文件。

映射关系

该插件对使用类型推断来检测关系的类型有限支持。对于所有这些无法检测到类型的情况,它都将发出一个信息丰富的错误消息,在所有情况下,可以明确提供适当的类型,要么使用Mapped类,要么选择在内联声明中省略它。该插件还需要确定关系是指向集合还是标量,并且为此依赖于relationship.uselist和/或relationship.collection_class参数的显式值。如果这些参数都不存在,则需要明确的类型,以及如果relationship()的目标类型是字符串或可调用对象而不是类,则也需要明确的类型:

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user = relationship(User)

上述映射将产生以下错误:

test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)

错误可以通过使用relationship(User, uselist=False)或者提供类型来解决,例如标量User对象:

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user: User = relationship(User)

对于集合,类似的模式也适用,如果没有uselist=True或者relationship.collection_class,可以使用集合注释,如List。在注释中使用类的字符串名称也是完全合适的,这是由 pep-484 支持的,确保在适当的时候在TYPE_CHECKING 块中导入该类:

from typing import TYPE_CHECKING, List

from .mymodel import Base

if TYPE_CHECKING:
    # if the target of the relationship is in another module
    # that cannot normally be imported at runtime
    from .myaddressmodel import Address

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    addresses: List["Address"] = relationship("Address")

与列相似,Mapped类也可以显式地应用:

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user: Mapped[User] = relationship(User, back_populates="addresses")

使用 @declared_attr 和声明性混合

declared_attr类允许在类级函数中声明 Declarative 映射属性,并且在使用声明性混入时特别有用。对于这些函数,函数的返回类型应该使用Mapped[]构造进行注释,或者指示函数返回的对象的确切类型。此外,未被映射的“混入”类(即不从declarative_base()类继承,也不使用诸如registry.mapped()之类的方法进行映射)应该用declarative_mixin()装饰器进行修饰,这为 Mypy 插件提供了一个提示,表明特定类意图作为声明性混入:

from sqlalchemy.orm import declarative_mixin, declared_attr

@declarative_mixin
class HasUpdatedAt:
    @declared_attr
    def updated_at(cls) -> Column[DateTime]:  # uses Column
        return Column(DateTime)

@declarative_mixin
class HasCompany:
    @declared_attr
    def company_id(cls) -> Mapped[int]:  # uses Mapped
        return Column(ForeignKey("company.id"))

    @declared_attr
    def company(cls) -> Mapped["Company"]:
        return relationship("Company")

class Employee(HasUpdatedAt, HasCompany, Base):
    __tablename__ = "employee"

    id = Column(Integer, primary_key=True)
    name = Column(String)

注意像HasCompany.company这样的方法的实际返回类型与注释的不匹配。Mypy 插件将所有@declared_attr函数转换为简单的注释属性,以避免这种复杂性:

# what Mypy sees
class HasCompany:
    company_id: Mapped[int]
    company: Mapped["Company"]

与 Dataclasses 或其他类型敏感的属性系统结合

Python dataclasses 集成的示例在将 ORM 映射应用于现有数据类(传统数据类用法)中提出了一个问题;Python dataclasses 期望一个明确的类型,它将用于构建类,并且每个赋值语句中给定的值是重要的。也就是说,一个如下所示的类必须要准确地声明才能被 dataclasses 接受:

mapper_registry: registry = registry()

@mapper_registry.mapped
@dataclass
class User:
    __table__ = Table(
        "user",
        mapper_registry.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("fullname", String(50)),
        Column("nickname", String(12)),
    )
    id: int = field(init=False)
    name: Optional[str] = None
    fullname: Optional[str] = None
    nickname: Optional[str] = None
    addresses: List[Address] = field(default_factory=list)

    __mapper_args__ = {  # type: ignore
        "properties": {"addresses": relationship("Address")}
    }

我们无法将我们的Mapped[]类型应用于属性idname等,因为它们将被@dataclass装饰器拒绝。此外,Mypy 还有另一个专门用于 dataclasses 的插件,这也可能影响我们的操作。

上述类实际上会通过 Mypy 的类型检查而没有问题;我们唯一缺少的是User上的属性能够在 SQL 表达式中使用,比如:

stmt = select(User.name).where(User.id.in_([1, 2, 3]))

为了解决这个问题,Mypy 插件有一个额外的功能,我们可以指定一个额外的属性_mypy_mapped_attrs,这是一个包含类级对象或它们的字符串名称的列表。这个属性可以在TYPE_CHECKING变量内部进行条件判断:

@mapper_registry.mapped
@dataclass
class User:
    __table__ = Table(
        "user",
        mapper_registry.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("fullname", String(50)),
        Column("nickname", String(12)),
    )
    id: int = field(init=False)
    name: Optional[str] = None
    fullname: Optional[str]
    nickname: Optional[str]
    addresses: List[Address] = field(default_factory=list)

    if TYPE_CHECKING:
        _mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]

    __mapper_args__ = {  # type: ignore
        "properties": {"addresses": relationship("Address")}
    }

使用上述方法,列在_mypy_mapped_attrs中列出的属性将应用于Mapped类型信息,以便在类绑定上下文中使用User类时,它将表现为一个 SQLAlchemy 映射类。

基于 TypeEngine 的列的内省

对于包含显式数据类型的映射列,当它们被映射为内联属性时,映射类型将自动进行内省:

class MyClass(Base):
    # ...

    id = Column(Integer, primary_key=True)
    name = Column("employee_name", String(50), nullable=False)
    other_name = Column(String(50))

在上面,idnameother_name的最终类级数据类型将被内省为Mapped[Optional[int]]Mapped[Optional[str]]Mapped[Optional[str]]。类型默认始终被视为可选,即使对于主键和非空列也是如此。原因是因为虽然数据库列idname不能为 NULL,但 Python 属性idname可以毫无疑问地是None,而不需要显式构造函数:

>>> m1 = MyClass()
>>> m1.id
None

上述列的类型可以明确声明,提供两个优势,即更清晰的自我文档化以及能够控制哪些类型是可选的:

class MyClass(Base):
    # ...

    id: int = Column(Integer, primary_key=True)
    name: str = Column("employee_name", String(50), nullable=False)
    other_name: Optional[str] = Column(String(50))

Mypy 插件将接受上述intstrOptional[str],并将它们转换为包围它们的Mapped[]类型。Mapped[]结构也可以明确使用:

from sqlalchemy.orm import Mapped

class MyClass(Base):
    # ...

    id: Mapped[int] = Column(Integer, primary_key=True)
    name: Mapped[str] = Column("employee_name", String(50), nullable=False)
    other_name: Mapped[Optional[str]] = Column(String(50))

当类型为非可选时,这意味着从MyClass实例中访问的属性将被视为非None

mc = MyClass(...)

# will pass mypy --strict
name: str = mc.name

对于可选属性,Mypy 认为类型必须包含 None,否则为Optional

mc = MyClass(...)

# will pass mypy --strict
other_name: Optional[str] = mc.name

无论映射属性是否被标记为Optional__init__()方法的生成仍然考虑所有关键字都是可选的。这再次与 SQLAlchemy ORM 实际创建构造函数时的行为相匹配,不应与验证系统(如 Python dataclasses)的行为混淆,后者将根据注释生成与可选与必需属性相匹配的构造函数。

不具有显式类型的列

包含 ForeignKey 修改器的列在 SQLAlchemy 声明性映射中不需要指定数据类型。对于这种类型的属性,Mypy 插件将通知用户需要发送显式类型:

# .. other imports
from sqlalchemy.sql.schema import ForeignKey

Base = declarative_base()

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id = Column(ForeignKey("user.id"))

插件将以以下方式发送消息:

$ mypy test3.py --strict
test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
ORM mapped expression assigned to attribute 'user_id'; please specify a
Python type or Mapped[<python type>] on the left hand side.
Found 1 error in 1 file (checked 1 source file)

要解决此问题,请对Address.user_id列应用显式类型注释:

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

使用命令式表映射列

在 命令式表风格 中,Column 定义放在一个独立于映射属性本身的 Table 结构中。Mypy 插件不考虑这个Table,而是支持可以明确声明属性,并且必须使用 Mapped 类来标识它们为映射属性:

class MyClass(Base):
    __table__ = Table(
        "mytable",
        Base.metadata,
        Column(Integer, primary_key=True),
        Column("employee_name", String(50), nullable=False),
        Column(String(50)),
    )

    id: Mapped[int]
    name: Mapped[str]
    other_name: Mapped[Optional[str]]

上述Mapped注释被视为映射列,并将包含在默认构造函数中,同时为MyClass提供正确的类型配置文件,无论是在类级别还是实例级别。

映射关系

该插件对使用类型推断来检测关系类型有限支持。对于所有无法检测类型的情况,它将发出信息丰富的错误消息,并且在所有情况下,可以明确提供适当的类型,可以使用Mapped类或选择性地省略内联声明。插件还需要确定关系是引用集合还是标量,为此依赖于relationship.uselist和/或relationship.collection_class参数的显式值。如果这些参数都不存在,则需要明确类型,以及如果relationship()的目标类型是字符串或可调用的,而不是类:

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user = relationship(User)

上述映射将产生以下错误:

test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)

可以通过使用relationship(User, uselist=False)或提供类型来解决错误,在这种情况下是标量User对象:

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user: User = relationship(User)

对于集合,类似的模式适用,如果没有uselist=Truerelationship.collection_class,可以使用List等集合注释。在注释中使用类的字符串名称也是完全适当的,支持 pep-484,确保类在TYPE_CHECKING block中适当导入:

from typing import TYPE_CHECKING, List

from .mymodel import Base

if TYPE_CHECKING:
    # if the target of the relationship is in another module
    # that cannot normally be imported at runtime
    from .myaddressmodel import Address

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    addresses: List["Address"] = relationship("Address")

与列一样,Mapped类也可以显式应用:

class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id: int = Column(ForeignKey("user.id"))

    user: Mapped[User] = relationship(User, back_populates="addresses")

使用@declared_attr 和声明性混合

declared_attr 类允许在类级函数中声明声明性映射属性,并且在使用声明性混合时特别有用。对于这些函数,函数的返回类型应该使用Mapped[]构造进行注释,或者指示函数返回的确切对象类型。另外,未以其他方式映射的“混合”类(即不从declarative_base()类扩展,也不使用诸如registry.mapped()之类的方法进行映射)应该用declarative_mixin()装饰器进行装饰,这为 Mypy 插件提供了一个提示,表明特定的类打算作为声明性混合使用:

from sqlalchemy.orm import declarative_mixin, declared_attr

@declarative_mixin
class HasUpdatedAt:
    @declared_attr
    def updated_at(cls) -> Column[DateTime]:  # uses Column
        return Column(DateTime)

@declarative_mixin
class HasCompany:
    @declared_attr
    def company_id(cls) -> Mapped[int]:  # uses Mapped
        return Column(ForeignKey("company.id"))

    @declared_attr
    def company(cls) -> Mapped["Company"]:
        return relationship("Company")

class Employee(HasUpdatedAt, HasCompany, Base):
    __tablename__ = "employee"

    id = Column(Integer, primary_key=True)
    name = Column(String)

请注意,像HasCompany.company这样的方法的实际返回类型与其注释之间存在不匹配。Mypy 插件将所有@declared_attr函数转换为简单的注释属性,以避免这种复杂性:

# what Mypy sees
class HasCompany:
    company_id: Mapped[int]
    company: Mapped["Company"]

与数据类或其他类型敏感的属性系统结合

Python 数据类集成示例中的将 ORM 映射应用到现有数据类(旧数据类使用)存在一个问题;Python 数据类期望一个明确的类型,它将用于构建类,并且在每个赋值语句中给定的值是重要的。也就是说,必须准确地声明如下的类才能被数据类接受:

mapper_registry: registry = registry()

@mapper_registry.mapped
@dataclass
class User:
    __table__ = Table(
        "user",
        mapper_registry.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("fullname", String(50)),
        Column("nickname", String(12)),
    )
    id: int = field(init=False)
    name: Optional[str] = None
    fullname: Optional[str] = None
    nickname: Optional[str] = None
    addresses: List[Address] = field(default_factory=list)

    __mapper_args__ = {  # type: ignore
        "properties": {"addresses": relationship("Address")}
    }

我们无法将我们的Mapped[]类型应用于属性idname等,因为它们将被@dataclass装饰器拒绝。另外,Mypy 还有另一个专门针对数据类的插件,这也可能妨碍我们的操作。

上述类实际上将无障碍地通过 Mypy 的类型检查;我们唯一缺少的是User上属性被用于 SQL 表达式的能力,例如:

stmt = select(User.name).where(User.id.in_([1, 2, 3]))

为此提供一种解决方案,Mypy 插件具有一个额外的功能,我们可以指定一个额外的属性_mypy_mapped_attrs,它是一个包含类级对象或它们的字符串名称的列表。该属性可以在TYPE_CHECKING变量中条件化:

@mapper_registry.mapped
@dataclass
class User:
    __table__ = Table(
        "user",
        mapper_registry.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("fullname", String(50)),
        Column("nickname", String(12)),
    )
    id: int = field(init=False)
    name: Optional[str] = None
    fullname: Optional[str]
    nickname: Optional[str]
    addresses: List[Address] = field(default_factory=list)

    if TYPE_CHECKING:
        _mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]

    __mapper_args__ = {  # type: ignore
        "properties": {"addresses": relationship("Address")}
    }

使用上述方法,将在_mypy_mapped_attrs中列出的属性应用Mapped类型信息,以便在类绑定上下文中使用User类时,它将表现为 SQLAlchemy 映射类。

突变跟踪

原文:docs.sqlalchemy.org/en/20/orm/extensions/mutable.html

提供对标量值的就地更改的跟踪支持,这些更改传播到拥有父对象上的 ORM 更改事件中。

在标量列值上建立可变性

“可变”结构的典型示例是 Python 字典。按照 SQL 数据类型对象 中介绍的示例,我们从一个自定义类型开始,该类型将 Python 字典编组为 JSON 字符串,然后再进行持久化:

from sqlalchemy.types import TypeDecorator, VARCHAR
import json

class JSONEncodedDict(TypeDecorator):
    "Represents an immutable structure as a json-encoded string."

    impl = VARCHAR

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = json.dumps(value)
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            value = json.loads(value)
        return value

仅出于示例目的使用 jsonsqlalchemy.ext.mutable 扩展可与任何目标 Python 类型可能是可变的类型一起使用,包括 PickleTypeARRAY 等。

使用 sqlalchemy.ext.mutable 扩展时,值本身会跟踪所有引用它的父对象。下面,我们展示了 MutableDict 字典对象的简单版本,它将 Mutable mixin 应用于普通 Python 字典:

from sqlalchemy.ext.mutable import Mutable

class MutableDict(Mutable, dict):
    @classmethod
    def coerce(cls, key, value):
        "Convert plain dictionaries to MutableDict."

        if not isinstance(value, MutableDict):
            if isinstance(value, dict):
                return MutableDict(value)

            # this call will raise ValueError
            return Mutable.coerce(key, value)
        else:
            return value

    def __setitem__(self, key, value):
        "Detect dictionary set events and emit change events."

        dict.__setitem__(self, key, value)
        self.changed()

    def __delitem__(self, key):
        "Detect dictionary del events and emit change events."

        dict.__delitem__(self, key)
        self.changed()

上述字典类采用了子类化 Python 内置的 dict 的方法,以生成一个 dict 子类,该子类通过 __setitem__ 将所有突变事件路由到。这种方法有其变体,例如子类化 UserDict.UserDictcollections.MutableMapping;对于此示例而言,重要的部分是当数据结构发生就地更改时,将调用 Mutable.changed() 方法。

我们还重新定义了 Mutable.coerce() 方法,该方法将用于将不是 MutableDict 实例的任何值转换为适当的类型,例如 json 模块返回的普通字典。定义此方法是可选的;我们也可以创建我们的 JSONEncodedDict,使其始终返回 MutableDict 的实例,并且还确保所有调用代码都显式使用 MutableDict。当未覆盖 Mutable.coerce() 时,应用于父对象的任何不是可变类型实例的值都将引发 ValueError

我们的新 MutableDict 类型提供了一个类方法 Mutable.as_mutable(),我们可以在列元数据中使用它来关联类型。该方法获取给定的类型对象或类,并关联一个监听器,该监听器将检测到该类型的所有未来映射,并对映射的属性应用事件监听仪器。例如,使用经典的表元数据:

from sqlalchemy import Table, Column, Integer

my_data = Table('my_data', metadata,
    Column('id', Integer, primary_key=True),
    Column('data', MutableDict.as_mutable(JSONEncodedDict))
)

在上面,Mutable.as_mutable() 返回一个 JSONEncodedDict 实例(如果类型对象尚不是实例),该实例将拦截针对该类型映射的任何属性。下面我们建立一个简单的映射与 my_data 表:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))

MyDataClass.data 成员现在将收到对其值的原地更改的通知。

MyDataClass.data 成员的任何原地更改都会在父对象上标记属性为“脏”:

>>> from sqlalchemy.orm import Session

>>> sess = Session(some_engine)
>>> m1 = MyDataClass(data={'value1':'foo'})
>>> sess.add(m1)
>>> sess.commit()

>>> m1.data['value1'] = 'bar'
>>> assert m1 in sess.dirty
True

MutableDict 可以通过一步关联所有未来的 JSONEncodedDict 实例,使用 Mutable.associate_with()。这类似于 Mutable.as_mutable(),但它将无条件地拦截所有映射中所有 MutableDict 的出现,而无需单独声明它:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

MutableDict.associate_with(JSONEncodedDict)

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(JSONEncodedDict)

支持 Pickling

sqlalchemy.ext.mutable 扩展的关键在于在值对象上放置了一个 weakref.WeakKeyDictionary,它存储了父映射对象到与该值相关联的属性名称的映射。 WeakKeyDictionary 对象不可 pickle,因为它们包含 weakrefs 和函数回调。在我们的情况下,这是件好事,因为如果这个字典是可 pickle 的,那么它可能会导致我们的值对象的 pickle 大小过大,因为它们在不涉及父对象上下文的情况下被单独 pickle。开发人员在这里的责任只是提供一个 __getstate__ 方法,该方法将 MutableBase._parents() 集合从 pickle 流中排除:

class MyMutableType(Mutable):
    def __getstate__(self):
        d = self.__dict__.copy()
        d.pop('_parents', None)
        return d

对于我们的字典示例,我们需要返回字典本身的内容(并在 __setstate__ 上也进行恢复):

class MutableDict(Mutable, dict):
    # ....

    def __getstate__(self):
        return dict(self)

    def __setstate__(self, state):
        self.update(state)

如果我们的可变值对象作为它附加到的一个或多个父对象一起被 pickle,那么 Mutable mixin 将在每个值对象上重新建立 Mutable._parents 集合,因为拥有父对象本身被 unpickle。

接收事件

AttributeEvents.modified() 事件处理程序可用于在可变标量发出更改事件时接收事件。 当从可变扩展内调用 flag_modified() 函数时,将调用此事件处理程序:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy import event

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))

@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance, initiator):
    print("json value modified:", instance.data)
```  ## 在复合上建立可变性

复合是一种特殊的 ORM 功能,允许将单个标量属性分配给一个对象值,该对象值表示从底层映射表的一个或多个列中“组合”而成的信息。 通常示例是几何“点”,并在 复合列类型 中介绍。

与 `Mutable` 一样,用户定义的复合类将 `MutableComposite` 作为一个混合类,通过 `MutableComposite.changed()` 方法检测并传递更改事件给其父对象。 在复合类的情况下,检测通常通过特殊的 Python 方法 `__setattr__()` 进行。 在下面的示例中,我们扩展了 复合列类型 中介绍的 `Point` 类,以包括 `MutableComposite` 在其基类中,并通过 `__setattr__` 将属性设置事件路由到 `MutableComposite.changed()` 方法:

```py
import dataclasses
from sqlalchemy.ext.mutable import MutableComposite

@dataclasses.dataclass
class Point(MutableComposite):
    x: int
    y: int

    def __setattr__(self, key, value):
        "Intercept set events"

        # set the attribute
        object.__setattr__(self, key, value)

        # alert all parents to the change
        self.changed()

MutableComposite 类利用类映射事件自动为任何使用指定我们的 Point 类型的 composite() 的地方建立监听器。 下面,当 Point 映射到 Vertex 类时,将建立监听器,这些监听器将将来自 Point 对象的更改事件路由到每个 Vertex.startVertex.end 属性:

from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.orm import composite, mapped_column

class Base(DeclarativeBase):
    pass

class Vertex(Base):
    __tablename__ = "vertices"

    id: Mapped[int] = mapped_column(primary_key=True)

    start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
    end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))

    def __repr__(self):
        return f"Vertex(start={self.start}, end={self.end})"

Vertex.startVertex.end 成员的任何原地更改都将在父对象上标记该属性为“脏”:

>>> from sqlalchemy.orm import Session
>>> sess = Session(engine)
>>> v1 = Vertex(start=Point(3, 4), end=Point(12, 15))
>>> sess.add(v1)
sql>>> sess.flush()
BEGIN  (implicit)
INSERT  INTO  vertices  (x1,  y1,  x2,  y2)  VALUES  (?,  ?,  ?,  ?)
[...]  (3,  4,  12,  15)
>>> v1.end.x = 8
>>> assert v1 in sess.dirty
True
sql>>> sess.commit()
UPDATE  vertices  SET  x2=?  WHERE  vertices.id  =  ?
[...]  (8,  1)
COMMIT 

强制转换可变组合

MutableBase.coerce() 方法也支持复合类型。对于 MutableCompositeMutableBase.coerce() 方法仅在属性设置操作时调用,而不在加载操作中调用。覆盖 MutableBase.coerce() 方法基本上等同于为使用自定义复合类型的所有属性使用 validates() 验证程序:

@dataclasses.dataclass
class Point(MutableComposite):
    # other Point methods
    # ...

    def coerce(cls, key, value):
        if isinstance(value, tuple):
            value = Point(*value)
        elif not isinstance(value, Point):
            raise ValueError("tuple or Point expected")
        return value

支持 Pickling

Mutable 类似,MutableComposite 辅助类使用 weakref.WeakKeyDictionary,可通过 MutableBase._parents() 属性获得,该属性不可 picklable。如果我们需要 pickle Point 的实例或其所属的类 Vertex,我们至少需要定义一个不包含 _parents 字典的 __getstate__。下面我们定义了 Point 类的最小形式的 __getstate____setstate__

@dataclasses.dataclass
class Point(MutableComposite):
    # ...

    def __getstate__(self):
        return self.x, self.y

    def __setstate__(self, state):
        self.x, self.y = state

Mutable 一样,MutableComposite 增强了父对象的对象关系状态的 pickling 过程,以便 MutableBase._parents() 集合被恢复到所有 Point 对象中。

API 参考

对象名称 描述
Mutable 混合类,定义对父对象的变更事件的透明传播。
MutableBase MutableMutableComposite 的通用基类。
MutableComposite 混合类,定义对 SQLAlchemy “composite” 对象的变更事件的透明传播,传播到其拥有的父对象或父对象。
MutableDict 实现了 Mutable 的字典类型。
MutableList 实现了 Mutable 的列表类型。
MutableSet 实现Mutable的集合类型。
class sqlalchemy.ext.mutable.MutableBase

成员

_parents,coerce()

公共基类,用于MutableMutableComposite

attribute _parents

父对象的InstanceState->父对象上的属性名称的字典。

该属性是所谓的“记忆化”属性。首次访问时,它会使用一个新的weakref.WeakKeyDictionary进行初始化,并在后续访问时返回相同的对象。

在 1.4 版本中更改:现在使用InstanceState作为弱字典中的键,而不是实例本身。

classmethod coerce(key: str, value: Any) → Any | None

给定一个值,将其强制转换为目标类型。

可以被自定义子类重写,将传入数据强制转换为特定类型。

默认情况下,引发ValueError

根据父类是Mutable类型还是MutableComposite类型,在不同情况下调用此方法。对于前者,它在属性设置操作和 ORM 加载操作期间都会被调用。对于后者,它仅在属性设置操作期间被调用;composite()构造的机制在加载操作期间处理强制转换。

参数:

  • key – 正在设置的 ORM 映射属性的字符串名称。

  • value – 输入值。

返回:

如果无法完成强制转换,则该方法应返回强制转换后的值,或引发ValueError

class sqlalchemy.ext.mutable.Mutable

定义透明传播更改事件到父对象的混入。

查看在标量列值上建立可变性中的示例以获取用法信息。

成员

_get_listen_keys(),_listen_on_attribute(),_parents,as_mutable(),associate_with(),associate_with_attribute(),changed(),coerce()

类签名

sqlalchemy.ext.mutable.Mutablesqlalchemy.ext.mutable.MutableBase)

classmethod _get_listen_keys(attribute: QueryableAttribute[Any]) → Set[str]

继承自 MutableBase sqlalchemy.ext.mutable.MutableBase._get_listen_keys 方法

给定一个描述符属性,返回一个指示此属性状态变化的属性键的set()

这通常只是set([attribute.key]),但可以被覆盖以提供额外的键。例如,MutableComposite会用与组成复合值的列相关联的属性键来增加这个集合。

在拦截InstanceEvents.refresh()InstanceEvents.refresh_flush()事件时,将查询此集合,这些事件传递了已刷新的属性名称列表;该列表与此集合进行比较,以确定是否需要采取行动。

classmethod _listen_on_attribute(attribute: QueryableAttribute[Any], coerce: bool, parent_cls: _ExternalEntityType[Any]) → None

继承自 MutableBase sqlalchemy.ext.mutable.MutableBase._listen_on_attribute 方法

将此类型建立为给定映射描述符的变异监听器。

attribute _parents

继承自 MutableBase sqlalchemy.ext.mutable.MutableBase._parents 属性

父对象的InstanceState->父对象上的属性名的字典。

此属性是所谓的“记忆化”属性。它在第一次访问时使用一个新的weakref.WeakKeyDictionary进行初始化,并在后续访问时返回相同的对象。

自版本 1.4 更改:InstanceState现在作为弱字典中的键,而不是实例本身。

classmethod as_mutable(sqltype: TypeEngine) → TypeEngine

将 SQL 类型与此可变 Python 类型关联起来。

这将建立侦听器,以检测针对给定类型的 ORM 映射,并向这些映射添加变异事件跟踪器。

类型无条件地作为实例返回,因此可以内联使用as_mutable()

Table('mytable', metadata,
    Column('id', Integer, primary_key=True),
    Column('data', MyMutableType.as_mutable(PickleType))
)

请注意,返回的类型始终是一个实例,即使给定一个类,也只有明确声明了该类型实例的列才会接收到额外的仪器设备。

要将特定的可变类型与所有特定类型的所有出现相关联,请使用特定Mutable子类的Mutable.associate_with()类方法来建立全局关联。

警告

此方法建立的侦听器对所有映射器都是全局的,并且会被垃圾回收。只能对应用程序中永久的类型使用as_mutable(),不要与临时类型一起使用,否则这将导致内存使用量无限增长。

classmethod associate_with(sqltype: type) → None

将此包装器与未来的给定类型的映射列相关联。

这是一个方便的方法,会自动调用associate_with_attribute

警告

此方法建立的侦听器对所有映射器都是全局的,并且会被垃圾回收。只能对应用程序中永久的类型使用associate_with(),不要与临时类型一起使用,否则这将导致内存使用量无限增长。

classmethod associate_with_attribute(attribute: InstrumentedAttribute[_O]) → None

将此类型建立为给定映射描述符的变异侦听器。

method changed() → None

子类应该在发生变更事件时调用此方法。

classmethod coerce(key: str, value: Any) → Any | None

继承自 MutableBase.coerce() 方法的 MutableBase

给定一个值,将其强制转换为目标类型。

可以由自定义子类重写以将传入数据强制转换为特定类型。

默认情况下,引发ValueError

根据父类是Mutable类型还是MutableComposite类型,在不同的情况下调用此方法。对于前者,在属性设置操作和 ORM 加载操作期间都会调用它。对于后者,在属性设置操作期间才会调用它;composite()构造的机制处理加载操作期间的强制转换。

参数:

  • key – 正在设置的 ORM 映射属性的字符串名称。

  • value – 输入值。

返回:

如果无法完成转换,则该方法应返回转换后的值,或引发ValueError

class sqlalchemy.ext.mutable.MutableComposite

混入,定义了将 SQLAlchemy“组合”对象上的变更事件透明传播到其拥有的父对象的机制。

查看在组合上建立可变性中的示例以获取用法信息。

成员

changed()

类签名

sqlalchemy.ext.mutable.MutableCompositesqlalchemy.ext.mutable.MutableBase

method changed() → None

子类应在更改事件发生时调用此方法。

class sqlalchemy.ext.mutable.MutableDict

一种实现了 Mutable 的字典类型。

MutableDict 对象实现了一个字典,当更改字典的内容时会向底层映射发送更改事件,包括添加或删除值时。

请注意,MutableDict 不会将可变跟踪应用于字典内部的值本身。因此,它不足以解决跟踪对递归字典结构进行深层更改的用例,例如 JSON 结构。要支持此用例,请构建 MutableDict 的子类,该子类提供适当的强制转换,以便将放置在字典中的值也“可变”,并将事件发送到其父结构。

另请参阅

MutableList

MutableSet

成员

clear(), coerce(), pop(), popitem(), setdefault(), update()

类签名

sqlalchemy.ext.mutable.MutableDictsqlalchemy.ext.mutable.Mutablebuiltins.dicttyping.Generic

method clear() → None.  Remove all items from D.
classmethod coerce(key: str, value: Any) → MutableDict[_KT, _VT] | None

将普通字典转换为此类的实例。

method pop(k[, d]) → v, remove specified key and return the corresponding value.

如果找不到键,则在给定默认值的情况下返回;否则,引发 KeyError。

method popitem() → Tuple[_KT, _VT]

移除并返回一个(键,值)对作为 2 元组。

以 LIFO(后进先出)顺序返回键值对。如果字典为空,则引发 KeyError。

method setdefault(*arg)

如果字典中没有键,则将键插入并将其值设置为默认值。

如果字典中存在键,则返回键的值,否则返回默认值。

method update([E, ]**F) → None.  Update D from dict/iterable E and F.

如果 E 存在且具有 .keys() 方法,则执行以下操作:for k in E: D[k] = E[k] 如果 E 存在但缺少 .keys() 方法,则执行以下操作:for k, v in E: D[k] = v 在任一情况下,接下来执行以下操作:for k in F: D[k] = F[k]

class sqlalchemy.ext.mutable.MutableList

一种实现了 Mutable 的列表类型。

MutableList对象实现了一个列表,在修改列表内容时会向底层映射发出更改事件,包括添加或删除值时。

注意MutableList不会对列表内部的值本身应用可变跟踪。因此,它不能解决跟踪递归可变结构(例如 JSON 结构)的深层更改的用例。要支持此用例,构建MutableList的子类,提供适当的强制转换以使放置在字典中的值也是“可变的”,并将事件传播到其父结构。

另请参见

MutableDict

MutableSet

成员

append(), clear(), coerce(), extend(), insert(), is_iterable(), is_scalar(), pop(), remove(), reverse(), sort()

类签名

sqlalchemy.ext.mutable.MutableListsqlalchemy.ext.mutable.Mutablebuiltins.listtyping.Generic

method append(x: _T) → None

将对象追加到列表末尾。

method clear() → None

从列表中删除所有项。

classmethod coerce(key: str, value: MutableList[_T] | _T) → MutableList[_T] | None

将普通列表转换为此类的实例。

method extend(x: Iterable[_T]) → None

通过从可迭代对象中追加元素来扩展列表。

method insert(i: SupportsIndex, x: _T) → None

在索引之前插入对象。

method is_iterable(value: _T | Iterable[_T]) → TypeGuard[Iterable[_T]]
method is_scalar(value: _T | Iterable[_T]) → TypeGuard[_T]
method pop(*arg: SupportsIndex) → _T

移除并返回索引处的项(默认为最后一个)。

如果列表为空或索引超出范围,则引发 IndexError。

method remove(i: _T) → None

移除第一次出现的值。

如果值不存在,则引发 ValueError。

method reverse() → None

原地反转。

method sort(**kw: Any) → None

对列表进行升序排序并返回 None。

排序是原地进行的(即修改列表本身)并且是稳定的(即保持两个相等元素的顺序)。

如果给定了键函数,则将其一次应用于每个列表项并根据其函数值升序或降序排序。

反转标志可以设置为按降序排序。

class sqlalchemy.ext.mutable.MutableSet

实现了Mutable的集合类型。

MutableSet 对象实现了一个集合,当集合的内容发生更改时,将向底层映射发出更改事件,包括添加或删除值时。

请注意,MutableSet 不会对集合中值本身应用可变跟踪。因此,它不是跟踪递归可变结构的深层更改的足够解决方案。为了支持这种用例,请构建一个MutableSet的子类,该子类提供适当的强制转换,使放置在字典中的值也是“可变的”,并向其父结构发出事件。

另请参阅

MutableDict

MutableList

成员

add(), clear(), coerce(), difference_update(), discard(), intersection_update(), pop(), remove(), symmetric_difference_update(), update()

类签名

sqlalchemy.ext.mutable.MutableSetsqlalchemy.ext.mutable.Mutablebuiltins.settyping.Generic

method add(elem: _T) → None

向集合添加一个元素。

如果元素已经存在,则不起作用。

method clear() → None

从此集合中移除所有元素。

classmethod coerce(index: str, value: Any) → MutableSet[_T] | None

将普通集合转换为此类的实例。

method difference_update(*arg: Iterable[Any]) → None

从此集合中删除另一个集合的所有元素。

method discard(elem: _T) → None

如果元素是成员,则从集合中删除一个元素。

如果元素不是成员,则不执行任何操作。

method intersection_update(*arg: Iterable[Any]) → None

使用自身与另一个集合的交集更新集合。

method pop(*arg: Any) → _T

移除并返回一个任意的集合元素。如果集合为空,则引发 KeyError。

method remove(elem: _T) → None

从集合中删除一个元素;它必须是成员。

如果元素不是成员,则引发 KeyError。

method symmetric_difference_update(*arg: Iterable[_T]) → None

使用自身与另一个集合的对称差更新集合。

method update(*arg: Iterable[_T]) → None

使用自身与其他集合的并集更新集合。

在标量列值上建立可变性

“可变”结构的典型示例是 Python 字典。在 SQL 数据类型对象中介绍的示例中,我们从自定义类型开始,该类型在持久化之前将 Python 字典编组为 JSON 字符串:

from sqlalchemy.types import TypeDecorator, VARCHAR
import json

class JSONEncodedDict(TypeDecorator):
    "Represents an immutable structure as a json-encoded string."

    impl = VARCHAR

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = json.dumps(value)
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            value = json.loads(value)
        return value

使用json仅用于示例目的。sqlalchemy.ext.mutable 扩展可以与任何目标 Python 类型可能是可变的类型一起使用,包括PickleTypeARRAY等。

当使用sqlalchemy.ext.mutable 扩展时,值本身跟踪所有引用它的父对象。下面,我们展示了一个简单版本的MutableDict字典对象,它将Mutable mixin 应用于普通的 Python 字典:

from sqlalchemy.ext.mutable import Mutable

class MutableDict(Mutable, dict):
    @classmethod
    def coerce(cls, key, value):
        "Convert plain dictionaries to MutableDict."

        if not isinstance(value, MutableDict):
            if isinstance(value, dict):
                return MutableDict(value)

            # this call will raise ValueError
            return Mutable.coerce(key, value)
        else:
            return value

    def __setitem__(self, key, value):
        "Detect dictionary set events and emit change events."

        dict.__setitem__(self, key, value)
        self.changed()

    def __delitem__(self, key):
        "Detect dictionary del events and emit change events."

        dict.__delitem__(self, key)
        self.changed()

上述字典类采用了子类化 Python 内置的dict的方法,以产生一个 dict 子类,通过__setitem__路由所有的变异事件。这种方法还有变体,比如子类化UserDict.UserDictcollections.MutableMapping;对于这个示例很重要的部分是,每当对数据结构进行就地更改时,都会调用Mutable.changed()方法。

我们还重新定义了Mutable.coerce() 方法,用于将不是MutableDict实例的任何值转换为适当的类型,比如json模块返回的普通字典。定义这个方法是可选的;我们也可以创建我们的JSONEncodedDict,使其始终返回MutableDict的实例,并确保所有调用代码都明确使用MutableDict。当未覆盖Mutable.coerce()时,应用于父对象的任何不是可变类型实例的值将引发ValueError

我们的新MutableDict类型提供了一个类方法Mutable.as_mutable(),我们可以在列元数据中使用它与类型关联。这个方法获取给定的类型对象或类,并关联一个监听器,将检测到所有将来映射到该类型的映射,应用事件监听仪器到映射的属性。例如,使用经典表元数据:

from sqlalchemy import Table, Column, Integer

my_data = Table('my_data', metadata,
    Column('id', Integer, primary_key=True),
    Column('data', MutableDict.as_mutable(JSONEncodedDict))
)

上面,Mutable.as_mutable() 返回一个JSONEncodedDict的实例(如果类型对象尚未是一个实例),它将拦截任何映射到该类型的属性。下面我们建立一个简单的映射到my_data表:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))

MyDataClass.data 成员现在将被通知其值的原地更改。

MyDataClass.data 成员的任何原地更改都将标记父对象的属性为“脏”:

>>> from sqlalchemy.orm import Session

>>> sess = Session(some_engine)
>>> m1 = MyDataClass(data={'value1':'foo'})
>>> sess.add(m1)
>>> sess.commit()

>>> m1.data['value1'] = 'bar'
>>> assert m1 in sess.dirty
True

MutableDict 可以通过一个步骤与所有未来的 JSONEncodedDict 实例关联,使用 Mutable.associate_with()。这类似于 Mutable.as_mutable(),但它将无条件拦截所有映射中 MutableDict 的所有出现,而无需单独声明它:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

MutableDict.associate_with(JSONEncodedDict)

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(JSONEncodedDict)

支持 Pickling

sqlalchemy.ext.mutable 扩展的关键在于在值对象上放置一个 weakref.WeakKeyDictionary,它存储了父映射对象的映射,键为它们与该值相关联的属性名。 WeakKeyDictionary 对象不可 pickle,因为它们包含弱引用和函数回调。在我们的情况下,这是一件好事,因为如果这个字典是可 pickle 的,它可能会导致独立于父对象上下文的值对象的 pickle 大小过大。在这里,开发者的责任仅仅是提供一个 __getstate__ 方法,从 pickle 流中排除 MutableBase._parents() 集合:

class MyMutableType(Mutable):
    def __getstate__(self):
        d = self.__dict__.copy()
        d.pop('_parents', None)
        return d

对于我们的字典示例,我们需要返回字典本身的内容(并在 setstate 中还原它们):

class MutableDict(Mutable, dict):
    # ....

    def __getstate__(self):
        return dict(self)

    def __setstate__(self, state):
        self.update(state)

如果我们的可变值对象被 pickle,而它附加到一个或多个也是 pickle 的父对象上,Mutable mixin 将在每个值对象上重新建立 Mutable._parents 集合,因为拥有父对象本身被 unpickle。

接收事件

当一个可变标量发出变更事件时,AttributeEvents.modified() 事件处理程序可以用于接收事件。当在可变扩展内部调用 flag_modified() 函数时,将调用此事件处理程序:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy import event

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))

@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance, initiator):
    print("json value modified:", instance.data)

支持 Pickling

sqlalchemy.ext.mutable 扩展的关键在于在值对象上放置一个 weakref.WeakKeyDictionary,该字典存储父映射对象的映射,以属性名称为键,这些父映射对象与该值相关联。由于 WeakKeyDictionary 对象包含弱引用和函数回调,因此它们不可 picklable。在我们的情况下,这是一件好事,因为如果这个字典是可 pickle 的,那么它可能会导致我们的值对象的 pickle 大小过大,这些值对象是在不涉及父对象的情况下 pickle 的。开发者在这里的责任只是提供一个 __getstate__ 方法,该方法从 pickle 流中排除了 MutableBase._parents() 集合:

class MyMutableType(Mutable):
    def __getstate__(self):
        d = self.__dict__.copy()
        d.pop('_parents', None)
        return d

对于我们的字典示例,我们需要返回字典本身的内容(并在 setstate 中还原它们):

class MutableDict(Mutable, dict):
    # ....

    def __getstate__(self):
        return dict(self)

    def __setstate__(self, state):
        self.update(state)

在我们可变的值对象作为 pickle 对象时,如果它附加在一个或多个也是 pickle 的父对象上,Mutable mixin 将在每个值对象上重新建立 Mutable._parents 集合,因为拥有父对象的本身会被 unpickle。

接收事件

AttributeEvents.modified() 事件处理程序可用于在可变标量发出更改事件时接收事件。当在可变扩展中调用 flag_modified() 函数时,将调用此事件处理程序:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy import event

class Base(DeclarativeBase):
    pass

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))

@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance, initiator):
    print("json value modified:", instance.data)

确立组合物的可变性

组合物是 ORM 的一种特殊功能,它允许将单个标量属性分配给一个对象值,该对象值表示从底层映射表中的一个或多个列中“组合”出的信息。通常的例子是几何“点”,并在 Composite Column Types 中介绍。

Mutable类似,用户定义的复合类作为一个混合类继承MutableComposite,通过MutableComposite.changed()方法检测并传递更改事件给其父类。对于复合类,通常是通过使用特殊的 Python 方法__setattr__()来进行检测。在下面的示例中,我们扩展了复合列类型中介绍的Point类,将MutableComposite包含在其基类中,并通过__setattr__将属性设置事件路由到MutableComposite.changed()方法:

import dataclasses
from sqlalchemy.ext.mutable import MutableComposite

@dataclasses.dataclass
class Point(MutableComposite):
    x: int
    y: int

    def __setattr__(self, key, value):
        "Intercept set events"

        # set the attribute
        object.__setattr__(self, key, value)

        # alert all parents to the change
        self.changed()

MutableComposite类利用类映射事件自动为任何指定我们的Point类型的composite()的使用建立监听器。下面,当Point映射到Vertex类时,将建立监听器,这些监听器将把Point对象的更改事件路由到Vertex.startVertex.end属性中的每一个:

from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.orm import composite, mapped_column

class Base(DeclarativeBase):
    pass

class Vertex(Base):
    __tablename__ = "vertices"

    id: Mapped[int] = mapped_column(primary_key=True)

    start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
    end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))

    def __repr__(self):
        return f"Vertex(start={self.start}, end={self.end})"

Vertex.startVertex.end成员的任何就地更改都会在父对象上标记属性为“脏”:

>>> from sqlalchemy.orm import Session
>>> sess = Session(engine)
>>> v1 = Vertex(start=Point(3, 4), end=Point(12, 15))
>>> sess.add(v1)
sql>>> sess.flush()
BEGIN  (implicit)
INSERT  INTO  vertices  (x1,  y1,  x2,  y2)  VALUES  (?,  ?,  ?,  ?)
[...]  (3,  4,  12,  15)
>>> v1.end.x = 8
>>> assert v1 in sess.dirty
True
sql>>> sess.commit()
UPDATE  vertices  SET  x2=?  WHERE  vertices.id  =  ?
[...]  (8,  1)
COMMIT 

强制可变复合类型

在复合类型上也支持MutableBase.coerce()方法。对于MutableCompositeMutableBase.coerce()方法仅在属性设置操作中调用,而不是在加载操作中调用。覆盖MutableBase.coerce()方法基本上等同于为使用自定义复合类型的所有属性使用validates()验证程序:

@dataclasses.dataclass
class Point(MutableComposite):
    # other Point methods
    # ...

    def coerce(cls, key, value):
        if isinstance(value, tuple):
            value = Point(*value)
        elif not isinstance(value, Point):
            raise ValueError("tuple or Point expected")
        return value

支持 Pickling

Mutable类似,MutableComposite辅助类使用了通过MutableBase._parents()属性获得的weakref.WeakKeyDictionary,该字典不可 pickle。如果我们需要 pickle Point 或其拥有类 Vertex 的实例,至少需要定义一个不包含 _parents 字典的__getstate__。下面我们定义了Point类的最小形式的__getstate____setstate__

@dataclasses.dataclass
class Point(MutableComposite):
    # ...

    def __getstate__(self):
        return self.x, self.y

    def __setstate__(self, state):
        self.x, self.y = state

Mutable类似,MutableComposite增强了父对象的对象关系状态的 pickling 过程,以便将MutableBase._parents()集合还原为所有Point对象。

强制转换可变复合类型

MutableBase.coerce()方法也支持复合类型。在MutableComposite的情况下,MutableBase.coerce()方法仅在属性设置操作而非加载操作时调用。覆盖MutableBase.coerce()方法基本上等同于对使用自定义复合类型的所有属性使用validates()验证程序:

@dataclasses.dataclass
class Point(MutableComposite):
    # other Point methods
    # ...

    def coerce(cls, key, value):
        if isinstance(value, tuple):
            value = Point(*value)
        elif not isinstance(value, Point):
            raise ValueError("tuple or Point expected")
        return value

支持 Pickling

Mutable类似,MutableComposite辅助类使用了通过MutableBase._parents()属性获得的weakref.WeakKeyDictionary,该字典不可 pickle。如果我们需要 pickle Point 或其拥有类 Vertex 的实例,至少需要定义一个不包含 _parents 字典的__getstate__。下面我们定义了Point类的最小形式的__getstate____setstate__

@dataclasses.dataclass
class Point(MutableComposite):
    # ...

    def __getstate__(self):
        return self.x, self.y

    def __setstate__(self, state):
        self.x, self.y = state

Mutable一样,MutableComposite增强了父对象的对象关系状态的 pickling 过程,以便将MutableBase._parents()集合还原为所有Point对象。

API 参考

对象名称 描述
Mutable 定义更改事件透明传播到父对象的混合类。
MutableBase MutableMutableComposite的通用基类。
MutableComposite 定义 SQLAlchemy “composite” 对象上的更改事件透明传播到其拥有的父对象或父对象的混合类。
MutableDict 一个实现Mutable的字典类型。
MutableList 一个实现Mutable的列表类型。
MutableSet 一个实现Mutable的集合类型。
class sqlalchemy.ext.mutable.MutableBase

成员

_parents, coerce()

MutableMutableComposite的通用基类。

attribute _parents

父对象的InstanceState字典->父对象上的属性名称。

此属性是所谓的“记忆化”属性。第一次访问时,它会用一个新的weakref.WeakKeyDictionary初始化自己,并在后续访问时返回相同的对象。

在 1.4 版中更改:InstanceState现在被用作弱字典中的键,而不是实例本身。

classmethod coerce(key: str, value: Any) → Any | None

给定一个值,将其强制转换为目标类型。

可以被自定义子类覆盖,将传入数据强制转换为特定类型。

默认情况下,引发ValueError

根据父类是Mutable类型还是MutableComposite类型,在不同的情况下调用此方法。对于前者,它在属性集操作和 ORM 加载操作期间都会被调用。对于后者,它仅在属性集操作期间被调用;composite()构造的机制在加载操作期间处理强制转换。

参数:

  • key – 正在设置的 ORM 映射属性的字符串名称。

  • value – 传入的值。

返回:

如果无法完成强制转换,该方法应返回强制转换后的值,或引发ValueError

class sqlalchemy.ext.mutable.Mutable

定义将更改事件透明传播到父对象的混合类。

查看在标量列值上建立可变性中的示例以获取用法信息。

成员

_get_listen_keys(), _listen_on_attribute(), _parents, as_mutable(), associate_with(), associate_with_attribute(), changed(), coerce()

类签名

sqlalchemy.ext.mutable.Mutablesqlalchemy.ext.mutable.MutableBase

classmethod _get_listen_keys(attribute: QueryableAttribute[Any]) → Set[str]

继承自 MutableBase sqlalchemy.ext.mutable.MutableBase._get_listen_keys 方法

给定一个描述符属性,返回指示此属性状态变化的属性键的set()

通常只是set([attribute.key]),但可以被覆盖以提供额外的键。例如,MutableComposite 会用包含组合值的列相关联的属性键来增加这个集合。

在拦截InstanceEvents.refresh()InstanceEvents.refresh_flush()事件时,会查询此集合,这些事件会传递一个已刷新的属性名称列表;该列表将与此集合进行比较,以确定是否需要采取行动。

classmethod _listen_on_attribute(attribute: QueryableAttribute[Any], coerce: bool, parent_cls: _ExternalEntityType[Any]) → None

继承自 MutableBasesqlalchemy.ext.mutable.MutableBase._listen_on_attribute 方法

将此类型作为给定映射描述符的变异监听器。

attribute _parents

继承自 MutableBasesqlalchemy.ext.mutable.MutableBase._parents 属性

父对象的InstanceState->父对象上的属性名称的字典。

此属性是所谓的“记忆化”属性。它在首次访问时使用一个新的weakref.WeakKeyDictionary进行初始化,并在后续访问时返回相同的对象。

在 1.4 版本中更改:现在使用InstanceState作为弱字典中的键,而不是实例本身。

classmethod as_mutable(sqltype: TypeEngine) → TypeEngine

将 SQL 类型与此可变 Python 类型关联。

这将建立监听器,用于检测针对给定类型的 ORM 映射,向这些映射添加变异事件跟踪器。

该类型无条件地作为一个实例返回,以便可以内联使用as_mutable()

Table('mytable', metadata,
    Column('id', Integer, primary_key=True),
    Column('data', MyMutableType.as_mutable(PickleType))
)

请注意,返回的类型始终是一个实例,即使给定一个类,也只有明确声明了该类型实例的列才会接收到额外的仪器化。

要将特定的可变类型与特定类型的所有出现关联起来,请使用Mutable.associate_with()类方法的特定Mutable子类来建立全局关联。

警告

此方法建立的监听器是全局的,适用于所有映射器,并且会被垃圾回收。只能对应用程序中永久的类型使用as_mutable(),而不是临时类型,否则会导致内存使用量无限增长。

classmethod associate_with(sqltype: type) → None

将此包装器与将来的给定类型的映射列关联起来。

这是一个方便的方法,会自动调用associate_with_attribute

警告

该方法建立的监听器是全局的,适用于所有映射器,并且会被垃圾回收。只能对应用程序中永久的类型使用associate_with(),而不是临时类型,否则会导致内存使用量无限增长。

classmethod associate_with_attribute(attribute: InstrumentedAttribute[_O]) → None

将此类型作为给定映射描述符的变异监听器。

method changed() → None

子类在发生更改事件时应调用此方法。

classmethod coerce(key: str, value: Any) → Any | None

继承自 MutableBase.coerce() 方法MutableBase

给定一个值,将其强制转换为目标类型。

可以被自定义子类重写以将传入的数据强制转换为特定类型。

默认情况下,引发 ValueError

此方法在不同的情况下被调用,具体取决于父类是 Mutable 类型还是 MutableComposite 类型。在前者的情况下,它将在属性设置操作以及 ORM 加载操作期间被调用。对于后者,它仅在属性设置操作期间被调用;composite() 构造的机制在加载操作期间处理强制转换。

参数:

  • key – 被设置的 ORM 映射属性的字符串名称。

  • value – 传入的值。

返回:

如果无法完成强制转换,则该方法应返回强制转换后的值,或引发 ValueError

class sqlalchemy.ext.mutable.MutableComposite

定义了对 SQLAlchemy “组合”对象的更改事件的透明传播的混合类到其拥有的父对象。

请参阅 在组合上建立可变性 中的示例以获取用法信息。

成员

changed()

类签名

class sqlalchemy.ext.mutable.MutableComposite (sqlalchemy.ext.mutable.MutableBase)

method changed() → None

子类应在更改事件发生时调用此方法。

class sqlalchemy.ext.mutable.MutableDict

实现了 Mutable 的字典类型。

MutableDict 对象实现了一个字典,在字典内容发生更改时将向基础映射发出更改事件,包括添加或移除值时。

请注意,MutableDict 不会 对字典内部的值本身应用可变跟踪。因此,它不足以解决跟踪递归字典结构(例如 JSON 结构)的深层更改的用例。要支持此用例,请构建一个 MutableDict 的子类,以提供适当的强制转换,以便放置在字典中的值也是“可变的”,并将事件传播到其父结构。

另请参见

MutableList

MutableSet

成员

clear(), coerce(), pop(), popitem(), setdefault(), update()

类签名

sqlalchemy.ext.mutable.MutableDict (sqlalchemy.ext.mutable.Mutable, builtins.dict, typing.Generic)

method clear() → None.  Remove all items from D.
classmethod coerce(key: str, value: Any) → MutableDict[_KT, _VT] | None

将普通字典转换为此类的实例。

method pop(k[, d]) → v, remove specified key and return the corresponding value.

如果找不到键,则返回默认值(如果给定);否则,引发 KeyError。

method popitem() → Tuple[_KT, _VT]

移除并返回一个(key, value)对作为 2 元组。

键值对以 LIFO(后进先出)顺序返回。如果字典为空,则引发 KeyError。

method setdefault(*arg)

如果键不在字典中,则将键插入并设置默认值。

如果键在字典中,则返回键的值,否则返回默认值。

method update([E, ]**F) → None.  Update D from dict/iterable E and F.

如果 E 存在并且具有.keys()方法,则执行: for k in E: D[k] = E[k] 如果 E 存在但缺少.keys()方法,则执行: for k, v in E: D[k] = v 在任一情况下,接下来执行: for k in F: D[k] = F[k]

class sqlalchemy.ext.mutable.MutableList

一个实现了Mutable的列表类型。

MutableList 对象实现了一个列表,当列表的内容被更改时,包括添加或删除值时,将向底层映射发送更改事件。

请注意,MutableList 不会对列表内部的值本身应用可变跟踪。因此,它不是跟踪对递归可变结构进行深层更改的使用案例的充分解决方案,例如 JSON 结构。为支持此使用案例,请构建MutableList的子类,该子类提供适当的强制转换以使放置在字典中的值也是“可变的”,并将事件发送到其父结构。

另请参阅

MutableDict

MutableSet

成员

append(), clear(), coerce(), extend(), insert(), is_iterable(), is_scalar(), pop(), remove(), reverse(), sort()

类签名

sqlalchemy.ext.mutable.MutableListsqlalchemy.ext.mutable.Mutablebuiltins.listtyping.Generic

method append(x: _T) → None

将对象追加到列表末尾。

method clear() → None

从列表中删除所有项。

classmethod coerce(key: str, value: MutableList[_T] | _T) → MutableList[_T] | None

将普通列表转换为此类的实例。

method extend(x: Iterable[_T]) → None

通过将来自可迭代对象的元素附加到列表来扩展列表。

method insert(i: SupportsIndex, x: _T) → None

在索引之前插入对象。

method is_iterable(value: _T | Iterable[_T]) → TypeGuard[Iterable[_T]]
method is_scalar(value: _T | Iterable[_T]) → TypeGuard[_T]
method pop(*arg: SupportsIndex) → _T

删除并返回索引处的项(默认为最后一个)。

如果列表为空或索引超出范围,则引发 IndexError。

method remove(i: _T) → None

删除值的第一个出现。

如果值不存在,则引发 ValueError。

method reverse() → None

就地反转。

method sort(**kw: Any) → None

将列表按升序排序并返回 None。

排序是原地进行的(即列表本身被修改)并且稳定的(即保持两个相等元素的顺序不变)。

如果给定了键函数,则将其应用于每个列表项一次,并根据其函数值按升序或降序对它们进行排序。

反转标志可以设置为按降序排序。

class sqlalchemy.ext.mutable.MutableSet

实现了Mutable的集合类型。

MutableSet 对象实现了一个集合,当集合的内容发生变化时,包括添加或移除值时,会向底层映射发送更改事件。

注意,MutableSet 不会对集合内部的值本身应用可变跟踪。因此,它不是跟踪对递归可变结构进行深层更改的足够解决方案。为了支持这种用例,构建一个MutableSet的子类,提供适当的强制转换,以便放置在字典中的值也是“可变的”,并向它们的父结构发出事件。

另请参阅

MutableDict

MutableList

成员

add(), clear(), coerce(), difference_update(), discard(), intersection_update(), pop(), remove(), symmetric_difference_update(), update()

类签名

sqlalchemy.ext.mutable.MutableSetsqlalchemy.ext.mutable.Mutable, builtins.set, typing.Generic)

method add(elem: _T) → None

向集合添加一个元素。

如果元素已经存在,则不产生任何效果。

method clear() → None

从此集合中移除所有元素。

classmethod coerce(index: str, value: Any) → MutableSet[_T] | None

将普通集合转换为此类的实例。

method difference_update(*arg: Iterable[Any]) → None

从此集合中移除另一个集合的所有元素。

method discard(elem: _T) → None

如果元素是集合的成员,则从集合中移除一个元素。

如果元素不是成员,则不执行任何操作。

method intersection_update(*arg: Iterable[Any]) → None

使用自身和另一个集合的交集更新集合。

method pop(*arg: Any) → _T

移除并返回任意集合元素。如果集合为空,则引发 KeyError。

method remove(elem: _T) → None

从集合中移除一个元素;它必须是成员。

如果元素不是成员,则引发 KeyError。

method symmetric_difference_update(*arg: Iterable[_T]) → None

使用自身和另一个集合的对称差集更新集合。

method update(*arg: Iterable[_T]) → None

使用自身和其他集合的并集更新集合。

标签:__,中文,SqlAlchemy,name,映射,Column,Base,文档,id
From: https://www.cnblogs.com/apachecn/p/18262080

相关文章