Azure Functions Python developer guide

This article is an introduction to developing Azure Functions using Python. The content below assumes that you've already read the Azure Functions developers guide.

For standalone Function sample projects in Python, see the Python Functions samples.

Programming model

Azure Functions expects a function to be a stateless method in your Python script that processes input and produces output. By default, the runtime expects the method to be implemented as a global method called main() in the __init__.py file. You can also specify an alternate entry point.

Data from triggers and bindings is bound to the function via method attributes using the name property defined in the function.json file. For example, the function.json below describes a simple function triggered by an HTTP request named req:

{
  "bindings": [
    {
      "name": "req",
      "direction": "in",
      "type": "httpTrigger",
      "authLevel": "anonymous"
    },
    {
      "name": "$return",
      "direction": "out",
      "type": "http"
    }
  ]
}

The __init__.py file contains the following function code:

def main(req):
    user = req.params.get('user')
    return f'Hello, {user}!'

you can also explicitly declare the attribute types and return type in the function using Python type annotations. This helps you use the intellisense and autocomplete features provided by many Python code editors.

import azure.functions


def main(req: azure.functions.HttpRequest) -> str:
    user = req.params.get('user')
    return f'Hello, {user}!'

Use the Python annotations included in the azure.functions.* package to bind input and outputs to your methods.

Alternate entry point

You can change the default behavior of a function by optionally specifying the scriptFile and entryPoint properties in the function.json file. For example, the function.json below tells the runtime to use the customentry() method in the main.py file, as the entry point for your Azure Function.

{
  "scriptFile": "main.py",
  "entryPoint": "customentry",
  "bindings": [
      ...
  ]
}

Folder structure

The recommended folder structure for a Python Functions project looks like the following example:

 __app__
 | - MyFirstFunction
 | | - __init__.py
 | | - function.json
 | | - example.py
 | - MySecondFunction
 | | - __init__.py
 | | - function.json
 | - SharedCode
 | | - myFirstHelperFunction.py
 | | - mySecondHelperFunction.py
 | - host.json
 | - requirements.txt
 tests

The main project folder (__app__) can contain the following files:

  • local.settings.json: Used to store app settings and connection strings when running locally. This file doesn't get published to Azure. To learn more, see local.settings.file.
  • requirements.txt: Contains the list of packages the system installs when publishing to Azure.
  • host.json: Contains global configuration options that affect all functions in a function app. This file does get published to Azure. Not all options are supported when running locally. To learn more, see host.json.
  • funcignore: (Optional) declares files that shouldn't get published to Azure.
  • gitignore: (Optional) declares files that are excluded from a git repo, such as local.settings.json.

Each function has its own code file and binding configuration file (function.json).

Shared code should be kept in a separate folder in __app__. To reference modules in the SharedCode folder, you can use the following syntax:

from __app__.SharedCode import myFirstHelperFunction

To reference modules local to a function, you can use the relative import syntax as follows:

from . import example

When deploying your project to a function app in Azure, the entire content of the FunctionApp folder should be included in the package, but not the folder itself. We recommend that you maintain your tests in a folder separate from the project folder, in this example tests. This keeps you from deploying test code with your app. For more information, see Unit Testing.

Triggers and Inputs

Inputs are divided into two categories in Azure Functions: trigger input and additional input. Although they are different in the function.json file, usage is identical in Python code. Connection strings or secrets for trigger and input sources map to values in the local.settings.json file when running locally, and the application settings when running in Azure.

For example, the following code demonstrates the difference between the two:

// function.json
{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "req",
      "direction": "in",
      "type": "httpTrigger",
      "authLevel": "anonymous",
      "route": "items/{id}"
    },
    {
      "name": "obj",
      "direction": "in",
      "type": "blob",
      "path": "samples/{id}",
      "connection": "AzureWebJobsStorage"
    }
  ]
}
// local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "AzureWebJobsStorage": "<azure-storage-connection-string>"
  }
}
# __init__.py
import azure.functions as func
import logging


