Fast API

First Look

Daniel Levin
6 min readDec 30, 2023

Abstract

In this article, I demonstrate how to set up a simple FastAPI application that consumes a public and free API for countries. The application will consume some endpoint of the countries’ API and then serve only some parts of the response.

Introduction

While my previous articles have delved deeply into theory, I believe it’s equally important to occasionally step back and add some fun and practicality into our exploration, especially when it comes to the foundational aspects of ML models. This article represents one such lighter moment. My background as a Java developer has me deeply entrenched in the Spring Boot framework, a space I am quite comfortable in. However, my growing interest in Machine Learning led me to think about integrating ML models into web applications. Recognizing the superiority of Python’s libraries in the realms of Machine Learning and AI, my initial plan was to leverage Python for the core logic, using Spring Boot merely as a bridge between Python’s backend and the frontend. This approach, however, soon felt like an uphill battle, overly complicated for initial attempts.

Then, not too long ago, I was introduced to FastAPI. To many seasoned Python developers, FastAPI and frameworks like it might be old news, but for me, it was a revelation. The ease of starting with FastAPI, along with its familiar feel, akin to the Spring Boot framework, made embracing it an effortless decision. It felt like a natural and straightforward choice right from the start.

Setting up the environment

For consistent performance across different environments, I recommend setting up a Python virtual environment. This ensures all dependencies are contained and can be replicated easily across systems.

Create a folder country-service and open that folder in a terminal.

Create the virtual environment and activate that environment.

# Running python -m venv env will create a env folder
python3 -m venv env

# Some times you need to drop the 3 in python, depending on your installation
python -m venv env

# WINDOWS: To activate the virtual environement in windows PowerShell
env/Scripts/activate

# LINUX: To activate the virtual environement in linux (bash)
source /env/bin/activate

Start Visual Studio code by typing “code .” in the terminal.

Starting Visual Studio Code after activating the virtual environment in this fashion ensures that dependencies are read from that environment. You know that the environment is activated when you see “(env)” in the terminal.

One way of installing your dependencies is by reading them from the requirements.txt file when applying ‘pip install’. This approach is especially useful when working with docker-compose but the file also functions as documentation for your dependencies. But nothing can hinder you from simply running ‘pip install’ for each dependency separately. The dependencies needed for this simple application are:

fastapi
uvicorn
httpx
pydantic
typing

If you decide to work on the terminal provided by VS Code, make sure the environment is activated before proceeding.

pip install -r requirements.txt

In the root folder create main.py and country_data.py with respective content. Keep in mind that those two modules will be modified further down.

from fastapi import FastAPI, HTTPException
import country_data

app = FastAPI()

# Expose the message on localhost:8000
@app.get("/")
def come_home():
return {"message":"welcome home traveler!"}

# Expose country data on
# localhost:8000//api/country/country-name
@app.get("/api/country/{name}")
async def get_country(name: str):
country = await country_data.get_country(name)
return country
import httpx

async def get_country(name: str):
# Check the format of the response by pasting the url in your browser
url = f"https://restcountries.com/v3.1/name/{name}"
async with httpx.AsyncClient() as client:
response: httpx.Response = await client.get(url)
data = response.json()
# Access the first (only) element in the array
result = data[0]
if not result:
return None
return result['name']

In your terminal run:

uvicorn main:app --reload

This will start the server and the — reload command will ensure that any changes done to the code will automatically be updated, so you won't have to restart the server each time you make a change to your code.

Now go to localhost:8000 and you should be met with the following welcoming text.

{“message”:” Welcome home traveler!”}

And if you go to localhost:8000/docs, you will see all the endpoints and can interact with those.

To see how the response looks like you simply paste the URL in your browser. Since I live in Sweden I will do that for Sweden.

restcountries.com/v3.1/name/sweden

