SqlAlchemy 2.0 中文文档(五)(2)

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

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构造(例如JoinSubquery),它是单独构造的。

这被称为“混合声明式”映射,因为类使用声明式风格进行所有涉及映射器配置的操作,然而映射的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构造时,例如JoinSubquery对象时,也使用“命令式表”形式。以下是一个示例:

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.idUser.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_idgroup_id,但未设置主键;相反,只建立了一个 UniqueConstraint 来确保这两列表示唯一键。Mapper 不会自动检查唯一约束以用作主键;而是我们利用 Mapper.primary_key 参数,传递了一个 [group_users.c.user_id, group_users.c.group_id] 的集合,指示应使用这两列来构建 GroupUsers 类实例的标识键。### 映射表列的子集

有时,表反射可能会提供一个 Table,其中包含许多对我们的需求不重要且可以安全忽略的列。对于这样一个具有许多不需要在应用程序中引用的列的表,Mapper.include_propertiesMapper.exclude_properties 参数可以指示要映射的列的子集,其中目标 Table 中的其他列不会以任何方式被 ORM 考虑。示例:

class User(Base):
    __table__ = user_table
    __mapper_args__ = {"include_properties": ["user_id", "user_name"]}

在上面的示例中,User 类将映射到 user_table 表,只包括 user_iduser_name 列 - 其余列不被引用。

同样地:

class Address(Base):
    __table__ = address_table
    __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}

Address 类映射到 address_table 表,包括除了 streetcitystatezip 之外的所有列。

如两个示例所示,列可以通过字符串名称或直接引用 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

相关文章
|
3月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(四)(3)
SqlAlchemy 2.0 中文文档(四)
36 3
|
3月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(一)(5)
SqlAlchemy 2.0 中文文档(一)
66 1
|
3月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(一)(4)
SqlAlchemy 2.0 中文文档(一)
44 1
|
3月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(十)(1)
SqlAlchemy 2.0 中文文档(十)
21 1
|
3月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(十)(5)
SqlAlchemy 2.0 中文文档(十)
21 1
|
3月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(三)(2)
SqlAlchemy 2.0 中文文档(三)
28 1
|
3月前
|
测试技术 API 数据库
SqlAlchemy 2.0 中文文档(十)(4)
SqlAlchemy 2.0 中文文档(十)
47 1
|
3月前
|
SQL 自然语言处理 数据库
SqlAlchemy 2.0 中文文档(二)(3)
SqlAlchemy 2.0 中文文档(二)
32 2
|
3月前
|
存储 Python
SqlAlchemy 2.0 中文文档(七)(5)
SqlAlchemy 2.0 中文文档(七)
22 1
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(五)(4)
SqlAlchemy 2.0 中文文档(五)
48 0