Sviluppare estensioni di lavoro Python per Funzioni di Azure

Funzioni di Azure consente di integrare comportamenti personalizzati come parte dell'esecuzione della funzione Python. Questa funzionalità consente di creare logica di business che i clienti possono usare facilmente nelle proprie app per le funzioni. Per altre informazioni, vedere il riferimento per sviluppatori Python. Le estensioni di lavoro sono supportate sia nei modelli di programmazione v1 che v2 Python.

In questa esercitazione si apprenderà come:

  • Creare un'estensione di lavoro Python a livello di applicazione per Funzioni di Azure.
  • Usare l'estensione in un'app nel modo in cui i clienti fanno.
  • Pacchetto e pubblicazione di un'estensione per l'utilizzo.

Prerequisiti

Prima di iniziare, è necessario soddisfare questi requisiti:

Creare l'estensione Python Worker

L'estensione creata segnala il tempo trascorso di una chiamata di trigger HTTP nei log della console e nel corpo della risposta HTTP.

Struttura di cartelle

La cartella per il progetto di estensione deve essere simile alla struttura seguente:

<python_worker_extension_root>/
 | - .venv/
 | - python_worker_extension_timer/
 | | - __init__.py
 | - setup.py
 | - readme.md
Cartella/file Descrizione
.venv/ (Facoltativo) Contiene un ambiente virtuale Python usato per lo sviluppo locale.
python_worker_extension/ Contiene il codice sorgente dell'estensione di lavoro Python. Questa cartella contiene il modulo Python principale da pubblicare in PyPI.
setup.py Contiene i metadati del pacchetto di estensione del ruolo di lavoro Python.
readme.md Contiene le istruzioni e l'utilizzo dell'estensione. Questo contenuto viene visualizzato come descrizione nella home page del progetto PyPI.

Configurare i metadati del progetto

Prima di tutto si crea setup.py, che fornisce informazioni essenziali sul pacchetto. Per assicurarsi che l'estensione sia distribuita e integrata nelle app per le funzioni del cliente correttamente, verificare che 'azure-functions >= 1.7.0, < 2.0.0' sia presente nella install_requires sezione.

Nel modello seguente è necessario modificare author, licensepackagesauthor_emailinstall_requirese url i campi in base alle esigenze.

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

Successivamente, si implementerà il codice di estensione nell'ambito a livello di applicazione.

Implementare l'estensione timer

Aggiungere il codice seguente in python_worker_extension_timer/__init__.py per implementare l'estensione a livello di applicazione:

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

Questo codice eredita da AppExtensionBase in modo che l'estensione si applica a ogni funzione nell'app. È anche possibile implementare l'estensione in un ambito a livello di funzione ereditando da FuncExtensionBase.

Il init metodo è un metodo di classe chiamato dal ruolo di lavoro quando viene importata la classe di estensione. È possibile eseguire azioni di inizializzazione qui per l'estensione. In questo caso, viene inizializzata una mappa hash per registrare l'ora di inizio della chiamata per ogni funzione.

Il configure metodo è rivolto al cliente. Nel file readme è possibile indicare ai clienti quando devono chiamare Extension.configure(). Il readme deve inoltre documentare le funzionalità di estensione, la possibile configurazione e l'utilizzo dell'estensione. In questo esempio, i clienti possono scegliere se il tempo trascorso viene segnalato in HttpResponse.

Il pre_invocation_app_level metodo viene chiamato dal ruolo di lavoro Python prima dell'esecuzione della funzione. Fornisce le informazioni della funzione, ad esempio contesto di funzione e argomenti. In questo esempio l'estensione registra un messaggio e registra l'ora di inizio di una chiamata in base al relativo invocation_id.

Analogamente, viene chiamato dopo l'esecuzione della post_invocation_app_level funzione. In questo esempio viene calcolato il tempo trascorso in base all'ora di inizio e all'ora corrente. Sovrascrive anche il valore restituito della risposta HTTP.

Creare un readme.md

Creare un file readme.md nella radice del progetto di estensione. Questo file contiene le istruzioni e l'utilizzo dell'estensione. Il contenuto readme.md viene visualizzato come descrizione nella home page del progetto 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.

Usare l'estensione in locale

Dopo aver creato un'estensione, è possibile usarla in un progetto di app per verificare che funzioni come previsto.

Creare una funzione trigger HTTP

  1. Creare una nuova cartella per il progetto dell'app e spostarla.

  2. Dalla shell appropriata, ad esempio Bash, eseguire il comando seguente per inizializzare il progetto:

    func init --python
    
  3. Usare il comando seguente per creare una nuova funzione trigger HTTP che consente l'accesso anonimo:

    func new -t HttpTrigger -n HttpTrigger -a anonymous
    

Attivare un ambiente virtuale

  1. Creare un ambiente virtuale Python basato sul sistema operativo come indicato di seguito:

    python3 -m venv .venv
    
  2. Attivare l'ambiente virtuale Python, in base al sistema operativo come indicato di seguito:

    source .venv/bin/activate
    

Configurare l'estensione

  1. Installare pacchetti remoti per il progetto dell'app per le funzioni usando il comando seguente:

    pip install -r requirements.txt
    
  2. Installare l'estensione dal percorso del file locale, in modalità modificabile come indicato di seguito:

    pip install -e <PYTHON_WORKER_EXTENSION_ROOT>
    

    In questo esempio sostituire <PYTHON_WORKER_EXTENSION_ROOT> con il percorso radice del progetto di estensione.

    Quando un cliente usa l'estensione, aggiungerà invece il percorso del pacchetto di estensione al file requirements.txt, come negli esempi seguenti:

    # requirements.txt
    python_worker_extension_timer==1.0.0
    
  3. Aprire il file di progetto local.settings.json e aggiungere il campo seguente a Values:

    "PYTHON_ENABLE_WORKER_EXTENSIONS": "1" 
    

    Quando si esegue in Azure, si aggiunge PYTHON_ENABLE_WORKER_EXTENSIONS=1 invece alle impostazioni dell'app nell'app per le funzioni.

  4. Aggiungere due righe seguenti prima della main funzione nel file __init.py__ per il modello di programmazione v1 o nel file function_app.py per il modello di programmazione v2:

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

    Questo codice importa il TimerExtension modulo e imposta il valore di append_to_http_response configurazione.

Verificare l'estensione

  1. Dalla cartella radice del progetto dell'app avviare l'host della funzione usando func host start --verbose. Verrà visualizzato l'endpoint locale della funzione nell'output come https://localhost:7071/api/HttpTrigger.

  2. Nel browser inviare una richiesta GET a https://localhost:7071/api/HttpTrigger. Verrà visualizzata una risposta simile alla seguente, con i dati TimeElapsed per la richiesta aggiunta.

    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)
    

Pubblicare l'estensione

Dopo aver creato e verificato l'estensione, è comunque necessario completare queste attività di pubblicazione rimanenti:

  • Scegliere una licenza.
  • Creare una readme.md e altre documentazione.
  • Pubblicare la libreria di estensioni in un registro pacchetti Python o in un sistema di controllo della versione (VCS).

Per pubblicare l'estensione in PyPI:

  1. Eseguire il comando seguente per installare twine e wheel nell'ambiente Python predefinito o in un ambiente virtuale:

    pip install twine wheel
    
  2. Rimuovere la cartella precedente dist/ dal repository dell'estensione.

  3. Eseguire il comando seguente per generare un nuovo pacchetto all'interno dist/di :

    python setup.py sdist bdist_wheel
    
  4. Eseguire il comando seguente per caricare il pacchetto in PyPI:

    twine upload dist/*
    

    Potrebbe essere necessario specificare le credenziali dell'account PyPI durante il caricamento. È anche possibile testare il caricamento del pacchetto con twine upload -r testpypi dist/*. Per altre informazioni, vedere la documentazione di Twine.

Dopo questi passaggi, i clienti possono usare l'estensione includendo il nome del pacchetto nella requirements.txt.

Per altre informazioni, vedere l'esercitazione ufficiale sulla creazione di pacchetti Python.

Esempio

  • È possibile visualizzare il progetto di estensione di esempio completato da questo articolo nel repository di esempio python_worker_extension_timer .

  • L'integrazione di OpenCensus è un progetto open source che usa l'interfaccia di estensione per integrare la traccia dei dati di telemetria nelle app Python Funzioni di Azure. Vedere il repository opencensus-python-extensions-azure per esaminare l'implementazione di questa estensione del ruolo di lavoro Python.

Passaggi successivi

Per altre informazioni sullo sviluppo di Funzioni di Azure Python, vedere le risorse seguenti: