Developing for Azure Functions is a fantastic experience but requires a slightly different mindset and approach. If your function code is interacting with Azure resources such as getting secrets from a Key Vault or writing to CosmosDB, running it locally isn’t as easy as just executing a file.

Contents

Note - this article will refer to Azure Functions Core Tools cmdlets. Quick guide here.

Quick set up

If you are still fairly new to Azure Functions and want to follow along with the guide, I suggest you install Azure Functions Core Tools then run the below cmdlets to quickly set up a Function to work with:

Commands based on bash…

mkdir MyFunctionApp # create a new directory that acts as your Funtion App
cd MyFunctionApp # change to the new directory
func init --python # create the project and select your runtime
func new --template "Http Trigger" --name MyHttpTrigger # create a new Http function
func new --template "Timer Trigger" --name MyTimerTrigger # create a new Timer function

The Runtime

Although this article doesn’t exclusively apply to Python functions I’m going to use it as an example here. Before you can run a function locally you need to have the right version of your chosen runtime installed.

I got caught out by this recently as I had been developing in Python 3.6 locally as (at that time) my understanding was that Azure Python Functions also used version 3.6. Well, at some point they upgraded to 3.7 so when I came to push my code to a new deployment it didn’t work as expected!

You also need to consider package versions. During development I installed several Pip packages to facilitate my code. You will need to ensure the same versions of these are also pushed to your function in Azure. The best way to do this (from a Python perspective) is to use the requirement.txt file.

If you have created a new Python Function, you will find this file in the root of the directory. Example below:

azure-functions==1.0.4
azure-keyvault==1.1.0
pymongo==3.9.0
requests==2.22.0
pandas==0.25.2
azure-mgmt-compute==10.0.0
azure-mgmt-network==8.0.0
azure-mgmt-resource==3.0.0

You define which packages and version you want to be included in your build. This way, your local and cloud based environments will be consistent. When you run the final command to publish your code to Azure (such as the below), the correct modules are installed remotely.

func azure functionapp publish my-functionapp-name-in-azure --build remote

It’s worth noting that using Python’s virtualenv along with the requirements.txt is considered best practice.

Local Functions Host

One of the greatest development features of Azure Functions is the local functions host. Within your functions directory, if you run the cmdlet func start a local functions host will spin-up, allowing you to run your functions on your local machine.

func start

Authentication

When your functions are executed in Azure they have access to the Application Settings section of the Function App. This is where you can save secrets and connection strings. I use it to save Service Principal (SP) credentials. When my functions execute in Azure, they get the SP credentials from the Application Settings and used them to authenticate against an Azure tenant. Once authenticated, they can perform tasks such as query a Key Vault or write to a database (subject to RBAC).

The problem when running your functions locally is that they do not have access to the Application Settings you configured up in Azure. This is where the local.settings.json file comes in.

Within this file you can define your Service Principal credentials and the tenant that you want to authenticate against. You can also use it to save connection strings and other parameters that you may wish to share amongst your code. Consider them as defined environment variables.

The "AzureWebJobsStorage" will need contain a live storage account connection string, so be mindful about how your code may interact with it. You don’t want to bugger up production data by mistake!

One thing to note is that, should you be using an Http triggered function, you will need to specify the connection string of the storage account linked to your Function App up in Azure.

The local.settings.json file is only used when running functions locally and it is not pushed up to Azure when you run a build. Here’s an example:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "AzureWebJobsStorage": "connectStringOfTheStorageAccount",
    "client_id": "servicePrincipalAppID",
    "client_secret": "servicePrincipalAppSecret",
    "client_tenant": "tenantIDforabove",
  }
}

Once populated, you will be able to run your Azure Functions on your local machine much in the same way as they would run in the cloud! All you need to do now is know how to trigger the functions locally.

Reference

The Trigger

This is the bit that I struggled to find an answer to online…

In one of my most recent projects, I had a mix of Timer triggered and Http triggered functions.

Http Trigger

Using the local functions host, you can manually invoke an Http trigger by making an API call to something like:

GET http://localhost:7071/api/myhttptrigger/

where your function.json is defined as something like:

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route": "myhttptrigger/",
      "methods": [
        "get"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}

Timer Trigger

This is a nice, quick and easy way to test your Http functions. But what about Timer functions? Surely you don’t have to mess about with the schedule and wait for it to trigger automatically? No, thankfully you do not!

With the functions host running, you can manually invoke a Timer function by making a localhost API call. Here’s an example:

Note - for this to work, you must use a POST request and define the ‘Content-Type’ header as ‘application/json’.

POST http://localhost:7071/admin/functions/mytimertrigger

where your function.json is defined as something like:

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "mytimertrigger",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 0 10 * * *",
      "runOnStartup" : false
    }
  ]
}

TL;DR;

  • Make sure your local development environment matches that of the Azure Function App. Don’t forget to define your package versions.

  • Use the local.settings.json file to define your secrets (such as Azure credentials) and connection strings (such as storage accounts).

  • To manually trigger a local Http function: GET http://localhost:7071/api/myhttptrigger/

  • To manually trigger a local Timer function: POST http://localhost:7071/admin/functions/mytimertrigger with ‘Content-Type’ : ‘application/json’ defined as a header.

  • The above also works in production - you simply replace ‘localhost:7071’ with the FQDN and access code of your published function.

Thanks for reading. Please feel free to leave a comment on get in touch.