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:
Python 3.7 oder höher. Die vollständige Liste der unterstützten Python-Versionen in Azure Functions finden Sie im Python-Entwicklerhandbuch.
Die Azure Functions Core Tools, Version 4.0.5095 oder höher, welche die Verwendung der Erweiterung mit dem Python-Programmiermodell v2 unterstützen. Überprüfen Sie Ihre Version mit
func --version
.Visual Studio Code auf einer der unterstützten Plattformen installiert.
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
Erstellen Sie einen neuen Ordner für Ihr App-Projekt, und navigieren Sie dorthin.
Führen Sie über eine geeignete Shell (beispielsweise Bash) den folgenden Befehl aus, um das Projekt zu initialisieren:
func init --python
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
Verwenden Sie das entsprechende Verfahren für Ihr Betriebssystem, um eine virtuelle Python-Umgebung zu erstellen:
Verwenden Sie das entsprechende Verfahren für Ihr Betriebssystem, um die virtuelle Python-Umgebung zu aktivieren:
Konfigurieren der Erweiterung
Verwenden Sie den folgenden Befehl, um Remotepakete für Ihr Funktions-App-Projekt zu installieren:
pip install -r requirements.txt
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:
Ö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.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 Konfigurationswertappend_to_http_response
festgelegt.
Überprüfen der Erweiterung
Starten Sie im Stammordner Ihres App-Projekts mithilfe von
func host start --verbose
den Funktionshost. Der lokale Endpunkt Ihrer Funktion sollte in der Ausgabe alshttps://localhost:7071/api/HttpTrigger
angezeigt werden.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:
Führen Sie den folgenden Befehl aus, um
twine
undwheel
in Ihrer Python-Standardumgebung oder in einer virtuellen Umgebung zu installieren:pip install twine wheel
Entfernen Sie den alten Ordner
dist/
aus Ihrem Erweiterungsrepository.Führen Sie den folgenden Befehl aus, um ein neues Paket in
dist/
zu generieren:python setup.py sdist bdist_wheel
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: