Azure Functions
This blogpost will demonstrate how to set up Azure Functions with some Python code. More precisely, it will show how to call an Azure Function, add a parameter that specifies the name of the file that we want to read from and store that information in a database. Between reading and storing we have the chance to execute any python code we want. As long as it stays below the 30 min threshold :)
Setup
Most of the setup code is directly taken from here: Microsoft Tutorial: Create your first function in the Azure portal As soon as the typos start, it is my code and tutorial again :)
Before you start, you must have the following:
- Python 3.6
- Azure Functions Core Tools version 2.6.666 or later
- Azure CLI version 2.x or later
- An active Azure subscription
Create and activate a virtual environment
# Bash
python3.6 -m venv .env
source .env/bin/activate
# PowerShell or CMD
py -3.6 -m venv .env
.env\scripts\activate
Create a local Functions project
func init FunctionSample
Choose python as worker runtime:
A folder named MyFunctionProj is created, which contains the following three files:
local.settings.json
is used to store app settings and connection strings when running locally. This file doesn’t get published to Azure.requirements.txt
contains the list of packages to be installed on publishing to Azure.host.json
contains global configuration options that affect all functions in a function app. This file does get published to Azure.
Create a function
To add a function to your project, run the following command:
func new
Choose the HTTP trigger template and gave a name to the function:
A subfolder named HttpTrigger is created, which contains the following files:
function.json
: configuration file that defines the function, trigger, and other bindings. Review this file and see that the value for scriptFile points to the file containing the function, while the invocation trigger and bindings are defined in the bindings array. Each binding requires a direction, type and a unique name. The HTTP trigger has an input binding of type httpTrigger and output binding of type http.init.py
: script file that is your HTTP triggered function. Review this script and see that it contains a default main(). HTTP data from the trigger is passed to this function using the req named binding parameter. Defined in function.json, req is an instance of the azure.functions.HttpRequest class.
Run the function locally
func host start
If we type http://localhost:7071/api/HttpTrigger?name=Thomas
into the browser bar now we will get the following result.
Analysis of the code so far
Let’s a have closer look into __init__.py
:
import logging
import azure.functions as func
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}!")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
We can see the parameter name
was expected. If you need you will get an error messag. We will build directly on this stub and not even change its name.
Adding Storage
We will need to import the necessary python package for the Azure storage
from azure.storage.file import FileService
Furthermore we add to functions:
- check_file
- read_file
def check_file(name):
file_service = FileService(account_name='<account_name>', account_key='<account_key>')
return file_service.exists('foobar', None, name+'.txt')
def read_file(name):
file_service = FileService(account_name='<account_name>', account_key='<account_key>')
file_service.get_file_to_path('foobar', None, name+'.txt', 'download.txt')
file = open("download.txt", "r")
file_content = file.read()
return file_content
Adding Database
Again we need to add the necessary library, this time pyodbc:
import pyodbc
We define one function that simply saves the content of the file into the database:
def save_db(file_content):
cnxn = pyodbc.connect('Driver={ODBC Driver 13 for SQL Server};Server=tcp:<name>.database.windows.net,1433;Database=<database>;Uid=XXX@XXX;Pwd=<password>;Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;')
cursor = cnxn.cursor()
cursor.execute("INSERT INTO <database> (<field>) VALUES('" + str(file_content) + "')")
cnxn.commit()
cursor.close()
cnxn.close()
Main Code
A little adaption to the main function:
if name:
if not check_file(name):
return func.HttpResponse("File doesn't exist")
file_content = read_file(name)
save_db(file_content)
return func.HttpResponse(f"Content of file... {file_content}!")
Final code base
Here is the final result:
import logging
import azure.functions as func
from azure.storage.file import FileService
import pyodbc
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:
if not check_file(name):
return func.HttpResponse("File doesn't exist")
file_content = read_file(name)
save_db(file_content)
return func.HttpResponse(f"Content of file... {file_content}!")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
def check_file(name):
file_service = FileService(account_name='<account_name>', account_key='<account_key>')
return file_service.exists('foobar', None, name+'.txt')
def read_file(name):
file_service = FileService(account_name='<account_name>', account_key='<account_key>')
file_service.get_file_to_path('foobar', None, name+'.txt', 'download.txt')
file = open("download.txt", "r")
file_content = file.read()
return file_content
def save_db(file_content):
cnxn = pyodbc.connect('Driver={ODBC Driver 13 for SQL Server};Server=tcp:<name>.database.windows.net,1433;Database=<database>;Uid=XXX@XXX;Pwd=<password>;Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;')
cursor = cnxn.cursor()
cursor.execute("INSERT INTO <database> (<field>) VALUES('" + str(file_content) + "')")
cnxn.commit()
cursor.close()
cnxn.close()
In the next blog post we will talk about how to make this secure.