SqlAlchemy 2.0 中文文档(五)(1)https://developer.aliyun.com/article/1563092
另请参阅
指定模式名称 - 在 使用 MetaData 描述数据库 文档中。 ### 设置声明性映射列的加载和持久化选项
mapped_column()
构造函数接受其他影响生成的 Column
映射的 ORM 特定参数,影响其加载和持久化行为。常用的选项包括:
- 延迟列加载 -
mapped_column.deferred
布尔值默认情况下建立Column
使用 延迟列加载。在下面的示例中,User.bio
列不会默认加载,而是在访问时加载:
class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] bio: Mapped[str] = mapped_column(Text, deferred=True)
- 另请参阅
限制列的加载方式与列延迟 - 延迟列加载的完整描述 - 活动历史 -
mapped_column.active_history
确保在更改属性的值时,先前的值将已加载并作为属性历史的一部分放入AttributeState.history
集合中,当检查属性的历史时,可能会产生额外的 SQL 语句:
class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) important_identifier: Mapped[str] = mapped_column(active_history=True)
请参阅 mapped_column()
的文档字符串以获取支持的参数列表。
另请参阅
对声明式表列应用加载、持久化和映射选项 - 描述了在 Imperative Table 配置中使用 column_property()
和 deferred()
的方法 ### 显式命名声明式映射列
到目前为止,所有的示例都是使用了 mapped_column()
构造与一个 ORM 映射的属性关联起来,其中给定给 mapped_column()
的 Python 属性名称也是 CREATE TABLE 语句以及查询中所见的列的名称。在 SQL 中表示列名可以通过将字符串位置参数 mapped_column.__name
作为第一个位置参数来指定。在下面的示例中,User
类被映射到了列本身的备用名称:
class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column("user_id", primary_key=True) name: Mapped[str] = mapped_column("user_name")
User.id
对应的是一个名为 user_id
的列,而 User.name
对应的是一个名为 user_name
的列。我们可以使用我们的 Python 属性名称编写一个 select()
语句,使用的是我们的 Python 属性名称,我们将看到生成的 SQL 名称:
>>> from sqlalchemy import select >>> print(select(User.id, User.name).where(User.name == "x")) SELECT "user".user_id, "user".user_name FROM "user" WHERE "user".user_name = :user_name_1
另请参阅
映射表列的备用属性名称 - 适用于声明式表 ### 向现有的声明式映射类添加附加列
声明式表配置允许在已经生成了 Table
元数据之后,向现有映射中添加新的 Column
对象。
对于使用声明式基类声明的声明式类,底层元类DeclarativeMeta
包括一个__setattr__()
方法,该方法将拦截额外的mapped_column()
或 Core Column
对象,并将它们添加到Table
和现有的Mapper
中,分别使用Table.append_column()
和Mapper.add_property()
:
MyClass.some_new_column = mapped_column(String)
使用核心Column
:
MyClass.some_new_column = Column(String)
支持所有参数,包括备用名称,例如MyClass.some_new_column = mapped_column("some_name", String)
。但是,必须显式传递 SQL 类型给mapped_column()
或Column
对象,就像上面的例子中传递String
类型一样。Mapped
注解类型无法参与此操作。
在使用单表继承的特定情况下,也可以向映射添加额外的Column
对象,在这种情况下,映射的子类上存在额外的列,但它们没有自己的Table
。这在单表继承部分有所说明。
另请参阅
在声明后向映射类添加关系 - 类似的例子可以参考relationship()
注意
将映射属性分配给已经映射的类,只有在使用“声明性基类”,即用户定义的DeclarativeBase
的子类,或者由declarative_base()
或者registry.generate_base()
返回的动态生成的类时,才能正确运行。这个“基类”包括一个 Python 元类,它实现了一个特殊的__setattr__()
方法来拦截这些操作。
使用类映射属性对映射类进行运行时分配,如果使用装饰器,如registry.mapped()
或者像registry.map_imperatively()
这样的命令式函数进行类映射,则无法正常工作。## 声明式与命令式表格(又名混合声明式)
声明式映射也可以使用预先存在的Table
对象,或者其他任意的FromClause
构造(例如Join
或Subquery
),它是单独构造的。
这被称为“混合声明式”映射,因为类使用声明式风格进行所有涉及映射器配置的操作,然而映射的Table
对象是单独生成的,并直接传递给声明性流程:
from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass # construct a Table directly. The Base.metadata collection is # usually a good choice for MetaData but any MetaData # collection may be used. user_table = Table( "user", Base.metadata, Column("id", Integer, primary_key=True), Column("name", String), Column("fullname", String), Column("nickname", String), ) # construct the User class using this table. class User(Base): __table__ = user_table
在上面的例子中,使用在用 MetaData 描述数据库中描述的方法构造了一个Table
对象。然后可以直接将其应用于声明性映射的类。在这种形式中,不使用__tablename__
和__table_args__
声明性类属性。上述配置通常更易读,作为内联定义:
class User(Base): __table__ = Table( "user", Base.metadata, Column("id", Integer, primary_key=True), Column("name", String), Column("fullname", String), Column("nickname", String), )
上述风格的自然结果是__table__
属性本身在类定义块中被定义。因此,它可以立即在后续属性中被引用,例如下面的例子,说明在多态映射器配置中引用type
列:
class Person(Base): __table__ = Table( "person", Base.metadata, Column("id", Integer, primary_key=True), Column("name", String(50)), Column("type", String(50)), ) __mapper_args__ = { "polymorphic_on": __table__.c.type, "polymorhpic_identity": "person", }
当要映射非Table
构造时,例如Join
或Subquery
对象时,也使用“命令式表”形式。以下是一个示例:
from sqlalchemy import func, select subq = ( select( func.count(orders.c.id).label("order_count"), func.max(orders.c.price).label("highest_order"), orders.c.customer_id, ) .group_by(orders.c.customer_id) .subquery() ) customer_select = ( select(customers, subq) .join_from(customers, subq, customers.c.id == subq.c.customer_id) .subquery() ) class Customer(Base): __table__ = customer_select
有关映射到非Table
构造的背景,请参阅将类映射到多个表和将类映射到任意子查询一节。
当类本身使用替代形式的属性声明时,例如 Python 数据类时,“命令式表”形式特别有用。详见将 ORM 映射应用于现有数据类(传统数据类用法)一节。
另请参见
使用 MetaData 描述数据库
将 ORM 映射应用于现有数据类(传统数据类用法)
映射表列的替代属性名称
显式命名声明式映射的列一节说明了如何使用mapped_column()
为生成的Column
对象提供一个特定名称,与其映射的属性名称分开。
当使用命令式表配置时,我们已经有Column
对象存在。为了将它们映射到替代名称,我们可以直接将Column
分配给所需的属性:
user_table = Table( "user", Base.metadata, Column("user_id", Integer, primary_key=True), Column("user_name", String), ) class User(Base): __table__ = user_table id = user_table.c.user_id name = user_table.c.user_name
上面的User
映射将通过User.id
和User.name
属性引用"user_id"
和"user_name"
列,方式与显式命名声明式映射的列一节所示相同。
对上述映射的一个注意事项是,当使用 PEP 484 类型工具时,对 Column
的直接内联链接将不会被正确类型化。解决此问题的一种策略是在 column_property()
函数中应用 Column
对象;虽然 Mapper
已经自动为其内部使用生成了此属性对象,但通过在类声明中命名它,类型工具将能够将属性与 Mapped
注释匹配起来:
from sqlalchemy.orm import column_property from sqlalchemy.orm import Mapped class User(Base): __table__ = user_table id: Mapped[int] = column_property(user_table.c.user_id) name: Mapped[str] = column_property(user_table.c.user_name)
请参阅
显式命名声明式映射列 - 适用于声明式表 ### 对命令式表列应用加载、持久化和映射选项
在为声明式映射列设置加载和持久化选项一节中,讲述了如何在使用声明式表配置时设置加载和持久化选项时,使用 mapped_column()
构造。在使用命令式表配置时,我们已经有了现有的与之映射的 Column
对象。为了映射这些与额外参数一起的 Column
对象,这些参数特定于 ORM 映射,我们可以使用 column_property()
和 deferred()
构造以将额外参数与列关联起来。选项包括:
- 推迟加载列 -
deferred()
函数是使用column_property.deferred
参数设置为True
调用column_property()
的速记方式;此构造默认使用 推迟加载列 来建立Column
。在下面的示例中,User.bio
列将不会默认加载,而只在访问时加载:
from sqlalchemy.orm import deferred user_table = Table( "user", Base.metadata, Column("id", Integer, primary_key=True), Column("name", String), Column("bio", Text), ) class User(Base): __table__ = user_table bio = deferred(user_table.c.bio)
请参阅
限制加载列与列推迟加载 - 推迟列加载的完整描述
- 活动历史 -
column_property.active_history
确保在属性值更改时,之前的值将已加载并成为检查属性历史时的AttributeState.history
集合的一部分。这可能会产生额外的 SQL 语句:
from sqlalchemy.orm import deferred user_table = Table( "user", Base.metadata, Column("id", Integer, primary_key=True), Column("important_identifier", String), ) class User(Base): __table__ = user_table important_identifier = column_property( user_table.c.important_identifier, active_history=True )
另请参阅
column_property()
构造对于类被映射到替代的 FROM 子句(例如连接和选择)的情况也很重要。有关这些情况的更多背景信息请参阅:
- 将类映射到多个表
- SQL 表达式作为映射属性
对于使用mapped_column()
进行声明式表配置,大多数选项都是直接可用的;请参阅设置声明式映射列的加载和持久化选项一节的示例。 ## 使用反射表声明式映射
有几种可用的模式,用于根据从数据库反射的一系列Table
对象生成映射类,使用的是在反映数据库对象中描述的反射过程。
从数据库反射到表的简单方法是使用声明式混合映射,将Table.autoload_with
参数传递给Table
的构造函数:
from sqlalchemy import create_engine from sqlalchemy import Table from sqlalchemy.orm import DeclarativeBase engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database") class Base(DeclarativeBase): pass class MyClass(Base): __table__ = Table( "mytable", Base.metadata, autoload_with=engine, )
上述模式的变体适用于许多表的情况,可以使用MetaData.reflect()
方法一次反射完整的Table
对象集合,然后从MetaData
中引用它们:
from sqlalchemy import create_engine from sqlalchemy import Table from sqlalchemy.orm import DeclarativeBase engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database") class Base(DeclarativeBase): pass Base.metadata.reflect(engine) class MyClass(Base): __table__ = Base.metadata.tables["mytable"]
使用 __table__
方法的一个注意事项是,映射的类不能声明,直到表被反射,这需要数据库连接源在声明应用程序类时存在;典型情况下,类是在应用程序模块被导入时声明的,但是数据库连接直到应用程序开始运行代码时才可用,以便它可以使用配置信息并创建引擎。目前有两种解决此问题的方法,描述在下面的两个部分中。
使用 DeferredReflection
为了适应声明映射类的用例,可以稍后对表元数据进行反射的情况,提供了一个简单的扩展,称为 DeferredReflection
mixin,它改变了声明映射过程,延迟到特殊的类级 DeferredReflection.prepare()
方法被调用,该方法将根据目标数据库执行反射过程,并将结果与声明表映射过程集成,即使用 __tablename__
属性的类:
from sqlalchemy.ext.declarative import DeferredReflection from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass class Reflected(DeferredReflection): __abstract__ = True class Foo(Reflected, Base): __tablename__ = "foo" bars = relationship("Bar") class Bar(Reflected, Base): __tablename__ = "bar" foo_id = mapped_column(Integer, ForeignKey("foo.id"))
在上述代码中,我们创建了一个名为 Reflected
的混合类,该类将作为声明性层次结构中的类的基础,当调用 Reflected.prepare
方法时应该被映射。在进行此映射之前,以上映射是不完整的,给定一个 Engine
:
engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database") Reflected.prepare(engine)
Reflected
类的目的是定义类应该被反射映射的范围。插件将在调用 .prepare()
的目标的子类树中搜索,并反射所有由声明类命名的表;目标数据库中不属于映射的表,且不通过外键约束与目标表相关的表将不会被反射。
使用自动映射
一个更自动化的解决方案是使用 自动映射 扩展来映射现有数据库,其中使用表反射。该扩展将从数据库模式生成整个映射的类,包括基于观察到的外键约束的类之间的关系。虽然它包含用于定制的挂钩,例如允许自定义类命名和关系命名方案的挂钩,但自动映射是面向迅速零配置的工作风格。如果应用程序希望具有完全明确的模型,并使用表反射,那么 DeferredReflection 类可能更可取,因为它的方法较少自动化。
另请参阅
自动映射
自动从反射表中命名列方案
当使用任何以前的反射技术时,我们有选择通过列映射的命名方案。 Column
对象包括一个参数 Column.key
,它是一个字符串名称,确定此 Column
将以何种名称独立于列的 SQL 名称出现在 Table.c
集合中。如果未通过其他方式提供,例如在 映射表列的备用属性名称 中说明的那样,此键也将由 Mapper
用作将 Column
映射到的属性名称。
在使用表反射时,我们可以拦截将作为参数接收到的 Column
的参数,并应用我们需要的任何更改,包括 .key
属性,以及诸如数据类型之类的内容。
事件挂钩最容易与正在使用的 MetaData
对象相关联,如下所示:
from sqlalchemy import event from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass @event.listens_for(Base.metadata, "column_reflect") def column_reflect(inspector, table, column_info): # set column.key = "attr_<lower_case_name>" column_info["key"] = "attr_%s" % column_info["name"].lower()
有了上述事件,Column
对象的反射将被我们添加新的“.key”元素的事件拦截,例如在下面的映射中:
class MyClass(Base): __table__ = Table("some_table", Base.metadata, autoload_with=some_engine)
这种方法也适用于 DeferredReflection
基类以及 Automap 扩展。特别是对于 automap,请参阅部分 拦截列定义 以了解背景信息。
另请参阅
使用反射表声明式映射
DDLEvents.column_reflect()
拦截列定义 - 在 Automap 文档中 ### 映射到明确的主键列集
为了成功映射一个表,Mapper
构造始终要求至少标识一个列为该可选择项的“主键”。这样,当加载或持久化 ORM 对象时,它可以被放置在具有适当 标识键 的标识映射中。
在那些被映射的反射表不包含主键约束的情况下,以及在针对任意可选择项进行映射的一般情况下,可能不存在主键列的情况下,提供了 Mapper.primary_key
参数,以便可以将任何一组列配置为表的“主键”,就 ORM 映射而言。
给定一个针对现有 Table
对象进行命令式表映射的示例,其中该表没有声明的主键(可能在反射情景中出现),我们可以将这样的表映射如下示例所示:
from sqlalchemy import Column from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import UniqueConstraint from sqlalchemy.orm import DeclarativeBase metadata = MetaData() group_users = Table( "group_users", metadata, Column("user_id", String(40), nullable=False), Column("group_id", String(40), nullable=False), UniqueConstraint("user_id", "group_id"), ) class Base(DeclarativeBase): pass class GroupUsers(Base): __table__ = group_users __mapper_args__ = {"primary_key": [group_users.c.user_id, group_users.c.group_id]}
上文中,group_users
表是一种关联表,具有字符串列 user_id
和 group_id
,但未设置主键;相反,只建立了一个 UniqueConstraint
来确保这两列表示唯一键。Mapper
不会自动检查唯一约束以用作主键;而是我们利用 Mapper.primary_key
参数,传递了一个 [group_users.c.user_id, group_users.c.group_id]
的集合,指示应使用这两列来构建 GroupUsers
类实例的标识键。### 映射表列的子集
有时,表反射可能会提供一个 Table
,其中包含许多对我们的需求不重要且可以安全忽略的列。对于这样一个具有许多不需要在应用程序中引用的列的表,Mapper.include_properties
或 Mapper.exclude_properties
参数可以指示要映射的列的子集,其中目标 Table
中的其他列不会以任何方式被 ORM 考虑。示例:
class User(Base): __table__ = user_table __mapper_args__ = {"include_properties": ["user_id", "user_name"]}
在上面的示例中,User
类将映射到 user_table
表,只包括 user_id
和 user_name
列 - 其余列不被引用。
同样地:
class Address(Base): __table__ = address_table __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}
将 Address
类映射到 address_table
表,包括除了 street
、city
、state
和 zip
之外的所有列。
如两个示例所示,列可以通过字符串名称或直接引用 Column
对象来引用。直接引用对象可能对明确性和解决映射到具有重复名称的多表构造时的歧义很有用:
class User(Base): __table__ = user_table __mapper_args__ = { "include_properties": [user_table.c.user_id, user_table.c.user_name] }
当列未包含在映射中时,在执行 select()
或传统的 Query
对象时,这些列不会被引用在任何 SELECT 语句中,映射类中也不会有任何代表该列的映射属性;给定该名称的属性赋值将不会产生除普通 Python 属性赋值以外的效果。
然而,重要的是要注意,模式级别的列默认值仍然会生效,对于那些包含这些默认值的 Column
对象,即使它们被排除在 ORM 映射之外。
SqlAlchemy 2.0 中文文档(五)(3)https://developer.aliyun.com/article/1563094