Flask拡張の開発 Flask Extension Development

Flaskは、小さなフレームワークなので、サードパーティのライブラリを機能させるために、しばしば反復的なステップを要求します。そのようなFlask拡張の多くはPyPIで入手可能です。 Flask, being a microframework, often requires some repetitive steps to get a third party library working. Many such extensions are already available on `PyPI`_.

まだ存在しない何かしら用にもし自分独自のFlask拡張を作りたい場合は、時間をかけずに自分のFlask拡張を走らせ、あなたのFlask拡張は動くとユーザが予想するようになるだろうと感じるために、Flask拡張開発のためのこのガイドが手助けになるでしょう。 If you want to create your own Flask extension for something that does not exist yet, this guide to extension development will help you get your extension running in no time and to feel like users would expect your extension to behave.

Flask拡張の解剖(Anatomy of an Extension) Anatomy of an Extension

Flask拡張はすべて、「something」部分は橋渡ししたいライブラリの名前とした、flask_somethingと呼ばれるパッケージの中に置きます。例えば、もしsimplexmlという名前のライブラリへのサポートをFlaskへ追加することを計画している場合、そのFlask拡張のパッケージはflask_simplexmlと名付けるでしょう。 Extensions are all located in a package called ``flask_something`` where "something" is the name of the library you want to bridge. So for example if you plan to add support for a library named `simplexml` to Flask, you would name your extension's package ``flask_simplexml``.

しかしながら、実際のFlask拡張の名前((コンピュータ処理用ではなく)人が読める名前)は「Flask-SimpleXML」のようなものになるでしょう。「Flask」をFlask拡張の名前のどこかに含め、どう大文字を使うかチェックすることを忘れないようにしてください。これ(人が読める名前)が、あなたのFlask拡張への依存関係をsetup.pyファイルの中でユーザが登録できるようにするやり方になります。 The name of the actual extension (the human readable name) however would be something like "Flask-SimpleXML". Make sure to include the name "Flask" somewhere in that name and that you check the capitalization. This is how users can then register dependencies to your extension in their :file:`setup.py` files.

しかし、Flask拡張自身はどのようなものなのでしょうか?Flask拡張は、同時に複数のFlaskアプリケーションのインスタンスで機能することを保証する必要があります。これは、ユニットテストを助け複数の設定を持つことをサポートするために、多くの人がアプリケーション製造工場(Application Factories)のようなパターンを使って必要に応じてアプリケーションを作成することから、要求されます。そのため、自分のアプリケーションがその種の動作に対応することは、決定的に重要です。 But what do extensions look like themselves? An extension has to ensure that it works with multiple Flask application instances at once. This is a requirement because many people will use patterns like the :ref:`app-factories` pattern to create their application as needed to aid unittests and to support multiple configurations. Because of that it is crucial that your application supports that kind of behavior.

最も重要なこととして、Flask拡張はsetup.pyファイルと一緒に出荷されPyPIに登録される必要があります。さらに、人々が手作業でライブラリをダウンロードすることなくvirtualenvに開発バージョンを容易にインストールできるように、開発版チェックアウトのリンクを機能させるべきです。 Most importantly the extension must be shipped with a :file:`setup.py` file and registered on PyPI. Also the development checkout link should work so that people can easily install the development version into their virtualenv without having to download the library by hand.

Flask Extension Registry(訳注: コミュニティで認められたFlask拡張のリスト、ただしこの翻訳を作成している時点では、今後メンテナンスを続けないことが既に合意されています)でリストされるためには、Flask拡張はBSD、MITもしくはもっと自由な(liberal)ライセンスの下におかれる必要があります。Flask Extension Registryは議論が管理されている(moderated)場所であり、ライブラリは要求通りの振る舞いをするか事前にレビューされることを覚えておいてください。 Flask extensions must be licensed under a BSD, MIT or more liberal license in order to be listed in the Flask Extension Registry. Keep in mind that the Flask Extension Registry is a moderated place and libraries will be reviewed upfront if they behave as required.

"Hello Flaskext!"

では、ここまで説明したようなFlask拡張の作成を始めましょう。ここで作ろうとしているFlask拡張はSQLite3への非常に基本的なサポートを提供していきます。 So let's get started with creating such a Flask extension. The extension we want to create here will provide very basic support for SQLite3.

最初に、以下のフォルダ構造を作成します: First we create the following folder structure::

flask-sqlite3/
    flask_sqlite3.py
    LICENSE
    README

以下は、最も重要なファイルの内容です: Here's the contents of the most important files:

setup.py

絶対必要になる以下のファイルはsetup.pyファイルであり、それは自分のFlask拡張をインストールするときに使います。以下の内容は、何かしら、あなたが作業に使うことができるものです: The next file that is absolutely required is the :file:`setup.py` file which is used to install your Flask extension. The following contents are something you can work with::

"""
Flask-SQLite3
-------------

This is the description for that library
"""
from setuptools import setup


setup(
    name='Flask-SQLite3',
    version='1.0',
    url='http://example.com/flask-sqlite3/',
    license='BSD',
    author='Your Name',
    author_email='your-email@example.com',
    description='Very short description',
    long_description=__doc__,
    py_modules=['flask_sqlite3'],
    # if you would be using a package instead use packages instead
    # of py_modules:
    # packages=['flask_sqlite3'],
    zip_safe=False,
    include_package_data=True,
    platforms='any',
    install_requires=[
        'Flask'
    ],
    classifiers=[
        'Environment :: Web Environment',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
        'Topic :: Software Development :: Libraries :: Python Modules'
    ]
)

たくさんのコードですが、既存のFlask拡張から本当にただコピー/ペーストだけして、それから調整できます。 That's a lot of code but you can really just copy/paste that from existing extensions and adapt.

flask_sqlite3.py

そして、ここからはFlask拡張の実際のコードに進みます。しかし、先ほど説明したようなFlask拡張は正確にはどのような姿であるべきでしょうか?何がベスト・プラクティスになるのでしょうか?いくらかの洞察を得るために、読み続けてください。 Now this is where your extension code goes. But how exactly should such an extension look like? What are the best practices? Continue reading for some insight.

Flask拡張の初期処理(Initializing Extensions) Initializing Extensions

多くのFlask拡張はある種の初期化のステップを必要とするでしょう。例えば、ドキュメントが提案する(FlaskでのSQLite 3の使用)ようにその時点でSQLiteへ接続しているアプリケーションを考えてください。それでは、アプリケーションのオブジェクトの名前(訳注: アプリケーションのオブジェクトを格納している変数名のような意味合い)を、Flask拡張はどうやって知ることができるでしょうか? Many extensions will need some kind of initialization step. For example, consider an application that's currently connecting to SQLite like the documentation suggests (:ref:`sqlite3`). So how does the extension know the name of the application object?

非常にシンプルです: Flask拡張へアプリケーションのオブジェクトを自分で(訳注: 引数などで明示的に)渡します Quite simple: you pass it to it.

Flask拡張を初期化するには2つの推奨されるやり方があります: There are two recommended ways for an extension to initialize:

初期化関数: initialization functions:

もし自分のFlask拡張がhelloworldと呼ばれる場合は、アプリケーション用にFlask拡張を初期化するinit_helloworld(app[, extra_args])とよばれる関数を持っているかもしれません。それは事前/事後処理機能(before / after handlers)などを付加するかもしれません。 If your extension is called `helloworld` you might have a function called ``init_helloworld(app[, extra_args])`` that initializes the extension for that application. It could attach before / after handlers etc.

クラス: classes:

クラスは殆ど初期化関数と同じように機能しますが、後からさらに振る舞いを変えるために使うことができます。例えば、OAuth extensionがどのように働くかを調べてください: そこでは、OAuthが使用するリモートのアプリケーションへの参照(reference)を作成するOAuth.remote_appのような、いくつかのヘルパー関数を提供するOAuthオブジェクトがあります。 Classes work mostly like initialization functions but can later be used to further change the behavior. For an example look at how the `OAuth extension`_ works: there is an `OAuth` object that provides some helper functions like `OAuth.remote_app` to create a reference to a remote application that uses OAuth.

何を使用するかは、何を考えているかに依存します。SQLite 3拡張用には、データベースの接続(connection)の開く処理と閉じる処理を行うオブジェクトをユーザへ提供するため、クラスをベースにしたアプローチを使用していきます。 What to use depends on what you have in mind. For the SQLite 3 extension we will use the class-based approach because it will provide users with an object that handles opening and closing database connections.

クラスを設計するとき、モジュールのレベルで容易に再利用可能にすることが重要です。これは、Flask拡張クラスのオブジェクト(インスタンス)自身は、アプリケーション固有であるあらゆる状態情報を、どのような状況下でも格納してはいけないこと、(Flask拡張クラスのオブジェクトは)違うアプリケーションの間で共有可能にしなければいけないことを意味しています。 When designing your classes, it's important to make them easily reusable at the module level. This means the object itself must not under any circumstances store any application specific state and must be shareable between different applications.

Flask拡張のコード The Extension Code

以下はコピー/ペースト用のflask_sqlite3.pyの内容です: Here's the contents of the `flask_sqlite3.py` for copy/paste::

import sqlite3
from flask import current_app, _app_ctx_stack


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

    def init_app(self, app):
        app.config.setdefault('SQLITE3_DATABASE', ':memory:')
        app.teardown_appcontext(self.teardown)

    def connect(self):
        return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])

    def teardown(self, exception):
        ctx = _app_ctx_stack.top
        if hasattr(ctx, 'sqlite3_db'):
            ctx.sqlite3_db.close()

    @property
    def connection(self):
        ctx = _app_ctx_stack.top
        if ctx is not None:
            if not hasattr(ctx, 'sqlite3_db'):
                ctx.sqlite3_db = self.connect()
            return ctx.sqlite3_db

そして、これらのコード行がすることは以下のとおりです: So here's what these lines of code do:

  1. __init__メソッドは、オプションのappオブジェクトは引き取り、もしappが供給された場合はinit_appを呼び出します。 The ``__init__`` method takes an optional app object and, if supplied, will call ``init_app``.

  2. init_appメソッドは、appオブジェクトを(Flask拡張内で保持することを)要求することなくSQLite3オブジェクトを初期化できるようにするために存在します。このメソッドはアプリケーション(Flaskアプリケーションのインスタンス)作成用のfactoryパターンをサポートします。init_appはデータベース用の設定をセットし、もし設定が供給されていない場合はメモリ中のデータベース(in memory database)を初期設定にします。加えて、このinit_appメソッドはteardown(取り壊し)処理機能(handler)を取り付けます。 The ``init_app`` method exists so that the ``SQLite3`` object can be instantiated without requiring an app object. This method supports the factory pattern for creating applications. The ``init_app`` will set the configuration for the database, defaulting to an in memory database if no configuration is supplied. In addition, the ``init_app`` method attaches the ``teardown`` handler.

  3. 次に、データベース接続(connection)を開くconnectメソッドを定義しています。 Next, we define a ``connect`` method that opens a database connection.

  4. 最後に、初めてアクセスしたときにデータベース接続(connection)を開き、それ(データベース接続)をコンテキストに格納するconnectionプロパティを追加しています。これはリソースを扱う推奨されるやり方でもあります: リソースが使われる最初の時までリソースの取得を遅らせます。 Finally, we add a ``connection`` property that on first access opens the database connection and stores it on the context. This is also the recommended way to handling resources: fetch resources lazily the first time they are used.

    ここでは、データベース接続を(内部管理用のスタックの)一番上のアプリケーションのコンテキストに、_app_ctx_stack.topを介して取り付けていることに注意してください。Flask拡張は自分の持つ情報を格納するときは、(他のFlask拡張などが使う名前と重複しないように)十分に複雑な名前と一緒に、(内部管理用スタックの)一番上のコンテキストを使うべきです。 Note here that we're attaching our database connection to the top application context via ``_app_ctx_stack.top``. Extensions should use the top context for storing their own information with a sufficiently complex name.

それでは、なぜここではクラスベースのアプローチに決めたのでしょうか?このFlask拡張は以下に示すような使い方をするためです: So why did we decide on a class-based approach here? Because using our extension looks something like this::

from flask import Flask
from flask_sqlite3 import SQLite3

app = Flask(__name__)
app.config.from_pyfile('the-config.cfg')
db = SQLite3(app)

こうした後で、以下のようにしてviewからデータベースを使用できます: You can then use the database from views like this::

@app.route('/')
def show_all():
    cur = db.connection.cursor()
    cur.execute(...)

似たようなやり方で、もしリクエストの外側(訳注: リクエストの処理開始前もしくは終了後で、内部管理用のスタックにアプリケーションのコンテキストがpushされていない状態)であった場合は、appのコンテキストをpushすることでデータベースを使用可能です。: Likewise if you are outside of a request you can use the database by pushing an app context::

