What are Azure functions?
Functions allow to run serverless code, code that you run only when you need it and that doesn’t need a whole infrastructure to live in. You can decide that a function is triggered when hitting an HTTP endpoint, or when new files are added into a blob storage for instance. It might always perform the same task, or it might do something different depending on your inputs. Basically, you can write code that does (almost) anything you can think of and host it as an Azure Function.
It’s really convenient and as a bonus, it’s cheap: you pay a fraction of a dollar for a million requests and the first million is free — that is unless you decide to go for the hosted plan (like a regular web app) and not the consumption plan.
For more information on Azure functions, take a look at the documentation.
In this tutorial, we’ll learn how to create an HTTP triggered function and receive and return data. To illustrate the use of external libraries and show how the code can be segmented, we’ll create a function that performs text processing, and we’ll finish by publishing it to Azure.
1. Create a new function app
The documentation explains well how to get started quickly with Azure Python Functions, but if you prefer to stay here with me (I’m honored), here are the steps to setting up an Azure Python Functions project:
- Install Python (supported versions: 3.6, 3.7, 3.8)
- Install Azure Functions Core Tools
- Install the Azure CLI version 2.4 or later
If you don’t already have an Azure subscription, you can sign up for a free trial here.
Note that only Python 3.6 is supported for now, so make sure you have this version installed (it won’t work with Python 3.7).
Next step is creating a new virtual environment. Even if it’s not mandatory, you should do this each time you want to create a new Functions Project, that way when you publish it you don’t have to think about which libraries will be packaged with it.
To create a virtual environment, navigate to where you want to set up your project and use this Bash command:
python3.8 -m venv .venv
Or this Powershell command on Windows:
py -3.8 -m venv .venv
If you’re on Windows and you can’t activate your virtual environment (you get the error “The file is not digitally signed. You cannot run this script on the current system”), set your Execution Policy to Remote Signed to be able to run the script:
Open a Powershell command line in admin mode and run
You should now be able to activate your virtual environment, and you’ll see that (.env) precedes every line in your command prompt.
Inside your virtual environment, create a new Functions project by running:
func init FunctionProjTest
Then select Python as worker runtime.
The project you just created can later be published to one Function App in Azure. Functions app can contain several functions (up to 200 for the regular consumption plan, 20 for premium and app service plans). But remember all of them share the same environment, so if you want to have several functions that don’t share anything and use completely different libraries, it might make more sense to create several Functions projects.
To create a new function, navigate inside the Functions project folder and run
Select the HTTP trigger template as it’s the type of functions that we will cover in this tutorial, and follow the prompts to name your function. If you have no particular interest in being original you can use the same name as me: func_test
And… Congrats! You have now created a new Azure function, and you can already test what the HTTP trigger template does.
Run it locally using the command
func host start
and make a GET request by either navigating to the corresponding URL in your browser or using Postman.
The URL is provided in the command line when you start your function, and it looks like this: http://localhost:7071/api/func_test
The template is designed to say ‘Hello’ to a given name, so to test it properly, append a query parameter, like this: http://localhost:7071/api/func_test?name=Katia
2. Receive and return data
That’s all great, but now we want to use our own code in there.
Let’s take a look at __init__.py (inside your function folder), which was generated automatically as part of the HTTP Trigger template. This file contains the main method of our function, which is executed when the function is triggered.
It looks like this:
As you can see, it tries to get the name from the query parameters, and if it doesn’t find it, tries to get it from the request body. Note that it’s possible to use req_body[‘name’] instead of req_body.get(‘name’).
This means that we can also test sending the function a POST request.
Open Postman and make a POST request to your function URL, passing this as body:
"name": "You can use a fake name if you don’t like yours"
As expected, we get back the same response as what we got with the GET request.
So with all that we’ve seen how to get a request parameters and body, which is already pretty cool.
But let’s go a bit further:
What happens if you want to send images or other files to your function?
Well instead of using req.get_json() you can use req.get_body().
Replace the main method of the template with this:
Note that when you save the code after changes have been made, the function restarts automatically.
Now create a new txt file with whatever you want in it (if you’re not inspired click here) and make a new POST request to your function with the txt file as request body. What you should get back is the text contained in your file:
As I’m sure you’ve noticed, for now the only thing we’ve sent back is text. But once in a while (or more like always), sending back json can come in handy.
If you try and just pass json as the HTTP response, you’ll find yourself with an annoying error:
The right way to do it is using json.dumps, so go ahead and import json at the beginning of your file before updating your code:
Now that we know how to receive and return data with Python functions, let’s dive into the most interesting part: develop anything you want!
3. Segment your code & Use external libraries
If your code requires using external libraries, you can install them the same way you would with a regular project: just run
pip install NAME_OF_LIBRARY
at the root of your Functions project (not in the folder of the function itself).
For the sake of this tutorial, we’re going to install two text/natural language processing libraries: nltk and spaCy, along with a spaCy module.
Go ahead and run (after stopping the execution of your function project):
pip install nltk
pip install spacy
python -m spacy download en_core_web_sm
Once you’ve done that, run
pip freeze > requirements.txt
to keep a record of the libraries you’ve installed in this environment. This is the file that will be used when you publish your Functions project.
If you open it, you’ll see that something’s wrong:
Guessed it? The spaCy module wasn’t recorded properly (there’s a en-core-web-sm something in there but that’s not what we want) — you’ll need to manually add the module to your requirements:
Just delete the en-core-web-sm==2.1.0 line and put this URL at the end of requirements.txt:
The same goes for other libraries you may want to use that do not have a “regular” installation.
In order to avoid messy code, we’ll create a new folder that will contain 2 files: one where we’re going to use nltk to remove stopwords from a text and the other where we’ll use SpaCy to find named entities in a text.
Now your project tree should look like this:
I created a new folder at the root of the Functions project so that it could be used by several functions inside the project (even though here I only have one), but you can also create the files and folders you want inside a function folder, it’s up to you really.
In __init.py__, import the files you just created like this:
from ..shared_code import entity_recognition
from ..shared_code import stopwords
If your files are in the same folder, use this instead:
from . import entity_recognition
from . import stopwords
Put this code in stopwords.py to remove the known English stopwords from an input text:
And this in entity_recognition.py to return a text containing named entities recognized in an input text:
Now all we need to do is update __init.py__ to call our new modules:
Let’s test it (remember to run func host start first) — this is the text I used:
And here’s the result :
As you can see, the stopwords have been removed and our entity recognition code picked up a few named entities.
Now that you know how to do this — you can do pretty much anything you would normally do in Python 😉
Perfect, so your function is now ready and running locally. That’s great if you want to keep it to yourself. But if you want others to be able to access it, you’ll want to publish it. Which is — as you might expect — what we’ll learn below.
Publishing your Functions project means an image of your virtual environment will be created and deployed to a Function App in Azure, and since recently the build is done on the server side, so you don’t need to install anything.
There are 2 ways to create a Function App: you can either use the Azure CLI and do it from your command line, or do it from the portal. I’ll show how to do it from the portal since the documentation covers the former.
From the Azure portal, click on Create a Resource and create a new Function App:
Choose a unique name, select Linux as OS, choose to publish Code, and select Python as the Runtime Stack. For the Hosting Plan and Location it’s up to you but if you want your Function App to be cheap, then go for the Consumption Plan.
Once it’s deployed, go back to your command prompt (still in the Functions project folder, and stop the execution of your Functions project). Make sure you’re logged into your Azure account. If you’ve never used Azure CLI before, run
and set your subscription with
az account set --subscription "SUBSCRIPTION_ID"
Now you can either do a local or remote build, but depending on binary dependencies needed in your project, the remote build might fail. In this tutorial, we use a spaCy module which has a different behavior so we’ll use the local build approach.
Install Docker and make sure it is running, then run
func azure functionapp publish APP_NAME --build-native-deps
(APP_NAME being the name of the Function App you just created on Azure)
You should see something like this (don’t be surprised if it takes a while):
If it fails for no apparent reason, increase the memory allocated to Docker (Right click on Docker icon > Settings > Advanced).
If for other projects you only use standard libraries, you can build remotely by running:
func azure functionapp publish APP_NAME --build remote
This is what a successful deployment looks like:
We are (at least I am — if it didn’t work for you, read through the steps again and make sure you followed them properly) almost done!
To make sure everything works as expected, open Postman again and just replace the endpoint with the invoke URL provided after deployment (with the code parameter, otherwise you’ll get an Unauthorized Access error).
Yay! We’re now ready to use this Python function anywhere!
Here’s an example of how to use this in a JS web app:
Hope you find Azure functions as useful as I do 😉