映射 Python 类
SQLAlchemy历史上有两种不同风格的映射器配置。原始的映射API通常称为“经典”样式,而更自动化的映射样式称为“声明式”样式。SQLAlchemy现在将这两种映射样式称为 命令映射 和 声明性映射 .
这两种样式可以互换使用,因为每种样式的最终结果都是完全相同的—用户定义的类具有 Mapper
针对可选单元配置,通常由 Table
对象。
命令式和声明式映射都以ORM开头 registry
对象,它维护一组映射的类。此注册表适用于所有映射。
在 1.4 版更改: 声明性映射和经典映射现在被称为“声明性”和“命令式”映射,并且在内部是统一的,所有这些都源于 registry
构造,它表示相关映射的集合。
全套样式可以按如下方式分层组织:
- 声明性映射
- 使用
registry.mapped()
陈述性装饰 Declarative Table - combine
registry.mapped()
with__tablename__
命令表(混合)-组合
registry.mapped()
使用__table__
- 使用
声明性映射
这个 声明性映射 是现代SQLAlchemy中构造映射的典型方式。最常见的模式是首先使用 declarative_base()
函数,该函数将声明性映射过程应用于从它派生的所有子类。下面提供了一个声明性基,然后在声明性表映射中使用:
from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import declarative_base # declarative base class Base = declarative_base() # an example mapping using the base class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) name = Column(String) fullname = Column(String) nickname = Column(String)
上面, declarative_base()
callable返回一个新的基类,新的要映射的类可以从中继承,就像上面的新映射类一样 User
是构造的。
基类引用 registry
对象,该对象维护相关映射类的集合。这个 declarative_base()
实际上,第一个函数是用速记创建注册表 registry
构造函数,然后使用 registry.generate_base()
方法:
from sqlalchemy.orm import registry # equivalent to Base = declarative_base() mapper_registry = registry() Base = mapper_registry.generate_base()
这个 registry
直接用于访问各种映射样式以适应不同的用例:
使用修饰符的声明性映射(无声明基) -使用修饰符而不是基类的声明性映射。
命令(又称经典)映射 -命令式映射,直接指定所有映射参数,而不是扫描一个类。
声明性映射的文档继续 使用声明式映射类 .
参见
非动态创建显式基础(与mypy一起使用,类似)
SQLAlChemy包括一个 Mypy plugin 自动适应动态生成的 Base
由SQLAlChemy函数提供的类,如 declarative_base()
。此插件与一组新的打字存根一起工作,发布地址为 sqlalchemy2-stubs 。
当此插件不在使用时,或在使用其他插件时 PEP 484 工具可能不知道如何解释此类,则声明性基类可以使用 DeclarativeMeta
直接如下所示:
from sqlalchemy.orm import registry from sqlalchemy.orm.decl_api import DeclarativeMeta mapper_registry = registry() class Base(metaclass=DeclarativeMeta): __abstract__ = True # these are supplied by the sqlalchemy2-stubs, so may be omitted # when they are installed registry = mapper_registry metadata = mapper_registry.metadata __init__ = mapper_registry.constructor
以上内容 Base
等同于使用 registry.generate_base()
方法,并且将被类型分析工具完全理解,而无需使用插件。
参见
Mypy/Pep-484对ORM映射的支持 详细说明:Mypy插件的背景知识,该插件在运行Mypy时自动应用上述结构。
使用修饰符的声明性映射(无声明基)
作为使用“声明性基”类的另一种选择是显式地将声明性映射应用于类,使用类似于“经典”映射的命令式技术,或者更简洁地使用修饰符。这个 registry.mapped()
Python的层次结构不能应用于任何一个类中。否则,Python类通常以声明式样式配置:
from sqlalchemy import Column, Integer, String, Text, ForeignKey from sqlalchemy.orm import registry from sqlalchemy.orm import relationship mapper_registry = registry() @mapper_registry.mapped class User: __tablename__ = 'user' id = Column(Integer, primary_key=True) name = Column(String) addresses = relationship("Address", back_populates="user") @mapper_registry.mapped class Address: __tablename__ = 'address' id = Column(Integer, primary_key=True) user_id = Column(ForeignKey("user.id")) email_address = Column(String) user = relationship("User", back_populates="addresses")
上面一样 registry
我们将通过 registry.generate_base()
方法还可以在不使用基的情况下将声明式样式映射应用于类。当使用上述样式时,特定类的映射将 only 如果decorator直接应用于该类,则继续。对于继承映射,修饰符应该应用于每个子类:
from sqlalchemy.orm import registry mapper_registry = registry() @mapper_registry.mapped class Person: __tablename__ = "person" person_id = Column(Integer, primary_key=True) type = Column(String, nullable=False) __mapper_args__ = { "polymorphic_on": type, "polymorphic_identity": "person" } @mapper_registry.mapped class Employee(Person): __tablename__ = "employee" person_id = Column(ForeignKey("person.person_id"), primary_key=True) __mapper_args__ = { "polymorphic_identity": "employee" }
声明性映射的“声明性表”和“命令表”样式都可以与上述映射样式一起使用。
在将SQLAlchemy声明性映射与其他形式的类声明(尤其是Python)结合时,decorator形式的映射特别有用 dataclasses
模块。见下一节。
具有数据类和属性的声明性映射
这个 dataclasses python3.7中添加的模块提供了一个 @dataclass
类decorator来自动生成 __init__()
, __eq__()
, __repr()__
等方法。另一个非常受欢迎的类库也做了同样的事情,而且更多的是 attrs. 这两个库都使用类装饰器来扫描类以查找定义类行为的属性,这些属性随后用于生成方法、文档和注释。
这个 registry.mapped()
类decorator允许类的声明性映射在类完全构造之后发生,从而允许其他类装饰器首先处理该类。这个 @dataclass
和 @attr.s
因此,在ORM映射过程通过 registry.mapped()
装饰或通过 registry.map_imperatively()
方法将在后面的章节中讨论。
映射使用 @dataclass
或 @attr.s
可以以一种直接的方式与 带命令表的声明性(又称混合声明性) 样式,其中 Table
,这意味着它是单独定义的,并通过 __table__
。具体地,对于数据类, 声明性表格 也是受支持的。
1.4.0b2 新版功能: 添加了使用数据类时对完全声明性映射的支持。
使用定义属性时 dataclasses
, the @dataclass
decorator使用它们,但将它们留在类中的适当位置。SQLAlchemy的映射过程,当遇到通常要映射到 Column
,显式检查属性是否是Dataclasses设置的一部分,如果是 代替 用其类的常规属性映射的DataBound属性。这个 __init__
方法创建者 @dataclass
完好无损。相比之下 @attr.s
decorator实际上在decorator运行之后删除了它自己的类绑定属性,因此SQLAlchemy的映射过程可以毫无问题地接管这些属性。
1.4 新版功能: 添加了对Python数据类的直接映射的支持,其中 Mapper
现在将检测特定于 @dataclasses
模块,并在映射时替换它们,而不是跳过它们,因为对于不属于映射的任何类属性,都是默认行为。
示例一-使用命令表的数据类
使用以下命令的映射示例 @dataclass
使用 带命令表的声明性(又称混合声明性) 详情如下:
from __future__ import annotations from dataclasses import dataclass from dataclasses import field from typing import List from typing import Optional from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy.orm import registry from sqlalchemy.orm import relationship mapper_registry = registry() @mapper_registry.mapped @dataclass class User: __table__ = Table( "user", mapper_registry.metadata, Column("id", Integer, primary_key=True), Column("name", String(50)), Column("fullname", String(50)), Column("nickname", String(12)), ) id: int = field(init=False) name: Optional[str] = None fullname: Optional[str] = None nickname: Optional[str] = None addresses: List[Address] = field(default_factory=list) __mapper_args__ = { # type: ignore "properties" : { "addresses": relationship("Address") } } @mapper_registry.mapped @dataclass class Address: __table__ = Table( "address", mapper_registry.metadata, Column("id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("user.id")), Column("email_address", String(50)), ) id: int = field(init=False) user_id: int = field(init=False) email_address: Optional[str] = None
在上面的示例中, User.id
, Address.id
和 Address.user_id
属性定义为 field(init=False)
. 这意味着这些参数不会被添加到 __init__()
方法,但是 Session
在从autoincrement或其他默认值生成器获取刷新期间的值后,仍然可以设置它们。为了允许在构造函数中显式地指定它们,它们将被赋予一个默认值 None
.
对于 relationship()
若要单独声明,则需要直接在 mapper.properties
字典传递给 mapper()
。此方法的另一个替代方法在下一个示例中。
示例2-使用声明性表的数据类
完全声明的方法要求 Column
对象被声明为类属性,在使用数据类时会与数据类级属性冲突。将这些组合在一起的一种方法是使用 metadata
属性上的 dataclass.field
对象,其中可以提供特定于SQLAlChemy的映射信息。当类指定属性时,声明性支持提取这些参数 __sa_dataclass_metadata_key__
。这也提供了一种更简洁的方法来指示 relationship()
协会::
from __future__ import annotations from dataclasses import dataclass from dataclasses import field from typing import List from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy.orm import registry from sqlalchemy.orm import relationship mapper_registry = registry() @mapper_registry.mapped @dataclass class User: __tablename__ = "user" __sa_dataclass_metadata_key__ = "sa" id: int = field( init=False, metadata={"sa": Column(Integer, primary_key=True)} ) name: str = field(default=None, metadata={"sa": Column(String(50))}) fullname: str = field(default=None, metadata={"sa": Column(String(50))}) nickname: str = field(default=None, metadata={"sa": Column(String(12))}) addresses: List[Address] = field( default_factory=list, metadata={"sa": relationship("Address")} ) @mapper_registry.mapped @dataclass class Address: __tablename__ = "address" __sa_dataclass_metadata_key__ = "sa" id: int = field( init=False, metadata={"sa": Column(Integer, primary_key=True)} ) user_id: int = field( init=False, metadata={"sa": Column(ForeignKey("user.id"))} ) email_address: str = field( default=None, metadata={"sa": Column(String(50))} )
在数据类中使用声明性混合
在该部分中 使用mixin组合映射层次 ,介绍了声明性Mixin类。声明性混合的一个要求是,某些不易复制的构造必须以可调用形式给出,使用 declared_attr
装饰符,如 混入关系 ::
class RefTargetMixin(object): @declared_attr def target_id(cls): return Column('target_id', ForeignKey('target.id')) @declared_attr def target(cls): return relationship("Target")
此表单在数据类中受支持 field()
对象中的SQLAlChemy构造,方法是使用lambda指示 field()
。使用 declared_attr()
环绕λ是可选的。如果我们想要生产我们的 User
类,其中ORM字段来自混合输入,而混合输入本身就是一个数据类,则格式为::
@dataclass class UserMixin: __tablename__ = "user" __sa_dataclass_metadata_key__ = "sa" id: int = field( init=False, metadata={"sa": Column(Integer, primary_key=True)} ) addresses: List[Address] = field( default_factory=list, metadata={"sa": lambda: relationship("Address")} ) @dataclass class AddressMixin: __tablename__ = "address" __sa_dataclass_metadata_key__ = "sa" id: int = field( init=False, metadata={"sa": Column(Integer, primary_key=True)} ) user_id: int = field( init=False, metadata={"sa": lambda: Column(ForeignKey("user.id"))} ) email_address: str = field( default=None, metadata={"sa": Column(String(50))} ) @mapper_registry.mapped class User(UserMixin): pass @mapper_registry.mapped class Address(AddressMixin): pass
1.4.2 新版功能: 添加了对“声明的attr”样式混合属性的支持,即 relationship()
构造以及 Column
带有外键声明的对象,将在“带声明性表的数据类”样式映射中使用。
示例三-带有命令性表格的特性
使用以下内容的映射 @attr.s
,与命令表::
import attr # other imports from sqlalchemy.orm import registry mapper_registry = registry() @mapper_registry.mapped @attr.s class User: __table__ = Table( "user", mapper_registry.metadata, Column("id", Integer, primary_key=True), Column("name", String(50)), Column("fullname", String(50)), Column("nickname", String(12)), ) id = attr.ib() name = attr.ib() fullname = attr.ib() nickname = attr.ib() addresses = attr.ib() # other classes...
@dataclass
和 attrs 映射也可用于经典映射,即 registry.map_imperatively()
功能。参见章节 具有数据类和属性的命令式映射 类似的例子。
命令(又称经典)映射
安 命令 或 古典的 映射是指使用 registry.map_imperatively()
方法,其中目标类不包含任何声明性类属性。“地图命令”样式在历史上是通过使用 mapper()
函数,但是此函数现在期望 sqlalchemy.orm.registry()
是存在的。
1.4 版后已移除: 使用 mapper()
函数直接实现经典映射是不推荐的。这个 registry.map_imperatively()
方法保留相同的功能,同时还允许从注册表中对其他映射类进行基于字符串的解析。
在“经典”形式中,表元数据是用 Table
构造,然后与 User
类通过 registry.map_imperatively()
方法:
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
,然后链接到 User
通过 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)
在使用经典映射时,必须直接提供类,而不必使用声明性提供的“字符串查找”系统。SQL表达式通常是根据 Table
对象,即 address.c.id
以上为 Address
关系,而不是 Address.id
作为 Address
可能尚未链接到表元数据,也不能在此处指定字符串。
文档中的一些示例仍然使用经典方法,但请注意,经典方法和声明性方法 完全可互换 . 两个系统最终创建相同的配置,由 Table
,用户定义的类,与 mapper()
. 当我们谈到 mapper()
“,这也包括在使用声明性系统时-它仍然在使用,只是在幕后。
具有数据类和属性的命令式映射
如本节所述 具有数据类和属性的声明性映射 , the @dataclass
装饰师和 attrs 这两个库都充当类装饰器,在类传递给SQLAlchemy进行映射之前,它们首先应用于类。就像我们可以用 registry.mapped()
为了对类应用声明式样式映射,我们还可以将其传递给 registry.map_imperatively()
这样我们就可以通过 Table
和 Mapper
函数的强制配置,而不是将它们定义为声明性类变量:
from __future__ import annotations from dataclasses import dataclass from dataclasses import field from typing import List from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table from sqlalchemy.orm import registry from sqlalchemy.orm import relationship mapper_registry = registry() @dataclass class User: id: int = field(init=False) name: str = None fullname: str = None nickname: str = None addresses: List[Address] = field(default_factory=list) @dataclass class Address: id: int = field(init=False) user_id: int = field(init=False) email_address: str = None metadata_obj = MetaData() user = Table( 'user', metadata_obj, Column('id', Integer, primary_key=True), Column('name', String(50)), Column('fullname', String(50)), Column('nickname', String(12)), ) 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)
映射器配置概述
对于所有映射形式,类的映射可以通过传递成为 Mapper
对象。最终接收这些参数的函数是 mapper()
函数,从上定义的一个前向映射函数传递给它 registry
对象。
有四种一般的配置信息类 mapper()
函数查找:
要映射的类
这是我们在应用程序中构造的类。这个类的结构一般没有限制。 1 当一个Python类被映射时,只有 one Mapper
类的对象。 2
当使用 declarative 映射样式,则要映射的类要么是声明性基类的子类,要么由诸如 registry.mapped()
.
当使用 imperative 样式,类直接作为 map_imperatively.class_
争论。
表或其他from子句对象
这种情况在大多数情况下都很常见 Table
. 对于更高级的用例,它还可以引用任何类型的 FromClause
对象,最常见的替代对象是 Subquery
和 Join
对象。
当使用 declarative 映射样式时,主题表要么由声明系统基于 __tablename__
属性和 Column
对象,或通过 __table__
属性。这两种类型的配置在 声明性表格 和 带命令表的声明性(又称混合声明性) .
当使用 imperative 样式,则主题表作为 map_imperatively.local_table
争论。
与映射类的“每个类一个映射器”要求不同 Table
或其他 FromClause
作为映射主题的对象可以与任意数量的映射相关联。这个 Mapper
将修改直接应用于用户定义的类,但不修改给定的 Table
或其他 FromClause
无论如何。
属性字典
这个类的所有属性都将被映射。默认情况下 Mapper
从给定的 Table
,形式为 ColumnProperty
每个对象都指向一个个体 Column
映射表的。属性字典还将包含所有其他类型的 MapperProperty
要配置的对象,最常见的实例是 relationship()
构造。
当使用 declarative 映射样式,则属性字典由声明系统通过扫描要映射的类以查找适当的属性来生成。参见章节 使用声明性定义映射属性 关于这个过程的注释。
当使用 imperative 样式,则属性字典直接作为 properties
参数 registry.map_imperatively()
,它将传递给 mapper.properties
参数。
其他映射器配置参数
这些标志记录在 mapper()
.
当使用 declarative 映射样式,其他映射器配置参数通过 __mapper_args__
类属性,记录在 具有声明性的映射器配置选项
当使用 imperative 将关键字参数传递给 registry.map_imperatively()
方法将它们传递给 mapper()
功能。
- 1
在Python2下运行时,Python2“旧式”类是唯一不兼容的类。在python2上运行代码时,所有类都必须从Python扩展
object
班级。在python3下,情况总是如此。- 2
有一个称为“非主映射器”的遗留功能,其中
Mapper
对象可能与已映射的类相关联,但是它们不会将检测应用于该类。自SQLAlchemy 1.3起,此功能已被弃用。
映射类行为
使用 registry
对象,以下行为是常见的:
缺省构造
这个 registry
应用默认构造函数,即。 __init__
方法的所有未显式拥有自己的映射类 __init__
方法。此方法的行为使得它提供了一个方便的关键字构造函数,该构造函数将接受命名的所有属性作为可选关键字参数。例如。::
from sqlalchemy.orm import declarative_base Base = declarative_base() class User(Base): __tablename__ = 'user' id = Column(...) name = Column(...) fullname = Column(...)
类型的对象 User
上面将有一个构造函数,它允许 User
要创建为的对象:
u1 = User(name='some name', fullname='some fullname')
上面的构造函数可以通过向 registry.constructor
提供所需默认值的参数 __init__()
行为。
构造函数也适用于命令映射:
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()
方法。
映射类和映射器的运行时自省
一个使用 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>)
参见