SqlAlchemy 2.0 中文文档(四)(3)

简介: SqlAlchemy 2.0 中文文档(四)

SqlAlchemy 2.0 中文文档(四)(2)https://developer.aliyun.com/article/1562995


ORM 映射类概述

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

ORM 类映射配置概述。

对于对 SQLAlchemy ORM 和/或对 Python 比较新的读者来说,建议浏览 ORM 快速入门,最好是通过 SQLAlchemy 统一教程进行学习,其中首次介绍了 ORM 配置,即使用 ORM 声明形式定义表元数据。

ORM 映射风格

SQLAlchemy  具有两种不同的映射器配置风格,然后具有更多的子选项来设置它们。映射器风格的可变性存在是为了适应各种开发人员偏好的列表,包括用户定义的类与如何映射到关系模式表和列之间的抽象程度,正在使用的类层次结构的种类,包括是否存在自定义元类方案,最后,是否同时存在其他类实例化方法,例如是否同时使用  Python dataclasses

在现代 SQLAlchemy 中,这些风格之间的差异基本上是表面的;当使用特定的 SQLAlchemy 配置风格来表达映射类的意图时,映射类的内部映射过程大部分都是相同的,最终的结果始终是一个用户定义的类,其配置了针对可选择单元的Mapper,通常由Table对象表示,并且该类本身已经被 instrumented 以包括与关系操作相关的行为,无论是在类的级别还是在该类的实例上。由于过程在所有情况下基本上都是相同的,因此从不同风格映射的类始终是完全可互操作的。协议MappedClassProtocol可用于在使用诸如 mypy 等类型检查器时指示映射类。

原始的映射 API 通常被称为“经典”风格,而更自动化的映射风格称为“声明”风格。SQLAlchemy 现在将这两种映射风格称为命令式映射声明式映射

无论使用何种映射样式,截至 SQLAlchemy 1.4 版本,所有 ORM 映射都源自一个名为registry的单个对象,它是映射类的注册表。使用此注册表,一组映射器配置可以作为一个组进行最终确定,并且在特定注册表内的类可以在配置过程中相互通过名称引用。

自 1.4 版本更改:声明式和经典映射现在被称为“声明式”和“命令式”映射,并在内部统一,都源自代表一组相关映射的registry 构造。

声明式映射

声明式映射是现代 SQLAlchemy 中构建映射的典型方式。最常见的模式是首先使用DeclarativeBase 超类构建一个基类。生成的基类,当被子类化时,将对从它派生的所有子类应用声明式映射过程,相对于默认情况下新基类的本地registry。下面的示例演示了使用声明基类,然后在声明表映射中使用它:

from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
# declarative base class
class Base(DeclarativeBase):
    pass
# an example mapping using the base
class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str] = mapped_column(String(30))
    nickname: Mapped[Optional[str]]

在上面的示例中,DeclarativeBase 类用于生成一个新的基类(在 SQLAlchemy 文档中通常被称为 Base,但可以有任何所需名称),从中新类可以继承映射,如上所示,构建了一个新的映射类 User

自 2.0 版本更改:DeclarativeBase 超类取代了declarative_base() 函数和registry.generate_base() 方法的使用;超类方法与PEP 484 工具集成,无需使用插件。有关迁移说明,请参阅 ORM 声明模型。

基类指的是维护一组相关映射类的registry对象,以及保留映射到类的一组Table对象的MetaData对象。

主要的声明式映射样式在以下各节中进一步详细说明:

  • 使用声明基类 - 使用基类进行声明式映射。
  • 使用装饰器的声明式映射(无声明基类) - 使用装饰器进行声明式映射,而不是使用基类。

在声明式映射类的范围内,Table 元数据的声明方式也有两种变体。包括:

  • 使用 mapped_column() 的声明式表 - 在映射类内联声明表列,使用 mapped_column() 指令(或在传统形式中,直接使用 Column 对象)。mapped_column() 指令也可以选择性地与使用 Mapped 类的类型注解结合,该类可以直接提供有关映射列的一些详细信息。列指令,结合 __tablename__ 和可选的 __table_args__ 类级指令,将允许声明式映射过程构建要映射的 Table 对象。
  • 具有命令式表的声明式(又名混合声明式) - 不是单独指定表名和属性,而是将显式构建的 Table 对象与以其他方式进行声明式映射的类关联。这种映射风格是“声明式”和“命令式”映射的混合,并适用于将类映射到反射的 Table 对象,以及将类映射到现有的 Core 构造,如连接和子查询。

声明式映射的文档继续在 使用声明式映射映射类 中。### 命令式映射

命令式经典映射是指使用 registry.map_imperatively() 方法配置映射类的情况,其中目标类不包含任何声明类属性。

提示

命令式映射形式是 SQLAlchemy 最早发布的版本中源自的较少使用的一种映射形式。它本质上是一种绕过声明式系统提供更“基础”的映射系统的方法,并且不提供像PEP 484支持这样的现代特性。因此,大多数文档示例都使用声明式形式,并建议新用户从声明式表配置开始。

在 2.0 版本中更改:现在使用registry.map_imperatively()方法来创建经典映射。sqlalchemy.orm.mapper()独立函数已被有效移除。

在“经典”形式中,表的元数据是分别用Table构造创建的,然后通过registry.map_imperatively()方法与User类关联,在建立registry实例后。通常,一个registry的单个实例被共享给所有彼此相关的映射类:

from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry
mapper_registry = registry()
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)),
)
class User:
    pass
mapper_registry.map_imperatively(User, user_table)

提供关于映射属性的信息,比如与其他类的关系,通过properties字典提供。下面的示例说明了第二个Table对象,映射到一个名为Address的类,然后通过relationship()User关联:

address = Table(
    "address",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("user_id", Integer, ForeignKey("user.id")),
    Column("email_address", String(50)),
)
mapper_registry.map_imperatively(
    User,
    user,
    properties={
        "addresses": relationship(Address, backref="user", order_by=address.c.id)
    },
)
mapper_registry.map_imperatively(Address, address)

注意,使用命令式方法映射的类与使用声明式方法映射的类完全可以互换。这两种系统最终都会创建相同的配置,包括一个Table、用户定义的类,以及一个Mapper对象。当我们谈论“Mapper的行为”时,这也包括了使用声明式系统时——它仍然被使用,只是在幕后进行。

对于所有的映射形式,可以通过传递构造参数来配置类的映射,这些构造参数最终成为Mapper对象的一部分,通过它的构造函数传递。传递给Mapper的参数来自给定的映射形式,包括传递给 Imperative 映射的registry.map_imperatively()的参数,或者在使用声明式系统时,来自被映射的表列、SQL 表达式和关系以及 mapper_args 等属性的组合。

Mapper类寻找的配置信息大致可以分为四类:

要映射的类

这是我们应用程序中构建的类。通常情况下,这个类的结构没有限制。[1] 当映射一个 Python 类时,对于这个类只能有一个Mapper对象。[2]

当使用声明式映射样式进行映射时,要映射的类要么是声明基类的子类,要么由装饰器或函数(如registry.mapped())处理。

当使用命令式映射样式进行映射时,类直接作为map_imperatively.class_参数传递。

表或其他来自子句对象

在绝大多数常见情况下,这是Table的实例。对于更高级的用例,它也可以指任何一种FromClause对象,最常见的替代对象是SubqueryJoin对象。

当使用声明式映射样式时,主题表格是根据__tablename__属性和所提供的Column对象,由声明式系统生成的,或者是通过__table__属性建立的。这两种配置样式分别在使用 mapped_column()定义声明式表格和命令式表格与声明式(又名混合声明式)中介绍。

当使用命令式样式进行映射时,主题表格作为map_imperatively.local_table参数位置传递。

与映射类“每个类一个映射器”的要求相反,用于映射的Table或其他FromClause对象可以与任意数量的映射关联。Mapper直接对用户定义的类应用修改,但不以任何方式修改给定的Table或其他FromClause

属性字典

这是与映射类关联的所有属性的字典。默认情况下,Mapper根据给定的Table从中派生出这个字典的条目,以ColumnProperty对象的形式表示,每个对象引用映射表的单个Column。属性字典还将包含所有其他类型的要配置的MapperProperty对象,最常见的是由relationship()构造生成的实例。

当使用声明式映射样式进行映射时,属性字典由声明式系统通过扫描要映射的类生成。有关此过程的说明,请参阅使用声明式定义映射属性部分。

当使用命令式风格进行映射时,属性字典直接作为properties参数传递给registry.map_imperatively(),该方法将将其传递给Mapper.properties参数。

其他映射器配置参数

当使用声明式映射风格进行映射时,额外的映射器配置参数通过__mapper_args__类属性进行配置。使用示例可在声明式映射器配置选项处找到。

当使用命令式风格进行映射时,关键字参数传递给registry.map_imperatively()方法,该方法将其传递给Mapper类。

可接受的所有参数范围在Mapper中有文档记录。## 映射类行为

使用registry对象进行所有映射风格时,以下行为是共同的:

默认构造函数

registry将默认构造函数,即__init__方法,应用于所有未明确具有自己__init__方法的映射类。该方法的行为是提供一个方便的关键字构造函数,将接受所有命名属性作为可选关键字参数。例如:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str]

上述User类型的对象将具有一个构造函数,允许像这样创建User对象:

u1 = User(name="some name", fullname="some fullname")

提示

声明式数据类映射功能提供了一种通过使用 Python 数据类生成默认__init__()方法的替代方法,并允许高度可配置的构造函数形式。

警告

当对象在 Python 代码中构造时,仅在调用类的__init__()方法时才会调用__init__()方法,而不是在从数据库加载或刷新对象时。请参阅下一节在加载时保持非映射状态了解如何在加载对象时调用特殊逻辑的基础知识。

包含显式__init__()方法的类将保留该方法,并且不会应用默认构造函数。

要更改使用的默认构造函数,可以向registry.constructor参数提供用户定义的 Python 可调用对象,该对象将用作默认构造函数。

构造函数也适用于命令式映射:

from sqlalchemy.orm import registry
mapper_registry = registry()
user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)
class User:
    pass
mapper_registry.map_imperatively(User, user_table)

上述类,如 命令式映射 中所述的那样被命令式映射,还将具有与 registry 关联的默认构造函数。

从版本 1.4 开始:经典映射现在支持通过 registry.map_imperatively() 方法进行映射时的标准配置级构造函数。### 在加载过程中保持非映射状态

当对象直接在 Python 代码中构造时,会调用映射类的 __init__() 方法:

u1 = User(name="some name", fullname="some fullname")

但是,当使用 ORM Session 加载对象时,会调用 __init__() 方法:

u1 = session.scalars(select(User).where(User.name == "some name")).first()

这样做的原因是,从数据库加载时,用于构造对象的操作,如上例中的 User,更类似于反序列化,比如反序列化,而不是初始构造。对象的大部分重要状态不是首次组装,而是重新从数据库行加载。

因此,为了在对象内部维护不属于存储到数据库的数据的状态,使得当对象加载和构造时都存在这些状态,有两种通用方法如下所述。

  1. 使用 Python 描述符,比如 @property,而不是状态,根据需要动态计算属性。
    对于简单的属性,这是最简单的方法,也是最不容易出错的方法。例如,如果一个对象 PointPoint.xPoint.y,想要一个这些属性的和的属性:
class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]
    @property
    def x_plus_y(self):
        return self.x + self.y
  1. 使用动态描述符的优点是值每次都会计算,这意味着它会根据底层属性(在本例中为 xy)的更改来维护正确的值。
    其他形式的上述模式包括 Python 标准库cached_property 装饰器(它是缓存的,并且不会每次重新计算),以及 SQLAlchemy 的hybrid_property 装饰器,允许属性在 SQL 查询中使用。
  2. 使用 InstanceEvents.load() 来在加载时建立状态,并可选地使用补充方法 InstanceEvents.refresh()InstanceEvents.refresh_flush()
    这些是在对象从数据库加载或在过期后刷新时调用的事件钩子。通常只需要InstanceEvents.load(),因为非映射的本地对象状态不受过期操作的影响。修改上面的Point示例如下所示:
from sqlalchemy import event
class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]
    def __init__(self, x, y, **kw):
        super().__init__(x=x, y=y, **kw)
        self.x_plus_y = x + y
@event.listens_for(Point, "load")
def receive_load(target, context):
    target.x_plus_y = target.x + target.y
  1. 如果还要使用刷新事件,可以根据需要将事件钩子叠加在一个可调用对象上,如下所示:
@event.listens_for(Point, "load")
@event.listens_for(Point, "refresh")
@event.listens_for(Point, "refresh_flush")
def receive_load(target, context, attrs=None):
    target.x_plus_y = target.x + target.y
  1. 上面,attrs属性将出现在refreshrefresh_flush事件中,并指示正在刷新的属性名称列表。### 映射类、实例和映射器的运行时内省

使用registry映射的类也将包含一些对所有映射共通的属性:

  • __mapper__属性将引用与该类相关联的Mapper
mapper = User.__mapper__
  • 这个Mapper也是使用inspect()函数对映射类进行检查时返回的对象:
from sqlalchemy import inspect
mapper = inspect(User)
  • __table__属性将引用与该类映射的Table,或更一般地,将引用FromClause对象:
table = User.__table__
  • 这个FromClause也是在使用Mapper.local_table属性时返回的对象Mapper
table = inspect(User).local_table
  • 对于单表继承映射,其中类是没有自己的表的子类,Mapper.local_table属性以及.__table__属性将为None。要检索在查询此类时实际选择的“可选择”对象,可以通过Mapper.selectable属性获取:
table = inspect(User).selectable
映射器对象的检查

如前一节所示,无论使用何种方法,Mapper对象都可以从任何映射类中获取,使用运行时检查 API 系统。使用inspect()函数,可以从映射类获取Mapper

>>> from sqlalchemy import inspect
>>> insp = inspect(User)

可用的详细信息包括Mapper.columns

>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>

这是一个可以以列表格式或单个名称查看的命名空间:

>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)

其他命名空间包括Mapper.all_orm_descriptors,其中包括所有映射属性以及混合属性,关联代理:

>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']

以及Mapper.column_attrs

>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)

另请参阅

Mapper #### Inspection of Mapped Instances

inspect()函数还提供有关映射类的实例的信息。当应用于映射类的实例时,而不是类本身时,返回的对象被称为InstanceState,它将提供链接到不仅是类使用的Mapper的详细接口,还提供有关实例内个别属性状态的信息,包括它们当前的值以及这如何与它们的数据库加载值相关联。

给定从数据库加载的User类的实例:

>>> u1 = session.scalars(select(User)).first()

inspect()函数会返回一个InstanceState对象给我们:

>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>

通过这个对象,我们可以看到诸如Mapper等元素:

>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>

对象所附加到的Session(如果有的话):

>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>

关于对象的当前 persistence state 的信息:

>>> insp.persistent
True
>>> insp.pending
False

属性状态信息,如尚未加载或延迟加载的属性(假设addresses指的是映射类上到相关类的relationship()):

>>> insp.unloaded
{'addresses'}

有关属性的当前 Python 状态的信息,例如自上次刷新以来未经修改的属性:

>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}

以及自上次刷新以来对属性进行的修改的特定历史记录:

>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])

另请参阅

InstanceState

InstanceState.attrs

AttributeState ## ORM Mapping Styles

SQLAlchemy  提供了两种不同风格的映射配置,然后进一步提供了设置它们的子选项。映射样式的可变性存在是为了适应开发者偏好的多样性,包括用户定义类与如何映射到关系模式表和列之间的抽象程度,使用的类层次结构种类,包括是否存在自定义元类方案,以及是否同时使用了其他类内部操作方法,例如是否同时使用了  Python dataclasses

在现代 SQLAlchemy 中,这些风格之间的区别主要是表面的;当使用特定的 SQLAlchemy 配置风格来表达映射类的意图时,映射类的内部映射过程在大多数情况下是相同的,最终的结果总是一个用户定义的类,该类已经针对可选择的单元(通常由一个Table对象表示)配置了一个Mapper,并且该类本身已经被 instrumented 以包括与关系操作相关的行为,既在类的级别,也在该类的实例上。由于在所有情况下该过程基本相同,从不同风格映射的类始终是完全可互操作的。当使用诸如 mypy 等类型检查器时,可以使用协议MappedClassProtocol来指示已映射的类。

最初的映射 API 通常被称为“古典”风格,而更自动化的映射风格则被称为“声明式”风格。SQLAlchemy 现在将这两种映射风格称为命令式映射声明式映射

无论使用何种映射样式,自 SQLAlchemy 1.4 起,所有 ORM 映射都源自一个名为registry的单个对象,它是一组映射类的注册表。使用此注册表,一组映射配置可以作为一个组完成,并且在配置过程中,特定注册表中的类可以通过名称相互引用。

在 1.4 版本中更改:声明式和古典映射现在被称为“声明式”和“命令式”映射,并在内部统一,所有这些都源自代表相关映射的registry 构造。


SqlAlchemy 2.0 中文文档(四)(4)https://developer.aliyun.com/article/1562997

相关文章
|
5月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(五)(5)
SqlAlchemy 2.0 中文文档(五)
64 4
|
5月前
|
SQL 测试技术 Python
SqlAlchemy 2.0 中文文档(四)(4)
SqlAlchemy 2.0 中文文档(四)
67 3
|
5月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(一)(3)
SqlAlchemy 2.0 中文文档(一)
94 1
|
5月前
|
SQL 数据库 数据库管理
SqlAlchemy 2.0 中文文档(一)(2)
SqlAlchemy 2.0 中文文档(一)
145 1
|
5月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(三)(1)
SqlAlchemy 2.0 中文文档(三)
51 1
|
5月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(十)(5)
SqlAlchemy 2.0 中文文档(十)
35 1
|
5月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(四)(2)
SqlAlchemy 2.0 中文文档(四)
58 1
|
5月前
|
测试技术 API 数据库
SqlAlchemy 2.0 中文文档(十)(4)
SqlAlchemy 2.0 中文文档(十)
74 1
|
5月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(4)
SqlAlchemy 2.0 中文文档(二)
52 2
|
5月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(2)
SqlAlchemy 2.0 中文文档(二)
122 2