ORM 映射类概述¶
ORM Mapped Class Overview
ORM类映射配置概述。
对于SQLAlchemy ORM和/或Python新手来说,建议浏览 ORM 快速入门,最好是通读 SQLAlchemy 统一教程,其中在 使用 ORM 声明形式定义表元数据 首次介绍了ORM配置。
Overview of ORM class mapping configuration.
For readers new to the SQLAlchemy ORM and/or new to Python in general, it’s recommended to browse through the ORM 快速入门 and preferably to work through the SQLAlchemy 统一教程, where ORM configuration is first introduced at 使用 ORM 声明形式定义表元数据.
ORM 映射风格¶
ORM Mapping Styles
SQLAlchemy具有两种截然不同的映射配置风格,这些风格又进一步细分为不同的设置选项。映射风格的多样性是为了适应各种开发者的偏好,包括用户定义类与其映射到关系模式表和列的抽象程度、使用的类层次结构类型(包括是否存在自定义元类方案),以及是否存在其他类仪器化方法,例如是否同时使用Python数据类。
在现代SQLAlchemy中,这些风格之间的差异主要是表面的;当使用特定的SQLAlchemy配置风格来表示映射类的意图时,映射类的内部过程在每种情况下大致相同,其最终结果总是一个用户定义的类,该类具有一个配置为可选择单元的 Mapper
,通常由 Table
对象表示,类本身已被 仪器化 (instrumented) 以包括与关系操作相关的行为,无论是在类的级别还是在该类的实例上。由于过程在所有情况下基本相同,因此从不同风格映射的类始终可以完全互操作。协议 MappedClassProtocol
可用于在使用类型检查器(如mypy)时指示映射类。
最初的映射API通常被称为“经典”风格,而更自动化的映射风格称为“声明式”风格。SQLAlchemy现在将这两种映射风格称为 命令式映射(imperative mapping) 和 声明式映射(declarative mapping) 。
无论使用何种映射风格,自SQLAlchemy 1.4起,所有ORM映射都源自一个名为 registry
的单一对象,这是一个映射类的注册表。使用此注册表,可以作为一个组来最终确定一组映射器配置,并且特定注册表中的类可以在配置过程中按名称相互引用。
在 1.4 版本发生变更: 声明式映射和经典映射现在被称为“声明式(declarative)”和“命令式(imperative)”映射,并在内部统一,所有映射都源自表示相关映射集合的 registry
构造。
SQLAlchemy features two distinct styles of mapper configuration, which then feature further sub-options for how they are set up. The variability in mapper styles is present to suit a varied list of developer preferences, including the degree of abstraction of a user-defined class from how it is to be mapped to relational schema tables and columns, what kinds of class hierarchies are in use, including whether or not custom metaclass schemes are present, and finally if there are other class-instrumentation approaches present such as if Python dataclasses are in use simultaneously.
In modern SQLAlchemy, the difference between these styles is mostly
superficial; when a particular SQLAlchemy configurational style is used to
express the intent to map a class, the internal process of mapping the class
proceeds in mostly the same way for each, where the end result is always a
user-defined class that has a Mapper
configured against a
selectable unit, typically represented by a Table
object, and
the class itself has been instrumented to include behaviors linked to
relational operations both at the level of the class as well as on instances of
that class. As the process is basically the same in all cases, classes mapped
from different styles are always fully interoperable with each other.
The protocol MappedClassProtocol
can be used to indicate a mapped
class when using type checkers such as mypy.
The original mapping API is commonly referred to as “classical” style, whereas the more automated style of mapping is known as “declarative” style. SQLAlchemy now refers to these two mapping styles as imperative mapping and declarative mapping.
Regardless of what style of mapping used, all ORM mappings as of SQLAlchemy 1.4
originate from a single object known as registry
, which is a
registry of mapped classes. Using this registry, a set of mapper configurations
can be finalized as a group, and classes within a particular registry may refer
to each other by name within the configurational process.
在 1.4 版本发生变更: Declarative and classical mapping are now referred
to as “declarative” and “imperative” mapping, and are unified internally,
all originating from the registry
construct that represents
a collection of related mappings.
声明式映射¶
Declarative Mapping
声明式映射 是现代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
# 声明基类
class Base(DeclarativeBase):
pass
# 使用基类的示例映射
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 Declarative Models 了解迁移说明。
基类指的是一个 registry
对象,该对象维护一组相关的映射类集合,以及一个保留类映射到的 Table
对象集合的 MetaData
对象。
主要的声明式映射风格在以下部分中有进一步详细介绍:
使用声明性基类 - 使用基类的声明式映射。
使用装饰器进行声明性映射(无声明性基类) - 使用装饰器而不是基类的声明式映射。
在声明式映射类的范围内,也有两种声明 Table
元数据的方式。这些包括:
带有 mapped_column() 的声明表 - 使用
mapped_column()
指令在映射类中内联声明表列(或在遗留形式中,直接使用Column
对象)。mapped_column()
指令还可以选择性地与使用Mapped
类的类型注释结合使用,以直接提供有关映射列的一些详细信息。列指令与__tablename__
和可选的__table_args__
类级别指令结合使用,将允许声明式映射过程构建一个要映射的Table
对象。使用命令式表的声明式(又名混合声明式) - 不单独指定表名和属性,而是将一个显式构造的
Table
对象与一个其他方面声明式映射的类关联。这种映射风格是“声明式”和“命令式”映射的混合体,适用于将类映射到 反射的Table
对象以及将类映射到现有Core构造(如连接和子查询)的技术。
声明式映射的文档在 使用声明式映射类 继续。
The Declarative Mapping is the typical way that mappings are constructed in
modern SQLAlchemy. The most common pattern is to first construct a base class
using the DeclarativeBase
superclass. The resulting base class,
when subclassed will apply the declarative mapping process to all subclasses
that derive from it, relative to a particular registry
that
is local to the new base by default. The example below illustrates
the use of a declarative base which is then used in a declarative table mapping:
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]]
Above, the DeclarativeBase
class is used to generate a new
base class (within SQLAlchemy’s documentation it’s typically referred to
as Base
, however can have any desired name) from
which new classes to be mapped may inherit from, as above a new mapped
class User
is constructed.
在 2.0 版本发生变更: The DeclarativeBase
superclass supersedes
the use of the declarative_base()
function and
registry.generate_base()
methods; the superclass approach
integrates with PEP 484 tools without the use of plugins.
See ORM Declarative Models for migration notes.
The base class refers to a registry
object that maintains a
collection of related mapped classes. as well as to a MetaData
object that retains a collection of Table
objects to which
the classes are mapped.
The major Declarative mapping styles are further detailed in the following sections:
使用声明性基类 - declarative mapping using a base class.
使用装饰器进行声明性映射(无声明性基类) - declarative mapping using a decorator, rather than a base class.
Within the scope of a Declarative mapped class, there are also two varieties
of how the Table
metadata may be declared. These include:
带有 mapped_column() 的声明表 - table columns are declared inline within the mapped class using the
mapped_column()
directive (or in legacy form, using theColumn
object directly). Themapped_column()
directive may also be optionally combined with type annotations using theMapped
class which can provide some details about the mapped columns directly. The column directives, in combination with the__tablename__
and optional__table_args__
class level directives will allow the Declarative mapping process to construct aTable
object to be mapped.使用命令式表的声明式(又名混合声明式) - Instead of specifying table name and attributes separately, an explicitly constructed
Table
object is associated with a class that is otherwise mapped declaratively. This style of mapping is a hybrid of “declarative” and “imperative” mapping, and applies to techniques such as mapping classes to reflectedTable
objects, as well as mapping classes to existing Core constructs such as joins and subqueries.
Documentation for Declarative mapping continues at 使用声明式映射类.
命令式映射¶
Imperative Mapping
命令式 或 经典 映射指的是使用 registry.map_imperatively()
方法配置映射类,其中目标类不包含任何声明式类属性。
小技巧
命令式映射形式是一种较少使用的映射形式,起源于2006年SQLAlchemy的最早版本。它本质上是一种绕过声明系统的方法,提供了一种更“简陋”的映射系统,并且不提供现代功能,如 PEP 484 支持。因此,大多数文档示例使用声明式形式,建议新用户从 Declarative Table 配置开始。
在 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)
注意,使用命令式方法映射的类与声明式方法映射的类是 完全可互换(fully interchangeable) 的。两种系统最终创建相同的配置,包括一个 Table
,用户定义的类,与 Mapper
对象链接在一起。当我们谈论 Mapper
的行为时,这也包括使用声明式系统时——它仍然在使用,只是在幕后进行。
An imperative or classical mapping refers to the configuration of a
mapped class using the registry.map_imperatively()
method,
where the target class does not include any declarative class attributes.
小技巧
The imperative mapping form is a lesser-used form of mapping that originates from the very first releases of SQLAlchemy in 2006. It’s essentially a means of bypassing the Declarative system to provide a more “barebones” system of mapping, and does not offer modern features such as PEP 484 support. As such, most documentation examples use Declarative forms, and it’s recommended that new users start with Declarative Table configuration.
在 2.0 版本发生变更: The registry.map_imperatively()
method
is now used to create classical mappings. The sqlalchemy.orm.mapper()
standalone function is effectively removed.
In “classical” form, the table metadata is created separately with the
Table
construct, then associated with the User
class via
the registry.map_imperatively()
method, after establishing
a registry
instance. Normally, a single instance of
registry
shared for all mapped classes that are related to each other:
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)
Information about mapped attributes, such as relationships to other classes, are provided
via the properties
dictionary. The example below illustrates a second Table
object, mapped to a class called Address
, then linked to User
via relationship()
:
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)
Note that classes which are mapped with the Imperative approach are fully
interchangeable with those mapped with the Declarative approach. Both systems
ultimately create the same configuration, consisting of a
Table
, user-defined class, linked together with a
Mapper
object. When we talk about “the behavior of
Mapper
”, this includes when using the Declarative system as well
- it’s still used, just behind the scenes.
映射类基本组件¶
Mapped Class Essential Components
在所有映射形式中,通过传递构造参数可以以多种方式配置类的映射,这些参数最终成为 Mapper
对象的一部分,通过其构造函数传递。传递给 Mapper
的参数源自给定的映射形式,包括传递给命令式映射的 registry.map_imperatively()
的参数,或者在使用声明式系统时,来自表列、SQL表达式和映射关系以及诸如 __mapper_args__ 之类的属性的组合。
Mapper
类寻找四类通用的配置信息:
With all mapping forms, the mapping of the class can be configured in many ways
by passing construction arguments that ultimately become part of the Mapper
object via its constructor. The parameters that are delivered to
Mapper
originate from the given mapping form, including
parameters passed to registry.map_imperatively()
for an Imperative
mapping, or when using the Declarative system, from a combination
of the table columns, SQL expressions and
relationships being mapped along with that of attributes such as
__mapper_args__.
There are four general classes of configuration information that the
Mapper
class looks for:
要映射的类¶
The class to be mapped
这是我们在应用程序中构建的一个类。
通常对该类的结构没有限制。 [1]
当一个Python类被映射时,该类只能有 一个 Mapper
对象。 [2]
使用 声明式 映射风格进行映射时,要映射的类要么是声明基类的子类,要么由装饰器或函数(如 registry.mapped()
)处理。
使用 命令式 风格进行映射时,该类直接作为 map_imperatively.class_
参数传递。
This is a class that we construct in our application.
There are generally no restrictions on the structure of this class. [3]
When a Python class is mapped, there can only be one Mapper
object for the class. [4]
When mapping with the declarative mapping
style, the class to be mapped is either a subclass of the declarative base class,
or is handled by a decorator or function such as registry.mapped()
.
When mapping with the imperative style, the
class is passed directly as the
map_imperatively.class_
argument.
表或其他 from 子句对象¶
The table, or other from clause object
在绝大多数常见情况下,这是 Table
的一个实例。对于更高级的用例,它也可以引用任何类型的 FromClause
对象,最常见的替代对象是 Subquery
和 Join
对象。
使用 声明式 映射风格进行映射时,目标表要么由声明系统基于 __tablename__
属性和呈现的 Column
对象生成,要么通过 __table__
属性建立。这两种配置风格在 带有 mapped_column() 的声明表 和 使用命令式表的声明式(又名混合声明式) 中介绍。
使用 命令式 风格进行映射时,目标表作为 map_imperatively.local_table
参数按位置传递。
与映射类的“每个类一个映射器”要求相反,作为映射对象的 Table
或其他 FromClause
对象可以与任意数量的映射关联。Mapper
直接对用户定义的类进行修改,但不会以任何方式修改给定的 Table
或其他 FromClause
。
In the vast majority of common cases this is an instance of
Table
. For more advanced use cases, it may also refer
to any kind of FromClause
object, the most common
alternative objects being the Subquery
and Join
object.
When mapping with the declarative mapping
style, the subject table is either generated by the declarative system based
on the __tablename__
attribute and the Column
objects
presented, or it is established via the __table__
attribute. These
two styles of configuration are presented at
带有 mapped_column() 的声明表 and 使用命令式表的声明式(又名混合声明式).
When mapping with the imperative style, the
subject table is passed positionally as the
map_imperatively.local_table
argument.
In contrast to the “one mapper per class” requirement of a mapped class,
the Table
or other FromClause
object that
is the subject of the mapping may be associated with any number of mappings.
The Mapper
applies modifications directly to the user-defined
class, but does not modify the given Table
or other
FromClause
in any way.
属性字典¶
The properties dictionary
这是将与映射类关联的所有属性的字典。默认情况下,Mapper
生成从给定 Table
派生的字典条目,以 ColumnProperty
对象的形式,每个对象引用映射表的单个 Column
。属性字典还将包含所有其他类型的 MapperProperty
对象,最常见的是由 relationship()
构造生成的实例。
使用 声明式 映射风格进行映射时,属性字典由声明系统通过扫描要映射的类以查找适当的属性生成。有关此过程的说明,请参见 使用声明式定义映射属性 部分。
使用 命令式 风格进行映射时,属性字典直接作为 properties
参数传递给 registry.map_imperatively()
,后者将其传递给 Mapper.properties
参数。
This is a dictionary of all of the attributes
that will be associated with the mapped class. By default, the
Mapper
generates entries for this dictionary derived from the
given Table
, in the form of ColumnProperty
objects which each refer to an individual Column
of the
mapped table. The properties dictionary will also contain all the other
kinds of MapperProperty
objects to be configured, most
commonly instances generated by the relationship()
construct.
When mapping with the declarative mapping style, the properties dictionary is generated by the declarative system by scanning the class to be mapped for appropriate attributes. See the section 使用声明式定义映射属性 for notes on this process.
When mapping with the imperative style, the
properties dictionary is passed directly as the
properties
parameter
to registry.map_imperatively()
, which will pass it along to the
Mapper.properties
parameter.
其他映射器配置参数¶
Other mapper configuration parameters
使用 声明式 映射风格进行映射时,通过 __mapper_args__
类属性配置其他映射器配置参数。使用示例可参见 使用声明式映射器配置选项。
使用 命令式 风格进行映射时,关键字参数传递给 registry.map_imperatively()
方法,该方法将它们传递给 Mapper
类。
接受的全部参数范围记录在 Mapper
中。
When mapping with the declarative mapping
style, additional mapper configuration arguments are configured via the
__mapper_args__
class attribute. Examples of use are available
at 使用声明式映射器配置选项.
When mapping with the imperative style,
keyword arguments are passed to the to registry.map_imperatively()
method which passes them along to the Mapper
class.
The full range of parameters accepted are documented at Mapper
.
映射类行为¶
Mapped Class Behavior
在使用 registry
对象的所有映射样式中,以下行为很常见:
Across all styles of mapping using the registry
object, the following behaviors are common:
默认构造函数¶
Default Constructor
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")
小技巧
声明式Dataclass映射 功能通过使用Python数据类提供了一种生成默认 __init__()
方法的替代方法,并允许高度可配置的构造函数形式。
警告
类的 __init__()
方法仅在对象在Python代码中构造时调用, 而不是在从数据库加载或刷新对象时调用 。有关如何在加载对象时调用特殊逻辑的入门知识,请参见下一节 在加载过程中维护非映射状态。
包含显式 __init__()
方法的类将保留该方法,并且不会应用默认构造函数。
要更改使用的默认构造函数,可以将用户定义的Python可调用对象提供给 registry.constructor
参数,该参数将用作默认构造函数。
构造函数还适用于命令式映射:
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()
方法映射时。
The registry
applies a default constructor, i.e. __init__
method, to all mapped classes that don’t explicitly have their own
__init__
method. The behavior of this method is such that it provides
a convenient keyword constructor that will accept as optional keyword arguments
all the attributes that are named. E.g.:
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]
An object of type User
above will have a constructor which allows
User
objects to be created as:
u1 = User(name="some name", fullname="some fullname")
小技巧
The 声明式Dataclass映射 feature provides an alternate
means of generating a default __init__()
method by using
Python dataclasses, and allows for a highly configurable constructor
form.
警告
The __init__()
method of the class is called only when the object is
constructed in Python code, and not when an object is loaded or refreshed
from the database. See the next section 在加载过程中维护非映射状态
for a primer on how to invoke special logic when objects are loaded.
A class that includes an explicit __init__()
method will maintain
that method, and no default constructor will be applied.
To change the default constructor used, a user-defined Python callable may be
provided to the registry.constructor
parameter which will be
used as the default constructor.
The constructor also applies to imperative mappings:
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)
The above class, mapped imperatively as described at 命令式映射,
will also feature the default constructor associated with the registry
.
在 1.4 版本加入: classical mappings now support a standard configuration-level
constructor when they are mapped via the registry.map_imperatively()
method.
在加载过程中维护非映射状态¶
Maintaining Non-Mapped State Across Loads
当映射类的``__init__()``方法在Python代码中直接构造对象时调用:
u1 = User(name="some name", fullname="some fullname")
但是,当使用ORM Session`加载对象时, ``__init__()`
方法 不会 被调用:
u1 = session.scalars(select(User).where(User.name == "some name")).first()
这是因为从数据库加载时,用于构造对象的操作(在上述示例中为 User
)更类似于 反序列化 ,例如解包,而不是初始构造。对象的大多数重要状态不是第一次组装,而是从数据库行重新加载。
因此,为了在对象内维护不属于存储到数据库的数据的状态,使得在加载和构造对象时都存在这种状态,下面详细介绍了两种通用方法。
使用Python描述符如
@property
,而不是状态,按需动态计算属性。对于简单属性,这是最简单且最不易出错的方法。例如,如果对象
Point
具有Point.x
和Point.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
使用动态描述符的优点是每次都计算值,这意味着它保持正确的值,因为基础属性(在这种情况下为
x
和y
)可能会更改。以上模式的其他形式包括Python标准库 cached_property 装饰器(它是缓存的,并且每次都不重新计算),以及SQLAlchemy的
hybrid_property
装饰器,允许可以用于SQL查询的属性。使用
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如果还使用刷新事件,可以根据需要将事件钩子堆叠在一个可调用对象之上,如:
@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上面,
attrs
属性将出现在refresh
和refresh_flush
事件中,并指示正在刷新的属性名称列表。
The __init__()
method of the mapped class is invoked when the object
is constructed directly in Python code:
u1 = User(name="some name", fullname="some fullname")
However, when an object is loaded using the ORM Session
,
the __init__()
method is not called:
u1 = session.scalars(select(User).where(User.name == "some name")).first()
The reason for this is that when loaded from the database, the operation
used to construct the object, in the above example the User
, is more
analogous to deserialization, such as unpickling, rather than initial
construction. The majority of the object’s important state is not being
assembled for the first time, it’s being re-loaded from database rows.
Therefore to maintain state within the object that is not part of the data that’s stored to the database, such that this state is present when objects are loaded as well as constructed, there are two general approaches detailed below.
Use Python descriptors like
@property
, rather than state, to dynamically compute attributes as needed.For simple attributes, this is the simplest approach and the least error prone. For example if an object
Point
withPoint.x
andPoint.y
wanted an attribute with the sum of these attributes: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
An advantage of using dynamic descriptors is that the value is computed every time, meaning it maintains the correct value as the underlying attributes (
x
andy
in this case) might change.Other forms of the above pattern include Python standard library cached_property decorator (which is cached, and not re-computed each time), as well as SQLAlchemy’s
hybrid_property
decorator which allows for attributes that can work for SQL querying as well.Establish state on-load using
InstanceEvents.load()
, and optionally supplemental methodsInstanceEvents.refresh()
andInstanceEvents.refresh_flush()
.These are event hooks that are invoked whenever the object is loaded from the database, or when it is refreshed after being expired. Typically only the
InstanceEvents.load()
is needed, since non-mapped local object state is not affected by expiration operations. To revise thePoint
example above looks like: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
If using the refresh events as well, the event hooks can be stacked on top of one callable if needed, as:
@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
Above, the
attrs
attribute will be present for therefresh
andrefresh_flush
events and indicate a list of attribute names that are being refreshed.
映射类、实例和映射器的运行时自检¶
Runtime Introspection of Mapped classes, Instances and Mappers
使用 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
属性时返回的内容:table = inspect(User).local_table
对于单表继承映射,其中类是没有自己表的子类,
Mapper.local_table
属性以及.__table__
属性将为None
。要检索在查询此类时实际选择的“可选择对象”,可以通过Mapper.selectable
属性获得:table = inspect(User).selectable
A class that is mapped using
registry
will also feature a few attributes that are common to all mappings:
The
__mapper__
attribute will refer to theMapper
that is associated with the class:mapper = User.__mapper__This
Mapper
is also what’s returned when using theinspect()
function against the mapped class:from sqlalchemy import inspect mapper = inspect(User)The
__table__
attribute will refer to theTable
, or more generically to theFromClause
object, to which the class is mapped:table = User.__table__This
FromClause
is also what’s returned when using theMapper.local_table
attribute of theMapper
:table = inspect(User).local_tableFor a single-table inheritance mapping, where the class is a subclass that does not have a table of its own, the
Mapper.local_table
attribute as well as the.__table__
attribute will beNone
. To retrieve the “selectable” that is actually selected from during a query for this class, this is available via theMapper.selectable
attribute:table = inspect(User).selectable
映射器对象的检查¶
Inspection of Mapper objects
如前一节所述,可以使用 运行时审查 API 系统从任何映射类(无论方法如何)获取 Mapper
对象。使用 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']
>>> 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>)
参见
As illustrated in the previous section, the Mapper
object is
available from any mapped class, regardless of method, using the
运行时审查 API system. Using the
inspect()
function, one can acquire the Mapper
from a
mapped class:
>>> from sqlalchemy import inspect
>>> insp = inspect(User)
Detailed information is available including Mapper.columns
:
>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>
This is a namespace that can be viewed in a list format or via individual names:
>>> 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>)
Other namespaces include Mapper.all_orm_descriptors
, which includes all mapped
attributes as well as hybrids, association proxies:
>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']
As well as 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>)
参见
映射实例的检查¶
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>
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>
有关对象当前 persistence state 的信息:
>>> insp.persistent
True
>>> insp.pending
False
属性状态信息,如尚未加载或 lazy loaded 的属性(假设 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'])
The inspect()
function also provides information about instances
of a mapped class. When applied to an instance of a mapped class, rather
than the class itself, the object returned is known as InstanceState
,
which will provide links to not only the Mapper
in use by the
class, but also a detailed interface that provides information on the state
of individual attributes within the instance including their current value
and how this relates to what their database-loaded value is.
Given an instance of the User
class loaded from the database:
>>> u1 = session.scalars(select(User)).first()
The inspect()
function will return to us an InstanceState
object:
>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>
With this object we can see elements such as the Mapper
:
>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>
The Session
to which the object is attached, if any:
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>
Information about the current persistence state for the object:
>>> insp.persistent
True
>>> insp.pending
False
Attribute state information such as attributes that have not been loaded or
lazy loaded (assume addresses
refers to a relationship()
on the mapped class to a related class):
>>> insp.unloaded
{'addresses'}
Information regarding the current in-Python status of attributes, such as attributes that have not been modified since the last flush:
>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}
as well as specific history on modifications to attributes since the last flush:
>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])