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”前缀的扩展名称。
添加行为¶
扩展可以通过多种方式添加行为。 Flask
对象上可用的任何设置方法都可以在扩展的 init_app
方法期间使用。
一种常见的模式是使用 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
,则应在 requirements 文件中指定测试依赖项。 测试必须是 sdist 发行版的一部分。PyPI 元数据或自述文件中必须包含指向文档或项目网站的链接。 文档应使用 Official Pallets Themes 中的 Flask 主题。
扩展的依赖项不应使用上限或假定任何特定的版本方案,而应使用下限来指示最低兼容性支持。 例如,
sqlalchemy>=1.4
。使用
python_requires=">=version"
指示支持的 Python 版本。 截至 2024 年 10 月,Flask 本身支持 Python >=3.9,并且这将随着时间的推移而更新。