Flask拡張の開発 Flask Extension Development

Flask拡張は、Flaskアプリケーションに機能を加える追加パッケージです。PyPIが多くのFlask拡張を含みますが、あなたのニーズに合うものは見つからないかもしれません。もしそのような場合、自分のものを作成し、自分以外も同様に使えるよう公開することができます。 Extensions are extra packages that add functionality to a Flask application. While `PyPI`_ contains many Flask extensions, you may not find one that fits your need. If this is the case, you can create your own, and publish it for others to use as well.

このガイドは、どのようにFlask拡張を作成するか、さらに関連するいくつかのよくあるパターンと要求事項を示します。Flask拡張はどのようなものでもあり得るため、このガイドで全ての可能性を網羅するのは不可能でしょう。 This guide will show how to create a Flask extension, and some of the common patterns and requirements involved. Since extensions can do anything, this guide won't be able to cover every possibility.

Flask拡張について学ぶ最良の方法は、自分が使っている他のFlask拡張がどのように書かれているかを調べ、他の人と論じることです。私たちのDiscord ChatまたはGitHub Discussionsで、自分のデザインのアイデアを他の人と論じてください。 The best ways to learn about extensions are to look at how other extensions you use are written, and discuss with others. Discuss your design ideas with others on our `Discord Chat`_ or `GitHub Discussions`_.

最良のFlask拡張は、1つのFlask拡張に慣れた人は誰でも、別のFlask拡張では完全に途方に暮れるようなことが無いように、よくあるパターンを共有しています。This can only work if collaboration happens early. The best extensions share common patterns, so that anyone familiar with using one extension won't feel completely lost with another. This can only work if collaboration happens early.

命名 Naming

Flask拡張は典型的には名前の先頭もしくは末尾にflaskがあります。もし他のライブラリを包んでいる(wrap)場合、同様に名前にもそのライブラリ名を含むべきです。これはFlask拡張の検索を容易にし、その目的もより明確にします。 A Flask extension typically has ``flask`` in its name as a prefix or suffix. If it wraps another library, it should include the library name as well. This makes it easy to search for extensions, and makes their purpose clearer.

一般的なPythonのパッケージングの推奨では、インストールに使う名前はパッケージのインデックス(訳注: PyPI - Python Package Indexに登録する名前とほぼ同じ意味合いだと思います)に由来し、import文に使われる名前は(インストールに使う名前に)関連するものにするべきです。import名は小文字で、単語をアンダースコア(_)で分けます。インストール名は小文字かタイトルのやり方(訳注: 先頭だけ大文字にして、あとは小文字)にし、単語はダッシュ(-)で分けます。もし他のライブラリを包む場合、そのライブラリの名前と同じやり方を使うのが好ましいです。 A general Python packaging recommendation is that the install name from the package index and the name used in ``import`` statements should be related. The import name is lowercase, with words separated by underscores (``_``). The install name is either lower case or title case, with words separated by dashes (``-``). If it wraps another library, prefer using the same case as that library's name.

以下は、いくつかのインストール名とimport名の例です。 Here are some example install and import names:

  • Flask-Nameflask_nameとしてimportされます ``Flask-Name`` imported as ``flask_name``

  • flask-name-lowerflask_name_lowerとしてimportされます ``flask-name-lower`` imported as ``flask_name_lower``

  • Flask-ComboNameflask_combonameとしてimportされます ``Flask-ComboName`` imported as ``flask_comboname``

  • Name-Flaskname_flaskとしてimportされます ``Name-Flask`` imported as ``name_flask``

Flask拡張のクラスと初期化 The Extension Class and Initialization

全てのFlask拡張は、Flaskアプリを使ってFlask拡張を初期化する、ある種のエントリーポイントが必要になります。最もよくあるパターンは、与えられたFlaskアプリケーションのインスタンスにFlask拡張を適用するinit_appメソッドを持つ、Flask拡張の設定と振舞を表現するクラスを作成することです。 All extensions will need some entry point that initializes the extension with the application. The most common pattern is to create a class that represents the extension's configuration and behavior, with an ``init_app`` method to apply the extension instance to the given application instance.

class HelloExtension:
    def __init__(self, app=None):
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.before_request(...)

そのFlaskアプリはFlask拡張には格納されないことが重要であり、self.app = appは実行しません。Flask拡張がFlaskアプリのインスタンスに直接アクセスする唯一の時は、init_appの間であり、それ以外はcurrent_appを使うべきです。 It is important that the app is not stored on the extension, don't do ``self.app = app``. The only time the extension should have direct access to an app is during ``init_app``, otherwise it should use :data:`current_app`.

これは、Flask拡張がapplication factoryのパターンをサポートできるようにし、ユーザのコードのどこからでもFlask拡張のインスタンスをimportする時に循環importの問題を回避し、異なる設定を使ったテストをより容易にします。 This allows the extension to support the application factory pattern, avoids circular import issues when importing the extension instance elsewhere in a user's code, and makes testing with different configurations easier.

hello = HelloExtension()

def create_app():
    app = Flask(__name__)
    hello.init_app(app)
    return app

上の例では、Flask拡張のインスタンスのhelloは、Flaskアプリケーションとは独立して存在しています。これは、ユーザのプロジェクトの中のその他のモジュールは、from project import helloを実行でき、そのFlask拡張をFlaskアプリのインスタンスが存在する前からblueprintの中で使えることを意味します。 Above, the ``hello`` extension instance exists independently of the application. This means that other modules in a user's project can do ``from project import hello`` and use the extension in blueprints before the app exists.

The Flask.extensions dict can be used to store a reference to the extension on the application, or some other state specific to the application. Be aware that this is a single namespace, so use a name unique to your extension, such as the extension's name without the "flask" prefix.

振舞の追加 Adding Behavior

Flask拡張は多くの方法で振る舞いを追加できます。Flask拡張のinit_appメソッドの処理中は、Flaskオブジェクト上で利用可能なあらゆる設定メソッド(setup method)を使用できます。 There are many ways that an extension can add behavior. Any setup methods that are available on the :class:`Flask` object can be used during an extension's ``init_app`` method.

よくあるパターンは、各リクエストの開始時にある種のデータまたは接続を初期化するためにbefore_request()を使い、それからリクエストの終了時にそれらをきれいにするためにteardown_request()を使うことです。これ(初期化したある種のデータや接続)は、gに格納でき、以下でさらに論じます。 A common pattern is to use :meth:`~Flask.before_request` to initialize some data or a connection at the beginning of each request, then :meth:`~Flask.teardown_request` to clean it up at the end. This can be stored on :data:`g`, discussed more below.

さらに(データの初期化などを)遅らせるアプローチは、データまたは接続を初期化しキャッシュするメソッドを提供することです。例えば、viewがデータベースを使わないときはデータベース接続を作成しないようにするために、ext.get_dbメソッドが、最初に呼び出されたときにデータベース接続を作成するようにできます。 A more lazy approach is to provide a method that initializes and caches the data or connection. For example, a ``ext.get_db`` method could create a database connection the first time it's called, so that a view that doesn't use the database doesn't create a connection.

