Manuel Kanetscheider
Posted on December 21, 2022
Introduction
Recently, Microsoft introduced a decorator based approach for the development of Python based functions. This concept is very similar to the approach that FastAPI follows. So what has changed?
⚠️ The V2 programming model is currently in public preview. Some things may still change, so the features described here should not be used in production yet. Also, not all trigger types are fully supported at this time.
The V1 programming model
With the v1 programming model, each trigger of a function consists of two files:
-
function.json
- This is a configuration file that defines, among other things, the trigger type of the function and how the data is bound.
-
__init__.py
- Contains the actual code of the function and must match the function.json (such as names of input or output variables defined in function.json).
For example, an HTTP Azure Function looks like this:
function.json:
{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
__init__.py:
import azure.functions
def main(req: azure.functions.HttpRequest) -> str:
user = req.params.get('user')
return f'Hello, {user}!'
The V2 programming model
As you could see in the V1 programming model, for each trigger two files are needed, one is a configuration of the trigger and the other is the actual code. This has changed with the V2 programming model, the configuration information can now be placed directly in the code via decoraters.
For example, an HTTP Azure Function looks like this:
import azure.functions
@app.function_name(name="HttpTrigger1")
@app.route(route="req")
def main(req: azure.functions.HttpRequest) -> str:
user = req.params.get('user')
return f'Hello, {user}!'
The triggers can now be configured via decorates. The types of the attributes, both input and output, can be specified via type annotations.
At the moment the following triggers and bingings are supported:
Type | Trigger | Input | Output |
---|---|---|---|
HTTP | ✅ | ||
Timer | ✅ | ||
Azure Queue Storage | ✅ | ✅ | |
Azure Service Bus topic | ✅ | ✅ | |
Azure Servuce Bus queue | ✅ | ✅ | |
Azure Comos DB | ✅ | ✅ | ✅ |
Azure Blob Storage | ✅ | ✅ | ✅ |
Azure Hub | ✅ | ✅ |
ℹ️ As you can see here, not all triggers and binding are supported yet, but Microsoft will deliver them later. If your desired type is not listed yet, you have to stay with the programming model V1.
For an up-to-date overview of all supported types, please check out this link.
As you could already see the new programming model V2 leads to a simpler file structure and a more code based approach. Another cool feature introduced are the blueprints.
In case you are familiar with FastAPI, blueprints may remind you of FastAPI router functionality. Blueprint allows to split the Azure Function into several modules and also to create interfaces that can be reused in other projects.
import logging
import azure.functions as func
bp = func.Blueprint()
@bp.route(route="default_template")
def default_template(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 "
f"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
)
This blueprint then only needs to be imported and registered:
import azure.functions as func
from http_blueprint import bp
app = func.FunctionApp()
app.register_functions(bp)
A unique feature of Azure functions in combination with Python is the use of ASGI and WSGI compatible web frameworks, such as FastAPI or Flask. This is already possible with the programming model V1 and is also maintained here:
ASGI (FastAPI)
# function_app.py
import azure.functions as func
from fastapi import FastAPI, Request, Response
fast_app = FastAPI()
@fast_app.get("/return_http_no_body")
async def return_http_no_body():
return Response(content='', media_type="text/plain")
app = func.AsgiFunctionApp(app=fast_app,
http_auth_level=func.AuthLevel.ANONYMOUS)
WSGI (Flask)
# function_app.py
import azure.functions as func
from flask import Flask, request, Response, redirect, url_for
flask_app = Flask(__name__)
logger = logging.getLogger("my-function")
@flask_app.get("/return_http")
def return_http():
return Response('<h1>Hello World™</h1>', mimetype='text/html')
app = func.WsgiFunctionApp(app=flask_app.wsgi_app,
http_auth_level=func.AuthLevel.ANONYMOUS)
Conclusion
These were my highlights of the programming model V2 for Python Azure Fuctions! Although this feature is still in preview, I find these changes incredibly exciting as they allow us to develop Azure Functions with a more code based approach, similar to FastAPI.
However, keep in mind that these are preview features at this time and may change. Also, not all triggers and binding types are
supported currently, but these will surely follow shortly!
Let me know your thoughts on this and thanks for reading!
Resources:
Posted on December 21, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.