Разработка расширений рабочих ролей Python для Функций Azure

Функции Azure позволяют интегрировать пользовательские поведения в рамках выполнения функции Python. Эта функция позволяет создавать бизнес-логику, которую клиенты могут легко использовать в своих собственных приложениях-функциях. Дополнительные сведения см. в разделе Справочник разработчика Python. Расширения рабочих ролей поддерживаются в моделях программирования Python версии 1 и 2.

Из этого руководства вы узнаете, как выполнять следующие задачи:

  • Создание расширения рабочей роли Python на уровне приложения для Функций Azure.
  • Используйте свое расширение в приложении так же, как это делают ваши клиенты.
  • Упакуйте и опубликуйте расширение для использования.

Предварительные требования

Перед началом работы необходимо выполнить следующие условия:

Создание расширения рабочей роли Python

Создаваемое расширение сообщает о времени, затраченном на вызов триггера HTTP в журналах консоли и в тексте HTTP-отклика.

Структура папок

Папка проекта расширения должна иметь следующую структуру:

<python_worker_extension_root>/
 | - .venv/
 | - python_worker_extension_timer/
 | | - __init__.py
 | - setup.py
 | - readme.md
Папка/файл Описание
.venv/ (Дополнительно) Содержит виртуальную среду Python, используемую для локальной разработки.
python_worker_extension/ Содержит исходный код расширения рабочей роли Python. Эта папка содержит основной модуль Python, который будет опубликован в PyPI.
setup.py Содержит метаданные пакета расширения рабочей роли Python.
readme.md Содержит инструкции и использование расширения. Это содержимое отображается как описание на домашней странице в проекте PyPI.

Настройка метаданных проекта

Сначала создайте setup.py, который предоставляет ценные сведения о пакете. Чтобы обеспечить корректное распространение и интеграцию расширения в приложения-функции клиента, убедитесь в том, что 'azure-functions >= 1.7.0, < 2.0.0' находится в разделе install_requires.

В следующем разделе при необходимости измените поля author, author_email, install_requires, license, packages и url.

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 — это метод класса, который вызывается рабочей ролью при импорте класса расширения. Здесь можно выполнить действия по инициализации расширения. В этом случае инициализируется сопоставление хэша для записи времени начала вызова для каждой функции.

Метод configure ориентирован на клиента. В файле Readme можно сообщить клиентам о необходимости вызова Extension.configure(). В файле Readme также должны быть документированы возможности расширения, доступная конфигурация и область применения вашего расширения. В этом примере клиенты могут выбрать, будет ли затраченное время указано в HttpResponse.

Метод pre_invocation_app_level вызывается рабочей ролью Python перед выполнением функции. Он предоставляет сведения из функции, такие как контекст функции и аргументы. В этом примере расширение записывает в журнал сообщение и записывает время начала вызова на основе его идентификатора 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. Создайте виртуальную среду Python на основе ОС следующим образом.

    python3 -m venv .venv
    
  2. Активируйте виртуальную среду 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. Добавьте следующие две строки перед main функцией в файле __init.py__ для модели программирования версии 1 или в файле function_app.py для модели программирования версии 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 или в системе управления версиями.

Чтобы опубликовать расширение в PyPI, выполните следующие действия.

  1. Выполните следующую команду, чтобы установить twine и wheel в среде 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 — это проект с открытым кодом, использующий интерфейс расширения для интеграции инструментов трассировки телеметрии в приложения Python для Функций Azure. Сведения о реализации этого расширения рабочей роли Python см. в репозитории opencensus-python-extensions-azure.

Дальнейшие действия

Дополнительные сведения о разработках под Python в рамках службы "Функции Azure" см. в статьях ниже.