すべてのviewの前および後に何かを行う他に、さらにFlask拡張である種の特定のviewを追加したくなるかもしれません。この場合、Blueprintを定義し、それからinit_appの間にregister_blueprint()を呼び出してblueprintをFlaskアプリのインスタンスに加えることができます。 Besides doing something before and after every view, your extension might want to add some specific views as well. In this case, you could define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint` during ``init_app`` to add the blueprint to the app.

設定の技法(Configuration Techniques) Configuration Techniques

There can be multiple levels and sources of configuration for an extension. You should consider what parts of your extension fall into each one.

  • Flaskアプリケーションのインスタンス毎の設定で、app.configの値を通すもの。これは、アプリケーションの各デプロイ用に合理的に変更できる設定です。よくある例は、データベースのような外部リソースへのURLです。設定のキーは、他のFlask拡張を邪魔しないように、Flask拡張の名前で始めるべきです。 Configuration per application instance, through ``app.config`` values. This is configuration that could reasonably change for each deployment of an application. A common example is a URL to an external resource, such as a database. Configuration keys should start with the extension's name so that they don't interfere with other extensions.

  • Flask拡張のインスタンス毎の設定で、__init__の引数を通すもの。この設定は普通は、デプロイごとに変えるのは合理的ではないような、Flask拡張がどのように使われるかに影響します Configuration per extension instance, through ``__init__`` arguments. This configuration usually affects how the extension is used, such that it wouldn't make sense to change it per deployment.

  • Flask拡張のインスタンス毎の設定で、インスタンスの属性およびデコレータのメソッドを通すもの。It might be more ergonomic to assign to ext.value, or use a @ext.register decorator to register a function, after the extension instance has been created. Configuration per extension instance, through instance attributes and decorator methods. It might be more ergonomic to assign to ``ext.value``, or use a ``@ext.register`` decorator to register a function, after the extension instance has been created.

  • クラスの属性を通すグローバルな設定。Ext.connection_classのようなクラス属性の変更は、サブクラスを作成せずに標準設定の振る舞いをカスタマイズできます。これは、標準設定を上書きするFlask拡張ごとの設定と組み合わせることができます。 Global configuration through class attributes. Changing a class attribute like ``Ext.connection_class`` can customize default behavior without making a subclass. This could be combined per-extension configuration to override defaults.

  • サブクラスの作成とメソッドおよび属性の上書き。Flask拡張自身のAPIを上書きできるものにすると、高度なカスタマイズ用の非常に強力なツールを提供します。 Subclassing and overriding methods and attributes. Making the API of the extension itself something that can be overridden provides a very powerful tool for advanced customization.

Flaskオブジェクト自身はこれらの技法をすべて使っています。 The :class:`~flask.Flask` object itself uses all of these techniques.

It's up to you to decide what configuration is appropriate for your extension, based on what you need and what you want to support.

Flaskアプリケーションの準備段階(setup phase)が完了し、サーバがリクエストの処理を始めた後では、設定は変更するべきではありません。設定はグローバルなものであり、設定へのあらゆる変更は他のworkerから見えるとは保証されないものです。 Configuration should not be changed after the application setup phase is complete and the server begins handling requests. Configuration is global, any changes to it are not guaranteed to be visible to other workers.

リクエスト処理中のデータ Data During a Request

Flaskアプリケーションを書いているとき、リクエスト処理中の情報を格納するためにgオブジェクトが使われます。例えば、チュートリアルではSQLiteデータベースへの接続をg.dbとして格納しています。Flask拡張もいくらかの注意を伴いつつ、gオブジェクトを使えます。gは1つだけのグローバルな名前空間であるため、Flask拡張はユーザのデータと衝突しないように固有の名前を使う必要があります。例えば、(以下のコード例のように)接頭辞もしくは名前空間にFlask拡張の名前を使います。 When writing a Flask application, the :data:`~flask.g` object is used to store information during a request. For example the :doc:`tutorial <tutorial/database>` stores a connection to a SQLite database as ``g.db``. Extensions can also use this, with some care. Since ``g`` is a single global namespace, extensions must use unique names that won't collide with user data. For example, use the extension name as a prefix, or as a namespace.

# 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の中のデータは、application contextが有効な間は存在します。application contextはrequest contextが有効な間、もしくはCLIのコマンドが実行されているときは有効です。もし閉じるべき何かを格納した場合、application contextが終了するときに確実にそれらが閉じられるように、teardown_appcontext()を使用します。もしgに格納したものがリクエストの処理中だけ正当であるか、リクエストの外側のCLIでは使用されないものである場合、teardown_request()を使います。 The data in ``g`` lasts for an application context. An application context is active when a request context is, or when a CLI command is run. If you're storing something that should be closed, use :meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed when the application context ends. If it should only be valid during a request, or would not be used in the CLI outside a reqeust, use :meth:`~flask.Flask.teardown_request`.

viewとモデル Views and Models

Flask拡張のviewがあなたのデータベース内の特定のモデル、もしくはなにか別のFlask拡張または自分のアプリケーションに接続されたデータとやり取りしたくなるかもしれません。例えば、投稿を読み書きするPostモデルとviewを提供するためにFlask-SQLAlchemyと一緒に働く、Flask-SimpleBlogというFlask拡張を考えてみましょう。 Your extension views might want to interact with specific models in your database, or some other extension or data connected to your application. For example, let's consider a ``Flask-SimpleBlog`` extension that works with Flask-SQLAlchemy to provide a ``Post`` model and views to write and read posts.

PostモデルはFlask-SQLAlchemyのdb.Modelオブジェクトのサブクラスである必要がありますが、Postモデルが初めて利用可能になるのは、いったんFlask拡張Flask-SQLAlchemyのインスタンスを作成してからであり、自分のFlask拡張がそのviewを定義したとき(訳注: 下の例ではPostAPIクラスの定義をimport・実行した時)ではありません(訳注: 下の例では「db = SQLAlchemy()」でFlask-SQLAlchemyのインスタンスを作成してから、そのインスタンスを「blog = BlogExtension(db)」でBlogExtensionに渡して、「class Post(db.Model)」で渡されたインスタンスの属性を使ってサブクラスを作成しているように、db.Modelのサブクラスを作るにはFlask-SQLAlchemyのインスタンスが必要という意味合いだと思います。Flask-SQLAlchemyのクイックスタートも参照ください)。それでは、モデルが存在する前に定義されたviewのコードは、どのようにしてモデルにアクセスできるのでしょうか? The ``Post`` model needs to subclass the Flask-SQLAlchemy ``db.Model`` object, but that's only available once you've created an instance of that extension, not when your extension is defining its views. So how can the view code, defined before the model exists, access the model?

1つのやり方は、クラスに基づいたビュー(Class-based Views)を使うことでしょう。__init__の間に、モデルを作成し、それからviewのクラスのas_view()メソッドにそのモデルを渡すことでviewを作成します。 One method could be to use :doc:`views`. During ``__init__``, create the model, then create the views by passing the model to the view class's :meth:`~views.View.as_view` method.

class PostAPI(MethodView):
    def __init__(self, model):
        self.model = model

    def get(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のような、Flask拡張の属性の使用があるでしょう。init_appの中でFlask拡張をapp.extensionsに追加し、それからcurrent_app.extensions["simple_blog"].post_modelにviewからアクセスします。 Another technique could be to use an attribute on the extension, such as ``self.post_model`` from above. Add the extension to ``app.extensions`` in ``init_app``, then access ``current_app.extensions["simple_blog"].post_model`` from views.

自分のFlask拡張が期待するAPIに準拠した自分独自のPostモデルをユーザが提供できるよう、ベースクラスも提供したくなるかもしれません。そうすると、ユーザがclass Post(blog.BasePost)を実装して、それからblog.post_modelとして設定できるようになります。 You may also want to provide base classes so that users can provide their own ``Post`` model that conforms to the API your extension expects. So they could implement ``class Post(blog.BasePost)``, then set it as ``blog.post_model``.

As you can see, this can get a bit complex. Unfortunately, there's no perfect solution here, only different strategies and tradeoffs depending on your needs and how much customization you want to offer. Luckily, this sort of resource dependency is not a common need for most extensions. Remember, if you need help with design, ask on our Discord Chat or GitHub Discussions.