Azure Functions 用の Python worker 拡張機能の開発

Azure Functions を使用すると、Python 関数の実行の一部としてカスタム動作を統合できます。 この機能により、お客様に各自の関数アプリで簡単に使用してもらえるビジネス ロジックを作成することができます。 詳細については、Python 開発者向けリファレンスを参照してください。 ワーカー拡張機能は、v1 と v2 の両方の Python プログラミング モデルでサポートされています。

このチュートリアルで学習する内容は次のとおりです。

  • Azure Functions 用のアプリケーションレベルの Python worker 拡張機能を作成します。
  • お客様と同じようにアプリで拡張機能を使用します。
  • 拡張機能を使用できるようにパッケージ化して公開します。

前提条件

開始する前に、次の要件を満たす必要があります。

Python worker 拡張機能を作成する

作成する拡張機能は、HTTP トリガー呼び出しの経過時間をコンソール ログと HTTP 応答本文で報告するものです。

フォルダー構造

この拡張機能プロジェクトのフォルダー構造は、次のようになります。

<python_worker_extension_root>/
 | - .venv/
 | - python_worker_extension_timer/
 | | - __init__.py
 | - setup.py
 | - readme.md
フォルダー/ファイル 説明
.venv/ (省略可能) ローカル開発に使用される Python 仮想環境が含まれます。
python_worker_extension/ Python worker 拡張機能のソース コードが含まれます。 このフォルダーには、PyPI に公開されるメインの Python モジュールが含まれます。
setup.py Python worker 拡張機能パッケージのメタデータが含まれます。
readme.md 拡張機能の説明と使用法が含まれます。 このコンテンツは、PyPI プロジェクトのホーム ページに説明として表示されます。

プロジェクト メタデータを構成する

まず setup.py を作成します。これはパッケージに関する重要な情報を提供するものです。 拡張機能が適切に配布されてお客様の関数アプリに統合されるようにするために、'azure-functions >= 1.7.0, < 2.0.0'install_requires セクションに含まれていることを確認してください。

次のテンプレートでは、必要に応じて authorauthor_emailinstall_requireslicensepackagesurl の各フィールドを変更する必要があります。

from setuptools import find_packages, setup
setup(
    name='python-worker-extension-timer',
    version='1.0.0',
    author='Your Name Here',
    author_email='your@email.here',
    classifiers=[
        'Intended Audience :: End Users/Desktop',
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: End Users/Desktop',
        'License :: OSI Approved :: Apache Software License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
    ],
    description='Python Worker Extension Demo',
    include_package_data=True,
    long_description=open('readme.md').read(),
    install_requires=[
        'azure-functions >= 1.7.0, < 2.0.0',
        # Any additional packages that will be used in your extension
    ],
    extras_require={},
    license='MIT',
    packages=find_packages(where='.'),
    url='https://your-github-or-pypi-link',
    zip_safe=False,
)

次に、アプリケーションレベルのスコープに拡張機能コードを実装します。

タイマー拡張機能を実装する

アプリケーションレベルの拡張機能を実装するために、python_worker_extension_timer/__init__.py に次のコードを追加します。

import typing
from logging import Logger
from time import time
from azure.functions import AppExtensionBase, Context, HttpResponse
class TimerExtension(AppExtensionBase):
    """A Python worker extension to record elapsed time in a function invocation
    """

    @classmethod
    def init(cls):
        # This records the starttime of each function
        cls.start_timestamps: typing.Dict[str, float] = {}

    @classmethod
    def configure(cls, *args, append_to_http_response:bool=False, **kwargs):
        # Customer can use TimerExtension.configure(append_to_http_response=)
        # to decide whether the elapsed time should be shown in HTTP response
        cls.append_to_http_response = append_to_http_response

    @classmethod
    def pre_invocation_app_level(
        cls, logger: Logger, context: Context,
        func_args: typing.Dict[str, object],
        *args, **kwargs
    ) -> None:
        logger.info(f'Recording start time of {context.function_name}')
        cls.start_timestamps[context.invocation_id] = time()

    @classmethod
    def post_invocation_app_level(
        cls, logger: Logger, context: Context,
        func_args: typing.Dict[str, object],
        func_ret: typing.Optional[object],
        *args, **kwargs
    ) -> None:
        if context.invocation_id in cls.start_timestamps:
            # Get the start_time of the invocation
            start_time: float = cls.start_timestamps.pop(context.invocation_id)
            end_time: float = time()
            # Calculate the elapsed time
            elapsed_time = end_time - start_time
            logger.info(f'Time taken to execute {context.function_name} is {elapsed_time} sec')
            # Append the elapsed time to the end of HTTP response
            # if the append_to_http_response is set to True
            if cls.append_to_http_response and isinstance(func_ret, HttpResponse):
                func_ret._HttpResponse__body += f' (TimeElapsed: {elapsed_time} sec)'.encode()

このコードは AppExtensionBase から継承され、アプリ内のすべての関数に拡張機能が適用されるようになります。 また、FuncExtensionBase から継承することによって、関数レベルのスコープに拡張機能を実装することもできます。

init メソッドは、拡張クラスがインポートされるときに worker によって呼び出されるクラス メソッドです。 ここで拡張機能の初期化操作を行うことができます。 この場合、各関数の呼び出し開始時刻を記録するために、ハッシュ マップが初期化されます。

configure メソッドはお客様向けです。 readme ファイルで、どのような場合に Extension.configure() を呼び出す必要があるかをお客様に知らせることができます。 また、拡張機能でできること、可能な構成、拡張機能の使用法についても readme で説明します。 この例では、HttpResponse で経過時間を報告するかどうかをお客様が選択できます。

