- Published on
Azure Functions Python Extensions
- Authors
- Name
- Stefanus Hinardi
- @stefanushinardi
Lifecycle management in Azure Functions executions are useful in enabling imporant scenarios while simplifying your applications code. In this blog post, we will explore Azure Functions Python Extentions and walkthrough how to use the feature in your Functions applications.
Overview
Azure Functions Python Extentions act as middlewares that can inject specific operations during the lifecycle of your function's execution.
Extensions are executed based on the following scopes:
Scope | Description |
---|---|
Application-level | When imported into any function trigger, the extension applies to every function execution in the app. |
Function-level | Execution is limited to only the specific function trigger into which it's imported. |
Extensions implement a Python worker extension interface that lets the Python worker process call into the extension code during the function execution lifecycle. To learn more, see Creating extensions.
Using Azure Functions Python Extensions
Extensions are imported in your function code much like a standard Python library module.
Overall, follow these steps to enable the Azure Functions Python Extentions in your Functions apps:
- Add the extension package in the requirements.txt file for your project.
- Install the library into your app.
- Add the application setting PYTHON_ENABLE_WORKER_EXTENSIONS:
- Local development: add
"PYTHON_ENABLE_WORKER_EXTENSIONS"
: "1" in the Values section of yourlocal.settings.json
file - Azure: add
PYTHON_ENABLE_WORKER_EXTENSIONS=1
to your app settings.
- Local development: add
- Import the extension module into your function trigger.
- Configure the extension instance, if needed. Configuration requirements should be called-out in the extension's documentation.
Integrating Azure Functions OpenCensus Python
For the purpose of this walktrhough, let us integrate the OpenCensus Python Extensions, which sends custom telemetry data to your Application Insights instance.
Before you get started, make sure you have the following requirements in place:
- An Azure account with an active subscription.
- The Azure Functions Core Tools version 4.x.
- Python versions that are supported by Azure Functions.
- Visual Studio Code on one of the supported platforms.
- The Python extension for Visual Studio Code.
- The Azure Functions extension for Visual Studio Code.
Here are the steps:
- Create a new Python Functions app
- Create a new HTTP trigger functions. Your app should looks something like this.
- Include
opencensus-extension-azure-functions
to yourrequirements.txt
- In
local.settings.json
, add new settings"PYTHON_ENABLE_WORKER_EXTENSIONS": "1"
and"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
. You can acquire your appinsights instrumentation key from your Azure Application Insights resource. - Change the HTTP trigger file in
<project_root>/HttpTrigger1/__init__.py
to enable the OpenCensus tracing integration.
import json
import logging
from opencensus.extension.azure.functions import OpenCensusExtension
logger = logging.getLogger('HttpTriggerLogger')
OpenCensusExtension.configure()
def main(req, context):
logger.info('Executing HttpTrigger with OpenCensus extension')
# You must use context.tracer to create spans
with context.tracer.span("parent"):
logger.info('Message from HttpTrigger')
return json.dumps({
'method': req.method,
'ctx_func_name': context.function_name,
'ctx_func_dir': context.function_directory,
'ctx_invocation_id': context.invocation_id,
'ctx_trace_context_Traceparent': context.trace_context.Traceparent,
'ctx_trace_context_Tracestate': context.trace_context.Tracestate,
})
- Run it in your local development environment by
func host start --verbose
in Core Tools or hittingF5
in VSCode. - You should now be able to check the trace information in your
Application Insight -> Investigate -> Application Map
.
Authoring your own extension and testing locally
In the section, we will attempt to create an extension for learning the authoring process. We will create an extension that utilizes a third party pacakge(better profanity) and use it to censor profanity words in the return responses.
- Create an a folder that would be the root folder of the extension. The following will be the final project structure.
<python_worker_extension_root>/
| - .venv/
| - python_worker_extension_filter/
| | - __init__.py
| - setup.py
| - readme.md
Folder/file | Description |
---|---|
.venv/ | (Optional) Contains a Python virtual environment used for local development. |
python_worker_extension/ | Contains the source code of the Python worker extension. This folder contains the main Python module to be published into PyPI. |
setup.py | Contains the metadata of the Python worker extension package. |
readme.md (Optional) | Contains the instruction and usage of your extension. This content is displayed as the description in the home page in your PyPI project. |
- First, create
setup.py
. This file contains the infromation and metadata about your package. Here is an example of what it should look like:
from setuptools import find_packages, setup
setup(
name='python-worker-extension-filter',
version='1.0.0',
author='Your Name Here', # Change to your name here
author_email='your@email.here', # Change to 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.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
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', # make sure that thi is here
# Any additional packages that will be used in your extension
'better-profanity >= 0.7.0',
],
extras_require={},
license='MIT',
packages=find_packages(where='.'),
url='https://your-github-or-pypi-link',
zip_safe=False,
)
- Next is to create the
python_worker_extension_filter/__init__.py
file. This is where we would define the logic for the exntension. In our case, we are implementing an application-level extension.
import typing
from logging import Logger
from time import time
from azure.functions import AppExtensionBase, Context, HttpResponse
from better_profanity import profanity
class FilterExtension(AppExtensionBase):
"""A Python worker extension to filter profanity from text.
"""
@classmethod
def init(cls):
# This loads the censor words.
# Use init to configure the settings when the extension gets loaded by the functions applications
profanity.load_censor_words()
@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:
# Here we use the library to scan every word in the return text of HTTP response of the consuming functions app
func_ret._HttpResponse__body = profanity.censor(str(func_ret._HttpResponse__body)).encode()
For more information regarding other hooks that could be overriden, follow this documentation.
- To consume this extension locally, create a new Azure Functions Python project.
- Install the extension on this workspace by executing this install command
pip install -e <local_path_to_extension_project>
- In
local.settings.json
, add"PYTHON_ENABLE_WORKER_EXTENSIONS": "1"
toValues
. - Add an HTTP trigger to the Azure Functions Project and add the instantiation lines for the
FilterExtension
.
import logging
import azure.functions as func
# Import the exntension and configure the Filter extension
from python_worker_extension_filter import FilterExtension
FilterExtension.configure()
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello, {name}. This HTTP triggered function executed successfully.")
else:
return func.HttpResponse(
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)
- To test, run the Functions app locally and hit the endpoint of the HTTP trigger. To test the filtering feature, try by hitting
http://localhost:7071/api/HttpTrigger1?name=shit
. You should see a response like the following
'Hello, ****. This HTTP triggered function executed successfully.'
Publishing your package
Once you are satisfied, you can publish it so that others could consume your newly created extension. There are you paths so that other applicaitons could consume your exntension: