Flask 扩展开发¶
扩展是为 Flask 应用程序添加功能的附加包。虽然 PyPI 包含许多 Flask 扩展,但你可能找不到符合你需求的扩展。如果是这种情况,你可以创建自己的扩展,并将其发布以供其他人使用。
本指南将展示如何创建 Flask 扩展,以及涉及的一些常见模式和要求。由于扩展可以执行任何操作,因此本指南无法涵盖所有可能性。
了解扩展的最佳方法是查看你使用的其他扩展是如何编写的,并与他人讨论。在我们的 Discord 聊天室 或 GitHub 讨论 中与他人讨论你的设计理念。
最好的扩展共享一些共同模式,以便熟悉使用一个扩展的任何人都不会在使用另一个扩展时感到完全迷失。只有在早期进行协作才能实现这一点。
命名¶
Flask 扩展通常在其名称中包含 flask
作为前缀或后缀。如果它包装另一个库,它还应该包含库名称。这使得搜索扩展变得容易,并使它们的用途更加清晰。
一个通用的 Python 打包建议是,包索引中的安装名称和 import
语句中使用的名称应该相关。导入名称为小写,单词之间用下划线 (_
) 分隔。安装名称为小写或标题大小写,单词之间用破折号 (-
) 分隔。如果它包装另一个库,则优先使用与该库名称相同的大小写。
以下是一些示例安装和导入名称
Flask-Name
导入为flask_name
flask-name-lower
导入为flask_name_lower
Flask-ComboName
导入为flask_comboname
Name-Flask
导入为name_flask
扩展类和初始化¶
所有扩展都需要某个入口点,以便使用应用程序初始化扩展。最常见的模式是创建一个类来表示扩展的配置和行为,并使用 init_app
方法将扩展实例应用到给定的应用程序实例。
class HelloExtension:
def __init__(self, app=None):
if app is not None:
self.init_app(app)
def init_app(self, app):
app.before_request(...)
应用程序不存储在扩展中非常重要,不要执行 self.app = app
。扩展唯一可以直接访问应用程序的时间是在 init_app
期间,否则它应该使用 current_app
。
这允许扩展支持应用程序工厂模式,避免在用户代码的其他位置导入扩展实例时出现循环导入问题,并使使用不同配置进行测试变得更容易。
hello = HelloExtension()
def create_app():
app = Flask(__name__)
hello.init_app(app)
return app
在上面,hello
扩展实例独立于应用程序存在。这意味着用户项目中的其他模块可以执行 from project import hello
,并在应用程序存在之前在蓝图中使用扩展。
可以使用 Flask.extensions
字典将对扩展的引用存储在应用程序上,或将某些其他状态存储在特定于应用程序上。请注意,这是一个单一名称空间,因此请使用对扩展唯一的名称,例如不带“flask”前缀的扩展名称。
添加行为¶
扩展可以通过多种方式添加行为。在扩展的 init_app
方法中,可以使用 Flask
对象上可用的任何设置方法。
一种常见模式是使用 before_request()
在每个请求的开始初始化一些数据或连接,然后使用 teardown_request()
在结束时清理它。这可以存储在 g
上,下面将对此进行更详细的讨论。
一种更懒惰的方法是提供一个初始化和缓存数据或连接的方法。例如,ext.get_db
方法可以在首次调用时创建一个数据库连接,以便不使用数据库的视图不会创建连接。
除了在每个视图之前和之后执行某些操作之外,扩展可能还需要添加一些特定视图。在这种情况下,可以定义一个 Blueprint
,然后在 init_app
期间调用 register_blueprint()
将蓝图添加到应用程序中。
配置技术¶
扩展的配置可以有多个级别和来源。你应该考虑扩展的哪些部分属于每一部分。
通过
app.config
值进行每个应用程序实例的配置。这是对于应用程序的每次部署都可能合理更改的配置。一个常见的示例是外部资源的 URL,例如数据库。配置键应以扩展名开头,以便它们不会干扰其他扩展。通过
__init__
参数进行每个扩展实例的配置。此配置通常会影响扩展的使用方式,因此在每次部署中更改它没有任何意义。通过实例属性和装饰器方法进行每个扩展实例的配置。在创建扩展实例后,将值分配给
ext.value
或使用@ext.register
装饰器来注册函数可能会更符合人体工程学。通过类属性进行全局配置。更改类属性(如
Ext.connection_class
)可以自定义默认行为,而无需创建子类。这可以与每个扩展配置相结合以覆盖默认值。子类化和重写方法和属性。使扩展本身的 API 成为可以重写的内容,为高级自定义提供了一个非常强大的工具。
Flask
对象本身使用所有这些技术。
根据你的需要和想要支持的内容,由你决定哪种配置适合你的扩展。
在应用程序设置阶段完成后并且服务器开始处理请求后,不应更改配置。配置是全局的,对其进行的任何更改都不保证对其他工作进程可见。
请求期间的数据¶
在编写 Flask 应用程序时,g
对象用于在请求期间存储信息。例如,教程将与 SQLite 数据库的连接存储为 g.db
。扩展也可以谨慎使用此功能。由于 g
是一个单一的全局命名空间,因此扩展必须使用不会与用户数据冲突的唯一名称。例如,使用扩展名称作为前缀或作为命名空间。
# an internal prefix with the extension name
g._hello_user_id = 2
# or an internal prefix as a namespace
from types import SimpleNamespace
g._hello = SimpleNamespace()
g._hello.user_id = 2
g
中的数据持续存在于应用程序上下文中。当请求上下文处于活动状态时,或当 CLI 命令运行时,应用程序上下文处于活动状态。如果你要存储应该关闭的内容,请使用 teardown_appcontext()
以确保在应用程序上下文结束时关闭它。如果它只应在请求期间有效,或在请求之外的 CLI 中不会使用,请使用 teardown_request()
。
视图和模型¶
您的扩展视图可能希望与数据库中的特定模型或连接到您的应用程序的其他扩展或数据进行交互。例如,让我们考虑一个 Flask-SimpleBlog
扩展,它与 Flask-SQLAlchemy 一起使用,以提供 Post
模型和视图来编写和阅读帖子。
Post
模型需要子类化 Flask-SQLAlchemy db.Model
对象,但只有在您创建了该扩展的实例后才能使用它,而不是在您的扩展定义其视图时。那么,在模型存在之前定义的视图代码如何访问模型?
一种方法是使用 基于类的视图。在 __init__
期间,创建模型,然后通过将模型传递给视图类的 as_view()
方法来创建视图。
class PostAPI(MethodView):
def __init__(self, model):
self.model = model
def get(self, id):
post = self.model.query.get(id)
return jsonify(post.to_json())
class BlogExtension:
def __init__(self, db):
class Post(db.Model):
id = db.Column(primary_key=True)
title = db.Column(db.String, nullable=False)
self.post_model = Post
def init_app(self, app):
api_view = PostAPI.as_view(model=self.post_model)
db = SQLAlchemy()
blog = BlogExtension(db)
db.init_app(app)
blog.init_app(app)
另一种技术是使用扩展上的属性,例如上面 self.post_model
。在 init_app
中将扩展添加到 app.extensions
,然后从视图中访问 current_app.extensions["simple_blog"].post_model
。
您可能还想提供基类,以便用户可以提供自己的 Post
模型,该模型符合您的扩展预期的 API。因此,他们可以实现 class Post(blog.BasePost)
,然后将其设置为 blog.post_model
。
如您所见,这可能会变得有点复杂。不幸的是,这里没有完美的解决方案,只有根据您的需求和您希望提供的自定义程度的不同策略和权衡。幸运的是,大多数扩展并不需要这种资源依赖性。请记住,如果您需要设计方面的帮助,请在我们的 Discord 聊天 或 GitHub 讨论 中提问。
推荐的扩展指南¶
Flask 以前有“已批准扩展”的概念,其中 Flask 维护者在列出扩展之前评估了扩展的质量、支持和兼容性。虽然随着时间的推移,该列表变得难以维护,但这些准则仍然与当今维护和开发的所有扩展相关,因为它们有助于 Flask 生态系统保持一致性和兼容性。
扩展需要维护者。如果扩展作者希望超越项目,则该项目应找到一位新的维护者,并转移对存储库、文档、PyPI 和任何其他服务的访问权限。GitHub 上的 Pallets-Eco 组织允许社区维护,并由 Pallets 维护者监督。
命名方案是 Flask-ExtensionName 或 ExtensionName-Flask。它必须提供一个名为
flask_extension_name
的包或模块。扩展必须使用开源许可证。Python Web 生态系统倾向于首选 BSD 或 MIT。它必须是开源的并且公开可用。
扩展的 API 必须具有以下特征
它必须支持在同一 Python 进程中运行多个应用程序。使用
current_app
代替self.app
,为每个应用程序实例存储配置和状态。必须能够使用工厂模式创建应用程序。使用
ext.init_app()
模式。
从存储库的克隆中,带有其依赖项的扩展必须能够使用
pip install -e .
以可编辑模式进行安装。它必须提供可以使用通用工具调用的测试,例如
tox -e py
、nox -s test
或pytest
。如果不使用tox
,则测试依赖项应在需求文件中指定。测试必须是 sdist 分发的组成部分。指向文档或项目网站的链接必须位于 PyPI 元数据或自述文件中。文档应使用来自 官方 Pallets 主题 的 Flask 主题。
扩展的依赖项不应使用上限或假定任何特定版本方案,但应使用下限来表示最低兼容性支持。例如,
sqlalchemy>=1.4
。使用
python_requires=">=version"
指示支持的 Python 版本。截至 2023 年 4 月,Flask 本身支持 Python >=3.8,但此版本会随着时间的推移而更新。