def main(req: func.HttpRequest,
         obj: func.InputStream):

    logging.info(f'Python HTTP triggered function processed: {obj.read()}')

When the function is invoked, the HTTP request is passed to the function as req. An entry will be retrieved from the Azure Blob Storage based on the ID in the route URL and made available as obj in the function body. Here the storage account specified is the connection string found in , which is the same storage account used by the function app.

Outputs

Output can be expressed both in return value and output parameters. If there's only one output, we recommend using the return value. For multiple outputs, you'll have to use output parameters.

To use the return value of a function as the value of an output binding, the name property of the binding should be set to $return in function.json.

To produce multiple outputs, use the set() method provided by the azure.functions.Out interface to assign a value to the binding. For example, the following function can push a message to a queue and also return an HTTP response.

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "req",
      "direction": "in",
      "type": "httpTrigger",
      "authLevel": "anonymous"
    },
    {
      "name": "msg",
      "direction": "out",
      "type": "queue",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "direction": "out",
      "type": "http"
    }
  ]
}
import azure.functions as func


def main(req: func.HttpRequest,
         msg: func.Out[func.QueueMessage]) -> str:

    message = req.params.get('body')
    msg.set(message)
    return message

Logging

Access to the Azure Functions runtime logger is available via a root logging handler in your function app. This logger is tied to Application Insights and allows you to flag warnings and errors encountered during the function execution.

The following example logs an info message when the function is invoked via an HTTP trigger.

import logging


def main(req):
    logging.info('Python HTTP trigger function processed a request.')

Additional logging methods are available that let you write to the console at different trace levels:

Method Description
critical(_message_) Writes a message with level CRITICAL on the root logger.
error(_message_) Writes a message with level ERROR on the root logger.
warning(_message_) Writes a message with level WARNING on the root logger.
info(_message_) Writes a message with level INFO on the root logger.
debug(_message_) Writes a message with level DEBUG on the root logger.

To learn more about logging, see Monitor Azure Functions.

HTTP Trigger and bindings

The HTTP trigger is defined in the function.jon file. The name of the binding must match the named parameter in the function. In the previous examples, a binding name req is used. This parameter is an HttpRequest object, and an HttpResponse object is returned.

From the HttpRequest object, you can get request headers, query parameters, route parameters, and the message body.

The following example is from the HTTP trigger template for Python.

def main(req: func.HttpRequest) -> func.HttpResponse:
    headers = {"my-http-header": "some-value"}

    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}!", headers=headers)
    else:
        return func.HttpResponse(
             "Please pass a name on the query string or in the request body",
             headers=headers, status_code=400
        )

In this function, the value of the name query parameter is obtained from the params parameter of the HttpRequest object. The JSON-encoded message body is read using the get_json method.

Likewise, you can set the status_code and headers for the response message in the returned HttpResponse object.

Concurrency

By default, the Functions Python runtime can only process one invocation of a function at a time. This concurrency level might not be sufficient under one or more of the following conditions:

  • You're trying to handle a number of invocations being made at the same time.
  • You're processing a large number of I/O events.
  • Your application is I/O bound.

In these situations, you can improve performance by running asynchronously and by using multiple language worker processes.

Async

We recommend that you use the async def statement to make your function run as an asynchronous coroutine.

# Runs with asyncio directly

async def main():
    await some_nonblocking_socket_io_op()

When the main() function is synchronous (without the async qualifier), the function is automatically run in an asyncio thread-pool.

# Runs in an asyncio thread-pool

def main():
    some_blocking_socket_io()

Use multiple language worker processes

By default, every Functions host instance has a single language worker process. However there's support to have multiple language worker processes per host instance. Function invocations can then be evenly distributed among these language worker processes. Use the FUNCTIONS_WORKER_PROCESS_COUNT application setting to change this value.

Context

To get the invocation context of a function during execution, include the context argument in its signature.

For example:

import azure.functions


def main(req: azure.functions.HttpRequest,
         context: azure.functions.Context) -> str:
    return f'{context.invocation_id}'

The Context class has the following string attributes:

function_directory
The directory in which the function is running.

function_name
Name of the function.

invocation_id
ID of the current function invocation.

Global variables

It is not guaranteed that the state of your app will be preserved for future executions. However, the Azure Functions runtime often reuses the same process for multiple executions of the same app. In order to cache the results of an expensive computation, declare it as a global variable.

CACHED_DATA = None


def main(req):
    global CACHED_DATA
    if CACHED_DATA is None:
        CACHED_DATA = load_json()

    # ... use CACHED_DATA in code

Environment variables

In Functions, application settings, such as service connection strings, are exposed as environment variables during execution. You can access these settings by declaring import os and then using, setting = os.environ["setting-name"].

The following example gets the application setting, with the key named myAppSetting:

import logging
import os
import azure.functions as func

def main(req: func.HttpRequest) -> func.HttpResponse:

    # Get the setting named 'myAppSetting'
    my_app_setting_value = os.environ["myAppSetting"]
    logging.info(f'My app setting value:{my_app_setting_value}')

For local development, application settings are maintained in the local.settings.json file.

Python version

Currently, Azure Functions supports both Python 3.6.x and 3.7.x (official CPython distributions). When running locally, the runtime uses the available Python version. To request a specific Python version when you create your function app in Azure, use the --runtime-version option of the az functionapp create command.

Package management

When developing locally using the Azure Functions Core Tools or Visual Studio Code, add the names and versions of the required packages to the requirements.txt file and install them using pip.

For example, the following requirements file and pip command can be used to install the requests package from PyPI.

requests==2.19.1
pip install -r requirements.txt

Publishing to Azure

When you're ready to publish, make sure that all your publicly available dependencies are listed in the requirements.txt file, which is located at the root of your project directory.

Project files and folders that are excluded from publishing, including the virtual environment folder, are listed in the .funcignore file.

There are three build actions supported for publishing your Python project to Azure:

  • Remote build: Dependencies are obtained remotely based on the contents of the requirements.txt file. Remote build is the recommended build method. Remote is also the default build option of Azure tooling.
  • Local build: Dependencies are obtained locally based on the contents of the requirements.txt file.
  • Custom dependencies: Your project uses packages not publicly available to our tools. (Requires Docker.)

To build your dependencies and publish using a continuous delivery (CD) system, use Azure Pipelines.

Remote build

By default, the Azure Functions Core Tools requests a remote build when you use the following func azure functionapp publish command to publish your Python project to Azure.

func azure functionapp publish <APP_NAME>

Remember to replace <APP_NAME> with the name of your function app in Azure.

The Azure Functions Extension for Visual Studio Code also requests a remote build by default.

Local build

You can prevent doing a remote build by using the following func azure functionapp publish command to publish with a local build.

func azure functionapp publish <APP_NAME> --build local

Remember to replace <APP_NAME> with the name of your function app in Azure.

Using the --build local option, project dependencies are read from the requirements.txt file and those dependent packages are downloaded and installed locally. Project files and dependencies are deployed from your local computer to Azure. This results in a larger deployment package being uploaded to Azure. If for some reason, dependencies in your requirements.txt file can't be acquired by Core Tools, you must use the custom dependencies option for publishing.

Custom dependencies

If your project uses packages not publicly available to our tools, you can make them available to your app by putting them in the __app__/.python_packages directory. Before publishing, run the following command to install the dependencies locally:

pip install  --target="<PROJECT_DIR>/.python_packages/lib/site-packages"  -r requirements.txt

When using custom dependencies, you should use the --no-build publishing option, since you have already installed the dependencies.

func azure functionapp publish <APP_NAME> --no-build

Remember to replace <APP_NAME> with the name of your function app in Azure.

Unit Testing

Functions written in Python can be tested like other Python code using standard testing frameworks. For most bindings, it's possible to create a mock input object by creating an instance of an appropriate class from the azure.functions package. Since the azure.functions package is not immediately available, be sure to install it via your requirements.txt file as described in the package management section above.

For example, following is a mock test of an HTTP triggered function:

{
  "scriptFile": "__init__.py",
  "entryPoint": "my_function",
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}
# __app__/HttpTrigger/__init__.py
import azure.functions as func
import logging

def my_function(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}")
    else:
        return func.HttpResponse(
             "Please pass a name on the query string or in the request body",
             status_code=400
        )
# tests/test_httptrigger.py
import unittest

import azure.functions as func
from __app__.HttpTrigger import my_function

class TestFunction(unittest.TestCase):
    def test_my_function(self):
        # Construct a mock HTTP request.
        req = func.HttpRequest(
            method='GET',
            body=None,
            url='/api/HttpTrigger',
            params={'name': 'Test'})

        # Call the function.
        resp = my_function(req)

        # Check the output.
        self.assertEqual(
            resp.get_body(),
            b'Hello Test',
        )

Here is another example, with a queue triggered function:

{
  "scriptFile": "__init__.py",
  "entryPoint": "my_function",
  "bindings": [
    {
      "name": "msg",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "python-queue-items",
      "connection": "AzureWebJobsStorage"
    }
  ]
}
# __app__/QueueTrigger/__init__.py
import azure.functions as func

def my_function(msg: func.QueueMessage) -> str:
    return f'msg body: {msg.get_body().decode()}'
# tests/test_queuetrigger.py
import unittest

import azure.functions as func
from __app__.QueueTrigger import my_function

class TestFunction(unittest.TestCase):
    def test_my_function(self):
        # Construct a mock Queue message.
        req = func.QueueMessage(
            body=b'test')

        # Call the function.
        resp = my_function(req)

        # Check the output.
        self.assertEqual(
            resp,
            'msg body: test',
        )

Temporary files

The tempfile.gettempdir() method returns a temporary folder, which on Linux is /tmp. Your application can use this directory to store temporary files generated and used by your functions during execution.

Important

Files written to the temporary directory aren't guaranteed to persist across invocations. During scale out, temporary files aren't shared between instances.

The following example creates a named temporary file in the temporary directory (/tmp):

import logging
import azure.functions as func
import tempfile
from os import listdir

#---
   tempFilePath = tempfile.gettempdir()   
   fp = tempfile.NamedTemporaryFile()     
   fp.write(b'Hello world!')              
   filesDirListInTemp = listdir(tempFilePath)     

We recommend that you maintain your tests in a folder separate from the project folder. This keeps you from deploying test code with your app.

Known issues and FAQ

All known issues and feature requests are tracked using GitHub issues list. If you run into a problem and can't find the issue in GitHub, open a new issue and include a detailed description of the problem.

Cross-origin resource sharing

Azure Functions supports cross-origin resource sharing (CORS). CORS is configured in the portal and through the Azure CLI. The CORS allowed origins list applies at the function app level. With CORS enabled, responses include the Access-Control-Allow-Origin header. For more information, see Cross-origin resource sharing.

The allowed origins list isn't currently supported for Python function apps. Because of this limitation, you must expressly set the Access-Control-Allow-Origin header in your HTTP functions, as shown in the following example:

def main(req: func.HttpRequest) -> func.HttpResponse:

    # Define the allow origin headers.
    headers = {"Access-Control-Allow-Origin": "https://contoso.com"}

    # Set the headers in the response.
    return func.HttpResponse(
            f"Allowed origin '{headers}'.",
            headers=headers, status_code=200
    )

Make sure that you also update your function.json to support the OPTIONS HTTP method:

    ...
      "methods": [
        "get",
        "post",
        "options"
      ]
    ...

This method is used by the Chrome browser to negotiate the allowed origins list.

Next steps

For more information, see the following resources: