SQLAlchemy 2.0 文档
使用数据库元数据¶
在引擎和 SQL 执行完成之后,我们就可以开始进行一些炼金术了。SQLAlchemy 的核心和 ORM 中的核心元素是 SQL 表达式语言,它允许以流畅、可组合的方式构建 SQL 查询。这些查询的基础是代表数据库概念(如表和列)的 Python 对象。这些对象统称为 数据库元数据。
SQLAlchemy 中最常见的数据库元数据基础对象是 MetaData
、Table
和 Column
。以下部分将说明如何在面向核心和面向 ORM 的风格中使用这些对象。
ORM 阅读者,请继续关注!
与其他部分一样,核心用户可以跳过 ORM 部分,但 ORM 用户最好从这两个角度熟悉这些对象。这里讨论的 Table
对象在使用 ORM 时以更间接(也是完全 Python 类型)的方式声明,但 ORM 的配置中仍然存在一个 Table
对象。
使用 Table 对象设置元数据¶
当我们使用关系数据库时,数据库中用于存储数据的基本结构称为 表。在 SQLAlchemy 中,数据库“表”最终由一个类似命名的 Python 对象 Table
表示。
要开始使用 SQLAlchemy 表达式语言,我们需要构造 Table
对象来表示我们要操作的所有数据库表。Table
是通过编程方式构建的,可以通过使用 Table
构造函数直接构建,也可以通过使用 ORM 映射类(稍后在 使用 ORM 声明式形式定义表元数据 中介绍)间接构建。还可以选择从现有数据库加载部分或所有表信息,称为 反射。
无论使用哪种方法,我们总是从一个集合开始,在这个集合中我们将放置我们的表,这个集合称为 MetaData
对象。该对象本质上是 外观,围绕一个 Python 字典,该字典存储一系列 Table
对象,并将其键值设置为其字符串名称。虽然 ORM 提供了一些获取此集合的选项,但我们始终可以选择直接创建一个,如下所示
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()
拥有一个 MetaData
对象后,我们可以声明一些 Table
对象。本教程将从经典的 SQLAlchemy 教程模型开始,该模型有一个名为 user_account
的表,用于存储网站的用户,例如,以及一个相关的表 address
,用于存储与 user_account
表中的行关联的电子邮件地址。如果根本不使用 ORM 声明式模型,我们直接构造每个 Table
对象,通常将其分配给一个变量,我们将在应用程序代码中使用该变量来引用该表
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... "user_account",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("name", String(30)),
... Column("fullname", String),
... )
使用上面的示例,当我们希望编写代码来引用数据库中的 user_account
表时,我们将使用 Python 变量 user_table
来引用它。
Table
的组件¶
我们可以看到,Python 中编写的 Table
结构与 SQL CREATE TABLE 语句类似;从表名开始,然后列出每个列,每个列都有一个名称和一个数据类型。我们上面使用的对象是
Column
- 代表数据库表中的一列,并将自身分配给Table
对象。Column
通常包含一个字符串名称和一个类型对象。从父级Table
角度来看,Column
对象的集合通常通过位于Table.c
的关联数组访问>>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname']
Integer
,String
- 这些类代表 SQL 数据类型,可以传递给Column
,无论是否实例化。在上面,我们想要给“name”列一个长度为“30”的值,所以我们实例化了String(30)
。但是对于“id”和“fullname”,我们没有指定这些,所以我们可以直接发送类本身。
另请参阅
MetaData
、Table
和 Column
的参考和 API 文档位于 使用 MetaData 描述数据库。数据类型的参考文档位于 SQL 数据类型对象。
在接下来的部分中,我们将说明 Table
的基本功能之一,即在特定数据库连接上生成 DDL。但首先我们将声明第二个 Table
。
声明简单约束¶
示例中 user_table
中的第一个 Column
包含 Column.primary_key
参数,它是一种简写方式,表示此 Column
应作为此表的 主键的一部分。主键本身通常是隐式声明的,并由 PrimaryKeyConstraint
结构表示,我们可以在 Table.primary_key
属性上看到,该属性位于 Table
对象上
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
最常明确声明的约束是 ForeignKeyConstraint
对象,它对应于数据库中的 外键约束。当我们声明相互关联的表时,SQLAlchemy 使用这些外键约束声明的存在不仅是为了在 CREATE 语句中将它们发送到数据库,而且还为了帮助构建 SQL 表达式。
仅涉及目标表上单个列的 ForeignKeyConstraint
通常使用列级简写符号通过 ForeignKey
对象声明。下面我们声明一个名为 address
的第二个表,它将有一个外键约束引用 user
表
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("user_id", ForeignKey("user_account.id"), nullable=False),
... Column("email_address", String, nullable=False),
... )
上面的表还包含第三种约束,它在 SQL 中是“NOT NULL”约束,在上面使用 Column.nullable
参数表示。
提示
在 Column
定义中使用 ForeignKey
对象时,我们可以省略该 Column
的数据类型;它会从相关列的数据类型自动推断,在上面的示例中是 user_account.id
列的 Integer
数据类型。
在下一节中,我们将发出 user
和 address
表的完整 DDL,以查看最终结果。
向数据库发出 DDL¶
我们构建了一个对象结构,它代表了数据库中的两个数据库表,从根 MetaData
对象开始,然后进入两个 Table
对象,每个对象都包含一系列 Column
和 Constraint
对象。这个对象结构将是我们今后使用 Core 和 ORM 执行的大多数操作的核心。
我们可以对这个结构做的第一件有用的事情是发出 CREATE TABLE 语句,或 DDL,到我们的 SQLite 数据库,以便我们可以从它们中插入和查询数据。我们已经具备了所有必要的工具,通过在我们的 MetaData
上调用 MetaData.create_all()
方法,并向它发送指向目标数据库的 Engine
>>> metadata_obj.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
上面的 DDL 创建过程包括一些 SQLite 特定的 PRAGMA 语句,这些语句在发出 CREATE 之前测试每个表是否存在。完整的步骤序列也包含在 BEGIN/COMMIT 对中,以适应事务性 DDL。
创建过程还负责以正确的顺序发出 CREATE 语句;在上面,FOREIGN KEY 约束依赖于 user
表的存在,因此 address
表是第二个创建的。在更复杂的依赖关系场景中,FOREIGN KEY 约束也可以使用 ALTER 在之后应用到表上。
MetaData
对象还包含一个 MetaData.drop_all()
方法,它将以相反的顺序发出 DROP 语句,就像它发出 CREATE 一样,以便删除架构元素。
使用 ORM 声明式表单定义表元数据¶
在使用 ORM 时,声明 Table
元数据的过程通常与声明 映射 类相结合。映射类是我们想要创建的任何 Python 类,它将拥有与数据库表中的列关联的属性。虽然实现这一点的方法有几种,但最常见的方式被称为 声明式,它允许我们同时声明用户定义的类和 Table
元数据。
建立声明式基类¶
在使用 ORM 时,MetaData
集合仍然存在,但它本身与一个仅用于 ORM 的构造相关联,通常被称为声明式基类。获取新的声明式基类的最便捷方法是创建一个新的类,该类是 SQLAlchemy DeclarativeBase
类的子类
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
... pass
上面,Base
类是我们所说的声明式基类。当我们创建新的类,这些类是 Base
的子类,并结合适当的类级别指令,它们将在类创建时被建立为新的ORM 映射类,每个类通常(但并非总是)引用特定的 Table
对象。
声明式基类引用一个 MetaData
集合,该集合会自动为我们创建,前提是我们没有从外部提供一个。这个 MetaData
集合可以通过 DeclarativeBase.metadata
类级别属性访问。当我们创建新的映射类时,它们将分别引用这个 MetaData
集合中的 Table
。
>>> Base.metadata
MetaData()
声明式基类还引用一个名为 registry
的集合,它是 SQLAlchemy ORM 中的中央“映射配置”单元。虽然很少直接访问,但这个对象对于映射配置过程至关重要,因为一组 ORM 映射类将通过这个注册表相互协调。与 MetaData
一样,我们的声明式基类也为我们创建了一个 registry
(同样可以传递我们自己的 registry
),我们可以通过 DeclarativeBase.registry
类变量访问它。
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
声明映射类¶
建立了 Base
类后,我们现在可以根据新的类 User
和 Address
,为 user_account
和 address
表定义 ORM 映射类。我们将在下面说明最现代的声明式形式,它由 PEP 484 类型注释驱动,使用特殊的类型 Mapped
,它指示要作为特定类型映射的属性
>>> from typing import List
>>> from typing import Optional
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... __tablename__ = "user_account"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... name: Mapped[str] = mapped_column(String(30))
... fullname: Mapped[Optional[str]]
...
... addresses: Mapped[List["Address"]] = relationship(back_populates="user")
...
... def __repr__(self) -> str:
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = "address"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... email_address: Mapped[str]
... user_id = mapped_column(ForeignKey("user_account.id"))
...
... user: Mapped[User] = relationship(back_populates="addresses")
...
... def __repr__(self) -> str:
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
上面的两个类,User
和 Address
,现在被称为ORM 映射类,可以在 ORM 持久性和查询操作中使用,这些操作将在后面介绍。有关这些类的详细信息包括
每个类都引用一个
Table
对象,该对象是在声明式映射过程的一部分生成的,其名称通过将字符串分配给DeclarativeBase.__tablename__
属性来确定。创建类后,这个生成的Table
可以从DeclarativeBase.__table__
属性访问。如前所述,这种形式被称为 声明式表配置。几种替代声明样式中的一种将让我们直接构建
Table
对象,并将其分配给DeclarativeBase.__table__
。这种样式被称为 使用命令式表的声明式。要指示
Table
中的列,我们使用mapped_column()
结构,并结合基于Mapped
类型的类型注释。这个对象将生成Column
对象,这些对象将应用于Table
的构造。对于具有简单数据类型和没有其他选项的列,我们可以只指示
Mapped
类型注释,使用像int
和str
这样的简单 Python 类型来表示Integer
和String
。Python 类型在声明式映射过程中的解释方式的定制非常开放;有关背景信息,请参阅 使用带注释的声明式表(用于 mapped_column() 的类型注释形式) 和 定制类型映射 部分。基于
Optional[<typ>]
类型注解(或其等效类型,<typ> | None
或Union[<typ>, None]
)的存在,可以将列声明为“可空”或“非空”。mapped_column.nullable
参数也可以显式使用(并且不需要与注解的可选性匹配)。使用显式类型注解是**完全可选的**。我们也可以使用
mapped_column()
而不使用注解。使用这种形式时,我们会使用更明确的类型对象,例如Integer
和String
,以及nullable=False
,根据需要在每个mapped_column()
结构中使用。另外两个属性,
User.addresses
和Address.user
,定义了一种不同类型的属性,称为relationship()
,它具有与所示类似的注解感知配置风格。relationship()
结构将在 使用 ORM 关联对象 中更详细地讨论。如果我们没有声明自己的
__init__()
方法,则会自动为类提供该方法。该方法的默认形式接受所有属性名称作为可选关键字参数>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
要自动生成一个功能齐全的
__init__()
方法,该方法提供位置参数以及带有默认关键字值的参数,可以使用 声明式数据类映射 中介绍的数据类功能。当然,也可以选择使用显式__init__()
方法。添加了
__repr__()
方法,以便我们获得可读的字符串输出;这些方法并不强制要求存在。与__init__()
一样,可以使用 数据类 功能自动生成__repr__()
方法。
另请参阅
ORM 映射风格 - 关于不同 ORM 配置风格的完整背景。
声明式映射 - 声明式类映射概述
使用 mapped_column() 的声明式表格 - 详细说明如何在使用声明式映射时使用 mapped_column()
和 Mapped
来定义 Table
中的列。
从 ORM 映射中向数据库发出 DDL¶
由于我们的 ORM 映射类引用了 Table
对象(包含在 MetaData
集合中),因此从声明式基类发出 DDL 使用与之前在 向数据库发出 DDL 中描述的相同过程。在本例中,我们已经在 SQLite 数据库中生成了 user
和 address
表。如果我们还没有这样做,我们可以自由地使用与 ORM 声明式基类关联的 MetaData
类来这样做,方法是从 DeclarativeBase.metadata
属性访问该集合,然后使用 MetaData.create_all()
,就像之前一样。在本例中,将运行 PRAGMA 语句,但不会生成新的表,因为发现它们已经存在
>>> Base.metadata.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
COMMIT
表格反射¶
为了完善关于处理表格元数据的章节,我们将演示另一个在本章开头提到的操作,即 **表格反射**。表格反射指的是通过读取数据库的当前状态来生成 Table
和相关对象的进程。在之前的章节中,我们一直在用 Python 声明 Table
对象,然后可以选择将 DDL 发送到数据库以生成这样的模式。而反射过程则反其道而行之,从现有的数据库开始,生成 Python 中的数据结构来表示数据库中的模式。
提示
使用 SQLAlchemy 连接现有数据库并不需要使用反射。通常,SQLAlchemy 应用程序会在 Python 中显式声明所有元数据,以使其结构与现有数据库相对应。元数据结构也不必包含预先存在的数据库中未被本地应用程序使用的表格、列或其他约束和结构。
作为反射的示例,我们将创建一个新的 Table
对象,它代表我们在本文档前面章节中手动创建的 some_table
对象。执行此操作的方式也有几种,但最基本的方式是构建一个 Table
对象,指定表格的名称和它将所属的 MetaData
集合。然后,而不是指定单独的 Column
和 Constraint
对象,而是使用 Engine
对象,通过 Table.autoload_with
参数传递目标 Engine
对象。
>>> some_table = Table("some_table", metadata_obj, autoload_with=engine)
BEGIN (implicit)
PRAGMA main.table_...info("some_table")
[raw sql] ()
SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type in ('table', 'view')
[raw sql] ('some_table',)
PRAGMA main.foreign_key_list("some_table")
...
PRAGMA main.index_list("some_table")
...
ROLLBACK
在进程结束后,some_table
对象将包含有关表格中存在的 Column
对象的信息,并且该对象的使用方式与我们显式声明的 Table
对象完全相同。
>>> some_table
Table('some_table', MetaData(),
Column('x', INTEGER(), table=<some_table>),
Column('y', INTEGER(), table=<some_table>),
schema=None)
下一步¶
我们现在已经准备好一个 SQLite 数据库,其中包含两个表格,以及我们可以使用 Core 和 ORM 面向表格的结构通过 Connection
或 ORM Session
与这些表格进行交互。
flambé! 火龙和 炼金术士 图像设计由 Rotem Yaari 创作并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:Fri 08 Nov 2024 08:41:19 AM EST