with app.app_context():
    cur = db.connection.cursor()
    cur.execute(...)

withブロックの最後で、取り壊し処理(teardown handles)が自動的に実行されます。 At the end of the ``with`` block the teardown handles will be executed automatically.

加えて、init_appメソッドはapp作成用のfactoryパターンに対応するためにも使用されます: Additionally, the ``init_app`` method is used to support the factory pattern for creating apps::

db = SQLite3()
# Then later on.
app = create_app('the-config.cfg')
db.init_app(app)

このapp作成用factoryパターンへの対応は、承認されたFlask拡張(approved flask extensions)(後で説明されます)で必要とされることを覚えておいてください。 Keep in mind that supporting this factory pattern for creating apps is required for approved flask extensions (described below).

init_appについての注意 Note on ``init_app``

お気づきのように、init_appappself(訳注: クラスのインスタンスのこと、Pythonでは慣例的にクラスのメソッドが自分自身のインスタンスを引数で受け取るときはselfが使用されることから、インスタンスを指して使われています)へ割り当てていません。これは意図的なものです!クラスベースのFlask拡張では、自分のオブジェクトにアプリケーションを保持するのは、アプリケーション(のインスタンス)がコンストラクタで渡されたときだけでなければいけません。これは、「複数のアプリケーションで使われることには関心がありません」ということをFlask拡張に伝えます。 As you noticed, ``init_app`` does not assign ``app`` to ``self``. This is intentional! Class based Flask extensions must only store the application on the object when the application was passed to the constructor. This tells the extension: I am not interested in using multiple applications.

もしFlask拡張がその時点のアプリケーションを見つけ出す必要があり、自分ではアプリケーションへの参照を持っていない場合は、context localのcurrent_appを使用するか、アプリケーションを明示的に渡せるようにAPIを変更するかが必要になります。 When the extension needs to find the current application and it does not have a reference to it, it must either use the :data:`~flask.current_app` context local or change the API in a way that you can pass the application explicitly.

_app_ctx_stackの使用 Using _app_ctx_stack

上記の例では、全てのリクエストの前に、sqlite3_db変数が_app_ctx_stack.topに割り当てられます。view関数の中では、この変数はSQLite3connectionプロパティを使用してアクセス可能です。リクエストの取り壊し(teardown)の間に、sqlite3_db接続(connection)は閉じられます。このパターンを使用することで、sqlite3データベースへの同じ接続(connection)が、それを必要とするあらゆるものからリクエストの期間中はアクセス可能になります。 In the example above, before every request, a ``sqlite3_db`` variable is assigned to ``_app_ctx_stack.top``. In a view function, this variable is accessible using the ``connection`` property of ``SQLite3``. During the teardown of a request, the ``sqlite3_db`` connection is closed. By using this pattern, the *same* connection to the sqlite3 database is accessible to anything that needs it for the duration of the request.

他から学習しましょう Learn from Others

このドキュメントではFlask拡張開発の必要最小限だけに触れています。もしさらに学びたいときは、PyPIにある既存のFlask拡張をチェックするのはとても良いアイデアです。もしも途方に暮れたときは、魅力的なAPIへのアイデアを得るためにmailinglistDiscord serverもあります。特に、もし誰もしたことのないことをやろうとしている場合は、さらにいくらかのインプットを得るのはとても良いアイデアになるかもしれません。これは人々がFlask拡張から何を求めることがあるかについての使えるフィードバックを生み出すだけでなく、殆ど同じ問題に対して複数の開発者が分け隔てられたまま作業することも回避させます。 This documentation only touches the bare minimum for extension development. If you want to learn more, it's a very good idea to check out existing extensions on the `PyPI`_. If you feel lost there is still the `mailinglist`_ and the `Discord server`_ to get some ideas for nice looking APIs. Especially if you do something nobody before you did, it might be a very good idea to get some more input. This not only generates useful feedback on what people might want from an extension, but also avoids having multiple developers working in isolation on pretty much the same problem.

覚えておいてください: 良いAPIの設計は困難ですので、自分のプロジェクトをメーリングリストで紹介し、他の開発者がAPIの設計と一緒に助けの手を差し伸べられるようにしましょう。 Remember: good API design is hard, so introduce your project on the mailing list, and let other developers give you a helping hand with designing the API.

最も良いFlask拡張は、APIについての一般的な慣用語(common idioms)を共有しているFlask拡張です。そして、それは早い段階での共同作業があって初めて機能するでしょう。 The best Flask extensions are extensions that share common idioms for the API. And this can only work if collaboration happens early.

承認されたFlask拡張(Approved Extensions) Approved Extensions

Flaskには以前approved extensionsという概念がありました。これらはサポートと互換性についてのいくつかの調査が伴っていました。approved extensionsのリストは時間とともに保守があまりにも難しくなっていきましたが、Flaskのエコシステムの一貫性と互換性を残す手助けになるため、approved extensionsのガイドラインは今日でも保守および開発されているすべてのFlask拡張に今でも関係があります。 Flask previously had the concept of approved extensions. These came with some vetting of support and compatibility. While this list became too difficult to maintain over time, the guidelines are still relevant to all extensions maintained and developed today, as they help the Flask ecosystem remain consistent and compatible.

  1. approved extensionにはメンテナーが必要です。Flask拡張の作者がプロジェクトの外へ移動するような出来事が起きたときは、プロジェクトは新しいメンテナーを見つけ、リポジトリ、ドキュメント、PyPI、その他のサービスへのアクセスを移管しなければなりません。もしメンテナーがいない場合は、Palletsのコアチームにアクセスを与えてください。 An approved Flask extension requires a maintainer. In the event an extension author would like to move beyond the project, the project should find a new maintainer and transfer access to the repository, documentation, PyPI, and any other services. If no maintainer is available, give access to the Pallets core team.

  2. 名前の枠組み(naming scheme)はFlask-ExtensionNameまたはExtensionName-Flaskです。Flask拡張はflask_extensionnameという名前の、正確に1つのパッケージもしくはモジュールを提供する必要があります。 The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*. It must provide exactly one package or module named ``flask_extension_name``.

  3. Flask拡張のライセンスはBSD/MIT/WTFPLである必要があります。それはオープンソースであり、公開されている必要があります。 The extension must be BSD or MIT licensed. It must be open source and publicly available.

  4. Flask拡張のAPIは以下の特性を持つ必要があります: The extension's API must have the following characteristics:

    • 同じPythonプロセスの中で走っている複数のアプリケーションに対応している必要があります。self.appの代わりにcurrent_appを使用し、アプリケーションのインスタンス毎に設定および状態を格納します。 It must support multiple applications running in the same Python process. Use ``current_app`` instead of ``self.app``, store configuration and state per application instance.

    • アプリケーションを作成するためのfactoryパターンが使用可能である必要があります。ext.init_app()パターンを使用してください。 It must be possible to use the factory pattern for creating applications. Use the ``ext.init_app()`` pattern.

  5. リポジトリのクローンから、Flask拡張はその依存対象も一緒にpip install -e .でインストールできる必要があります。 From a clone of the repository, an extension with its dependencies must be installable with ``pip install -e .``.

  6. tox -e pyまたはpytestを使って起動できるテスト一式(testing suite)をリリース(ship)する必要があります。もしtoxを使わない場合、テストの依存対象はrequirements.txtファイルの中で指定するべきです。テストはソース配布物(sdist distribution)の一部である必要があります。 It must ship a testing suite that can be invoked with ``tox -e py`` or ``pytest``. If not using ``tox``, the test dependencies should be specified in a ``requirements.txt`` file. The tests must be part of the sdist distribution.

  7. ドキュメントはOfficial Pallets Themesflaskテーマを使用する必要があります。ドキュメントまたはプロジェクトのwebサイトへのリンクがPyPIメタデータまたはreadmeの中にある必要があります。 The documentation must use the ``flask`` theme from the `Official Pallets Themes`_. A link to the documentation or project website must be in the PyPI metadata or the readme.

  8. 互換性を最大化するために、Flask拡張はFlaskがサポートするものと同じバージョンのPythonをサポートする必要があります。2020年時点では3.6以上が推奨されます。サポートされるバージョンを指示するために、setup.pyの中でpython_requires=">=3.6"を使用します。 For maximum compatibility, the extension should support the same versions of Python that Flask supports. 3.6+ is recommended as of 2020. Use ``python_requires=">= 3.6"`` in ``setup.py`` to indicate supported versions.