Published on

Azure Functions Python Extensions

Authors

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:

ScopeDescription
Application-levelWhen imported into any function trigger, the extension applies to every function execution in the app.
Function-levelExecution 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:

  1. Add the extension package in the requirements.txt file for your project.
  2. Install the library into your app.
  3. Add the application setting PYTHON_ENABLE_WORKER_EXTENSIONS:
    • Local development: add "PYTHON_ENABLE_WORKER_EXTENSIONS": "1" in the Values section of your local.settings.json file
    • Azure: add PYTHON_ENABLE_WORKER_EXTENSIONS=1 to your app settings.
  4. Import the extension module into your function trigger.
  5. 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:

  1. Create a new Python Functions app
  2. Create a new HTTP trigger functions. Your app should looks something like this.
how-to-step-3
  1. Include opencensus-extension-azure-functions to your requirements.txt
  2. 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.
  3. 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,
    })
  1. Run it in your local development environment by func host start --verbose in Core Tools or hitting F5 in VSCode.
  2. 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.

  1. 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/fileDescription
.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.pyContains 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.
  1. 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,
)
  1. 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.

  1. To consume this extension locally, create a new Azure Functions Python project.
  2. Install the extension on this workspace by executing this install command pip install -e <local_path_to_extension_project>
  3. In local.settings.json, add "PYTHON_ENABLE_WORKER_EXTENSIONS": "1" to Values.
  4. 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
        )

  1. To test, run the Functions app locally and hit the endpoint of the HTTP trigger. To test the filtering feature, try by hittinghttp://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: