SqlAlchemy 2.0 中文文档(八)

2024-06-26 14:29:38 浏览数 (2)

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

SQL 表达式作为映射属性

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

映射类上的属性可以链接到 SQL 表达式,这些表达式可以在查询中使用。

使用混合

将相对简单的 SQL 表达式链接到类的最简单和最灵活的方法是使用所谓的“混合属性”,在 混合属性 部分中描述。混合提供了一个同时在 Python 级别和 SQL 表达式级别工作的表达式。例如,我们将一个类 User,其中包含属性 firstnamelastname,映射到下面一个混合,该混合将为我们提供 fullname,即这两者的字符串连接:

代码语言:javascript复制
from sqlalchemy.ext.hybrid import hybrid_property

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))

    @hybrid_property
    def fullname(self):
        return self.firstname   " "   self.lastname

在上面,fullname 属性在实例和类级别都被解释,因此可以从一个实例中使用:

代码语言:javascript复制
some_user = session.scalars(select(User).limit(1)).first()
print(some_user.fullname)

以及可在查询中使用:

代码语言:javascript复制
some_user = session.scalars(
    select(User).where(User.fullname == "John Smith").limit(1)
).first()

字符串连接示例是一个简单的示例,其中 Python 表达式可以在实例和类级别上兼用。通常,必须区分 SQL 表达式和 Python 表达式,可以使用hybrid_property.expression()来实现。下面我们展示了在混合内部需要存在条件的情况,使用 Python 中的if语句和 SQL 表达式的case()构造:

代码语言:javascript复制
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import case

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))

    @hybrid_property
    def fullname(self):
        if self.firstname is not None:
            return self.firstname   " "   self.lastname
        else:
            return self.lastname

    @fullname.expression
    def fullname(cls):
        return case(
            (cls.firstname != None, cls.firstname   " "   cls.lastname),
            else_=cls.lastname,
        )

使用 column_property

column_property() 函数可用于将 SQL 表达式映射到与常规映射的 Column 类似的方式。使用这种技术,属性在加载时与所有其他列映射的属性一起加载。这在某些情况下优于使用混合的用法,因为该值可以在对象的父行加载时一次性加载,特别是如果表达式是链接到其他表(通常作为相关子查询)以访问通常不会在已加载对象上可用的数据的情况。

使用column_property()来表示 SQL 表达式的缺点包括表达式必须与整个类所发出的 SELECT 语句兼容,以及在使用来自声明性混合的column_property()时可能会出现一些配置怪癖。

我们的“fullname”示例可以使用column_property()表示如下:

代码语言:javascript复制
from sqlalchemy.orm import column_property

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))
    fullname = column_property(firstname   " "   lastname)

也可以使用相关子查询。 下面我们使用select()构造创建一个ScalarSelect,表示一个面向列的 SELECT 语句,将特定User的可用Address对象的计数链接在一起:

代码语言:javascript复制
from sqlalchemy.orm import column_property
from sqlalchemy import select, func
from sqlalchemy import Column, Integer, String, ForeignKey

from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(Integer, ForeignKey("user.id"))

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    address_count = column_property(
        select(func.count(Address.id))
        .where(Address.user_id == id)
        .correlate_except(Address)
        .scalar_subquery()
    )

在上述示例中,我们定义了一个类似以下的ScalarSelect()构造:

代码语言:javascript复制
stmt = (
    select(func.count(Address.id))
    .where(Address.user_id == id)
    .correlate_except(Address)
    .scalar_subquery()
)

首先,我们使用select()创建一个Select构造,然后使用Select.scalar_subquery()方法将其转换为标量子查询,表示我们打算在列表达式上下文中使用此Select语句。

Select本身中,我们选择计数Address.id行,其中Address.user_id列等于id,在User类的上下文中,id是名为idColumn(请注意,id也是 Python 内置函数的名称,这不是我们想在这里使用的 - 如果我们在User类定义之外,我们将使用User.id)。

Select.correlate_except()方法指示此select()中 FROM 子句的每个元素都可以从 FROM 列表中省略(即与针对User的封闭 SELECT 语句相关联),除了与Address对应的元素。 这并不是绝对必要的,但是在UserAddress表之间进行一长串连接的情况下,防止Address意外地从 FROM 列表中省略。

对于引用从多对多关系链接的列的column_property(),使用and_()将关联表的字段与关系中的两个表连接起来:

代码语言:javascript复制
from sqlalchemy import and_

class Author(Base):
    # ...

    book_count = column_property(
        select(func.count(books.c.id))
        .where(
            and_(
                book_authors.c.author_id == authors.c.id,
                book_authors.c.book_id == books.c.id,
            )
        )
        .scalar_subquery()
    )
将 column_property()添加到现有的声明映射类

如果导入问题阻止在类中定义column_property(),则可以在两者配置后将其分配给类。当使用使用声明性基类(即由DeclarativeBase超类或遗留函数(例如declarative_base())生成的映射时,此属性分配的效果是在事后调用Mapper.add_property()以添加额外的属性:

代码语言:javascript复制
# only works if a declarative base class is in use
User.address_count = column_property(
    select(func.count(Address.id)).where(Address.user_id == User.id).scalar_subquery()
)

当使用不使用声明性基类的映射样式,如registry.mapped()装饰器时,可以在底层Mapper对象上显式调用Mapper.add_property()方法,该对象可以使用inspect()获取:

代码语言:javascript复制
from sqlalchemy.orm import registry

reg = registry()

@reg.mapped
class User:
    __tablename__ = "user"

    # ... additional mapping directives

# later ...

# works for any kind of mapping
from sqlalchemy import inspect

inspect(User).add_property(
    column_property(
        select(func.count(Address.id))
        .where(Address.user_id == User.id)
        .scalar_subquery()
    )
)

另请参阅

将附加列添加到现有的声明式映射类

在映射时从列属性组合

可以创建将多个ColumnProperty对象组合在一起的映射。当在核心表达式上下文中使用ColumnProperty时,它将被解释为 SQL 表达式,前提是它被现有的表达式对象所指向;这是通过核心检测到对象具有__clause_element__()方法并返回 SQL 表达式来实现的。然而,如果ColumnProperty作为表达式中的主对象使用,而没有其他核心 SQL 表达式对象来指向它,那么ColumnProperty.expression属性将返回底层 SQL 表达式,以便可以一致地用于构建 SQL 表达式。下面,File类包含一个属性File.path,它将一个字符串令牌连接到File.filename属性上,该属性本身是一个ColumnProperty

代码语言:javascript复制
class File(Base):
    __tablename__ = "file"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(64))
    extension = mapped_column(String(8))
    filename = column_property(name   "."   extension)
    path = column_property("C:/"   filename.expression)

File类通常在表达式中使用时,分配给filenamepath的属性可以直接使用。仅当直接在映射定义中使用ColumnProperty时,才需要使用ColumnProperty.expression属性:

代码语言:javascript复制
stmt = select(File.path).where(File.filename == "foo.txt")
使用 Column Deferral 与column_property()

在 ORM 查询指南中引入的列延迟特性可在映射时应用于由column_property()映射的 SQL 表达式,方法是在column_property()的位置使用deferred()函数而不是column_property()

代码语言:javascript复制
from sqlalchemy.orm import deferred

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname   " "   lastname)

参见

使用 deferred()用于命令式映射器,映射的 SQL 表达式

使用简单描述符

在需要发出比column_property()hybrid_property提供的 SQL 查询更复杂的情况下,可以使用作为属性访问的常规 Python 函数,假设表达式仅需要在已加载的实例上可用。该函数使用 Python 自己的@property装饰器装饰,将其标记为只读属性。在函数内部,使用object_session()定位到与当前对象对应的Session,然后用于发出查询:

代码语言:javascript复制
from sqlalchemy.orm import object_session
from sqlalchemy import select, func

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))

    @property
    def address_count(self):
        return object_session(self).scalar(
            select(func.count(Address.id)).where(Address.user_id == self.id)
        )

简单描述符方法在紧急情况下很有用,但在通常情况下比混合和列属性方法性能更低,因为它需要在每次访问时发出 SQL 查询。

映射属性中的查询时 SQL 表达式

除了能够在映射类上配置固定的 SQL 表达式之外,SQLAlchemy ORM 还包括一个功能,可以在查询时将对象加载为任意 SQL 表达式的结果,并将其设置为其状态的一部分。通过使用 query_expression() 配置 ORM 映射属性,然后在查询时使用 with_expression() 加载器选项来实现此行为。查看 将任意 SQL 表达式加载到对象上 中的示例映射和用法。

使用混合

将相对简单的 SQL 表达式链接到类的最简单和最灵活的方法是使用所谓的“混合属性”,在 混合属性 部分中描述。混合提供了一个在 Python 级别和 SQL 表达式级别都起作用的表达式。例如,下面我们映射一个类 User,包含属性 firstnamelastname,并包含一个混合,将为我们提供 fullname,即两者的字符串连接:

代码语言:javascript复制
from sqlalchemy.ext.hybrid import hybrid_property

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))

    @hybrid_property
    def fullname(self):
        return self.firstname   " "   self.lastname

在上面的示例中,fullname 属性在实例和类级别都被解释,因此可以从实例中使用:

代码语言:javascript复制
some_user = session.scalars(select(User).limit(1)).first()
print(some_user.fullname)

以及可在查询中使用:

代码语言:javascript复制
some_user = session.scalars(
    select(User).where(User.fullname == "John Smith").limit(1)
).first()

字符串拼接示例是一个简单的示例,其中 Python 表达式可以在实例和类级别上都起到双重作用。通常,必须区分 SQL 表达式和 Python 表达式,可以使用 hybrid_property.expression() 来实现。下面我们举例说明一个需要在混合中存在条件的情况,使用 Python 中的 if 语句和 SQL 表达式的 case() 结构:

代码语言:javascript复制
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import case

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))

    @hybrid_property
    def fullname(self):
        if self.firstname is not None:
            return self.firstname   " "   self.lastname
        else:
            return self.lastname

    @fullname.expression
    def fullname(cls):
        return case(
            (cls.firstname != None, cls.firstname   " "   cls.lastname),
            else_=cls.lastname,
        )

使用 column_property

column_property() 函数可用于以与常规映射的 Column 类似的方式映射 SQL 表达式。通过此技术,属性在加载时与所有其他列映射的属性一起加载。在某些情况下,这比使用混合的优势更大,因为值可以在与对象的父行同时加载的同时前置加载,特别是如果表达式是链接到其他表的(通常作为关联子查询)以访问在已加载对象上通常不可用的数据。

使用column_property()进行 SQL 表达式的缺点包括表达式必须与整个类的 SELECT 语句兼容,并且在使用column_property()时可能会出现一些配置怪癖。

我们的“fullname”示例可以使用column_property()表示如下:

代码语言:javascript复制
from sqlalchemy.orm import column_property

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))
    fullname = column_property(firstname   " "   lastname)

也可以使用相关子查询。下面我们使用select()构造创建一个ScalarSelect,表示一个面向列的 SELECT 语句,它链接了特定User的可用Address对象的计数:

代码语言:javascript复制
from sqlalchemy.orm import column_property
from sqlalchemy import select, func
from sqlalchemy import Column, Integer, String, ForeignKey

from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(Integer, ForeignKey("user.id"))

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    address_count = column_property(
        select(func.count(Address.id))
        .where(Address.user_id == id)
        .correlate_except(Address)
        .scalar_subquery()
    )

在上面的例子中,我们定义一个如下所示的ScalarSelect()构造:

代码语言:javascript复制
stmt = (
    select(func.count(Address.id))
    .where(Address.user_id == id)
    .correlate_except(Address)
    .scalar_subquery()
)

首先,我们使用select()创建一个Select构造,然后使用Select.scalar_subquery()方法将其转换为标量子查询,表明我们打算在列表达式上下文中使用这个Select语句。

Select本身中,我们选择Address.id行的计数,其中Address.user_id列等于id,在User类的上下文中,id是名为idColumn(请注意,id也是 Python 内置函数的名称,这不是我们想要在此处使用的 - 如果我们在User类定义之外,我们将使用User.id)。

Select.correlate_except() 方法指示此 select() 的 FROM 子句中的每个元素都可以从 FROM 列表中省略(即与针对 User 的封闭 SELECT 语句相关联),除了与 Address 对应的元素。这并非绝对必要,但在 UserAddress 表之间的一长串联接中,防止了 Address 在 SELECT 语句嵌套中无意中被省略出 FROM 列表。

对于引用来自多对多关系的列的 column_property(),使用 and_() 来将关联表的字段连接到关系中的两个表:

代码语言:javascript复制
from sqlalchemy import and_

class Author(Base):
    # ...

    book_count = column_property(
        select(func.count(books.c.id))
        .where(
            and_(
                book_authors.c.author_id == authors.c.id,
                book_authors.c.book_id == books.c.id,
            )
        )
        .scalar_subquery()
    )
向现有的声明式映射类添加 column_property()

如果导入问题阻止内联定义 column_property() 与类一起定义,则在两者配置后可以将其分配给类。当使用使用声明式基类(即由 DeclarativeBase 超类或遗留函数如 declarative_base() 生成的映射)时,此属性分配具有调用 Mapper.add_property() 的效果,以在事后添加附加属性。

代码语言:javascript复制
# only works if a declarative base class is in use
User.address_count = column_property(
    select(func.count(Address.id)).where(Address.user_id == User.id).scalar_subquery()
)

当使用不使用声明式基类的映射样式时,例如 registry.mapped() 装饰器时,可以在底层的 Mapper 对象上显式调用 Mapper.add_property() 方法,可以使用 inspect() 获取该对象:

代码语言:javascript复制
from sqlalchemy.orm import registry

reg = registry()

@reg.mapped
class User:
    __tablename__ = "user"

    # ... additional mapping directives

# later ...

# works for any kind of mapping
from sqlalchemy import inspect

inspect(User).add_property(
    column_property(
        select(func.count(Address.id))
        .where(Address.user_id == User.id)
        .scalar_subquery()
    )
)

另请参阅

向现有的声明式映射类添加额外的列

在映射时从列属性组成

可以创建结合多个 ColumnProperty 对象的映射。当在核心表达式上下文中使用时,ColumnProperty 将被解释为 SQL 表达式,前提是它被现有表达式对象所定位;这通过核心检测对象是否具有返回 SQL 表达式的 __clause_element__() 方法来完成。然而,如果在表达式中将 ColumnProperty 用作领导对象,而没有其他核心 SQL 表达式对象来定位它,那么 ColumnProperty.expression 属性将返回底层 SQL 表达式,以便可以一致地构建 SQL 表达式。下面,File 类包含一个属性 File.path,它将一个字符串标记连接到 File.filename 属性,后者本身就是一个 ColumnProperty

代码语言:javascript复制
class File(Base):
    __tablename__ = "file"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(64))
    extension = mapped_column(String(8))
    filename = column_property(name   "."   extension)
    path = column_property("C:/"   filename.expression)

File 类在表达式中正常使用时,分配给 filenamepath 的属性可以直接使用。只有在映射定义中直接使用 ColumnProperty 时才需要使用 ColumnProperty.expression 属性:

代码语言:javascript复制
stmt = select(File.path).where(File.filename == "foo.txt")
使用 column_property() 进行列延迟

ORM 查询指南中介绍的列延迟功能可在映射时应用到由 column_property() 映射的 SQL 表达式上,方法是使用 deferred() 函数代替 column_property()

代码语言:javascript复制
from sqlalchemy.orm import deferred

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname   " "   lastname)

另请参阅

使用 imperative mappers、映射的 SQL 表达式进行延迟加载

向现有的 Declarative 映射类添加 column_property()

如果导入问题阻止 column_property() 在类中内联定义,可以在两者配置后将其分配给类。在使用使用声明基类(即由 DeclarativeBase 超类或遗留函数如 declarative_base() 生成的)的映射时,此属性分配将调用 Mapper.add_property() 来添加一个额外的属性:

代码语言:javascript复制
# only works if a declarative base class is in use
User.address_count = column_property(
    select(func.count(Address.id)).where(Address.user_id == User.id).scalar_subquery()
)

在使用不使用声明基类的映射样式,例如 registry.mapped() 装饰器时,可以显式调用底层的 Mapper.add_property() 方法,这可以通过 inspect() 获取底层的 Mapper 对象来实现:

代码语言:javascript复制
from sqlalchemy.orm import registry

reg = registry()

@reg.mapped
class User:
    __tablename__ = "user"

    # ... additional mapping directives

# later ...

# works for any kind of mapping
from sqlalchemy import inspect

inspect(User).add_property(
    column_property(
        select(func.count(Address.id))
        .where(Address.user_id == User.id)
        .scalar_subquery()
    )
)

详见

向现有的声明映射类添加额外列

在映射时从列属性组成

可以创建将多个 ColumnProperty 对象组合在一起的映射。当在核心表达式上下文中使用时,如果 ColumnProperty 被现有表达式对象所定位,则它将被解释为 SQL 表达式;这是通过核心检测到对象具有返回 SQL 表达式的 __clause_element__() 方法来完成的。但是,如果在表达式中使用 ColumnProperty 作为主要对象,而没有其他核心 SQL 表达式对象来定位它,则 ColumnProperty.expression 属性将返回底层的 SQL 表达式,以便可以一致地构建 SQL 表达式。在下面的示例中,File 类包含一个属性 File.path,它将一个字符串令牌连接到 File.filename 属性上,后者本身是一个 ColumnProperty

代码语言:javascript复制
class File(Base):
    __tablename__ = "file"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(64))
    extension = mapped_column(String(8))
    filename = column_property(name   "."   extension)
    path = column_property("C:/"   filename.expression)

File类在表达式中正常使用时,分配给filenamepath的属性可以直接使用。仅在映射定义中直接使用ColumnProperty时才需要使用ColumnProperty.expression属性:

代码语言:javascript复制
stmt = select(File.path).where(File.filename == "foo.txt")
使用column_property()进行列延迟

在 ORM 查询指南中引入的列延迟功能可以在映射时应用于由column_property()映射的 SQL 表达式,方法是在映射定义中使用 deferred() 函数代替column_property()

代码语言:javascript复制
from sqlalchemy.orm import deferred

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname   " "   lastname)

另请参阅

对于命令式映射器,映射 SQL 表达式使用 deferred()

使用简单描述符

在需要发出比column_property()hybrid_property提供的更复杂的 SQL 查询的情况下,可以使用作为属性访问的常规 Python 函数,假设表达式仅需要在已加载的实例上可用。该函数使用 Python 自己的 @property 装饰器来将其标记为只读属性。在函数内部,使用object_session()来定位与当前对象对应的Session,然后用于发出查询:

代码语言:javascript复制
from sqlalchemy.orm import object_session
from sqlalchemy import select, func

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    firstname = mapped_column(String(50))
    lastname = mapped_column(String(50))

    @property
    def address_count(self):
        return object_session(self).scalar(
            select(func.count(Address.id)).where(Address.user_id == self.id)
        )

简单描述符方法通常作为最后一手之计,但在通常情况下,它的性能不如混合和列属性方法,因为每次访问都需要发出一条 SQL 查询。

查询时 SQL 表达式作为映射属性

除了能够在映射类上配置固定的 SQL 表达式之外,SQLAlchemy ORM 还包括一个功能,即对象可以使用在查询时设置为其状态的任意 SQL 表达式的结果进行加载。通过使用 query_expression() 配置 ORM 映射属性,然后在查询时使用 with_expression() 加载选项来实现这种行为。有关示例映射和用法,请参阅 将任意 SQL 表达式加载到对象上。

更改属性行为

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

本节将讨论用于修改 ORM 映射属性行为的特性和技术,包括那些使用mapped_column()relationship()等映射的属性。

简单的验证器

一个快速添加“验证”程序到属性的方法是使用validates()装饰器。属性验证器可以引发异常,停止突变属性值的过程,或者可以将给定值更改为其他值。像所有属性扩展一样,验证器仅在普通用户代码中调用;在 ORM 填充对象时,它们不会被调用:

代码语言:javascript复制
from sqlalchemy.orm import validates

class EmailAddress(Base):
    __tablename__ = "address"

    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)

    @validates("email")
    def validate_email(self, key, address):
        if "@" not in address:
            raise ValueError("failed simple email validation")
        return address

当向集合添加项目时,验证器还会接收集合追加事件:

代码语言:javascript复制
from sqlalchemy.orm import validates

class User(Base):
    # ...

    addresses = relationship("Address")

    @validates("addresses")
    def validate_address(self, key, address):
        if "@" not in address.email:
            raise ValueError("failed simplified email validation")
        return address

验证函数默认不会为集合移除事件发出,因为典型的期望是被丢弃的值不需要验证。然而,validates()支持通过向装饰器指定include_removes=True来接收这些事件。当设置了此标志时,验证函数必须接收一个额外的布尔参数,如果为True,则表示操作是一个移除:

代码语言:javascript复制
from sqlalchemy.orm import validates

class User(Base):
    # ...

    addresses = relationship("Address")

    @validates("addresses", include_removes=True)
    def validate_address(self, key, address, is_remove):
        if is_remove:
            raise ValueError("not allowed to remove items from the collection")
        else:
            if "@" not in address.email:
                raise ValueError("failed simplified email validation")
            return address

通过反向引用链接的相互依赖验证器的情况也可以进行定制,使用include_backrefs=False选项;当设置为False时,此选项会阻止验证函数在由反向引用导致的事件发生时发出:

代码语言:javascript复制
from sqlalchemy.orm import validates

class User(Base):
    # ...

    addresses = relationship("Address", backref="user")

    @validates("addresses", include_backrefs=False)
    def validate_address(self, key, address):
        if "@" not in address:
            raise ValueError("failed simplified email validation")
        return address

在上面的例子中,如果我们像这样分配到Address.user,如some_address.user = some_user,那么validate_address()函数将不会被发出,即使some_user.addresses中发生了追加 - 该事件是由反向引用引起的。

请注意,validates()装饰器是建立在属性事件之上的方便函数。需要更多控制属性更改行为配置的应用程序可以利用此系统,详见AttributeEvents

对象名称

描述

validates(*names, [include_removes, include_backrefs])

将方法装饰为一个或多个命名属性的“验证器”。

代码语言:javascript复制
function sqlalchemy.orm.validates(*names: str, include_removes: bool = False, include_backrefs: bool = True) → Callable[[_Fn], _Fn]

将方法装饰为一个或多个命名属性的“验证器”。

将方法指定为验证器,该方法接收属性的名称以及要分配的值,或者在集合的情况下,要添加到集合的值。然后,函数可以引发验证异常以阻止进程继续(在这种情况下,Python 的内置ValueErrorAssertionError异常是合理的选择),或者可以在继续之前修改或替换值。否则,该函数应返回给定的值。

注意,集合的验证器不能在验证过程中发出该集合的加载操作 - 这种用法会引发断言以避免递归溢出。这是一种不支持的可重入条件。

参数:

  • *names – 要验证的属性名称列表。
  • include_removes – 如果为 True,则也将发送“remove”事件 - 验证函数必须接受一个额外参数“is_remove”,其值为布尔值。
  • include_backrefs – 默认为True;如果为False,则验证函数不会在原始操作者是通过 backref 相关的属性事件时发出。这可用于双向validates()使用,其中每个属性操作只应发出一个验证器。 从版本 2.0.16 开始更改:此参数在版本 2.0.0 到 2.0.15 中无意中默认为False。在 2.0.16 中恢复了其正确的默认值为True

另请参阅

简单验证器 - validates()的使用示例

在核心级别使用自定义数据类型

影响列值的非 ORM 方式,以适合在 Python 中的表示方式与在数据库中的表示方式之间转换数据,可以通过使用应用于映射的Table元数据的自定义数据类型来实现。这在一些编码/解码风格在数据进入数据库和返回时都会发生的情况下更为常见;在 Core 文档的扩充现有类型中了解更多信息。

使用描述符和混合体

影响属性的修改行为的更全面的方法是使用描述符。这在 Python 中通常使用property()函数。描述符的标准 SQLAlchemy 技术是创建一个普通的描述符,并从具有不同名称的映射属性读取/写入。下面我们使用 Python 2.6 风格的属性进行说明:

代码语言:javascript复制
class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    # name the attribute with an underscore,
    # different from the column name
    _email = mapped_column("email", String)

    # then create an ".email" attribute
    # to get/set "._email"
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email

上述方法可以工作,但我们可以添加更多内容。虽然我们的 EmailAddress 对象将通过 email 描述符将值传递到 _email 映射属性中,但类级别的 EmailAddress.email 属性没有通常的表达式语义可用于 Select。为了提供这些,我们可以使用 hybrid 扩展,如下所示:

代码语言:javascript复制
from sqlalchemy.ext.hybrid import hybrid_property

class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    _email = mapped_column("email", String)

    @hybrid_property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email

.email 属性除了在有 EmailAddress 实例时提供 getter/setter 行为外,还在类级别使用时提供 SQL 表达式,即直接从 EmailAddress 类中使用时:

代码语言:javascript复制
from sqlalchemy.orm import Session
from sqlalchemy import select

session = Session()

address = session.scalars(
    select(EmailAddress).where(EmailAddress.email == "address@example.com")
).one()
SELECT  address.email  AS  address_email,  address.id  AS  address_id
FROM  address
WHERE  address.email  =  ?
('address@example.com',)
address.email = "otheraddress@example.com"
session.commit()
UPDATE  address  SET  email=?  WHERE  address.id  =  ?
('otheraddress@example.com',  1)
COMMIT 

hybrid_property 还允许我们更改属性的行为,包括在实例级别与类/表达式级别访问属性时定义不同的行为,使用 hybrid_property.expression() 修饰符。例如,如果我们想要自动添加主机名,我们可以定义两组字符串操作逻辑:

代码语言:javascript复制
class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    _email = mapped_column("email", String)

    @hybrid_property
    def email(self):
  """Return the value of _email up until the last twelve
 characters."""

        return self._email[:-12]

    @email.setter
    def email(self, email):
  """Set the value of _email, tacking on the twelve character
 value @example.com."""

        self._email = email   "@example.com"

    @email.expression
    def email(cls):
  """Produce a SQL expression that represents the value
 of the _email column, minus the last twelve characters."""

        return func.substr(cls._email, 0, func.length(cls._email) - 12)

在上面的例子中,访问 EmailAddress 实例的 email 属性将返回 _email 属性的值,从值中移除或添加主机名 @example.com。当我们针对 email 属性进行查询时,会呈现一个产生相同效果的 SQL 函数:

代码语言:javascript复制
address = session.scalars(
    select(EmailAddress).where(EmailAddress.email == "address")
).one()
SELECT  address.email  AS  address_email,  address.id  AS  address_id
FROM  address
WHERE  substr(address.email,  ?,  length(address.email)  -  ?)  =  ?
(0,  12,  'address') 

阅读更多关于混合属性的信息请参阅 混合属性。 ## 同义词

同义词是一个映射器级别的构造,允许类上的任何属性“镜像”另一个映射的属性。

从最基本的角度来看,同义词是一种使某个属性通过额外的名称轻松可用的方式:

代码语言:javascript复制
from sqlalchemy.orm import synonym

class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    job_status = mapped_column(String(50))

    status = synonym("job_status")

上面的 MyClass 类具有两个属性,.job_status.status,它们将作为一个属性在表达式级别上行为一致:

代码语言:javascript复制
>>> print(MyClass.job_status == "some_status")
my_table.job_status  =  :job_status_1
>>> print(MyClass.status == "some_status")
my_table.job_status  =  :job_status_1 

并在实例级别:

代码语言:javascript复制
>>> m1 = MyClass(status="x")
>>> m1.status, m1.job_status
('x', 'x')

>>> m1.job_status = "y"
>>> m1.status, m1.job_status
('y', 'y')

synonym() 可用于任何类型的映射属性,包括映射列和关系,以及同义词本身,它们都是 MapperProperty 的子类。

除了简单的镜像外,synonym() 还可以被设置为引用用户定义的 描述符。我们可以用 @property 来提供我们的 status 同义词:

代码语言:javascript复制
class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    status = mapped_column(String(50))

    @property
    def job_status(self):
        return "Status: "   self.status

    job_status = synonym("status", descriptor=job_status)

在使用 Declarative 时,可以更简洁地使用 synonym_for() 装饰器表达上述模式:

代码语言:javascript复制
from sqlalchemy.ext.declarative import synonym_for

class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    status = mapped_column(String(50))

    @synonym_for("status")
    @property
    def job_status(self):
        return "Status: "   self.status

虽然 synonym() 对于简单的镜像很有用,但是使用描述符增强属性行为的用例更好地使用了现代用法中的 混合属性 功能,后者更加面向 Python 描述符。 从技术上讲,synonym() 可以做到与 hybrid_property 相同的所有事情,因为它还支持注入自定义 SQL 功能,但是混合属性在更复杂的情况下更容易使用。

对象名称

描述

同义词(name, *, [map_column, descriptor, comparator_factory, init, repr, default, default_factory, compare, kw_only, info, doc])

将属性名称标记为映射属性的同义词,即属性将反映另一个属性的值和表达行为。

代码语言:javascript复制
function sqlalchemy.orm.synonym(name: str, *, map_column: bool | None = None, descriptor: Any | None = None, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = None) → Synonym[Any]

将属性名称标记为映射属性的同义词,即属性将反映另一个属性的值和表达行为。

例如:

代码语言:javascript复制
class MyClass(Base):
    __tablename__ = 'my_table'

    id = Column(Integer, primary_key=True)
    job_status = Column(String(50))

    status = synonym("job_status")

参数:

name – 现有映射属性的名称。这可以是配置在类上的字符串名称 ORM 映射属性,包括列绑定属性和关系。

descriptor – 当在实例级别访问此属性时将用作 getter(和可能的 setter)的 Python descriptor。

map_column

仅适用于经典映射和映射到现有 Table 对象的情况。 如果为 Truesynonym() 构造将定位到与此同义词的属性名称通常关联的映射表上的 Column 对象,并生成一个新的 ColumnProperty,将此 Column 映射到作为“name”参数给定的替代名称;这样,重新定义将 Column 的映射放在不同名称下的常规步骤是不必要的。这通常用于当 Column 要替换为也使用描述符的属性时,即与 synonym.descriptor 参数一起使用:

代码语言:javascript复制
my_table = Table(
    "my_table", metadata,
    Column('id', Integer, primary_key=True),
    Column('job_status', String(50))
)

class MyClass:
    @property
    def _job_status_descriptor(self):
        return "Status: %s" % self._job_status

mapper(
    MyClass, my_table, properties={
        "job_status": synonym(
            "_job_status", map_column=True,
            descriptor=MyClass._job_status_descriptor)
    }
)

上述,名为 _job_status 的属性自动映射到 job_status 列:

代码语言:javascript复制
>>> j1 = MyClass()
>>> j1._job_status = "employed"
>>> j1.job_status
Status: employed

在使用 Declarative 时,为了在同义词中提供一个描述符,请使用sqlalchemy.ext.declarative.synonym_for()辅助程序。但是,请注意,通常应优先选择混合属性功能,特别是在重新定义属性行为时。

info – 将填充到此对象的InspectionAttr.info属性中的可选数据字典。

comparator_factory

一个PropComparator的子类,将在 SQL 表达式级别提供自定义比较行为。

注意

对于提供重新定义属性的 Python 级别和 SQL 表达式级别行为的用例,请参考使用描述符和混合属性中介绍的混合属性,以获得更有效的技术。

另请参阅

同义词 - 同义词概述

synonym_for() - 面向 Declarative 的辅助程序

使用描述符和混合属性 - 混合属性扩展提供了一种更新的方法,比使用同义词更灵活地增强属性行为。## 操作符定制

SQLAlchemy ORM 和 Core 表达式语言使用的“操作符”是完全可定制的。例如,比较表达式User.name == 'ed'使用了 Python 本身内置的名为operator.eq的操作符 - SQLAlchemy 将与此类操作符关联的实际 SQL 构造可以进行修改。新操作也可以与列表达式关联。列表达式发生的操作符最直接在类型级别重新定义 - 请参阅 Redefining and Creating New Operators 部分进行描述。

ORM 级别的函数,如column_property()relationship()composite()还提供了在 ORM 级别重新定义操作符的功能,通过将PropComparator子类传递给每个函数的comparator_factory参数。在这个级别上定制操作符是一个罕见的用例。请参阅PropComparator的文档以获取概述。## 简单验证器

将“验证”程序快速添加到属性的一种方法是使用 validates() 装饰器。属性验证器可以引发异常,从而停止变异属性值的过程,或者可以将给定值更改为其他内容。验证器,如所有属性扩展一样,仅在正常用户代码中调用;当 ORM 正在填充对象时,不会发出它们:

代码语言:javascript复制
from sqlalchemy.orm import validates

class EmailAddress(Base):
    __tablename__ = "address"

    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)

    @validates("email")
    def validate_email(self, key, address):
        if "@" not in address:
            raise ValueError("failed simple email validation")
        return address

当项目被添加到集合时,验证器也会收到集合追加事件:

代码语言:javascript复制
from sqlalchemy.orm import validates

class User(Base):
    # ...

    addresses = relationship("Address")

    @validates("addresses")
    def validate_address(self, key, address):
        if "@" not in address.email:
            raise ValueError("failed simplified email validation")
        return address

默认情况下,验证函数不会为集合删除事件发出,因为典型的期望是被丢弃的值不需要验证。但是,validates() 通过将 include_removes=True 指定给装饰器来支持接收这些事件。当设置了此标志时,验证函数必须接收一个额外的布尔参数,如果为 True,则表示该操作是一个删除操作:

代码语言:javascript复制
from sqlalchemy.orm import validates

class User(Base):
    # ...

    addresses = relationship("Address")

    @validates("addresses", include_removes=True)
    def validate_address(self, key, address, is_remove):
        if is_remove:
            raise ValueError("not allowed to remove items from the collection")
        else:
            if "@" not in address.email:
                raise ValueError("failed simplified email validation")
            return address

通过使用 include_backrefs=False 选项,还可以针对通过反向引用链接的相互依赖验证器的情况进行定制;当设置为 False 时,该选项将阻止验证函数在事件发生时由于反向引用而发出:

代码语言:javascript复制
from sqlalchemy.orm import validates

class User(Base):
    # ...

    addresses = relationship("Address", backref="user")

    @validates("addresses", include_backrefs=False)
    def validate_address(self, key, address):
        if "@" not in address:
            raise ValueError("failed simplified email validation")
        return address

在上面的例子中,如果我们像这样分配给 Address.usersome_address.user = some_user,即使向 some_user.addresses 追加了一个元素,也不会触发 validate_address() 函数 - 事件是由一个反向引用引起的。

请注意,validates() 装饰器是在属性事件之上构建的一个方便函数。需要更多控制属性更改行为配置的应用程序可以使用此系统,该系统在 AttributeEvents 中描述。

对象名称

描述

验证(*names, [include_removes, include_backrefs])

将方法装饰为一个或多个命名属性的“验证器”。

代码语言:javascript复制
function sqlalchemy.orm.validates(*names: str, include_removes: bool = False, include_backrefs: bool = True) → Callable[[_Fn], _Fn]

将方法装饰为一个或多个命名属性的“验证器”。

指定一个方法作为验证器,该方法接收属性的名称以及要分配的值,或者在集合的情况下,要添加到集合的值。该函数然后可以引发验证异常以阻止继续处理过程(在这种情况下,Python 的内置ValueErrorAssertionError异常是合理的选择),或者可以修改或替换值然后继续。该函数否则应返回给定的值。

请注意,集合的验证器不能在验证例程中发出该集合的加载 - 这种用法会引发一个断言以避免递归溢出。这是一个不支持的可重入条件。

参数:

  • *names – 要验证的属性名称列表。
  • include_removes – 如果为 True,则“remove”事件也将发送 - 验证函数必须接受一个额外的参数“is_remove”,它将是一个布尔值。
  • include_backrefs – 默认为True;如果为False,则验证函数在原始生成器是通过 backref 相关的属性事件时不会发出。这可用于双向 validates() 用法,其中每个属性操作只应发出一个验证器。 从版本 2.0.16 开始更改:此参数意外地在 2.0.0 至 2.0.15 版本中默认为 False。在 2.0.16 版本中恢复了其正确的默认值为True

另请参阅

简单验证器 - validates() 的用法示例

在核心级别使用自定义数据类型

通过使用应用于映射的 Table 元数据的自定义数据类型,可以以适合在 Python 中的表示方式与在数据库中的表示方式之间转换数据的方式来影响列的值的非 ORM 方法。这在某些编码/解码样式在数据进入数据库和返回时都发生的情况下更为常见;在核心文档中阅读更多关于此的内容,参见扩充现有类型。

使用描述符和混合类型

产生修改后的属性行为的更全面的方法是使用描述符。在 Python 中,通常使用 property() 函数来使用这些。描述符的标准 SQLAlchemy 技术是创建一个普通描述符,并从具有不同名称的映射属性读取/写入。下面我们使用 Python 2.6 风格的属性来说明这一点:

代码语言:javascript复制
class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    # name the attribute with an underscore,
    # different from the column name
    _email = mapped_column("email", String)

    # then create an ".email" attribute
    # to get/set "._email"
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email

上述方法可行,但我们还可以添加更多内容。虽然我们的EmailAddress对象将值通过email描述符传递到_email映射属性中,但类级别的EmailAddress.email属性不具有通常可用于Select的表达语义。为了提供这些功能,我们使用 hybrid 扩展,如下所示:

代码语言:javascript复制
from sqlalchemy.ext.hybrid import hybrid_property

class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    _email = mapped_column("email", String)

    @hybrid_property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email

.email 属性除了在我们有EmailAddress实例时提供 getter/setter 行为外,在类级别使用时也提供了一个 SQL 表达式,即直接从EmailAddress类中:

代码语言:javascript复制
from sqlalchemy.orm import Session
from sqlalchemy import select

session = Session()

address = session.scalars(
    select(EmailAddress).where(EmailAddress.email == "address@example.com")
).one()
SELECT  address.email  AS  address_email,  address.id  AS  address_id
FROM  address
WHERE  address.email  =  ?
('address@example.com',)
address.email = "otheraddress@example.com"
session.commit()
UPDATE  address  SET  email=?  WHERE  address.id  =  ?
('otheraddress@example.com',  1)
COMMIT 

hybrid_property还允许我们更改属性的行为,包括在实例级别与类/表达式级别访问属性时定义不同的行为,使用hybrid_property.expression()修饰符。例如,如果我们想要自动添加主机名,我们可能会定义两组字符串操作逻辑:

代码语言:javascript复制
class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    _email = mapped_column("email", String)

    @hybrid_property
    def email(self):
  """Return the value of _email up until the last twelve
 characters."""

        return self._email[:-12]

    @email.setter
    def email(self, email):
  """Set the value of _email, tacking on the twelve character
 value @example.com."""

        self._email = email   "@example.com"

    @email.expression
    def email(cls):
  """Produce a SQL expression that represents the value
 of the _email column, minus the last twelve characters."""

        return func.substr(cls._email, 0, func.length(cls._email) - 12)

以上,访问EmailAddress实例的email属性将返回_email属性的值,从值中删除或添加主机名@example.com。当我们针对email属性进行查询时,将呈现出一个产生相同效果的 SQL 函数:

代码语言:javascript复制
address = session.scalars(
    select(EmailAddress).where(EmailAddress.email == "address")
).one()
SELECT  address.email  AS  address_email,  address.id  AS  address_id
FROM  address
WHERE  substr(address.email,  ?,  length(address.email)  -  ?)  =  ?
(0,  12,  'address') 

在混合属性中阅读更多内容。

同义词

同义词是一个映射级别的构造,允许类上的任何属性“镜像”另一个被映射的属性。

从最基本的意义上讲,同义词是一种简单的方式,通过额外的名称使某个属性可用:

代码语言:javascript复制
from sqlalchemy.orm import synonym

class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    job_status = mapped_column(String(50))

    status = synonym("job_status")

上述MyClass类有两个属性,.job_status.status,它们将作为一个属性行为,无论在表达式级别还是在实例级别:

代码语言:javascript复制
>>> print(MyClass.job_status == "some_status")
my_table.job_status  =  :job_status_1
>>> print(MyClass.status == "some_status")
my_table.job_status  =  :job_status_1 

在实例级别上:

代码语言:javascript复制
>>> m1 = MyClass(status="x")
>>> m1.status, m1.job_status
('x', 'x')

>>> m1.job_status = "y"
>>> m1.status, m1.job_status
('y', 'y')

synonym()可以用于任何一种映射属性,包括映射列和关系,以及同义词本身,这些属性都是MapperProperty的子类。

除了简单的镜像之外,synonym()还可以引用用户定义的描述符。我们可以用@property来提供我们的status同义词:

代码语言:javascript复制
class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    status = mapped_column(String(50))

    @property
    def job_status(self):
        return "Status: "   self.status

    job_status = synonym("status", descriptor=job_status)

在使用声明性时,可以使用synonym_for()装饰器更简洁地表达上述模式:

代码语言:javascript复制
from sqlalchemy.ext.declarative import synonym_for

class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    status = mapped_column(String(50))

    @synonym_for("status")
    @property
    def job_status(self):
        return "Status: "   self.status

虽然synonym()对于简单的镜像很有用,但是使用描述符增强属性行为的用例更好地在现代使用中使用混合属性特性来处理,后者更加面向 Python 描述符。从技术上讲,一个synonym()可以做任何一个hybrid_property能做的事情,因为它也支持注入自定义 SQL 功能,但是在更复杂的情况下混合属性更容易使用。

对象名称

描述

synonym(name, *, [map_column, descriptor, comparator_factory, init, repr, default, default_factory, compare, kw_only, info, doc])

将一个属性名表示为映射属性的同义词,即该属性将反映另一个属性的值和表达式行为。

代码语言:javascript复制
function sqlalchemy.orm.synonym(name: str, *, map_column: bool | None = None, descriptor: Any | None = None, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = None) → Synonym[Any]

将一个属性名表示为映射属性的同义词,即该属性将反映另一个属性的值和表达式行为。

例如:

代码语言:javascript复制
class MyClass(Base):
    __tablename__ = 'my_table'

    id = Column(Integer, primary_key=True)
    job_status = Column(String(50))

    status = synonym("job_status")

参数:

name – 现有映射属性的名称。这可以引用在类上配置的 ORM 映射属性的字符串名称,包括列绑定属性和关系。

descriptor – 一个 Python 描述符,当访问此属性时将用作 getter(和可能的 setter)。

map_column

仅适用于传统映射和对现有表对象的映射。如果为True,则synonym()构造将定位到在此同义词的属性名称通常与该同义词的属性名称相关联的映射表上的Column对象,并生成一个新的ColumnProperty,该属性将此Column映射到作为同义词的“name”参数给定的替代名称;通过这种方式,重新定义Column的映射为不同名称的步骤是不必要的。这通常用于当Column要被替换为也使用描述符的属性时,也就是与synonym.descriptor参数结合使用时:

代码语言:javascript复制
my_table = Table(
    "my_table", metadata,
    Column('id', Integer, primary_key=True),
    Column('job_status', String(50))
)

class MyClass:
    @property
    def _job_status_descriptor(self):
        return "Status: %s" % self._job_status

mapper(
    MyClass, my_table, properties={
        "job_status": synonym(
            "_job_status", map_column=True,
            descriptor=MyClass._job_status_descriptor)
    }
)

在上面的例子中,名为_job_status的属性会自动映射到job_status列:

代码语言:javascript复制
>>> j1 = MyClass()
>>> j1._job_status = "employed"
>>> j1.job_status
Status: employed

当使用声明式时,为了与同义词结合使用提供描述符,请使用sqlalchemy.ext.declarative.synonym_for()助手。但是,请注意,通常应优选混合属性功能,特别是在重新定义属性行为时。

info – 可选的数据字典,将填充到此对象的InspectionAttr.info属性中。

comparator_factory

PropComparator的子类,将在 SQL 表达式级别提供自定义比较行为。

注意

对于提供重新定义属性的 Python 级别和 SQL 表达式级别行为的用例,请参阅使用描述符和混合中介绍的混合属性,这是一种更有效的技术。

另请参阅

同义词 - 同义词概述

synonym_for() - 一种面向声明式的辅助工具

使用描述符和混合 - 混合属性扩展提供了一种更新的方法,可以更灵活地增强属性行为,比同义词更有效。

运算符定制

SQLAlchemy ORM 和 Core 表达式语言使用的“运算符”是完全可定制的。例如,比较表达式 User.name == 'ed' 使用了 Python 本身内置的名为 operator.eq 的运算符 - SQLAlchemy 关联的实际 SQL 构造可以被修改。新的操作也可以与列表达式关联起来。最直接重新定义列表达式的运算符的方法是在类型级别进行 - 详细信息请参阅重新定义和创建新的运算符。

ORM 级别的函数如column_property()relationship()composite()还提供了在 ORM 级别重新定义运算符的功能,方法是将PropComparator子类传递给每个函数的comparator_factory参数。在这个级别定制运算符的情况很少见。详细信息请参阅PropComparator的文档概述。

复合列类型

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

列集合可以关联到一个单一用户定义的数据类型,现代使用中通常是一个 Python dataclass。ORM 提供了一个属性,使用您提供的类来表示列的组。

一个简单的例子表示一对 Integer 列作为一个 Point 对象,带有属性 .x.y。使用 dataclass,这些属性使用相应的 int Python 类型定义:

代码语言:javascript复制
import dataclasses

@dataclasses.dataclass
class Point:
    x: int
    y: int

也接受非 dataclass 形式,但需要实现额外的方法。有关使用非 dataclass 类的示例,请参见 Using Legacy Non-Dataclasses 部分。

2.0 版中新增:composite() 构造完全支持 Python dataclasses,包括从复合类派生映射列数据类型的能力。

我们将创建一个映射到表 vertices 的映射,表示两个点为 x1/y1x2/y2。使用 composite() 构造将 Point 类与映射列关联起来。

下面的示例说明了与完全 Annotated Declarative Table 配置一起使用的最现代形式的 composite()mapped_column() 构造表示每个列直接传递给 composite(),指示要生成的列的零个或多个方面,在这种情况下是名称;composite() 构造直接从数据类中推导列类型(在本例中为 int,对应于 Integer):

代码语言:javascript复制
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})"

提示

在上面的示例中,表示复合的列(x1y1 等)也可以在类上访问,但类型检查器不能正确理解。如果访问单列很重要,可以明确声明它们,如 Map columns directly, pass attribute names to composite 中所示。

上述映射将对应于 CREATE TABLE 语句:

代码语言:javascript复制
>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(Vertex.__table__))
CREATE  TABLE  vertices  (
  id  INTEGER  NOT  NULL,
  x1  INTEGER  NOT  NULL,
  y1  INTEGER  NOT  NULL,
  x2  INTEGER  NOT  NULL,
  y2  INTEGER  NOT  NULL,
  PRIMARY  KEY  (id)
) 

使用映射复合列类型

使用顶部部分示例中说明的映射,我们可以使用 Vertex 类,在其中 .start.end 属性将透明地引用 Point 类引用的列,以及使用 Vertex 类的实例,在其中 .start.end 属性将引用 Point 类的实例。 x1y1x2y2 列被透明处理:

持久化 Point 对象

我们可以创建一个 Vertex 对象,将 Point 对象分配为成员,并且它们将如预期一样持久化:

代码语言:javascript复制
>>> v = Vertex(start=Point(3, 4), end=Point(5, 6))
>>> session.add(v)
>>> session.commit()
BEGIN  (implicit)
INSERT  INTO  vertices  (x1,  y1,  x2,  y2)  VALUES  (?,  ?,  ?,  ?)
[generated  in  ...]  (3,  4,  5,  6)
COMMIT 

选择 Point 对象作为列

composite() 将允许 Vertex.startVertex.end 属性在使用 ORM Session(包括传统的 Query 对象)选择 Point 对象时尽可能地行为像单个 SQL 表达式:

代码语言:javascript复制
>>> stmt = select(Vertex.start, Vertex.end)
>>> session.execute(stmt).all()
SELECT  vertices.x1,  vertices.y1,  vertices.x2,  vertices.y2
FROM  vertices
[...]  ()
[(Point(x=3, y=4), Point(x=5, y=6))]

在 SQL 表达式中比较 Point 对象

Vertex.startVertex.end 属性可以在 WHERE 条件和类似情况下使用,使用临时的 Point 对象进行比较:

代码语言:javascript复制
>>> stmt = select(Vertex).where(Vertex.start == Point(3, 4)).where(Vertex.end < Point(7, 8))
>>> session.scalars(stmt).all()
SELECT  vertices.id,  vertices.x1,  vertices.y1,  vertices.x2,  vertices.y2
FROM  vertices
WHERE  vertices.x1  =  ?  AND  vertices.y1  =  ?  AND  vertices.x2  <  ?  AND  vertices.y2  <  ?
[...]  (3,  4,  7,  8)
[Vertex(Point(x=3, y=4), Point(x=5, y=6))]

从 2.0 版开始:composite() 构造现在支持“排序”比较,例如 <>= 等,除了已经存在的支持 ==!=

提示

上面使用“小于”运算符 (<) 的“排序”比较以及使用 == 的“相等”比较,当用于生成 SQL 表达式时,是由 Comparator 类实现的,并不使用复合类本身的比较方法,例如 __lt__()__eq__() 方法。 由此可见,上述 SQL 操作也不需要实现数据类 order=True 参数。重新定义复合操作部分包含如何自定义比较操作的背景信息。

更新顶点实例上的 Point 对象

默认情况下,必须用新对象替换 Point 对象才能检测到更改:

代码语言:javascript复制
>>> v1 = session.scalars(select(Vertex)).one()
SELECT  vertices.id,  vertices.x1,  vertices.y1,  vertices.x2,  vertices.y2
FROM  vertices
[...]  ()
>>> v1.end = Point(x=10, y=14)
>>> session.commit()
UPDATE  vertices  SET  x2=?,  y2=?  WHERE  vertices.id  =  ?
[...]  (10,  14,  1)
COMMIT 

为了允许在复合对象上进行原地更改,必须使用 Mutation Tracking 扩展。请参阅在复合对象上建立可变性部分以获取示例。

复合对象的其他映射形式

composite() 构造可以使用 mapped_column() 构造、Column 或现有映射列的字符串名称来传递相关列。以下示例说明了与上述主要部分相同的等效映射。

直接映射列,然后传递给复合对象

在这里,我们将现有的 mapped_column() 实例传递给 composite() 构造函数,就像下面的非注释示例中一样,我们还将 Point 类作为第一个参数传递给 composite()

代码语言:javascript复制
from sqlalchemy import Integer
from sqlalchemy.orm import mapped_column, composite

class Vertex(Base):
    __tablename__ = "vertices"

    id = mapped_column(Integer, primary_key=True)
    x1 = mapped_column(Integer)
    y1 = mapped_column(Integer)
    x2 = mapped_column(Integer)
    y2 = mapped_column(Integer)

    start = composite(Point, x1, y1)
    end = composite(Point, x2, y2)
直接映射列,将属性名称传递给组合类型

我们可以使用更多的注释形式编写上面相同的示例,其中我们有选项将属性名称传递给 composite(),而不是完整的列构造:

代码语言:javascript复制
from sqlalchemy.orm import mapped_column, composite, Mapped

class Vertex(Base):
    __tablename__ = "vertices"

    id: Mapped[int] = mapped_column(primary_key=True)
    x1: Mapped[int]
    y1: Mapped[int]
    x2: Mapped[int]
    y2: Mapped[int]

    start: Mapped[Point] = composite("x1", "y1")
    end: Mapped[Point] = composite("x2", "y2")
命令式映射和命令式表

在使用命令式表或完全命令式映射时,我们可以直接访问 Column 对象。这些也可以传递给 composite(),就像下面的命令式示例中一样:

代码语言:javascript复制
mapper_registry.map_imperatively(
    Vertex,
    vertices_table,
    properties={
        "start": composite(Point, vertices_table.c.x1, vertices_table.c.y1),
        "end": composite(Point, vertices_table.c.x2, vertices_table.c.y2),
    },
)
```## 使用遗留的非数据类

如果不使用数据类,则自定义数据类型类的要求是,它具有一个构造函数,该构造函数接受与其列格式相对应的位置参数,并且还提供一个方法 `__composite_values__()`,该方法返回对象的状态作为列表或元组,按照其基于列的属性顺序。它还应该提供足够的 `__eq__()` 和 `__ne__()` 方法,用于测试两个实例的相等性。

为了说明主要部分中的等效 `Point` 类不使用数据类:

```py
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __repr__(self):
        return f"Point(x={self.x!r}, y={self.y!r})"

    def __eq__(self, other):
        return isinstance(other, Point) and other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

使用 composite() 时,需要先声明要与 Point 类关联的列,并使用 其他复合类型的映射形式 中的一种形式进行显式类型声明。

跟踪组合类型的就地变更

对现有的组合类型值进行的就地更改不会自动跟踪。相反,组合类需要显式向其父对象提供事件。通过使用 MutableComposite 混合类,大部分工作已自动化,该类使用事件将每个用户定义的组合对象与所有父关联关联起来。请参阅在组合类型上建立可变性中的示例。

重新定义组合类型的比较操作

默认情况下,“equals”比较操作产生所有相应列相等的 AND。这可以通过composite()comparator_factory参数进行更改,其中我们指定一个自定义的Comparator类来定义现有或新的操作。下面我们举例说明“大于”运算符,实现与基本“大于”相同的表达式:

代码语言:javascript复制
import dataclasses

from sqlalchemy.orm import composite
from sqlalchemy.orm import CompositeProperty
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.sql import and_

@dataclasses.dataclass
class Point:
    x: int
    y: int

class PointComparator(CompositeProperty.Comparator):
    def __gt__(self, other):
  """redefine the 'greater than' operation"""

        return and_(
            *[
                a > b
                for a, b in zip(
                    self.__clause_element__().clauses,
                    dataclasses.astuple(other),
                )
            ]
        )

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"), comparator_factory=PointComparator
    )
    end: Mapped[Point] = composite(
        mapped_column("x2"), mapped_column("y2"), comparator_factory=PointComparator
    )

由于Point是一个数据类,我们可以使用dataclasses.astuple()来获得Point实例的元组形式。

然后,自定义比较器返回适当的 SQL 表达式:

代码语言:javascript复制
>>> print(Vertex.start > Point(5, 6))
vertices.x1  >  :x1_1  AND  vertices.y1  >  :y1_1 

嵌套复合体

复合对象可以被定义为在简单的嵌套方案中工作,通过在复合类内重新定义所需的行为,然后将复合类映射到通常的各列的全长。这需要定义额外的方法来在“嵌套”和“平面”形式之间移动。

下面我们重新组织Vertex类,使其本身成为一个复合对象,引用Point对象。VertexPoint可以是数据类,但是我们将在Vertex中添加一个自定义的构造方法,该方法可以用于根据四个列值创建新的Vertex对象,我们将其任意命名为_generate()并定义为一个类方法,这样我们就可以通过向Vertex._generate()方法传递值来创建新的Vertex对象。

我们还将实现__composite_values__()方法,这是一个固定名称,被composite()构造函数所识别(在使用传统非数据类中介绍过),它指示了一种接收对象作为列值的标准方式,这种情况下将取代通常的数据类方法论。

有了我们自定义的_generate()构造函数和__composite_values__()序列化方法,我们现在可以在列的平面元组和包含Point实例的Vertex对象之间进行转换。Vertex._generate方法作为composite()构造函数的第一个参数传递,用于源Vertex实例的创建,并且__composite_values__()方法将隐式地被composite()使用。

为了例子的目的,Vertex复合体随后被映射到一个名为HasVertex的类中,该类是包含四个源列的Table最终所在的地方:

代码语言:javascript复制
from __future__ import annotations

import dataclasses
from typing import Any
from typing import Tuple

from sqlalchemy.orm import composite
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

@dataclasses.dataclass
class Point:
    x: int
    y: int

@dataclasses.dataclass
class Vertex:
    start: Point
    end: Point

    @classmethod
    def _generate(cls, x1: int, y1: int, x2: int, y2: int) -> Vertex:
  """generate a Vertex from a row"""
        return Vertex(Point(x1, y1), Point(x2, y2))

    def __composite_values__(self) -> Tuple[Any, ...]:
  """generate a row from a Vertex"""
        return dataclasses.astuple(self.start)   dataclasses.astuple(self.end)

class Base(DeclarativeBase):
    pass

class HasVertex(Base):
    __tablename__ = "has_vertex"
    id: Mapped[int] = mapped_column(primary_key=True)
    x1: Mapped[int]
    y1: Mapped[int]
    x2: Mapped[int]
    y2: Mapped[int]

    vertex: Mapped[Vertex] = composite(Vertex._generate, "x1", "y1", "x2", "y2")

然后,上述映射可以根据HasVertexVertexPoint来使用:

代码语言:javascript复制
hv = HasVertex(vertex=Vertex(Point(1, 2), Point(3, 4)))

session.add(hv)
session.commit()

stmt = select(HasVertex).where(HasVertex.vertex == Vertex(Point(1, 2), Point(3, 4)))

hv = session.scalars(stmt).first()
print(hv.vertex.start)
print(hv.vertex.end)

复合 API

对象名称

描述

composite([_class_or_attr], *attrs, [group, deferred, raiseload, comparator_factory, active_history, init, repr, default, default_factory, compare, kw_only, info, doc], **__kw)

返回一个基于复合列的属性,供 Mapper 使用。

代码语言:javascript复制
function sqlalchemy.orm.composite(_class_or_attr: None | Type[_CC] | Callable[..., _CC] | _CompositeAttrType[Any] = None, *attrs: _CompositeAttrType[Any], group: str | None = None, deferred: bool = False, raiseload: bool = False, comparator_factory: Type[Composite.Comparator[_T]] | None = None, active_history: bool = False, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: Any | None = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = None, **__kw: Any) → Composite[Any]

返回一个基于复合列的属性,供 Mapper 使用。

参见映射文档部分 复合列类型 以获取完整的使用示例。

composite() 返回的 MapperPropertyComposite

参数:

  • class_ – “复合类型”类,或任何类方法或可调用对象,根据顺序给出列值来产生复合对象的新实例。
  • *attrs – 要映射的元素列表,可能包括:
    • Column 对象
    • mapped_column() 构造
    • 映射类上的其他属性的字符串名称,这些属性可以是任何其他 SQL 或对象映射属性。例如,这可以允许一个复合列引用一个一对多关系。
  • active_history=False – 当为 True 时,表示替换时标量属性的“上一个”值应加载,如果尚未加载。请参见 column_property() 上相同的标志。
  • group – 当标记为延迟加载时,此属性的分组名称。
  • deferred – 如果为 True,则列属性是“延迟加载”的,意味着它不会立即加载,而是在首次在实例上访问属性时加载。另请参见 deferred()
  • comparator_factory – 一个扩展了 Comparator 的类,为比较操作提供自定义的 SQL 子句生成。
  • doc – 可选字符串,将作为类绑定描述符的文档应用。
  • info – 可选的数据字典,将填充到此对象的 MapperProperty.info 属性中。
  • init – 特定于 声明性数据类映射,指定映射属性是否应该是由数据类流程生成的 __init__() 方法的一部分。
  • repr – 特定于 声明性数据类映射,指定映射属性是否应该是由数据类流程生成的 __repr__() 方法的一部分。
  • default_factory – 特定于 声明性 Dataclass 映射,指定将作为 __init__() 方法的一部分执行的默认值生成函数。
  • compare – 特定于 声明性 Dataclass 映射,指示在生成映射类的 __eq__()__ne__() 方法时是否应包括此字段的比较操作。 新版本 2.0.0b4 中。
  • kw_only – 特定于 声明性 Dataclass 映射,指示是否在生成 __init__() 时将此字段标记为关键字参数。

使用映射复合列类型

如上一节所示的映射,我们可以使用 Vertex 类,其中 .start.end 属性将透明地引用 Point 类引用的列,以及使用 Vertex 类的实例,其中 .start.end 属性将引用 Point 类的实例。x1y1x2y2 列将被透明处理:

持久化 Point 对象

我们可以创建一个 Vertex 对象,将 Point 对象分配为成员,并且它们将按预期持久化:

代码语言:javascript复制
>>> v = Vertex(start=Point(3, 4), end=Point(5, 6))
>>> session.add(v)
>>> session.commit()
BEGIN  (implicit)
INSERT  INTO  vertices  (x1,  y1,  x2,  y2)  VALUES  (?,  ?,  ?,  ?)
[generated  in  ...]  (3,  4,  5,  6)
COMMIT 

选择 Point 对象作为列

composite() 将允许 Vertex.startVertex.end 属性在使用 ORM Session(包括传统的 Query 对象)选择 Point 对象时尽可能地像单个 SQL 表达式一样行为:

代码语言:javascript复制
>>> stmt = select(Vertex.start, Vertex.end)
>>> session.execute(stmt).all()
SELECT  vertices.x1,  vertices.y1,  vertices.x2,  vertices.y2
FROM  vertices
[...]  ()
[(Point(x=3, y=4), Point(x=5, y=6))]

比较 SQL 表达式中的 Point 对象

Vertex.startVertex.end 属性可用于 WHERE 条件和类似条件,使用临时 Point 对象进行比较:

代码语言:javascript复制
>>> stmt = select(Vertex).where(Vertex.start == Point(3, 4)).where(Vertex.end < Point(7, 8))
>>> session.scalars(stmt).all()
SELECT  vertices.id,  vertices.x1,  vertices.y1,  vertices.x2,  vertices.y2
FROM  vertices
WHERE  vertices.x1  =  ?  AND  vertices.y1  =  ?  AND  vertices.x2  <  ?  AND  vertices.y2  <  ?
[...]  (3,  4,  7,  8)
[Vertex(Point(x=3, y=4), Point(x=5, y=6))]

新版本 2.0 中:composite() 构造现在支持“排序”比较,如 <>=,以及已经存在的 ==!= 的支持。

提示

使用“小于”运算符(<)的“排序”比较以及使用 == 的“相等性”比较,用于生成 SQL 表达式时,由 Comparator 类实现,并不使用复合类本身的比较方法,例如 __lt__()__eq__() 方法。由此可见,上面的 Point 数据类也无需实现 dataclasses 的 order=True 参数,上述 SQL 操作就可以正常工作。复合操作重新定义比较操作 包含了如何定制比较操作的背景信息。

在 Vertex 实例上更新 Point 对象

默认情况下,必须通过一个新对象来替换 Point 对象才能检测到更改:

代码语言:javascript复制
>>> v1 = session.scalars(select(Vertex)).one()
SELECT  vertices.id,  vertices.x1,  vertices.y1,  vertices.x2,  vertices.y2
FROM  vertices
[...]  ()
>>> v1.end = Point(x=10, y=14)
>>> session.commit()
UPDATE  vertices  SET  x2=?,  y2=?  WHERE  vertices.id  =  ?
[...]  (10,  14,  1)
COMMIT 

为了允许对复合对象进行原地更改,必须使用 Mutation Tracking 扩展。参见在复合对象上建立可变性部分中的示例。

复合对象的其他映射形式

composite() 构造可以使用 mapped_column() 构造、Column 或现有映射列的字符串名称来传递相关列。以下示例说明了与上述主要部分相同的等效映射。

直接映射列,然后传递给复合对象

在这里,我们将现有的 mapped_column() 实例传递给 composite() 构造,就像下面的非注释示例中我们还将 Point 类作为 composite() 的第一个参数传递一样:

代码语言:javascript复制
from sqlalchemy import Integer
from sqlalchemy.orm import mapped_column, composite

class Vertex(Base):
    __tablename__ = "vertices"

    id = mapped_column(Integer, primary_key=True)
    x1 = mapped_column(Integer)
    y1 = mapped_column(Integer)
    x2 = mapped_column(Integer)
    y2 = mapped_column(Integer)

    start = composite(Point, x1, y1)
    end = composite(Point, x2, y2)
直接映射列,将属性名称传递给复合对象

我们可以使用更多带有注释形式编写上面相同的示例,其中我们可以选择将属性名称传递给 composite() 而不是完整的列构造:

代码语言:javascript复制
from sqlalchemy.orm import mapped_column, composite, Mapped

class Vertex(Base):
    __tablename__ = "vertices"

    id: Mapped[int] = mapped_column(primary_key=True)
    x1: Mapped[int]
    y1: Mapped[int]
    x2: Mapped[int]
    y2: Mapped[int]

    start: Mapped[Point] = composite("x1", "y1")
    end: Mapped[Point] = composite("x2", "y2")
命令式映射和命令式表

在使用命令式表或完全命令式映射时,我们直接可以访问 Column 对象。这些也可以像以下命令式示例中那样传递给 composite()

代码语言:javascript复制
mapper_registry.map_imperatively(
    Vertex,
    vertices_table,
    properties={
        "start": composite(Point, vertices_table.c.x1, vertices_table.c.y1),
        "end": composite(Point, vertices_table.c.x2, vertices_table.c.y2),
    },
)
直接映射列,然后传递给复合对象

在这里,我们将现有的 mapped_column() 实例传递给 composite() 构造,就像下面的非注释示例中我们还将 Point 类作为 composite() 的第一个参数传递一样:

代码语言:javascript复制
from sqlalchemy import Integer
from sqlalchemy.orm import mapped_column, composite

class Vertex(Base):
    __tablename__ = "vertices"

    id = mapped_column(Integer, primary_key=True)
    x1 = mapped_column(Integer)
    y1 = mapped_column(Integer)
    x2 = mapped_column(Integer)
    y2 = mapped_column(Integer)

    start = composite(Point, x1, y1)
    end = composite(Point, x2, y2)
直接映射列,将属性名称传递给复合对象

我们可以使用更多带有注释形式编写上面相同的示例,其中我们可以选择将属性名称传递给 composite() 而不是完整的列构造:

代码语言:javascript复制
from sqlalchemy.orm import mapped_column, composite, Mapped

class Vertex(Base):
    __tablename__ = "vertices"

    id: Mapped[int] = mapped_column(primary_key=True)
    x1: Mapped[int]
    y1: Mapped[int]
    x2: Mapped[int]
    y2: Mapped[int]

    start: Mapped[Point] = composite("x1", "y1")
    end: Mapped[Point] = composite("x2", "y2")
命令式映射和命令式表

当使用命令式表或完全命令式映射时,我们可以直接访问Column对象。这些也可以传递给composite(),如下所示的命令式示例:

代码语言:javascript复制
mapper_registry.map_imperatively(
    Vertex,
    vertices_table,
    properties={
        "start": composite(Point, vertices_table.c.x1, vertices_table.c.y1),
        "end": composite(Point, vertices_table.c.x2, vertices_table.c.y2),
    },
)

使用传统非数据类

如果不使用数据类,则自定义数据类型类的要求是它具有一个构造函数,该构造函数接受与其列格式对应的位置参数,并且还提供一个__composite_values__()方法,该方法按照其基于列的属性的顺序返回对象的状态列表或元组。它还应该提供足够的__eq__()__ne__()方法来测试两个实例的相等性。

为了说明主要部分中的等效Point类不使用数据类的情况:

代码语言:javascript复制
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __repr__(self):
        return f"Point(x={self.x!r}, y={self.y!r})"

    def __eq__(self, other):
        return isinstance(other, Point) and other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

使用composite()进行如下操作,必须使用显式类型声明与Point类关联的列,使用其他复合对象映射形式中的一种形式。

跟踪复合对象的原位变化

对现有复合值的原位更改不会自动跟踪。相反,复合类需要显式为其父对象提供事件。通过使用MutableComposite mixin,这项任务主要通过使用事件将每个用户定义的复合对象与所有父关联关联起来来自动完成。请参阅为复合对象建立可变性中的示例。

重新定义复合对象的比较操作

默认情况下,“equals”比较操作会产生所有对应列等于彼此的 AND。可以使用composite()comparator_factory参数进行更改,其中我们指定一个自定义的Comparator类来定义现有或新的操作。下面我们说明“greater than”运算符,实现与基本“greater than”相同的表达式:

代码语言:javascript复制
import dataclasses

from sqlalchemy.orm import composite
from sqlalchemy.orm import CompositeProperty
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.sql import and_

@dataclasses.dataclass
class Point:
    x: int
    y: int

class PointComparator(CompositeProperty.Comparator):
    def __gt__(self, other):
  """redefine the 'greater than' operation"""

        return and_(
            *[
                a > b
                for a, b in zip(
                    self.__clause_element__().clauses,
                    dataclasses.astuple(other),
                )
            ]
        )

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"), comparator_factory=PointComparator
    )
    end: Mapped[Point] = composite(
        mapped_column("x2"), mapped_column("y2"), comparator_factory=PointComparator
    )

由于Point是一个数据类,我们可以利用dataclasses.astuple()来获得Point实例的元组形式。

然后,自定义比较器返回适当的 SQL 表达式:

代码语言:javascript复制
>>> print(Vertex.start > Point(5, 6))
vertices.x1  >  :x1_1  AND  vertices.y1  >  :y1_1 

嵌套复合对象

可以定义复合对象以在简单的嵌套方案中工作,方法是在复合类中重新定义所需的行为,然后将复合类映射到通常的单个列的完整长度。这要求定义额外的方法来在“嵌套”和“扁平”形式之间移动。

接下来,我们重新组织Vertex类本身成为一个引用Point对象的复合对象。 VertexPoint可以是数据类,但是我们将向Vertex添加一个自定义构造方法,该方法可用于根据四个列值创建新的Vertex对象,我们将任意命名为_generate()并定义为类方法,以便我们可以通过将值传递给Vertex._generate()方法来创建新的Vertex对象。

我们还将实现__composite_values__()方法,这是由composite()构造(在使用传统非数据类中介绍)中识别的固定名称,表示以列值的扁平元组形式接收对象的标准方式,在这种情况下将取代通常的数据类导向方法。

通过我们的自定义_generate()构造函数和__composite_values__()序列化方法,我们现在可以在扁平列元组和包含Point实例的Vertex对象之间进行转换。 Vertex._generate方法作为composite()构造的第一个参数传递,用作新Vertex实例的来源,并且__composite_values__()方法将隐式地被composite()使用。

为了示例的目的,Vertex复合然后映射到一个名为HasVertex的类,其中包含最终包含四个源列的Table

代码语言:javascript复制
from __future__ import annotations

import dataclasses
from typing import Any
from typing import Tuple

from sqlalchemy.orm import composite
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

@dataclasses.dataclass
class Point:
    x: int
    y: int

@dataclasses.dataclass
class Vertex:
    start: Point
    end: Point

    @classmethod
    def _generate(cls, x1: int, y1: int, x2: int, y2: int) -> Vertex:
  """generate a Vertex from a row"""
        return Vertex(Point(x1, y1), Point(x2, y2))

    def __composite_values__(self) -> Tuple[Any, ...]:
  """generate a row from a Vertex"""
        return dataclasses.astuple(self.start)   dataclasses.astuple(self.end)

class Base(DeclarativeBase):
    pass

class HasVertex(Base):
    __tablename__ = "has_vertex"
    id: Mapped[int] = mapped_column(primary_key=True)
    x1: Mapped[int]
    y1: Mapped[int]
    x2: Mapped[int]
    y2: Mapped[int]

    vertex: Mapped[Vertex] = composite(Vertex._generate, "x1", "y1", "x2", "y2")

上述映射可以根据HasVertexVertexPoint来使用:

代码语言:javascript复制
hv = HasVertex(vertex=Vertex(Point(1, 2), Point(3, 4)))

session.add(hv)
session.commit()

stmt = select(HasVertex).where(HasVertex.vertex == Vertex(Point(1, 2), Point(3, 4)))

hv = session.scalars(stmt).first()
print(hv.vertex.start)
print(hv.vertex.end)

复合 API

对象名称

描述

composite([_class_or_attr], *attrs, [group, deferred, raiseload, comparator_factory, active_history, init, repr, default, default_factory, compare, kw_only, info, doc], **__kw)

返回用于与 Mapper 一起使用的基于复合列的属性。

代码语言:javascript复制
function sqlalchemy.orm.composite(_class_or_attr: None | Type[_CC] | Callable[..., _CC] | _CompositeAttrType[Any] = None, *attrs: _CompositeAttrType[Any], group: str | None = None, deferred: bool = False, raiseload: bool = False, comparator_factory: Type[Composite.Comparator[_T]] | None = None, active_history: bool = False, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: Any | None = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = None, **__kw: Any) → Composite[Any]

返回用于与 Mapper 一起使用的基于复合列的属性。

查看映射文档部分复合列类型以获取完整的使用示例。

composite()返回的MapperPropertyComposite

参数:

  • class_ – “复合类型”类,或任何类方法或可调用对象,将根据顺序的列值生成复合对象的新实例。
  • *attrs – 要映射的元素列表,可能包括:
    • Column对象
    • mapped_column()构造
    • 映射类上的其他属性的字符串名称,这些属性可以是任何其他 SQL 或对象映射的属性。例如,这可以允许一个复合属性引用到一个多对一的关系。
  • active_history=False – 当为True时,指示在替换时应加载标量属性的“先前”值,如果尚未加载。请参阅column_property()上的相同标志。
  • group – 标记为延迟加载时,此属性的组名。
  • deferred – 当为 True 时,列属性为“延迟加载”,意味着它不会立即加载,而是在首次访问实例上的属性时加载。另请参阅deferred()
  • comparator_factory – 一个扩展Comparator的类,提供自定义的 SQL 子句生成以进行比较操作。
  • doc – 可选字符串,将应用为类绑定描述符的文档。
  • info – 可选的数据字典,将填充到此对象的MapperProperty.info属性中。
  • init – 特定于声明式数据类映射,指定映射属性是否应作为数据类处理生成的__init__()方法的一部分。
  • repr – 特定于声明式数据类映射,指定映射属性是否应作为数据类处理生成的__repr__()方法的一部分。
  • default_factory – 特定于声明式数据类映射,指定将作为数据类处理生成的__init__()方法的一部分而发生的默认值生成函数。
  • compare – 特定于声明式数据类映射,指示在为映射类生成__eq__()__ne__()方法时,此字段是否应包含在比较操作中。 新功能在版本 2.0.0b4 中引入。
  • kw_only – 特定于声明式数据类映射,指示在生成__init__()时此字段是否应标记为关键字参数。 == Vertex(Point(1, 2), Point(3, 4)))

hv = session.scalars(stmt).first() print(hv.vertex.start) print(hv.vertex.end)

代码语言:javascript复制
## 复合 API

| 对象名称 | 描述 |
| --- | --- |
| composite([_class_or_attr], *attrs, [group, deferred, raiseload, comparator_factory, active_history, init, repr, default, default_factory, compare, kw_only, info, doc], **__kw) | 返回用于与 Mapper 一起使用的基于复合列的属性。 |

```py
function sqlalchemy.orm.composite(_class_or_attr: None | Type[_CC] | Callable[..., _CC] | _CompositeAttrType[Any] = None, *attrs: _CompositeAttrType[Any], group: str | None = None, deferred: bool = False, raiseload: bool = False, comparator_factory: Type[Composite.Comparator[_T]] | None = None, active_history: bool = False, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: Any | None = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = None, **__kw: Any) → Composite[Any]

返回用于与 Mapper 一起使用的基于复合列的属性。

查看映射文档部分复合列类型以获取完整的使用示例。

composite()返回的MapperPropertyComposite

参数:

  • class_ – “复合类型”类,或任何类方法或可调用对象,将根据顺序的列值生成复合对象的新实例。
  • *attrs – 要映射的元素列表,可能包括:
    • Column对象
    • mapped_column()构造
    • 映射类上的其他属性的字符串名称,这些属性可以是任何其他 SQL 或对象映射的属性。例如,这可以允许一个复合属性引用到一个多对一的关系。
  • active_history=False – 当为True时,指示在替换时应加载标量属性的“先前”值,如果尚未加载。请参阅column_property()上的相同标志。
  • group – 标记为延迟加载时,此属性的组名。
  • deferred – 当为 True 时,列属性为“延迟加载”,意味着它不会立即加载,而是在首次访问实例上的属性时加载。另请参阅deferred()
  • comparator_factory – 一个扩展Comparator的类,提供自定义的 SQL 子句生成以进行比较操作。
  • doc – 可选字符串,将应用为类绑定描述符的文档。
  • info – 可选的数据字典,将填充到此对象的MapperProperty.info属性中。
  • init – 特定于声明式数据类映射,指定映射属性是否应作为数据类处理生成的__init__()方法的一部分。
  • repr – 特定于声明式数据类映射,指定映射属性是否应作为数据类处理生成的__repr__()方法的一部分。
  • default_factory – 特定于声明式数据类映射,指定将作为数据类处理生成的__init__()方法的一部分而发生的默认值生成函数。
  • compare – 特定于声明式数据类映射,指示在为映射类生成__eq__()__ne__()方法时,此字段是否应包含在比较操作中。 新功能在版本 2.0.0b4 中引入。
  • kw_only – 特定于声明式数据类映射,指示在生成__init__()时此字段是否应标记为关键字参数。

0 人点赞