Flask扩展开发

扩展是为FlaskTM应用程序添加功能的额外程序包。而当 PyPI 包含许多烧瓶扩展,您可能找不到一个符合您的需要。如果是这种情况,您可以创建自己的文档,并将其发布以供他人使用。

本指南将展示如何创建一个FlASK扩展,以及涉及到的一些常见模式和要求。因为扩展可以做任何事情,所以本指南不能涵盖所有的可能性。

了解扩展的最好方法是查看您使用的其他扩展是如何编写的,并与其他人讨论。在我们的网站上与其他人讨论您的设计理念 Discord ChatGitHub Discussions

最好的扩展都有共同的模式,因此熟悉使用一个扩展的任何人都不会觉得完全迷失了另一个扩展。这只有在合作进行得早的情况下才能奏效。

命名

烧瓶扩展通常具有 flask 在其名称中作为前缀或后缀。如果它包装了另一个库,它还应该包括库名。这使得搜索扩展名变得很容易,并且使它们的目的更加明确。

一般的Python打包建议是包索引中的安装名称和中使用的名称 import 陈述应该是相关的。导入名称为小写,单词之间用下划线分隔 (_ )。安装名称为小写或标题大小写,单词之间用短划线分隔 (- )。如果它包装了另一个库,则更喜欢使用与该库名称相同的大小写。

以下是一些安装和导入名称示例:

  • Flask-Name imported as flask_name

  • flask-name-lower imported as flask_name_lower

  • Flask-ComboName imported as flask_comboname

  • Name-Flask imported as 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 Dict可用于存储对应用程序上的扩展的引用,或存储特定于应用程序的某些其他状态。请注意,这是一个单一的名称空间,因此请使用您的扩展名唯一的名称,例如不带“flask”前缀的扩展名。

添加行为

扩展可以通过多种方式添加行为。上可用的任何设置方法 Flask 对象可以在扩展的 init_app 方法。

一种常见的模式是使用 before_request() 要在每个请求开始时初始化一些数据或连接,则 teardown_request() 在最后把它清理干净。可以将其存储在 g ,下面将讨论更多内容。

一种更懒惰的方法是提供一个初始化和缓存数据或连接的方法。例如,一个 ext.get_db 方法可以在第一次调用时创建数据库连接,因此不使用数据库的视图不会创建连接。

除了在每个视图之前和之后执行一些操作外,您的扩展可能还希望添加一些特定的视图。在这种情况下,您可以定义一个 Blueprint ,然后呼叫 register_blueprint() 在.期间 init_app 将蓝图添加到应用程序中。

配置技术

一个分机可以有多个级别和来源的配置。您应该考虑扩展的哪些部分属于每个扩展。

  • 每个应用程序实例的配置,通过 app.config 价值观。对于应用程序的每次部署,这一配置可能会合理地更改。一个常见的例子是指向外部资源(如数据库)的URL。配置密钥应该以扩展名开头,这样它们就不会干扰其他扩展名。

  • 每个扩展实例的配置,通过 __init__ 争论。这种配置通常会影响扩展的使用方式,因此每次部署更改它都没有意义。

  • 通过实例属性和修饰符方法为每个扩展实例进行配置。分配给它可能更符合人体工程学 ext.value ,或使用 @ext.register 在创建扩展实例之后注册函数的修饰符。

  • 通过类属性进行全局配置。更改类属性,如 Ext.connection_class 无需生成子类即可自定义默认行为。这可以结合每个分机配置来覆盖默认设置。

  • 子类化和重写方法和属性。使扩展本身的API成为可以覆盖的东西,这为高级定制提供了一个非常强大的工具。

这个 Flask 对象本身使用所有这些技术。

根据您的需要和想要支持的内容,您可以自行决定适合您的扩展的配置。

在应用程序设置阶段完成且服务器开始处理请求后,不应更改配置。配置是全局的,对其所做的任何更改都不能保证对其他工作进程可见。

请求期间的数据

在编写FlaskTM应用程序时, g 对象用于在请求期间存储信息。例如, tutorial 将与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 与FlaskSQLAlChemy配合使用的扩展,以提供 Post 用于写和读帖子的模型和视图。

这个 Post 模型需要将Flask子类化为SQLAlChemy db.Model 对象,但这仅在创建了该扩展的实例后才可用,而不是在扩展定义其视图时可用。那么,在模型存在之前定义的视图代码如何访问模型呢?

一种方法可以是使用 基于类的视图 。在.期间 __init__ ,创建模型,然后通过将模型传递给view类的 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 从上面看。将扩展名添加到 app.extensions 在……里面 init_app ,然后访问 current_app.extensions["simple_blog"].post_model 从视图。

您可能还想提供基类,以便用户可以提供他们自己的 Post 符合您的扩展期望的API的模型。这样他们就可以实施 class Post(blog.BasePost) ,然后将其设置为 blog.post_model

正如您所看到的,这可能会变得有点复杂。不幸的是,这里没有完美的解决方案,只有不同的策略和权衡,这取决于您的需求和您想要提供的定制程度。幸运的是,这种资源依赖并不是大多数扩展的常见需求。请记住,如果您在设计方面需要帮助,请联系我们的 Discord ChatGitHub Discussions