Entwickeln von Python-Workererweiterungen für Azure Functions

Mit Azure Functions können Sie benutzerdefiniertes Verhalten in die Ausführung von Python-Funktionen integrieren. Dieses Feature ermöglicht die Erstellung von Geschäftslogik, die Kunden problemlos in ihren eigenen Funktions-Apps verwenden können. Weitere Informationen finden Sie in der Python-Entwicklerreferenz. Workererweiterungen werden sowohl in den Python-Programmiermodellen v1 als auch v2 unterstützt.

In diesem Tutorial lernen Sie Folgendes:

  • Erstellen einer Python-Workererweiterung auf Anwendungsebene für Azure Functions
  • Nutzen der Erweiterung in einer App wie Ihre Kunden
  • Packen und Veröffentlichen einer Erweiterung für die Nutzung

Voraussetzungen

Für den Einstieg müssen Sie über Folgendes verfügen:

Erstellen der Python-Workererweiterung

Durch die von Ihnen erstellte Erweiterung wird die verstrichene Zeit eines HTTP-Triggeraufrufs in den Konsolenprotokollen und im HTTP-Antwortkörper gemeldet.

Ordnerstruktur

Der Ordner für Ihr Erweiterungsprojekt muss folgende Struktur haben:

<python_worker_extension_root>/
 | - .venv/
 | - python_worker_extension_timer/
 | | - __init__.py
 | - setup.py
 | - readme.md
Ordner/Datei BESCHREIBUNG
.venv/ (Optional) Diese Datei enthält eine virtuelle Python-Umgebung für die lokale Entwicklung.
python_worker_extension/ Enthält den Quellcode der Python-Workererweiterung. Dieser Ordner enthält das Python-Hauptmodul für die Veröffentlichung in PyPI.
setup.py Enthält die Metadaten des Pakets der Python-Workererweiterung.
readme.md Enthält die Anweisungen zur Verwendung Ihrer Erweiterung. Dieser Inhalt wird als Beschreibung auf der Homepage Ihres PyPI-Projekts angezeigt.

Konfigurieren von Projektmetadaten

Erstellen Sie zunächst setup.py, um wichtige Informationen zu Ihrem Paket bereitzustellen. Der Abschnitt install_requires muss 'azure-functions >= 1.7.0, < 2.0.0' enthalten, um sicherzustellen, dass Ihre Erweiterung ordnungsgemäß verteilt und in die Funktions-Apps Ihres Kunden integriert wird.

Ändern Sie in der folgenden Vorlage die Felder author, author_email, install_requires, license, packages und url nach Bedarf.

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

Implementieren Sie als Nächstes Ihren Erweiterungscode auf Anwendungsebene.

Implementieren der Timer-Erweiterung

Fügen Sie in python_worker_extension_timer/__init__.py den folgenden Code hinzu, um die Erweiterung auf Anwendungsebene zu implementieren:

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

Dieser Code erbt von AppExtensionBase, sodass die Erweiterung für jede Funktion in der App gilt. Durch Erben von FuncExtensionBase hätte die Erweiterung auch auf Funktionsebene implementiert werden können.

Bei der Methode init handelt es sich um eine Klassenmethode, die vom Worker beim Importieren der Erweiterungsklasse aufgerufen wird. Hier können Initialisierungsaktionen für die Erweiterung ausgeführt werden. In diesem Fall wird eine Hashzuordnung initialisiert, um die Startzeit des Aufrufs für jede Funktion zu erfassen.

Bei der Methode configure handelt es sich um eine Methode für Kunden. In Ihrer Infodatei können Sie Ihren Kunden mitteilen, wann sie Extension.configure() aufrufen müssen. In der Infodatei müssen auch die Erweiterungsfunktionen, die mögliche Konfiguration und die Verwendung Ihrer Erweiterung dokumentiert werden. In diesem Beispiel können Kunden wählen, ob die verstrichene Zeit in der HTTP-Antwort (HttpResponse) gemeldet wird.

Die Methode pre_invocation_app_level wird vom Python-Worker aufgerufen, bevor die Funktion ausgeführt wird. Sie stellt die Informationen aus der Funktion zur Verfügung – also beispielsweise Funktionskontext und Argumente. In diesem Beispiel protokolliert die Erweiterung eine Meldung und zeichnet die Startzeit eines Aufrufs basierend auf der zugehörigen Aufruf-ID (invocation_id) auf.

post_invocation_app_level wird ähnlich dazu nach der Funktionsausführung aufgerufen. In diesem Beispiel wird die verstrichene Zeit auf der Grundlage von Startzeit und aktueller Zeit berechnet. Außerdem wird der Rückgabewert der HTTP-Antwort überschrieben.

Erstellen einer readme.md

Erstellen Sie eine readme.md-Datei im Stamm Ihres Erweiterungsprojekts. Diese Datei enthält die Anweisungen zur Verwendung Ihrer Erweiterung. Der readme.md-Inhalt wird als Beschreibung auf der Homepage Ihres PyPI-Projekts angezeigt.

# 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.

Lokales Nutzen Ihrer Erweiterung

Nachdem Sie nun eine Erweiterung erstellt haben, können Sie sie in einem App-Projekt verwenden, um sich zu vergewissern, dass sie ordnungsgemäß funktioniert.

