Leveraging Machine Learning Models with Fast API for External Integration

Sergio Sampedro Diaz
SDG Group
Published in
12 min readMar 22, 2024

The ability to deploy and integrate machine learning (ML) models easily into real-world applications is fundamental for any advanced analytics problem. Thus, exposing ML models enables developers to harness the predictive power of these models in diverse contexts, such as web applications. While solutions like Amazon API Gateway and Azure API Management have gained popularity in recent years, another powerful tool has emerged also: Fast API. Fast API is a lightweight and powerful solution for building robust APIs with minimal effort. Its asynchronous design and automatic documentation generation streamline the development process, allowing developers to focus on deploying and serving their models quickly and efficiently.

1. Overview

An Application Programming Interface (API) serves as an intermediary layer for connecting diverse software applications to interact with each other and exchange data efficiently. Conceptually, it acts as a bridge, facilitating communication and data transfer between various computer programs, cloud components, or websites. In an API, a developer defines the protocols, functions, and endpoints necessary for these interactions. There are different software libraries and cloud components to implement an API layer such as Flask, Django, and Spring Boot, but in this article, we will focus on FastAPI, a framework for building APIs with Python (https://fastapi.tiangolo.com/).

The above figure shows an example of a solution using FastAPI. In (1) we show a typical scenario of a backend side that involves the creation of an API. In this case, the API interacts with a database storing data for a business case. Then, in (2) a frontend application, such as a website, consumes the data from FastAPI and serves it to the final users. This approach is extensible also to other possible consumers since other applications have access to the same endpoints as well (3).

Using FastAPI in this kind of solution gives four advantages [1]:

  1. Performance: FastAPI leverages asynchronous frameworks like Starlette and Pydantic to enable the non-blocking handling of numerous concurrent requests. The integration of Pydantic validation enables developers to define data models for API payloads, granting robustness and bug prevention through automatic validation.
  2. Documentation: FastAPI automatically generates interactive API documentation adhering to the OpenAPI standard, offering developers a comprehensive overview of endpoints, request/response schemas, and available parameters. This feature, accessible via a web interface, significantly improves documentation maintenance and provides users with an intuitive guide to API utilization.
  3. Dependency Injection: FastAPI supports dependency injection, facilitating clean dependency management. Thus, endpoint parameters are defined as standard input parameters of Python functions, enhancing maintainability and ease of use compared to other frameworks.

Web Standards Support: FastAPI embraces modern web standards including OAuth2, CORS, and WebSocket (leveraging Starlette features), simplifying integration with authentication systems and enabling real-time communication features in web applications.

2. Components of the solution

To illustrate the structure and relationships within a FastAPI application, we are going to show how a UML class diagram can be constructed for this solution:

Our solution relies on five main parts. First, there’s the FastAPI app object, which acts as the central piece of a FastAPI application. It helps set up endpoints and brings in FastAPI routers. Next up is the FastAPI APIRouter. This is a class provided by FastAPI that lets you organize and structure your API endpoints in a way that’s easy to reuse and maintain. Then, there are the Request/Response Models. These are just Pydantic models used in FastAPI to define how data should look and what rules it should follow. The fourth part is the Database Connector. It’s basically the tool we use to manage transactions to and from the database. Each endpoint relies on this connector to handle data storage and retrieval tasks. Lastly, here's the Authentication Module. This is where we set up how our application is secured. It’s also called upon by our FastAPI endpoints to ensure only authorized users can access certain parts of the application.

Next, we describe each part in detail:

2.1 FastAPI app

In FastAPI, the primary object is usually the FastAPI class itself. You can create a FastAPI [2] object like this:

app = FastAPI()

With the FastAPI object, you can do four main things:

  • Routing: The FastAPI class allows you to define routes using HTTP methods like GET, POST, PUT, DELETE, etc. Routes are defined by decorators such as @app.get, @app.post, etc., which associate URL paths with functions or methods that handle incoming requests. For instance:
@app.get("/name")
def name():
# Function to execute
  • Middleware: FastAPI allows you to apply middleware to your application using the add_middleware method. Middleware functions can intercept and modify requests and responses as they move through the application. This feature enables you to add cross-cutting concerns such as logging or authentication. For example:
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# Function to execute
return response
  • Exception Handling: FastAPI comes with built-in exception-handling capabilities to manage errors that occur during request processing. You can use the add_exception_handler method to register custom exception handlers, allowing you to customize error responses. For example:
@app.exception_handler(“exception_handler”)
async def datamissing_handler(request: Request, exception):
"""
Exception for “”
"""
return “returns”
  • Start/Stop functions: FastAPI enables the execution of processes during the start and stop events of the API. For instance, you can create a state object and then utilize it later in the endpoint, which can save computation time for static values and avoid the need for global variables. See [3].

2.2 APIRouter

It’s an object that allows you to construct modular APIs with FastAPI. You can create an APIRouter [4] object like this:

router = APIRouter()

APIRouter offers 3 main advantages:

  • Modularity: With APIRouter, you can define groups of related routes and endpoints within separate router instances. This promotes a modular design, making your codebase more organized and easier to maintain, especially as your API becomes more complex. It’s similar to using the FastAPI object but with the APIRouter object.
  • Route Prefixing: APIRouter enables you to specify a prefix for all routes defined within it. This is handy for grouping routes under a common base path.
router = APIRouter(prefix = ”/some_prefix”)
  • Middleware: APIRouter can also use middleware functions to intercept and process requests and responses. This allows you to apply middleware selectively to specific groups of routes, providing flexibility in handling cross-cutting concerns. It is the same as the FastAPI object but with the APIRouter object.

Once defined the APIRouter, you can mount an APIRouter instance onto the main FastAPI application using the “app.include_router()” method. This merges the routes from the router into the main application, allowing them to be served alongside other routes defined directly on the main application. In summary, APIRouter enables you to build scalable and maintainable APIs with FastAPI. It encourages best practices such as modularity, separation of concerns, and code reusability, making it easier to manage complex API implementations.

2.3 Request/Response models

Request/Response models in FastAPI [5] serve to define the structure of the data that your API endpoints handle during calls and returns to clients. Here’s how request/response models work for FastAPI:

  • Defining response models: With Pydantic, you can specify the structure and data types of all fields in the input/output model. These fields can have default values, validation rules, and additional metadata. For instance:
class Model_1(BaseModel):
field_1: str
field_2: List[Model_2]
  • Using request/response models in endpoints: Once defined, you can use request/response models in your API endpoints by specifying them. For example:
@router.get(
"/endpoint_name/",
response_model=Model_1,
)
def endpoint_name(
input_1: Model_input,
) -> Model_1:
  • Automatic documentation generation: Request/response models also contribute to the automatic generation of API documentation by FastAPI. The models defined in your API endpoints are utilized to document the structure of the response data in the interactive API documentation generated by FastAPI.

In essence, request/response models in FastAPI offer a robust mechanism for defining and enforcing the structure of your API requests/responses. By leveraging response models, you can ensure that your API delivers well-structured and validated data to clients.

2.4 Database module

The database module in FastAPI facilitates the interaction between your application and a database, allowing for data storage, retrieval, and manipulation within your API endpoints. Here’s how it works:

  • ORM integration: You can integrate an Object-Relational Mapper (ORM) such as SQLAlchemy or an Object Document Mapper (ODM) like MongoDB with a FastAPI application [6].
  • Dependency injection: FastAPI offers a dependency injection feature [7], enabling you to inject the database connection or ORM session into your API endpoints. For instance:
@router.get(
"/endpoint_name/",
response_model=Model_1,
)
def endpoint_name(
input_1: Model_input,
db: DataBase = Depends(get_DB),
) -> Model_1:

In this example, get_DB is a function defined elsewhere in your codebase that retrieves the database object.

2.5 Authentication module

An Authentication module in FastAPI offers mechanisms to verify the identity of users accessing API endpoints and enforcing access control policies. This module integrates with FastAPI’s dependency injection feature and can range from a simple user and password checker to a more complex authentication system. Here’s how it works [8]:

@router.get(
"/endpoint_name/",
response_model=Model_1,
)
def endpoint_name(
input_1: Model_input,
db: DataBase = Depends(get_DB),
authorized: bool = Depends(auth.validate),
) -> Model_1:

In this example, auth.validate is a function defined elsewhere in the codebase that handles user authentication.

In conclusion, FastAPI offers a robust set of features to streamline the development of APIs. With its support for request/response models, routing, middleware, and automatic documentation generation, developers can define and enforce the structure of their API requests and responses while ensuring the automatic generation of documentation for their endpoints. The integration of the different parts involved such as the Database module and the Authentication module facilitates interaction with databases and user authentication mechanisms, further enhancing the security and functionality of FastAPI applications.

In the following section, we will show an example of implementation.

3. Implementation

In this section, we will review a FastAPI example encapsulating all the essential concepts outlined in Section 2. We will make references to the GitHub repository created for this article. First, like any other Python project, you’ll need to set up your virtual environment. To make this more easily you will find a requirements.txt in the root of the repository. With this file you can create an environment with the basic packages needed for this example, FastAPI and Uvicorn.

With that, you can start creating the modules for a FastAPI application.

3.1 FastAPI app

This module is in the path “src/app.py” of the GitHub repository. Here you can find the initialization of the FastAPI object and a couple of tests endpoints:

app = FastAPI()

@app.get("/")
async def root():
"""
Root url of the API
"""
return {"message": "Hello World"}

@app.get("/test-auth/")
async def test(authorized: bool = Depends(auth.validate)):
"""
Check the autentication tocken
"""
return {"message": "Authentication success!"}

The initial endpoint serves as a basic health check to confirm the operational status of the API, while the subsequent endpoint verifies the authentication status. Within this endpoint, the utilization of the authentication function is demonstrated through the dependency injection mechanism of FastAPI. Detailed exploration of the authentication function is reserved for Section 3.5. Additionally, this file illustrates the process of integrating routers into the application, showing how to extend this functionality within the API architecture:

app.include_router(router_1.router)

With this line, we have included the router_1 python module to the main FastAPI object. Finally, this file has the entrypoint for the run of our FastAPI application:

# Main function to execute FastAPI
if __name__ == "__main__":
"""
Run the main only for debugging purpose, as it is assigning a
random (free) port to run the API.

In a production set-up please run the API in its container
or call uvicorn directly from the CLI:
uvicorn src.app:app --host 0.0.0.0 --port {DESIRED PORT}
"""
import uvicorn

from src.utils import find_free_port

port = find_free_port()
logger.info(f"Running API on port: {port}")
uvicorn.run("src.app:app", host="0.0.0.0", port=port, reload=True)

This main function searches for an empty port and uses it to launch the uvicorn server. By running “python -m src.app” in the console and after the initialization we can enter to the URL that the console give to us and see the next page:

If we want to use the integrated swagger of FastAPI we can add “/docs” to the URL:

3.2 APIRouter

This module is in the path “src/routers/router_1.py” of the GitHub repository. Here you can find the initialization of the APIRouter object and the definition of the main endpoints of the API. For example:

@router.post("/compute/", response_model=Compute)
def compute(
number: int,
mode: Mode = "SUM",
db: DB = Depends(get_db),
authorized: bool = Depends(auth.validate),
) -> Compute:

Here, we can see the definition of the endpoint “/compute/” that will execute the function “compute”. This endpoint is validated with the “Compute” FastAPI model (see section 3.3) and have two user inputs:

  • number: an int number.
  • Mode: a string that defines the execution mode of the function and is validated through the “Mode” model (see section 3.3)

Also, this endpoint has two dependency injection inputs:

  • db: the database connection object. It is defined with the “Depends” FastAPI function that calls the “get_db” function every time that the endpoint is called. With this special dependency injection function we will have a cached value that is stored in FastAPI so we don’t need to start the DB in every endpoint call.
  • authorized: the authentication checker. It is also defined with the “Depends” and validates the authentication user and password that is passed in the call with the function “auth.validate”.

3.3 Request/Response models

This module is in the path “src/models.py” of the GitHub repository. Here you can find all the models that the FastAPI application uses to check the inputs and outputs of the endpoints, for example:

class Results(BaseModel):
addition: Optional[int] = None
subtraction: Optional[int] = None
multiplication: Optional[int] = None


class Compute(BaseModel):
db_number: int
results: Results

Here we can see the model “Compute” has two fields in the output:

  • Db_number: an int number.
  • Results: This is another model that is a dictionary of three fields.

With this models FastAPI can validate the outputs and rais formats errors and also creates the automatic documentation to see the output format, for example:

3.4 Database module

This module is in the path “src/db.py” of the GitHub repository. Here you can find a dummy example of how we can create a function to recover the database object:

def get_db() -> Generator:
db = DB()
try:
yield db
finally:
db.close()

As we don’t have a real database the only important thing here is the function structure.

3.5 Authentication module

This module is in the path “src/auth.py” of the GitHub repository. Here you can find a very simple example of how we can validate the username and password of the API calls:

def validate(credentials: HTTPBasicCredentials = Depends(security)):
correct_username = "admin"
correct_password = "password"
if (
credentials.username != correct_username
or credentials.password != correct_password
):
raise HTTPException(status_code=401, detail="Unauthorized")
return True

Conclusions

Fast API bridges the gap between machine learning models and practical applications, driving meaningful impact in diverse domains. Its asynchronous design, automatic documentation generation, and compatibility with popular frameworks enable rapid development and deployment of APIs, accelerating the delivery of data in time. Performance optimization and support for well-known web standards ensure responsive and reliable API endpoints, capable of meeting the demands of high-volume, real-time applications. Fast API also serves to democratize access to AI-driven insights, making them more accessible and actionable for a broader audience. This democratization fosters collaboration, leading to the development of more adaptive systems that enhance critical business cases and add value.

Who we are

  • Sergio Sampedro Díaz is an ML Engineer participating in the Data Science practice unit at SDG Group España. He has experience in different sectors such as the banking sector and the pharmaceutical sector. Currently, he is participating in end-to-end Machine Learning solutions with different technologies. https://www.linkedin.com/in/sersampedro/
  • Ángel Mora is a machine learning (ML) architect and specialist lead participating in the architecture and methodology area of the Data Science practice unit at SDG Group España. He has experience in different sectors such as the pharmaceutical sector, the insurance sector, telecommunications, and the utilities sector, managing different technologies in the Azure and AWS ecosystems. https://www.linkedin.com/in/angelmoras/

--

--