使用声明式映射器配置¶
Mapper Configuration with Declarative
The section 映射类基本组件 discusses the general
configurational elements of a Mapper
construct, which is the
structure that defines how a particular user defined class is mapped to a
database table or other SQL construct. The following sections describe
specific details about how the declarative system goes about constructing
the Mapper
.
使用声明式定义映射属性¶
Defining Mapped Properties with Declarative
声明式的表配置 部分给出的示例展示了使用 mapped_column()
构造的表绑定列的映射。除了表绑定列外,还有几种其他类型的ORM映射构造可以配置,最常见的是 relationship()
构造。其他类型的属性包括使用 column_property()
构造定义的SQL表达式和使用 composite()
构造的多列映射。
虽然 命令式映射 使用 properties 字典来建立所有映射类属性,但在声明式映射中,这些属性都是在类定义中内联指定的,在声明式表映射的情况下,这些属性与将用于生成 Table
对象的 Column
对象一起内联。
使用 User
和 Address
的示例映射,我们可以说明不仅包括 mapped_column()
对象,还包括关系和SQL表达式的声明式表映射:
from typing import List
from typing import Optional
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy import Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
firstname: Mapped[str] = mapped_column(String(50))
lastname: Mapped[str] = mapped_column(String(50))
fullname: Mapped[str] = column_property(firstname + " " + lastname)
addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
__tablename__ = "address"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
email_address: Mapped[str]
address_statistics: Mapped[Optional[str]] = mapped_column(Text, deferred=True)
user: Mapped["User"] = relationship(back_populates="addresses")
上述声明式表映射具有两个表,每个表都有一个引用另一个的 relationship()
,以及由 column_property()
映射的简单SQL表达式和一个指示加载应在 “延迟(deferred)” 基础上进行的额外 mapped_column()
,由 mapped_column.deferred
关键字定义。有关这些特定概念的更多文档,请参见 基本关系模式、使用 column_property 和 使用【列延迟】限制加载哪些列。
如上所示,可以使用“混合表(hybrid table)”风格在声明式映射中指定属性;直接作为表一部分的 Column
对象移入 Table
定义,但其他所有内容,包括组合SQL表达式,仍将在类定义中内联。需要直接引用 Column
的构造将以 Table
对象的形式引用它。使用混合表样式说明上述映射:
# 使用声明式和命令式表映射属性
# 即 __table__
from sqlalchemy import Column, ForeignKey, Integer, String, Table, Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import deferred
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class User(Base):
__table__ = Table(
"user",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("name", String),
Column("firstname", String(50)),
Column("lastname", String(50)),
)
fullname = column_property(__table__.c.firstname + " " + __table__.c.lastname)
addresses = relationship("Address", back_populates="user")
class Address(Base):
__table__ = Table(
"address",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("user_id", ForeignKey("user.id")),
Column("email_address", String),
Column("address_statistics", Text),
)
address_statistics = deferred(__table__.c.address_statistics)
user = relationship("User", back_populates="addresses")
上面需要注意的事项:
地址
Table
包含一个名为address_statistics
的列,但我们在同一个属性名下重新映射该列,以便由deferred()
构造控制。对于声明式表和混合表映射,当我们定义
ForeignKey
构造时,我们总是使用 表名(table name) 命名目标表,而不是映射类名。当我们定义
relationship()
构造时,由于这些构造在映射类之间建立了链接,其中一个类必然在另一个类之前定义,我们可以使用类名字符串引用远程类。这种功能还扩展到relationship()
上指定的其他参数,如 “主连接(primary join)” 和 “排序(order by)” 参数。有关详细信息,请参见 关系参数的后期评估 部分。
The examples given at 声明式的表配置
illustrate mappings against table-bound columns, using the mapped_column()
construct. There are several other varieties of ORM mapped constructs
that may be configured besides table-bound columns, the most common being the
relationship()
construct. Other kinds of properties include
SQL expressions that are defined using the column_property()
construct and multiple-column mappings using the composite()
construct.
While an imperative mapping makes use of
the properties dictionary to establish
all the mapped class attributes, in the declarative
mapping, these properties are all specified inline with the class definition,
which in the case of a declarative table mapping are inline with the
Column
objects that will be used to generate a
Table
object.
Working with the example mapping of User
and Address
, we may illustrate
a declarative table mapping that includes not just mapped_column()
objects but also relationships and SQL expressions:
from typing import List
from typing import Optional
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy import Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
firstname: Mapped[str] = mapped_column(String(50))
lastname: Mapped[str] = mapped_column(String(50))
fullname: Mapped[str] = column_property(firstname + " " + lastname)
addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
__tablename__ = "address"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
email_address: Mapped[str]
address_statistics: Mapped[Optional[str]] = mapped_column(Text, deferred=True)
user: Mapped["User"] = relationship(back_populates="addresses")
The above declarative table mapping features two tables, each with a
relationship()
referring to the other, as well as a simple
SQL expression mapped by column_property()
, and an additional
mapped_column()
that indicates loading should be on a
“deferred” basis as defined
by the mapped_column.deferred
keyword. More documentation
on these particular concepts may be found at 基本关系模式,
使用 column_property, and 使用【列延迟】限制加载哪些列.
Properties may be specified with a declarative mapping as above using
“hybrid table” style as well; the Column
objects that
are directly part of a table move into the Table
definition
but everything else, including composed SQL expressions, would still be
inline with the class definition. Constructs that need to refer to a
Column
directly would reference it in terms of the
Table
object. To illustrate the above mapping using
hybrid table style:
# mapping attributes using declarative with imperative table
# i.e. __table__
from sqlalchemy import Column, ForeignKey, Integer, String, Table, Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import deferred
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class User(Base):
__table__ = Table(
"user",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("name", String),
Column("firstname", String(50)),
Column("lastname", String(50)),
)
fullname = column_property(__table__.c.firstname + " " + __table__.c.lastname)
addresses = relationship("Address", back_populates="user")
class Address(Base):
__table__ = Table(
"address",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("user_id", ForeignKey("user.id")),
Column("email_address", String),
Column("address_statistics", Text),
)
address_statistics = deferred(__table__.c.address_statistics)
user = relationship("User", back_populates="addresses")
Things to note above:
The address
Table
contains a column calledaddress_statistics
, however we re-map this column under the same attribute name to be under the control of adeferred()
construct.With both declararative table and hybrid table mappings, when we define a
ForeignKey
construct, we always name the target table using the table name, and not the mapped class name.When we define
relationship()
constructs, as these constructs create a linkage between two mapped classes where one necessarily is defined before the other, we can refer to the remote class using its string name. This functionality also extends into the area of other arguments specified on therelationship()
such as the “primary join” and “order by” arguments. See the section 关系参数的后期评估 for details on this.
使用声明式映射器配置选项¶
Mapper Configuration Options with Declarative
对于所有映射形式,类的映射是通过成为 Mapper
对象一部分的参数配置的。最终接收这些参数的函数是 Mapper
函数,并由定义在 registry
对象上的前端映射函数传递给它。
对于声明式映射形式,映射参数使用声明式类变量 __mapper_args__
指定,这是一个作为关键字参数传递给 Mapper
函数的字典。一些示例:
映射特定的主键列(Map Specific Primary Key Columns)
下面的示例说明了 Mapper.primary_key
参数的声明式级别设置,它独立于模式级别的主键约束,建立了ORM应作为类的主键考虑的特定列:
class GroupUsers(Base):
__tablename__ = "group_users"
user_id = mapped_column(String(40))
group_id = mapped_column(String(40))
__mapper_args__ = {"primary_key": [user_id, group_id]}
参见
映射到一组显式主键列 - 有关将显式列映射为主键列的ORM映射的更多背景
版本ID列(Version ID Column)
下面的示例说明了 Mapper.version_id_col
和 Mapper.version_id_generator
参数的声明式级别设置,这些参数配置了在 unit of work 刷新过程中更新和检查的ORM维护的版本计数器:
from datetime import datetime
class Widget(Base):
__tablename__ = "widgets"
id = mapped_column(Integer, primary_key=True)
timestamp = mapped_column(DateTime, nullable=False)
__mapper_args__ = {
"version_id_col": timestamp,
"version_id_generator": lambda v: datetime.now(),
}
参见
配置版本计数器 - 有关ORM版本计数器功能的背景
单表继承(Single Table Inheritance)
下面的示例说明了 Mapper.polymorphic_on
和 Mapper.polymorphic_identity
参数的声明式级别设置,这些参数在配置单表继承映射时使用:
class Person(Base):
__tablename__ = "person"
person_id = mapped_column(Integer, primary_key=True)
type = mapped_column(String, nullable=False)
__mapper_args__ = dict(
polymorphic_on=type,
polymorphic_identity="person",
)
class Employee(Person):
__mapper_args__ = dict(
polymorphic_identity="employee",
)
参见
单表继承 - 有关ORM单表继承映射功能的背景。
With all mapping forms, the mapping of the class is configured through
parameters that become part of the Mapper
object.
The function which ultimately receives these arguments is the
Mapper
function, and are delivered to it from one of
the front-facing mapping functions defined on the registry
object.
For the declarative form of mapping, mapper arguments are specified
using the __mapper_args__
declarative class variable, which is a dictionary
that is passed as keyword arguments to the Mapper
function.
Some examples:
Map Specific Primary Key Columns
The example below illustrates Declarative-level settings for the
Mapper.primary_key
parameter, which establishes
particular columns as part of what the ORM should consider to be a primary
key for the class, independently of schema-level primary key constraints:
class GroupUsers(Base):
__tablename__ = "group_users"
user_id = mapped_column(String(40))
group_id = mapped_column(String(40))
__mapper_args__ = {"primary_key": [user_id, group_id]}
参见
映射到一组显式主键列 - further background on ORM mapping of explicit columns as primary key columns
Version ID Column
The example below illustrates Declarative-level settings for the
Mapper.version_id_col
and
Mapper.version_id_generator
parameters, which configure
an ORM-maintained version counter that is updated and checked within the
unit of work flush process:
from datetime import datetime
class Widget(Base):
__tablename__ = "widgets"
id = mapped_column(Integer, primary_key=True)
timestamp = mapped_column(DateTime, nullable=False)
__mapper_args__ = {
"version_id_col": timestamp,
"version_id_generator": lambda v: datetime.now(),
}
参见
配置版本计数器 - background on the ORM version counter feature
Single Table Inheritance
The example below illustrates Declarative-level settings for the
Mapper.polymorphic_on
and
Mapper.polymorphic_identity
parameters, which are used when
configuring a single-table inheritance mapping:
class Person(Base):
__tablename__ = "person"
person_id = mapped_column(Integer, primary_key=True)
type = mapped_column(String, nullable=False)
__mapper_args__ = dict(
polymorphic_on=type,
polymorphic_identity="person",
)
class Employee(Person):
__mapper_args__ = dict(
polymorphic_identity="employee",
)
参见
单表继承 - background on the ORM single table inheritance mapping feature.
动态构造映射器参数¶
Constructing mapper arguments dynamically
__mapper_args__
字典可以通过使用 declared_attr()
构造从类绑定的描述符方法生成,而不是从固定字典生成。这对于创建从表配置或映射类的其他方面程序生成的映射器参数非常有用。当使用声明式混入或抽象基类时,动态 __mapper_args__
属性通常很有用。
例如,为了从映射中省略具有特殊 Column.info
值的任何列,可以使用 __mapper_args__
方法从 cls.__table__
属性扫描这些列并将它们传递给 Mapper.exclude_properties
集合:
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
class ExcludeColsWFlag:
@declared_attr
def __mapper_args__(cls):
return {
"exclude_properties": [
column.key
for column in cls.__table__.c
if column.info.get("exclude", False)
]
}
class Base(DeclarativeBase):
pass
class SomeClass(ExcludeColsWFlag, Base):
__tablename__ = "some_table"
id = mapped_column(Integer, primary_key=True)
data = mapped_column(String)
not_needed = mapped_column(String, info={"exclude": True})
上面, ExcludeColsWFlag
混入提供了一个每类的 __mapper_args__
钩子,
将扫描包含传递给 Column.info
参数的键/值 'exclude': True
的 Column
对象,然后将它们的字符串“key”名称添加到 Mapper.exclude_properties
集合中,从而防止生成的 Mapper
将这些列考虑在任何SQL操作中。
The __mapper_args__
dictionary may be generated from a class-bound
descriptor method rather than from a fixed dictionary by making use of the
declared_attr()
construct. This is useful to create arguments
for mappers that are programmatically derived from the table configuration
or other aspects of the mapped class. A dynamic __mapper_args__
attribute will typically be useful when using a Declarative Mixin or
abstract base class.
For example, to omit from the mapping
any columns that have a special Column.info
value, a mixin
can use a __mapper_args__
method that scans for these columns from the
cls.__table__
attribute and passes them to the Mapper.exclude_properties
collection:
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
class ExcludeColsWFlag:
@declared_attr
def __mapper_args__(cls):
return {
"exclude_properties": [
column.key
for column in cls.__table__.c
if column.info.get("exclude", False)
]
}
class Base(DeclarativeBase):
pass
class SomeClass(ExcludeColsWFlag, Base):
__tablename__ = "some_table"
id = mapped_column(Integer, primary_key=True)
data = mapped_column(String)
not_needed = mapped_column(String, info={"exclude": True})
Above, the ExcludeColsWFlag
mixin provides a per-class __mapper_args__
hook that will scan for Column
objects that include the key/value
'exclude': True
passed to the Column.info
parameter, and then
add their string “key” name to the Mapper.exclude_properties
collection which will prevent the resulting Mapper
from considering
these columns for any SQL operations.
其他声明式映射指令¶
Other Declarative Mapping Directives
__declare_last__()
¶
__declare_last__()
钩子允许定义一个类级别的函数,该函数由 MapperEvents.after_configured()
事件自动调用,该事件在映射假定完成并且“配置(configure)”步骤完成后发生:
class MyClass(Base):
@classmethod
def __declare_last__(cls):
""" """
# do something with mappings
The __declare_last__()
hook allows definition of
a class level function that is automatically called by the
MapperEvents.after_configured()
event, which occurs after mappings are
assumed to be completed and the ‘configure’ step has finished:
class MyClass(Base):
@classmethod
def __declare_last__(cls):
""" """
# do something with mappings
__declare_first__()
¶
与 __declare_last__()
类似,但在映射器配置开始时通过 MapperEvents.before_configured()
事件调用:
class MyClass(Base):
@classmethod
def __declare_first__(cls):
""" """
# do something before mappings are configured
Like __declare_last__()
, but is called at the beginning of mapper
configuration via the MapperEvents.before_configured()
event:
class MyClass(Base):
@classmethod
def __declare_first__(cls):
""" """
# do something before mappings are configured
metadata
¶
MetaData
集合通常用于分配新的 Table
,它是与使用中的 registry
对象关联的 registry.metadata
属性。当使用声明式基类(例如由 DeclarativeBase
超类生成的基类)以及遗留函数(例如 declarative_base()
和 registry.generate_base()
)时,此 MetaData
通常也是直接在基类上的名为 .metadata
的属性,因此也通过继承在映射类上。声明式使用此属性(如果存在)来确定目标 MetaData
集合,如果不存在,则使用直接与 registry
关联的 MetaData
。
此属性还可以在每个映射层次结构基础上分配,以影响单个基类和/或 registry
上使用的 MetaData
集合。这无论是使用声明式基类还是直接使用 registry.mapped()
装饰器都有效,从而允许模式,如下一节中的每个抽象基类的元数据示例,__abstract__ 。可以使用 registry.mapped()
来展示类似的模式,如下所示:
reg = registry()
class BaseOne:
metadata = MetaData()
class BaseTwo:
metadata = MetaData()
@reg.mapped
class ClassOne:
__tablename__ = "t1" # 将使用 reg.metadata
id = mapped_column(Integer, primary_key=True)
@reg.mapped
class ClassTwo(BaseOne):
__tablename__ = "t1" # 将使用 BaseOne.metadata
id = mapped_column(Integer, primary_key=True)
@reg.mapped
class ClassThree(BaseTwo):
__tablename__ = "t1" # 将使用 BaseTwo.metadata
id = mapped_column(Integer, primary_key=True)
参见
The MetaData
collection normally used to assign a new
Table
is the registry.metadata
attribute
associated with the registry
object in use. When using a
declarative base class such as that produced by the
DeclarativeBase
superclass, as well as legacy functions such as
declarative_base()
and registry.generate_base()
, this
MetaData
is also normally present as an attribute named
.metadata
that’s directly on the base class, and thus also on the mapped
class via inheritance. Declarative uses this attribute, when present, in order
to determine the target MetaData
collection, or if not
present, uses the MetaData
associated directly with the
registry
.
This attribute may also be assigned towards in order to affect the
MetaData
collection to be used on a per-mapped-hierarchy basis
for a single base and/or registry
. This takes effect whether a
declarative base class is used or if the registry.mapped()
decorator
is used directly, thus allowing patterns such as the metadata-per-abstract base
example in the next section, __abstract__. A similar pattern can
be illustrated using registry.mapped()
as follows:
reg = registry()
class BaseOne:
metadata = MetaData()
class BaseTwo:
metadata = MetaData()
@reg.mapped
class ClassOne:
__tablename__ = "t1" # will use reg.metadata
id = mapped_column(Integer, primary_key=True)
@reg.mapped
class ClassTwo(BaseOne):
__tablename__ = "t1" # will use BaseOne.metadata
id = mapped_column(Integer, primary_key=True)
@reg.mapped
class ClassThree(BaseTwo):
__tablename__ = "t1" # will use BaseTwo.metadata
id = mapped_column(Integer, primary_key=True)
参见
__abstract__
¶
__abstract__
使声明式完全跳过为类生成表或映射器的过程。可以在层次结构中以与混入相同的方式添加类(见 Mixin and Custom Base Classes),允许子类仅从特殊类扩展:
class SomeAbstractBase(Base):
__abstract__ = True
def some_helpful_method(self):
""" """
@declared_attr
def __mapper_args__(cls):
return {"helpful mapper arguments": True}
class MyMappedClass(SomeAbstractBase):
pass
__abstract__
的一种可能用途是为不同的基类使用不同的 MetaData
:
class Base(DeclarativeBase):
pass
class DefaultBase(Base):
__abstract__ = True
metadata = MetaData()
class OtherBase(Base):
__abstract__ = True
metadata = MetaData()
如上所示,继承自 DefaultBase
的类将使用一个 MetaData
作为表的注册表,而继承自 OtherBase
的类将使用另一个。然后,这些表本身可以在不同的数据库中创建:
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)
参见
使用 polymorphic_abstract 构建更深层次的层次结构 - 适用于继承层次结构的“抽象(abstract)”映射类的替代形式。
__abstract__
causes declarative to skip the production
of a table or mapper for the class entirely. A class can be added within a
hierarchy in the same way as mixin (see Mixin and Custom Base Classes), allowing
subclasses to extend just from the special class:
class SomeAbstractBase(Base):
__abstract__ = True
def some_helpful_method(self):
""" """
@declared_attr
def __mapper_args__(cls):
return {"helpful mapper arguments": True}
class MyMappedClass(SomeAbstractBase):
pass
One possible use of __abstract__
is to use a distinct
MetaData
for different bases:
class Base(DeclarativeBase):
pass
class DefaultBase(Base):
__abstract__ = True
metadata = MetaData()
class OtherBase(Base):
__abstract__ = True
metadata = MetaData()
Above, classes which inherit from DefaultBase
will use one
MetaData
as the registry of tables, and those which inherit from
OtherBase
will use a different one. The tables themselves can then be
created perhaps within distinct databases:
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)
参见
使用 polymorphic_abstract 构建更深层次的层次结构 - an alternative form of “abstract” mapped class that is appropriate for inheritance hierarchies.
__table_cls__
¶
允许用于生成 Table
的可调用对象/类进行自定义。
这是一个非常开放的钩子,可以允许对这里生成的 Table
进行特殊定制:
class MyMixin:
@classmethod
def __table_cls__(cls, name, metadata_obj, *arg, **kw):
return Table(f"my_{name}", metadata_obj, *arg, **kw)
上述混入将导致所有生成的 Table
对象包含前缀 "my_"
,后跟使用 __tablename__
属性通常指定的名称。
__table_cls__
还支持返回 None
的情况,这导致类被视为单表继承与其子类。这在某些定制方案中可能有用,以基于表本身的参数确定应进行单表继承,例如,如果没有主键存在,则定义为单继承:
class AutoTable:
@declared_attr
def __tablename__(cls):
return cls.__name__
@classmethod
def __table_cls__(cls, *arg, **kw):
for obj in arg[1:]:
if (isinstance(obj, Column) and obj.primary_key) or isinstance(
obj, PrimaryKeyConstraint
):
return Table(*arg, **kw)
return None
class Person(AutoTable, Base):
id = mapped_column(Integer, primary_key=True)
class Employee(Person):
employee_name = mapped_column(String)
上述 Employee
类将被映射为针对 Person
的单表继承; employee_name
列将作为 Person
表的成员添加。
Allows the callable / class used to generate a Table
to be customized.
This is a very open-ended hook that can allow special customizations
to a Table
that one generates here:
class MyMixin:
@classmethod
def __table_cls__(cls, name, metadata_obj, *arg, **kw):
return Table(f"my_{name}", metadata_obj, *arg, **kw)
The above mixin would cause all Table
objects generated to include
the prefix "my_"
, followed by the name normally specified using the
__tablename__
attribute.
__table_cls__
also supports the case of returning None
, which
causes the class to be considered as single-table inheritance vs. its subclass.
This may be useful in some customization schemes to determine that single-table
inheritance should take place based on the arguments for the table itself,
such as, define as single-inheritance if there is no primary key present:
class AutoTable:
@declared_attr
def __tablename__(cls):
return cls.__name__
@classmethod
def __table_cls__(cls, *arg, **kw):
for obj in arg[1:]:
if (isinstance(obj, Column) and obj.primary_key) or isinstance(
obj, PrimaryKeyConstraint
):
return Table(*arg, **kw)
return None
class Person(AutoTable, Base):
id = mapped_column(Integer, primary_key=True)
class Employee(Person):
employee_name = mapped_column(String)
The above Employee
class would be mapped as single-table inheritance
against Person
; the employee_name
column would be added as a member
of the Person
table.