Erstellen einer HTTP-Triggerfunktion

  1. Erstellen Sie einen neuen Ordner für Ihr App-Projekt, und navigieren Sie dorthin.

  2. Führen Sie über eine geeignete Shell (beispielsweise Bash) den folgenden Befehl aus, um das Projekt zu initialisieren:

    func init --python
    
  3. Verwenden Sie den folgenden Befehl, um eine neue HTTP-Triggerfunktion zu erstellen, die anonymen Zugriff zulässt:

    func new -t HttpTrigger -n HttpTrigger -a anonymous
    

Aktivieren einer virtuellen Umgebung

  1. Verwenden Sie das entsprechende Verfahren für Ihr Betriebssystem, um eine virtuelle Python-Umgebung zu erstellen:

    python3 -m venv .venv
    
  2. Verwenden Sie das entsprechende Verfahren für Ihr Betriebssystem, um die virtuelle Python-Umgebung zu aktivieren:

    source .venv/bin/activate
    

Konfigurieren der Erweiterung

  1. Verwenden Sie den folgenden Befehl, um Remotepakete für Ihr Funktions-App-Projekt zu installieren:

    pip install -r requirements.txt
    
  2. Installieren Sie die Erweiterung über Ihren lokalen Dateipfad im bearbeitbaren Modus:

    pip install -e <PYTHON_WORKER_EXTENSION_ROOT>
    

    Ersetzen Sie in diesem Beispiel <PYTHON_WORKER_EXTENSION_ROOT> durch den Stammdateispeicherort Ihres Erweiterungsprojekts.

    Wenn ein Kunde Ihre Erweiterung verwendet, fügt er stattdessen den Speicherort Ihres Erweiterungspakets der Datei „requirements.txt“ hinzu, wie in den folgenden Beispielen gezeigt:

    # requirements.txt
    python_worker_extension_timer==1.0.0
    
  3. Öffnen Sie die Projektdatei „local.settings.js“, und fügen Sie Values das folgende Feld hinzu:

    "PYTHON_ENABLE_WORKER_EXTENSIONS": "1" 
    

    Bei Ausführung in Azure fügen Sie stattdessen PYTHON_ENABLE_WORKER_EXTENSIONS=1 zu den App-Einstellungen in der Funktions-App hinzu.

  4. Fügen Sie die folgenden zwei Zeilen vor der main-Funktion in der Datei __init.py__ für das Programmiermodell v1 oder in der Datei function_app.py für das Programmiermodell v2 hinzu:

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

    Durch diesen Code wird das Modul TimerExtension importiert und der Konfigurationswert append_to_http_response festgelegt.

Überprüfen der Erweiterung

  1. Starten Sie im Stammordner Ihres App-Projekts mithilfe von func host start --verbose den Funktionshost. Der lokale Endpunkt Ihrer Funktion sollte in der Ausgabe als https://localhost:7071/api/HttpTrigger angezeigt werden.

  2. Senden Sie im Browser eine GET-Anforderung an https://localhost:7071/api/HttpTrigger. Daraufhin sollte eine Antwort wie die folgende angezeigt werden – mit angefügten Daten zur verstrichenen Zeit (TimeElapsed) für die Anforderung:

    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)
    

Veröffentlichen der Erweiterung

Nach dem Erstellen und Überprüfen der Erweiterung müssen noch die folgenden Veröffentlichungsaufgaben ausgeführt werden:

  • Auswählen einer Lizenz
  • Erstellen von Infodatei (readme.md) und weiterem Dokumentationsmaterial
  • Veröffentlichen der Erweiterungsbibliothek in einer Python-Paketregistrierung oder in einem Versionskontrollsystem (Version Control System, VCS)

So veröffentlichen Sie Ihre Erweiterung in PyPI:

  1. Führen Sie den folgenden Befehl aus, um twine und wheel in Ihrer Python-Standardumgebung oder in einer virtuellen Umgebung zu installieren:

    pip install twine wheel
    
  2. Entfernen Sie den alten Ordner dist/ aus Ihrem Erweiterungsrepository.

  3. Führen Sie den folgenden Befehl aus, um ein neues Paket in dist/ zu generieren:

    python setup.py sdist bdist_wheel
    
  4. Führen Sie den folgenden Befehl aus, um das Paket in PyPI hochzuladen:

    twine upload dist/*
    

    Im Rahmen des Uploads ist unter Umständen die Angabe Ihrer PyPI-Kontoanmeldeinformationen erforderlich. Sie können den Paketupload auch mit twine upload -r testpypi dist/* testen. Weitere Informationen finden Sie in der Twine-Dokumentation.

Nach diesen Schritten können Kunden Ihre Erweiterung verwenden, indem sie Ihren Paketnamen in die Datei „requirements.txt“ einschließen.

Weitere Informationen finden Sie im offiziellen Tutorial zur Python-Paketerstellung.

Beispiele

  • Das fertige Beispielerweiterungsprojekt aus diesem Artikel steht im Beispielrepository python_worker_extension_timer zur Verfügung.

  • Die OpenCensus-Integration ist ein Open-Source-Projekt, das die Erweiterungsschnittstelle verwendet, um Telemetrieablaufverfolgung in Python-basierte Azure Functions-Apps zu integrieren. Informationen zur Implementierung dieser Python-Workererweiterung finden Sie im Repository opencensus-python-extensions-azure.

Nächste Schritte

Weitere Informationen zur Python-Entwicklung für Azure Functions finden Sie in den folgenden Ressourcen: