Azure Function to FastAPI App Service

Introduction

If you have used Azure functions, especially in the Consumption Plan, chances are that the cold start times of the functions have made your APIs very slow. You may be looking at options of either migrating the function from Consumption to Premium/ App Service plans or doing away with Azure Functions altogether.

If your functions use the python runtime, then FastAPI is a very popular alternative. I personally found the migration of Azure functions to a FastAPI App Service to be very smooth and hassle-free. In this tutorial, we will see exactly how to perform this migration.

The structure of this tutorial is as follows:

  1. Function Code
  2. FastAPI Code
  3. Differences between the two
  4. More than one function/ endpoint

Function Code:

We will keep the function code very simple because the migration is very less dependent on the function content, and more dependent on the structure.

In fact, let’s just keep a shorter version of the boilerplate function that gets created in VS Code.

The function code is given below:

import logging

import azure.functions as func


def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')
    
    req_body = req.get_json()
    name = req_body.get('name')

    if name:
        return func.HttpResponse(f"Hello, {name}. This HTTP triggered function 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
        )

FastAPI Code

Here is the equivalent FastAPI code for the above function

from fastapi import FastAPI, status
from fastapi.middleware.cors import CORSMiddleware
import logging

app = FastAPI(title="Azure Function to FastAPI migration",debug=True)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

@app.post("/fast_api_test/", status_code = status.HTTP_200_OK)
def fast_api_test(req_body: dict):
    logging.info('Python HTTP trigger function processed a request.')
    name = req_body.get('name')

    if name:
        return {"Hello, " + str(name)+". This HTTP triggered function executed successfully."}
    else:
        return {"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."}

Differences between the two

If you compare the above code with the code for the Azure Function, you will notice that there are two types of changes: function independent changes, and function dependent changes.

Function Independent Changes

Changes in the Include Statements:
from fastapi import FastAPI, status
from fastapi.middleware.cors import CORSMiddleware

instead of

import azure.functions as func

We primarily need to import FastAPI and status. The second include statement, CORSMiddleware, is primarily to avoid CORS issues when trying to make the API call using a browser.

Declaration of the app
app = FastAPI(title="Azure Function to FastAPI migration",debug=True)

This creates the FastAPI app object.

Addition of the CORS middleware

This step is optional, and, as mentioned earlier, you need to include this only when you anticipate the API to be accessed using a browser originating from a different source. In this step, we are essentially allowing all origins, headers, and methods. You can keep custom restrictions depending on your application.

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

In the Azure Functions, you may enable CORS through the portal. The way to do that is illustrated here.

Function Dependent Changes

Function Definition

While the Azure Functions are all named as main, the FastAPI functions have unique names, generally equal to the endpoint, as in the example above. Before every API function, the API endpoint, the method, and the default response status code is defined

@app.post("/fast_api_test/", status_code = status.HTTP_200_OK)

If a function has such a statement preceding it, it is an endpoint, else it is just an ordinary function, which may be invoked within an endpoint.

While all the Azure Functions take in req as the argument, the name and type of the argument can be specified within the FastAPI functions. In this case, we knew that we will receive a JSON in the POST body, and so defined the type of the argument as dict (python converts the JSON to dict).

def fast_api_test(req_body: dict):

Please note that if you have a path parameter, it can be defined as an argument in the same way. So if the function was taking a string name as a path parameter, it would be defined as following:

def fast_api_test(name: str):

The rest of the function remains more or less similar. Note that we no longer need the following statement:

req_body = req.get_json()

This is because we are directly taking the req_body as the input argument.

Return Statement

While the Azure Function contains a return statement of the following type:

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
        )

The App Service function contains a dict in the return

return {"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."}

We don’t need to explicitly specify the status code as it was already specified in the @app line before the function definition. If you want to return a different status code, you can find the instructions here, for how to do that: https://fastapi.tiangolo.com/tutorial/response-status-code/

If you are new to Azure App Service, and wish to learn how to deploy your FastAPI code to Azure App Services, you can find a good tutorial here.

More than one function/ endpoint

If you are wondering how to add more than one function/endpoint within the FastAPI App Service, you can just add them one below the other. See the example below:

from fastapi import FastAPI, status
from fastapi.middleware.cors import CORSMiddleware
import logging

app = FastAPI(title="Azure Function to FastAPI migration",debug=True)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

def get_pi_n_decimals(n):
    pi_by_4 = 0
    max_denominator = 10**(n+3)
    max_iter = int((max_denominator-1)/2) #denominator = 2*iter+1
    for i in range(max_iter):
        pi_by_4 += ((-1)**i)*(1/(2*i+1))*(10**(n+3))
    return int(pi_by_4 * 4/1000)

@app.get("/test_endpoint/", status_code = status.HTTP_200_OK)
def test_endpoint():
    get_pi_n_decimals(2)
    return {"Request Processed Successfully"}

@app.post("/fast_api_test/", status_code = status.HTTP_200_OK)
def fast_api_test(req_body: dict):
    logging.info('Python HTTP trigger function processed a request.')
    name = req_body.get('name')

    if name:
        return {"Hello, " + str(name)+". This HTTP triggered function executed successfully."}
    else:
        return {"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."}

You can also see from the above that test_endpoint() and fast_api_test() are functions corresponding to endpoints, whereas get_pi_n_decimals() is a helper function. It is not an endpoint because it does not have the @app statement preceding it.

Also, if you are wondering how I got the get_pi_n_decimals() function, you can read more in this post.

I hope this tutorial helped. Click here for more tutorials on Azure.

If you are planning to appear for the Azure Administrator Exam Certification, check out this course on Udemy.

Leave a comment

Your email address will not be published. Required fields are marked *