pre_invocation_app_level メソッドは、関数が実行される前に Python worker によって呼び出されます。 それにより、関数のコンテキストや引数など、関数からの情報が提供されます。 この例では、拡張機能によってメッセージがログに記録され、その invocation_id に基づいて呼び出しの開始時刻が記録されます。

同様に、post_invocation_app_level が関数の実行後に呼び出されます。 この例では、開始時刻と現在の時刻に基づいて経過時間を計算します。 また、HTTP 応答の戻り値を上書きします。

readme.md を作成する

拡張機能プロジェクトのルートに readme.md ファイルを作成します。 このファイルには、拡張機能の説明と使用法が含まれます。 readme.md コンテンツは、PyPI プロジェクトのホーム ページに説明として表示されます。

# Python Worker Extension Timer

In this file, tell your customers when they need to call `Extension.configure()`.

The readme should also document the extension capabilities, possible configuration,
and usage of your extension.

拡張機能をローカルで使用する

拡張機能を作成したので、アプリ プロジェクトでそれを使用して、意図したとおりに動作することを確認できます。

HTTP トリガー関数の作成

  1. アプリ プロジェクト用の新しいフォルダーを作成し、そこに移動します。

  2. Bash などの適切なシェルから、次のコマンドを実行してプロジェクトを初期化します。

    func init --python
    
  3. 次のコマンドを使用して、匿名アクセスができる新しい HTTP トリガー関数を作成します。

    func new -t HttpTrigger -n HttpTrigger -a anonymous
    

仮想環境をアクティブ化する

  1. OS に応じて次のように Python 仮想環境を作成します。

    python3 -m venv .venv
    
  2. OS に応じて次のように Python 仮想環境をアクティブ化します。

    source .venv/bin/activate
    

拡張機能を構成する

  1. 次のコマンドを使用して、関数アプリ プロジェクトのリモート パッケージをインストールします。

    pip install -r requirements.txt
    
  2. 次のように、編集可能モードでローカル ファイル パスから拡張機能をインストールします。

    pip install -e <PYTHON_WORKER_EXTENSION_ROOT>
    

    この例では、<PYTHON_WORKER_EXTENSION_ROOT> を拡張機能プロジェクトのルート ファイルの場所に置き換えます。

    お客様がこの拡張機能を使用するときは、そうではなく、次の例のように拡張機能パッケージの場所を requirements.txt ファイルに追加します。

    # requirements.txt
    python_worker_extension_timer==1.0.0
    
  3. プロジェクト ファイルの local.settings.json を開き、次のフィールドを Values に追加します。

    "PYTHON_ENABLE_WORKER_EXTENSIONS": "1" 
    

    Azure で実行するときは、そうではなく、PYTHON_ENABLE_WORKER_EXTENSIONS=1関数アプリのアプリ設定に追加します。

  4. v1 プログラミング モデルの __init.py__ ファイル、または v2 プログラミング モデルの function_app.py ファイルで、main 関数の前に次の 2 行を追加します。

    from python_worker_extension_timer import TimerExtension
    TimerExtension.configure(append_to_http_response=True)
    

    このコードで TimerExtension モジュールをインポートし、append_to_http_response 構成の値を設定します。

拡張機能を確認する

  1. アプリ プロジェクトのルート フォルダーから、func host start --verbose を使用して関数ホストを起動します。 出力に関数のローカル エンドポイントが https://localhost:7071/api/HttpTrigger として表示されます。

  2. ブラウザーで、GET 要求を https://localhost:7071/api/HttpTrigger に送信します。 次のような応答が表示され、要求の TimeElapsed データが追加されています。

    This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response. (TimeElapsed: 0.0009996891021728516 sec)
    

拡張機能の公開

拡張機能を作成して確認した後、引き続き残りの公開タスクを完了する必要があります。

  • ライセンスを選択します。
  • readme.md などのその他のドキュメントを作成します。
  • 拡張ライブラリを Python パッケージ レジストリまたはバージョン管理システム (VCS) に公開します。

拡張機能を PyPI に公開するには:

  1. 次のコマンドを実行して twinewheel を既定の Python 環境または仮想環境にインストールします。

    pip install twine wheel
    
  2. 拡張機能リポジトリから古い dist/ フォルダーを削除します。

  3. 次のコマンドを実行して、dist/ 内に新しいパッケージを生成します。

    python setup.py sdist bdist_wheel
    
  4. 次のコマンドを実行して、パッケージを PyPI にアップロードします。

    twine upload dist/*
    

    アップロード時に、PyPI アカウントの資格情報の入力が必要になる場合があります。 twine upload -r testpypi dist/* を使用してパッケージのアップロードをテストすることもできます。 詳しくは、Twine のドキュメントをご覧ください。

これらの手順の後、お客様は requirements.txt にパッケージ名を含めることによって拡張機能を使用できます。

詳細については、Python のパッケージ化に関する公式チュートリアルを参照してください。

  • この記事の完成したサンプル拡張機能プロジェクトは、python_worker_extension_timer サンプル リポジトリで確認できます。

  • OpenCensus 統合は、拡張インターフェイスを使用して Azure Functions Python アプリにテレメトリ トレースを統合するオープンソース プロジェクトです。 この Python worker 拡張機能の実装を確認するには、opencensus-python-extensions-azure リポジトリを参照してください。

次のステップ

Python による Azure Functions 開発の詳細については、次のリソースを参照してください。