Flask-SQLAlchemy 3.1.x 用户指南
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It simplifies using SQLAlchemy with Flask by setting up common objects and patterns for using those objects, such as a session tied to each web request, models, and engines.
Flask-SQLAlchemy 是 Flask 的扩展,可为您的应用程序添加对 SQLAlchemy 的支持。它通过设置通用对象和使用这些对象的模式(例如与每个 Web 请求、模型和引擎相关的会话),简化了 SQLAlchemy 与 Flask 的使用。
Flask-SQLAlchemy does not change how SQLAlchemy works or is used. See the SQLAlchemy documentation to learn how to work with the ORM in depth. The documentation here will only cover setting up the extension, not how to use SQLAlchemy.
Flask-SQLAlchemy 不会改变 SQLAlchemy 的工作或使用方式。请参阅 SQLAlchemy 文档以了解如何深入使用 ORM。这里的文档仅涵盖设置扩展,而不是如何使用 SQLAlchemy。
UPDATED BY YULIKE 2024-01-19
快速开始 ¶
警告:这是开发版本。最新的稳定版本是版本3.1.x。
Flask-SQLAlchemy 通过自动处理创建、使用和清理您通常使用的 SQLAlchemy 对象来简化 SQLAlchemy 的使用。虽然它添加了一些有用的功能,但它的工作方式仍然类似于 SQLAlchemy。
本页将引导您完成 Flask-SQLAlchemy 的基本使用。有关完整功能和自定义,请参阅这些文档的其余部分,包括 SQLAlchemy
对象的 API 文档。
检查 SQLAlchemy 文档 ¶
Flask-SQLAlchemy 是 SQLAlchemy 的包装器。您应该按照 SQLAlchemy 教程来了解如何使用它,并查阅其文档以获取有关其功能的详细信息。这些文档展示了如何设置 Flask-SQLAlchemy 本身,而不是如何使用 SQLAlchemy。
Flask-SQLAlchemy 会自动设置引擎和作用域会话,因此您可以跳过 SQLAlchemy 教程的这些部分。
本指南假设您使用 SQLAlchemy 2.x,它具有用于定义模型的新 API 以及对 Python 类型提示和数据类的更好支持。如果您使用的是 SQLAlchemy 1.x,请参阅旧版快速入门。
安装 ¶
Flask-SQLAlchemy 可在 PyPI 上使用,并且可以与各种 Python 工具一起安装。例如,要使用 pip 安装或更新最新版本:
$ pip install -U Flask-SQLAlchemy
初始化扩展 ¶
首先使用 SQLAlchemy
构造函数创建 db
对象。
将 DeclarativeBase 或 DeclarativeBaseNoMeta 的子类传递给构造函数。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
了解有关在模型和表.中自定义基本模型类的更多信息。
About the SQLAlchemy
object 关于 SQLAlchemy
对象 ¶
构造完成后, db
对象使您可以访问 db.Model
类来定义模型,并使用 db.session
来执行查询。
SQLAlchemy
对象还采用附加参数来自定义它管理的对象。
配置扩展 ¶
下一步是将扩展连接到您的 Flask 应用程序。唯一需要的 Flask 应用配置是 SQLALCHEMY_DATABASE_URI
键。这是一个连接字符串,告诉 SQLAlchemy 要连接到哪个数据库。
创建 Flask 应用程序对象,加载任何配置,然后通过调用 db.init_app
使用应用程序初始化 SQLAlchemy
扩展类。此示例连接到 SQLite 数据库,该数据库存储在应用程序的实例文件夹中。
# create the app
app = Flask(__name__)
# configure the SQLite database, relative to the app instance folder
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
# initialize the app with the extension
db.init_app(app)
有关连接字符串以及使用的其他配置键的说明,请参阅 配置。
定义模型 ¶
子类 db.Model
来定义模型类。该模型将通过将 CamelCase
类名转换为 snake_case
来生成表名称。
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column
class User(db.Model):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
username: Mapped[str] = mapped_column(String, unique=True, nullable=False)
email: Mapped[str] = mapped_column(String)
有关定义和创建模型和表 的更多信息,请参阅模型和表。
创建表 ¶
定义完所有模型和表后,调用 SQLAlchemy.create_all()
在数据库中创建表架构。这需要应用程序上下文。由于您此时尚未收到请求,因此请手动创建一个。
with app.app_context():
db.create_all()
如果您在其他模块中定义模型,则必须在调用 create_all
之前导入它们,否则 SQLAlchemy 将不知道它们。
如果表已存在于数据库中, create_all
不会更新表。如果更改模型的列,请使用 迁移库 Alembic 与 Flask-Alembic 或 Flask-Migrate 来生成更新数据库架构的迁移。
Query the Data 查询数据 ¶
在 Flask 视图或 CLI 命令中,您可以使用 db.session
执行查询和修改模型数据。
SQLAlchemy 自动为每个模型定义一个 __init__
方法,该方法将任何关键字参数分配给相应的数据库列和其他属性。
db.session.add(obj)
将一个对象添加到要插入的会话中。修改对象的属性会更新该对象。 db.session.delete(obj)
删除一个对象。请记住在修改、添加或删除任何数据后调用 db.session.commit()
。
db.session.execute(db.select(...))
构造一个查询以从数据库中选择数据。构建查询是 SQLAlchemy 的主要功能,因此您需要阅读其有关 select 的教程来了解所有相关信息。您通常会使用 Result.scalars()
方法来获取结果列表,或使用 Result.scalar()
方法来获取单个结果。
@app.route("/users")
def user_list():
users = db.session.execute(db.select(User).order_by(User.username)).scalars()
return render_template("user/list.html", users=users)
@app.route("/users/create", methods=["GET", "POST"])
def user_create():
if request.method == "POST":
user = User(
username=request.form["username"],
email=request.form["email"],
)
db.session.add(user)
db.session.commit()
return redirect(url_for("user_detail", id=user.id))
return render_template("user/create.html")
@app.route("/user/<int:id>")
def user_detail(id):
user = db.get_or_404(User, id)
return render_template("user/detail.html", user=user)
@app.route("/user/<int:id>/delete", methods=["GET", "POST"])
def user_delete(id):
user = db.get_or_404(User, id)
if request.method == "POST":
db.session.delete(user)
db.session.commit()
return redirect(url_for("user_list"))
return render_template("user/delete.html", user=user)
您可能会看到使用 Model.query
来构建查询。这是一个较旧的查询接口,在 SQLAlchemy 中被视为遗留。更喜欢使用 db.session.execute(db.select(...))
代替。
有关查询的更多信息,请参阅修改和查询数据。
要记住什么 ¶
大多数情况下,您应该像往常一样使用 SQLAlchemy。 [SQLAlchemy
] SQLAlchemy
扩展实例创建、配置并授予对以下内容的访问权限:
SQLAlchemy.Model
声明性模型基类。它自动设置表名称,而不需要__tablename__
。SQLAlchemy.session
是一个范围为当前 Flask 应用程序上下文的会话。每次请求后都会对其进行清理。SQLAlchemy.metadata
和SQLAlchemy.metadatas
允许访问配置中定义的每个元数据。SQLAlchemy.engine
和SQLAlchemy.engines
允许访问配置中定义的每个引擎。SQLAlchemy.create_all()
创建所有表。- 您必须处于活动的 Flask 应用程序上下文中才能执行查询并访问会话和引擎。
配置 ¶
配置键 ¶
调用 SQLAlchemy.init_app()
时,会从 Flask app.config
加载配置。此后不再读取配置。因此,所有配置都必须在初始化应用程序之前进行。
-
flask_sqlalchemy.config.SQLALCHEMY_DATABASE_URI
用于默认引擎的数据库连接 URI。它可以是字符串或 SQLAlchemy
URL
实例。请参阅下文和引擎配置 示例。必须至少设置此和SQLALCHEMY_BINDS
之一。版本 3.0 中的更改:如果未设置,则不再默认为内存中 SQLite 数据库。
-
flask_sqlalchemy.config.SQLALCHEMY_ENGINE_OPTIONS
要传递给默认引擎的
sqlalchemy.create_engine()
的参数字典。这优先于SQLAlchemy
的引擎配置
参数,后者可用于设置所有引擎的默认选项。3.0 版更改:仅适用于默认绑定。 2.4 版本中的新功能。 -
flask_sqlalchemy.config.SQLALCHEMY_BINDS
将键映射到引擎选项的字典。该值可以是字符串或 SQLAlchemy
URL
实例。或者它可以是参数的字典,包括将传递给sqlalchemy.create_engine()
的url
键。None
键可用于配置默认绑定,但SQLALCHEMY_ENGINE_OPTIONS
和SQLALCHEMY_DATABASE_URI
优先。必须至少设置此和SQLALCHEMY_DATABASE_URI
之一。 0.12 版本中的新增功能。 -
flask_sqlalchemy.config.SQLALCHEMY_ECHO
每个引擎的
echo
和echo_pool
的默认值。这对于快速调试从 SQLAlchemy 发出的连接和查询非常有用。在 3.0 版中更改:除了
echo
之外,还设置echo_pool
。 -
flask_sqlalchemy.config.SQLALCHEMY_RECORD_QUERIES
如果启用,将记录请求期间每个查询的信息。使用
get_recorded_queries()
获取请求期间发出的查询列表。版本 3.0 中的更改:在调试或测试模式下不会自动启用。
-
flask_sqlalchemy.config.SQLALCHEMY_TRACK_MODIFICATIONS
如果启用,则会记录模型上的所有
insert
、update
和delete
操作,然后在models_committed
和before_models_committed
时发出信号。这给每个会话增加了大量的开销。更喜欢直接使用 SQLAlchemy 的 ORM 事件 来获取您需要的确切信息。3.0 版更改:默认禁用。 2.0版本中的新增功能。
版本 3.1 中的更改:删除了 SQLALCHEMY_COMMIT_ON_TEARDOWN
。
版本 3.0 中的更改:删除了 SQLALCHEMY_NATIVE_UNICODE
、 SQLALCHEMY_POOL_SIZE
、 SQLALCHEMY_POOL_TIMEOUT
、 SQLALCHEMY_POOL_RECYCLE
和 SQLALCHEMY_MAX_OVERFLOW
。
连接 URL 格式 ¶
有关语法、方言和选项的完整说明,请参阅 SQLAlchemy 有关引擎配置的文档。
基本数据库连接 URL 使用以下格式。用户名、密码、主机和端口是可选的,具体取决于数据库类型和配置。
dialect://username:password@host:port/database
以下是一些连接字符串示例:
# SQLite, relative to Flask instance path
sqlite:///project.db
# PostgreSQL
postgresql://scott:tiger@localhost/project
# MySQL / MariaDB
mysql://scott:tiger@localhost/project
SQLite 不使用用户或主机,因此它的 URL 始终以三个斜杠
而不是两个斜杠开头。 dbname
值是文件路径。绝对路径以 第四条 斜杠开头(在 Linux 或 Mac 上)。相对路径是相对于 Flask 应用程序的 instance_path
的。
默认驱动程序选项 ¶
为 SQLite 和 MySQL 引擎设置了一些默认选项,以使它们在默认情况下在 Web 应用程序中更可用。
SQLite 相对文件路径是相对于 Flask 实例路径而不是当前工作目录。内存数据库使用静态池和 check_same_thread
来跨请求工作。
MySQL(和 MariaDB)服务器配置为删除闲置 8 小时的连接,这可能会导致类似 2013: Lost connection to MySQL server during query
的错误。默认的 pool_recycle
值 2 小时(7200 秒)用于在该超时之前重新创建连接。
引擎配置优先级 ¶
由于 Flask-SQLAlchemy 支持多个引擎,因此存在配置覆盖其他配置的规则。大多数应用程序只有一个数据库,并且只需要使用 SQLALCHEMY_DATABASE_URI
和 SQLALCHEMY_ENGINE_OPTIONS
。
- 如果将
engine_options
参数赋予SQLAlchemy
,它将为所有引擎设置默认选项。SQLALCHEMY_ECHO
为所有引擎设置echo
和echo_pool
的默认值。 SQLALCHEMY_BINDS
中每个引擎的选项都会覆盖这些默认值。SQLALCHEMY_ENGINE_OPTIONS
覆盖SQLALCHEMY_BINDS
中的None
键,而SQLALCHEMY_DATABASE_URI
覆盖该引擎选项中的url
键。
超时 ¶
某些数据库可以配置为在一段时间后关闭不活动的连接。默认情况下,MySQL 和 MariaDB 已为此配置,但数据库服务也可能配置此类限制。这可能会导致类似 2013: Lost connection to MySQL server during query
的错误。
如果遇到此错误,请尝试将引擎选项中的 pool_recycle
设置为小于数据库超时的值。
或者,如果您希望数据库经常关闭连接,例如如果它在可能重新启动的容器中运行,则可以尝试设置 pool_pre_ping
。
有关更多信息,请参阅 SQAlchemy 有关处理断开连接的文档。
模型和表格 ¶
使用 db.Model
类定义模型,或使用 db.Table
类创建表。两者都处理 Flask-SQLAlchemy 的绑定键以与特定引擎关联。
初始化基类 ¶
SQLAlchemy
2.x 为您的模型提供了几种可能的基类:DeclarativeBase 或 DeclarativeBaseNoMeta。
创建这些类之一的子类:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
如果需要,您可以通过添加 MappedAsDataclass 作为附加父类来启用 [SQLAlchemy 对数据类的原生支持](What’s New in SQLAlchemy 2.0? — SQLAlchemy 2.0 Documentation)。
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
class Base(DeclarativeBase, MappedAsDataclass):
pass
您可以选择使用自定义 MetaData
对象构造 SQLAlchemy
对象。这允许您指定自定义约束命名转换。这使得约束名称一致且可预测,在使用迁移时非常有用,如 Alembic 所描述的。
from sqlalchemy import MetaData
class Base(DeclarativeBase):
metadata = MetaData(naming_convention={
"ix": 'ix_%(column_0_label)s',
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
})
初始化扩展 ¶
定义基类后,使用 SQLAlchemy
构造函数创建 db
对象。
db = SQLAlchemy(model_class=Base)
定义模型 ¶
有关以声明方式定义模型类的完整信息,请参阅 SQLAlchemy 的声明性文档。
子类 db.Model
来创建模型类。与普通的 SQLAlchemy 不同,如果未设置 __tablename__
并且定义了主键列,Flask-SQLAlchemy 的模型将自动生成表名。
from sqlalchemy.orm import Mapped, mapped_column
class User(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(unique=True)
email: Mapped[str]
定义模型并不在数据库中创建它。定义模型和表后,使用 create_all()
创建它们。如果您在子模块中定义模型,则必须导入它们,以便 SQLAlchemy 在调用 create_all
之前了解它们。
with app.app_context():
db.create_all()
定义表 ¶
有关定义表对象的完整信息,请参阅 SQLAlchemy 的表文档。
创建 db.Table
实例来定义表。该类采用表名,然后是任何列和其他表部分,例如列和约束。与普通的 SQLAlchemy 不同, metadata
参数不是必需的。将根据 bind_key
参数选择元数据,或者使用默认值。
直接创建表的一个常见原因是定义多对多关系时。关联表不需要自己的模型类,因为它将通过相关模型上的相关关系属性来访问。
import sqlalchemy as sa
user_book_m2m = db.Table(
"user_book",
sa.Column("user_id", sa.ForeignKey(User.id), primary_key=True),
sa.Column("book_id", sa.ForeignKey(Book.id), primary_key=True),
)
反射表 ¶
如果您要连接到已有表的数据库,SQLAlchemy 可以检测该架构并自动创建包含列的表。这称为反射。这些表还可以使用 __table__
属性分配给模型类,而不是定义完整的模型。
调用扩展上的 reflect()
方法。它将反映每个绑定键的所有表。每个元数据的 tables
属性将包含检测到的表对象。有关绑定键的更多详细信息,请参阅使用绑定的多个数据库。
with app.app_context():
db.reflect()
# From the default bind key
class Book(db.Model):
__table__ = db.metadata.tables["book"]
# From an "auth" bind key
class User(db.Model):
__table__ = db.metadatas["auth"].tables["user"]
在大多数情况下,自己定义模型类会更易于维护。即使您要连接到更广泛的架构,您也只需定义实际使用的模型和列。
IDE 将了解可用的属性,并且 Alembic 等迁移工具可以检测更改并生成架构迁移。
修改和查询数据 ¶
插入、更新、删除 ¶
有关使用 ORM 修改数据的更多信息,请参阅 SQLAlchemy 的 ORM 教程和其他 SQLAlchemy 文档。
要插入数据,请将模型对象传递给 db.session.add()
:
user = User()
db.session.add(user)
db.session.commit()
要更新数据,请修改模型对象的属性:
user.verified = True
db.session.commit()
要删除数据,请将模型对象传递给 db.session.delete()
:
db.session.delete(user)
db.session.commit()
修改数据后,必须调用 db.session.commit()
将更改提交到数据库。否则,它们将在请求结束时被丢弃。
选择 ¶
有关使用 ORM 查询数据的更多信息,请参阅 SQLAlchemy 的查询指南和其他 SQLAlchemy 文档。
查询通过 db.session.execute()
执行。它们可以使用 select()
构建。执行选择会返回一个 Result
对象,该对象具有许多用于处理返回行的方法。
user = db.session.execute(db.select(User).filter_by(username=username)).scalar_one()
users = db.session.execute(db.select(User).order_by(User.username)).scalars()
视图查询 ¶
如果您编写 Flask 视图函数,则返回缺少条目的 404 Not Found
错误通常很有用。 Flask-SQLAlchemy 提供了一些额外的查询方法。
- 如果给定 id 的行不存在,
SQLAlchemy.get_or_404()
将引发 404,否则将返回实例。 - 如果查询没有返回任何结果,
SQLAlchemy.first_or_404()
将引发404,否则将返回第一个结果。 - 如果查询不完全返回一个结果,
SQLAlchemy.one_or_404()
将引发 404,否则将返回结果。
@app.route("/user-by-id/<int:id>")
def user_by_id(id):
user = db.get_or_404(User, id)
return render_template("show_user.html", user=user)
@app.route("/user-by-username/<username>")
def user_by_username(username):
user = db.one_or_404(db.select(User).filter_by(username=username))
return render_template("show_user.html", user=user)
您可以向 404 错误添加自定义消息:
user = db.one_or_404(
db.select(User).filter_by(username=username),
description=f"No user named '{username}'."
)
旧版查询接口 ¶
您可能会看到使用 Model.query
或 session.query
来构建查询。该查询接口被认为是 SQLAlchemy 中的遗留接口。更喜欢使用 session.execute(select(...))
代替。
请参阅旧版查询接口: 旧版查询接口中,Flask-SQLAlchemy 向每个模型添加一个 query
对象。这可用于查询给定模型的实例。 User.query
是 db.session.query(User)
的快捷方式。注:目前仍可使用
# get the user with id 5
user = User.query.get(5)
# get a user by username
user = User.query.filter_by(username=username).one()
分页查询结果 ¶
如果您有很多结果,您可能只想一次显示特定数量,从而允许用户单击下一个和上一个链接来查看数据页。这有时称为分页,并使用动词 paginate。
在 select 语句上调用 SQLAlchemy.paginate()
以获取 Pagination
对象。
在请求期间,这将从查询字符串 request.args
中获取 page
和 per_page
参数。传递 max_per_page
以防止用户在单个页面上请求太多结果。如果未给出,默认值为第 1 页,每页 20 个项目。
page = db.paginate(db.select(User).order_by(User.join_date))
return render_template("user/list.html", page=page)
显示项目 ¶
Pagination
对象的 Pagination.items
属性是当前页面的项目列表。也可以直接迭代该对象。
<ul>
{% for user in page %}
<li>{{ user.username }}
{% endfor %}
</ul>
页面选择小部件 ¶
Pagination
对象具有可用于通过迭代页码并检查当前页面来创建页面选择小部件的属性。 iter_pages()
将生成最多三组数字,以 None
分隔。默认情况下,在任一边缘显示 2 个页码,当前页前、当前页前 2 个页码,当前页后 4 个页码。例如,如果有 20 页且当前页为 7,则将生成以下值。
users.iter_pages()
[1, 2, None, 5, 6, 7, 8, 9, 10, 11, None, 19, 20]
您可以使用 total
属性显示结果总数,使用 first
和 last
属性显示当前页面上的项目范围。
以下 Jinja 宏呈现一个简单的分页小部件。
{% macro render_pagination(pagination, endpoint) %}
<div class="page-items">
{{ pagination.first }} - {{ pagination.last }} of {{ pagination.total }}
</div>
<div class=pagination>
{% for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{% endfor %}
</div>
{% endmacro %}
Flask 应用上下文 ¶
需要活动的 Flask 应用程序上下文才能进行查询并访问 db.engine
和 db.session
。这是因为会话的范围仅限于上下文,以便在每次请求或 CLI 命令后都会正确清理它。
无论应用程序如何使用扩展进行初始化,都不会存储它以供以后使用。相反,扩展使用 Flask 的 current_app
代理来获取活动应用程序,这需要活动应用程序上下文。
自动上下文 ¶
当 Flask 处理请求或 CLI 命令时,将自动推送应用程序上下文。因此,您无需执行任何特殊操作即可在请求或 CLI 命令期间使用数据库。
手动上下文 ¶
如果您在应用程序上下文不活动时尝试使用数据库,您将看到以下错误。
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
如果您发现自己处于需要数据库但没有上下文的情况,您可以使用 app_context
推送数据库。例如,在调用 db.create_all
创建表时,这种情况很常见。
def create_app():
app = Flask(__name__)
app.config.from_object("project.config")
import project.models
with app.app_context():
db.create_all()
return app
测试 ¶
如果您使用 Flask 测试客户端向端点发出请求来测试应用程序,则上下文将作为请求的一部分提供。
如果您需要直接测试有关数据库或模型的某些内容,而不是通过请求,则需要手动推送上下文。
仅将上下文推送到每次测试所需的位置和持续时间。不要为每个测试全局推送应用程序上下文,因为这可能会干扰会话的清理方式。
def test_user_model(app):
user = User()
with app.app_context():
db.session.add(user)
db.session.commit()
如果您发现自己编写了许多这样的测试,则可以使用 pytest 夹具来推送特定测试的上下文。
import pytest
@pytest.fixture
def app_ctx(app):
with app.app_context():
yield
@pytest.mark.usefixtures("app_ctx")
def test_user_model():
user = User()
db.session.add(user)
db.session.commit()
SQLAlchemy 一次可以连接到多个数据库。它将不同的引擎称为“绑定”。 Flask-SQLAlchemy 通过将每个引擎与一个短字符串(“绑定键”)相关联,然后将每个模型和表与绑定键相关联,简化了绑定的工作方式。
会话将根据正在查询的事物的绑定键选择用于查询的引擎。如果未给出绑定密钥,则使用默认引擎。
配置绑定 ¶
默认绑定仍然通过为任何引擎选项设置 SQLALCHEMY_DATABASE_URI
和 SQLALCHEMY_ENGINE_OPTIONS
来配置。其他绑定在 SQLALCHEMY_BINDS
中给出,这是一个将绑定键映射到引擎 URL 的字典。要指定绑定的引擎选项,该值可以是带有 "url"
键的引擎选项字典,而不仅仅是 URL 字符串。
SQLALCHEMY_DATABASE_URI = "postgresql:///main"
SQLALCHEMY_BINDS = {
"meta": "sqlite:////path/to/meta.db",
"auth": {
"url": "mysql://localhost/users",
"pool_recycle": 3600,
},
}
使用绑定定义模型和表 ¶
Flask-SQLAlchemy 将为每个配置的绑定创建元数据和引擎。具有绑定键的模型和表将使用相应的元数据注册,并且会话将使用相应的引擎查询它们。
要设置模型的绑定,请设置 __bind_key__
类属性。不设置绑定键相当于将其设置为默认键 None
。
class User(db.Model):
__bind_key__ = "auth"
id = db.Column(db.Integer, primary_key=True)
从该模型继承的模型将共享相同的绑定键,或者可以覆盖它。
要设置表的绑定,请传递 bind_key
关键字参数。
user_table = db.Table(
"user",
db.Column("id", db.Integer, primary_key=True),
bind_key="auth",
)
最终,会话在与模型或表关联的元数据上查找绑定键。这种关联发生在创建过程中。因此,在创建模型或表后更改绑定键将不会产生任何效果。
访问元数据和引擎 ¶
您可能需要检查绑定的元数据或引擎。请注意,您应该通过会话执行查询,而不是直接在引擎上执行。
默认引擎是 SQLAlchemy.engine
,默认元数据是 SQLAlchemy.metadata
。 SQLAlchemy.engines
和 SQLAlchemy.metadatas
是映射所有绑定键的字典。
创建和删除表 ¶
默认情况下, create_all()
和 drop_all()
方法对所有绑定进行操作。这些方法的 bind_key
参数可以是字符串或 None
来操作单个绑定,也可以是字符串列表或 None
来操作子集绑定。由于这些方法访问引擎,因此必须在应用程序上下文中调用它们。
# create tables for all binds
db.create_all()
# create tables for the default and "auth" binds
db.create_all(bind_key=[None, "auth"])
# create tables for the "meta" bind
db.create_all(bind_key="meta")
# drop tables for the default bind
db.drop_all(bind_key=None)
记录查询信息 ¶
警告
此功能仅用于调试。
Flask-SQLAlchemy 可以记录请求期间执行的每个查询的一些信息。然后可以检索该信息以帮助调试性能。
例如,它可以揭示关系执行了太多的单独选择,或者揭示了花费很长时间的查询。
要启用此功能,请在 Flask 应用程序配置中将 SQLALCHEMY_RECORD_QUERIES
设置为 True
。使用get_recorded_queries()
获取查询信息对象的列表。每个对象都有以下属性:
-
statement
SQLAlchemy 生成的带有参数占位符的 SQL 字符串。
-
parameters
与 SQL 语句一起发送的参数。
-
start_time
/end_time
有关查询何时开始执行以及何时返回结果的计时信息。准确性和价值取决于操作系统。
-
duration
查询花费的时间(以秒为单位)。
-
location
跟踪变更 ¶
警告
跟踪变更会增加大量开销。在大多数情况下,直接使用 SQLAlchemy 事件会为您提供更好的服务。
Flask-SQLAlchemy 可以设置其会话来跟踪模型的插入、更新和删除,然后在调用 session.flush()
和 session.commit()
.
要启用此功能,请在 Flask 应用程序配置中设置 SQLALCHEMY_TRACK_MODIFICATIONS
。然后向 models_committed
(提交后发出)或 before_models_committed
(提交前发出)添加监听器。
from flask_sqlalchemy.track_modifications import models_committed
def get_modifications(sender: Flask, changes: list[tuple[t.Any, str]]) -> None:
...
models_committed.connect(get_modifications)
高级定制
可以通过将参数传递给 SQLAlchemy
构造函数来自定义扩展管理的各种对象。
Model Class 模型类 ¶
SQLAlchemy 模型全部继承自声明性基类。这在 Flask-SQLAlchemy 中公开为 db.Model
,所有模型都扩展了它。这可以通过对默认类进行子类化并将自定义类传递给 model_class
来自定义。
以下示例为每个模型提供一个整数主键或用于连接表继承的外键。
注意:
所有内容的整数主键不一定是最好的数据库设计(这取决于您的项目的要求),这只是一个示例。
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr
class Base(DeclarativeBase):
@declared_attr.cascading
@classmethod
def id(cls):
for base in cls.__mro__[1:-1]:
if getattr(base, "__table__", None) is not None:
return mapped_column(ForeignKey(base.id), primary_key=True)
else:
return mapped_column(Integer, primary_key=True)
db = SQLAlchemy(app, model_class=Base)
class User(db.Model):
name: Mapped[str]
class Employee(User):
title: Mapped[str]
抽象模型和杂糅 Mixins ¶
如果仅某些模型而不是所有模型需要行为,请使用抽象模型基类仅自定义这些模型。例如,某些模型是否应该跟踪它们的创建或更新时间。
from datetime import datetime, timezone
from sqlalchemy.orm import Mapped, mapped_column
class TimestampModel(db.Model):
__abstract__ = True
created: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))
updated: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
class Author(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(unique=True)
class Post(TimestampModel):
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
这也可以通过单独继承自 db.Model
的 mixin 类来完成。
class TimestampMixin:
created: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))
updated: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
class Post(TimestampMixin, db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
禁用表名称生成 ¶
有些项目更喜欢手动设置每个模型的 __tablename__
,而不是依赖于 Flask-SQLAlchemy 的检测和生成。实现这一点的简单方法是设置每个 __tablename__
并且不修改基类。但是,可以通过在 SQLAlchemy 构造函数中设置disable_autonaming=True 来禁用表名生成。
class Base(sa_orm.DeclarativeBase):
pass
db = SQLAlchemy(app, model_class=Base, disable_autonaming=True)
Session Class 会话类 ¶
Flask-SQLAlchemy 的 Session
类根据与模型或表关联的绑定键选择要查询的引擎。但是,还有其他策略,例如可以使用不同的会话类来实现水平分片。扩展的 session_options
参数的 class_
键用于更改会话类。
Flask-SQLAlchemy 始终将扩展实例作为 db
参数传递给会话,因此它必须接受该实例才能继续工作。这可用于访问 db.engines
。
from sqlalchemy.ext.horizontal_shard import ShardedSession
from flask_sqlalchemy.session import Session
class CustomSession(ShardedSession, Session):
...
db = SQLAlchemy(session_options={"class_": CustomSession})
Query查询类 ¶
警告
查询接口被认为是 SQLAlchemy 中的遗留接口。这包括 session.query
、 Model.query
、 db.Query
和 lazy="dynamic"
关系。更喜欢使用 session.execute(select(...))
代替。
可以定制会话、模型和关系使用的查询接口。这可用于添加额外的查询方法。例如,您可以添加一个 get_or
方法来获取行或返回默认值。
from flask_sqlalchemy.query import Query
class GetOrQuery(Query):
def get_or(self, ident, default=None):
out = self.get(ident)
if out is None:
return default
return out
db = SQLAlchemy(query_class=GetOrQuery)
user = User.query.get_or(user_id, anonymous_user)
传递 query_class
参数将自定义 db.Query
、 db.session.query
、 Model.query
和 db.relationship(lazy="dynamic")
关系。还可以针对每个对象进行自定义。
要自定义特定模型的 query
属性,请在模型类上设置 query_class
属性。
class User(db.Model):
query_class = GetOrQuery
要自定义特定的动态关系,请将 query_class
参数传递给该关系。
db.relationship(User, lazy="dynamic", query_class=GetOrQuery)
要仅自定义 session.query
,请将 query_cls
键传递给构造函数的 session_options
参数。
db = SQLAlchemy(session_options={"query_cls": GetOrQuery})