[
{
"name": {
"common": "Sweden",
"official": "Kingdom of Sweden",
"nativeName": {
"swe": {
"official": "Konungariket Sverige",
"common": "Sverige"
}
}
},
"tld": [
".se"
],
"cca2": "SE",
"ccn3": "752",
"cca3": "SWE",
"cioc": "SWE",
"independent": true,
"status": "officially-assigned",
"unMember": true,
"currencies": {
"SEK": {
"name": "Swedish krona",
"symbol": "kr"
}
},
"idd": {
"root": "+4",
"suffixes": [
"6"
]
},
"capital": [
"Stockholm"
],...

You notice that the response is an array of JSON objects containing only one element.

To create a country object that only contains the fields of interest create a new module country_data.py. You can put that file in a sub-folder called models.

from pydantic import BaseModel
from typing import List

class NameModel(BaseModel):
common: str
official: str

class CountryModel(BaseModel):
name: NameModel
tld: List[str] = []
capital: List[str] = []
population: int

Having created the NameModel and CountryModel in FastAPI, it's necessary to update our main.py and country_data.py files to reflect these new models. This change will align our code with the structure and data types defined in the models. Additionally, I introduce a new feature in our application: an endpoint that provides details for all countries. With added Exception handling and logging, the modules now contain the following codes:

import httpx
import logging
from fastapi import FastAPI, HTTPException
from typing import List

from models.country_model import CountryModel
import country_data

app = FastAPI()
logging.basicConfig(level=logging.INFO)

@app.get("/api/countries", response_model=List[CountryModel])
async def get_all_countries():
try:
countries = await country_data.get_all_countries()
return countries
except httpx.HTTPError as e:
logging.error(f"HTTP error occurred: {e}")
raise HTTPException(status_code=503, detail="Service temporarily unavailable, try again later.")
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise HTTPException(status_code=500, detail="Internal server error")

@app.get("/api/country/{name}", response_model=CountryModel)
async def get_country(name: str):
country = await country_data.get_country(name)
if not country:
raise HTTPException(status_code=404, detail="Country not found")
return country
import httpx
from typing import List, Optional
from models.country_model import CountryModel
import logging

# Initialize logging
logging.basicConfig(level=logging.INFO)

async def get_country(name: str) -> Optional[CountryModel]:
url = f"https://restcountries.com/v3.1/name/{name}"
try:
async with httpx.AsyncClient() as client:
response = await client.get(url)
if response.status_code == 404:
return None
response.raise_for_status()
data = response.json()

return CountryModel(**data[0])

except httpx.HTTPStatusError as e:
logging.warning(f"HTTP error occurred while fetching country: {e}")
return None
except (IndexError, ValueError) as e:
logging.error(f"Error parsing country data: {e}")
return None


async def get_all_countries() -> List[CountryModel]:
url = "https://restcountries.com/v3.1/all"
try:
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
countries_data = response.json()

countries = [CountryModel(**country_data) for country_data in countries_data]
return countries

except httpx.HTTPStatusError as e:
logging.warning(f"HTTP error occurred while fetching all countries: {e}")
return []
except ValueError as e:
logging.error(f"Error parsing countries data: {e}")
return []

Adopting an object-oriented approach is a compelling reason to create these two models. Besides one of the advantages of using FastAPI is its ability to offer detailed insights into the data served at the endpoints. Specifically, when you navigate to localhost:8000/docs, FastAPI generates an interactive API documentation. This documentation dynamically outlines the structure of the data being returned, including the fields and types defined in our models. This feature not only enhances clarity for developers but also ensures that anyone interacting with the API has a clear understanding of the data structure and expected responses.

Final words

FastAPI, being a Python framework, naturally becomes the go-to choice for developing web applications that serve machine learning models. This article guided you through the setup of a basic FastAPI application. Moving forward, I plan to expand on this project by integrating various machine learning models. Stay tuned, and happy coding!

--

--

Daniel Levin

M.Sc. Physics, B.Ed., and A.S. in Software Development. Teaching for 15 year and coding proffesionally since 2021.