Desarrollo de extensiones de trabajo de Python para Azure Functions

Azure Functions permite integrar comportamientos personalizados como parte de la ejecución de funciones de Python. Esta característica le permite crear lógica de negocios que los clientes pueden usar fácilmente en sus propias aplicaciones de funciones. Para obtener más información, vea la Referencia para desarrolladores de Python. Las extensiones de trabajo se admiten en los modelos de programación de Python v1 y v2.

En este tutorial, aprenderá a:

  • Crear una extensión de trabajo de Python de nivel de aplicación para Azure Functions
  • Consumir la extensión en una aplicación tal como lo hacen los clientes
  • Empaquetar y publicar una extensión para su consumo

Requisitos previos

Antes de empezar, debe cumplir estos requisitos:

Creación de la extensión de trabajo de Python

La extensión que cree informará del tiempo transcurrido de una invocación de desencadenador HTTP en los registros de la consola y en el cuerpo de la respuesta HTTP.

Estructura de carpetas

La carpeta del proyecto de extensión debería tener una estructura similar a la siguiente:

<python_worker_extension_root>/
 | - .venv/
 | - python_worker_extension_timer/
 | | - __init__.py
 | - setup.py
 | - readme.md
Carpeta/Archivo Descripción
.venv/ (Opcional) Contiene un entorno virtual de Python usado para el desarrollo local.
python_worker_extension/ Contiene el código fuente de la extensión de trabajo de Python. Esta carpeta contiene el módulo principal de Python que se va a publicar en PyPI.
setup.py Contiene los metadatos del paquete de extensión de trabajo de Python.
readme.md Contiene la instrucción y el uso de la extensión. Este contenido se muestra como descripción en la página principal del proyecto de PyPI.

Configuración de los metadatos del proyecto

Primero debe crear setup.py, que proporciona información esencial sobre el paquete. Para asegurarse de que la extensión se distribuya y se integre correctamente en las aplicaciones de funciones del cliente, confirme que 'azure-functions >= 1.7.0, < 2.0.0' se encuentra en la sección install_requires.

En la plantilla siguiente, debe cambiar los camposauthor, author_email, install_requires, license, packages y url, según sea necesario.

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,
)

A continuación, implementará el código de extensión en el ámbito de nivel de aplicación.

Implementación de la extensión de temporizador

Agregue el código siguiente en python_worker_extension_timer/__init__.py para implementar la extensión de nivel de aplicación:

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()

Este código hereda de AppExtensionBase para que la extensión se aplique a todas las funciones de la aplicación. También podría implementar la extensión en un ámbito de nivel de función si hereda de FuncExtensionBase.

El método init es un método de clase al que llama el trabajo cuando se importa la clase de extensión. Puede realizar acciones de inicialización aquí para la extensión. En este caso, se inicializa una asignación de códigos hash para registrar la hora de inicio de la invocación para cada función.

El método configure está orientado al cliente. En el archivo Léame, puede indicar a los clientes cuándo necesitan llamar a Extension.configure(). El archivo Léame también debe documentar las funcionalidades de la extensión, su posible configuración y su uso. En este ejemplo, los clientes pueden decidir si el tiempo transcurrido se notifica en HttpResponse.

El trabajo de Python llama al método pre_invocation_app_level antes de que se ejecute la función. Proporciona la información de la función, como su contexto y argumentos. En este ejemplo, la extensión registra un mensaje y guarda la hora de inicio de una invocación en función de su invocation_id.

De forma similar, se llama a post_invocation_app_level después de la ejecución de la función. En este ejemplo se calcula el tiempo transcurrido en función de la hora de inicio y la hora actual. También sobrescribe el valor devuelto de la respuesta HTTP.

Crear un léame.md

Cree un archivo léame.md en la raíz del proyecto de extensión. Este archivo contiene las instrucciones y el uso de la extensión. El contenido de readme.md se muestra como descripción en la página principal del proyecto de 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.

Consumo local de la extensión

Ahora que ha creado una extensión, puede usarla en un proyecto de aplicación para comprobar que funciona según lo previsto.

Creación de una función desencadenada por HTTP

  1. Cree una carpeta para el proyecto de la aplicación y vaya a ella.

  2. Desde el shell adecuado, como Bash, ejecute el siguiente comando para inicializar el proyecto:

    func init --python
    
  3. Use el siguiente comando para crear una función de desencadenador HTTP que permita el acceso anónimo:

    func new -t HttpTrigger -n HttpTrigger -a anonymous
    

Activación de un entorno virtual

  1. Cree un entorno virtual de Python basado en el sistema operativo, como se muestra a continuación:

    python3 -m venv .venv
    
  2. Active el entorno virtual de Python basado en el sistema operativo, como se muestra a continuación:

    source .venv/bin/activate
    

Configuración de la extensión

  1. Instale paquetes remotos para el proyecto de aplicación de funciones con el siguiente comando:

    pip install -r requirements.txt
    
  2. Instale la extensión desde la ruta de acceso del archivo local en modo editable, como se muestra a continuación:

    pip install -e <PYTHON_WORKER_EXTENSION_ROOT>
    

    En este ejemplo, reemplace <PYTHON_WORKER_EXTENSION_ROOT> por la ubicación del archivo raíz del proyecto de extensión.

    Cuando un cliente use la extensión, en su lugar agregará la ubicación del paquete de extensión al archivo requirements.txt, como en los ejemplos siguientes:

    # requirements.txt
    python_worker_extension_timer==1.0.0
    
  3. Abra el archivo de proyecto local.settings.json y agregue el siguiente campo a Values:

    "PYTHON_ENABLE_WORKER_EXTENSIONS": "1" 
    

    Si lo ejecuta en Azure, en su lugar agregue PYTHON_ENABLE_WORKER_EXTENSIONS=1 a la configuración de la aplicación en la aplicación de funciones.

  4. Agregue las dos líneas siguientes antes de la función main en el archivo __init.py__ para el modelo de programación v1 o en el archivo function_app.py para el modelo de programación v2:

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

    Este código importa el módulo TimerExtension y establece el valor de configuración append_to_http_response.

Comprobación de la extensión

  1. En la carpeta raíz del proyecto de aplicación, inicie el host de función mediante func host start --verbose. Debería ver el punto de conexión local de la función en la salida como https://localhost:7071/api/HttpTrigger.

  2. En el explorador, envíe una solicitud GET a https://localhost:7071/api/HttpTrigger. Debería ver una respuesta como la siguiente, con los datos de TimeElapsed de la solicitud anexados.

    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)
    

Publicación de la extensión

Después de crear y comprobar la extensión, debe completar las tareas de publicación siguientes:

  • Elija una licencia.
  • Cree un archivo readme.md y otra documentación.
  • Publique la biblioteca de extensiones en un registro de paquetes de Python o en un sistema de control de versiones (VCS).

Para publicar la extensión en PyPI:

  1. Ejecute el siguiente comando para instalar twine y wheel en el entorno predeterminado de Python o en un entorno virtual:

    pip install twine wheel
    
  2. Quite la carpeta dist/ anterior del repositorio de extensiones.

  3. Ejecute el siguiente comando para generar un nuevo paquete en dist/:

    python setup.py sdist bdist_wheel
    
  4. Ejecute el siguiente comando para cargar el paquete en PyPI:

    twine upload dist/*
    

    Es posible que tenga que proporcionar las credenciales de la cuenta de PyPI durante la carga. También puede probar la carga del paquete con twine upload -r testpypi dist/*. Para obtener más información, consulte la documentación de Twine.

Después de estos pasos, los clientes pueden usar la extensión si incluyen el nombre del paquete en el archivo requirements.txt.

Para obtener más información, consulte el tutorial oficial de empaquetado de Python.

Ejemplos

  • Puede ver cómo se ha completado el proyecto de extensión de ejemplo de este artículo en el repositorio de ejemplo python_worker_extension_timer.

  • La integración de OpenCensus es un proyecto de código abierto que usa la interfaz de extensión para integrar el seguimiento de telemetría en aplicaciones de Python para Azure Functions. Consulte el repositorio opencensus-python-extensions-azure para revisar la implementación de esta extensión de trabajo de Python.

Pasos siguientes

Para obtener más información sobre el desarrollo de Python de Azure Functions, consulte los siguientes recursos: