Setting up FastAPI

Posted by Daksh on Sunday, November 13, 2022

FastAPI

A robust and fast API framework for building APIs with Python. It has automatic documentation and interactive API docs, 100% test coverage, dependency injection, security and authentication, and more.

FastAPI doucmentation

Autogenerated and is available in two types of documentation:

FastAPI Hello World

# name: file_name.py
from fastapi import FastAPI

myApp = FastAPI()

# instance.method(path)
@myApp.get("/")
# the name of the function here doesn't matter, but still keep it something descriptive
def index():
    # fastapi will automatically convert this return value to JSON
    return {"Hello": "World"}
uvicorn file_name:myApp --reload

Remove –reload flag in production.

Get Method

  • Path Parameters
  • Query Parameters
  • Predefined values

Path Parameters

  • Path Parameters - @myApp.get("/items/{item_id}"), item_id is a path parameter. It means that the parameter will be passed as part of the URL path.
@myApp.get("/items/{item_id}")
# type hinting
# FastAPI uses pydantic to validate the types 
def read_item(item_id: int):
    return {"message": f"Item with id {item_id}"}

If you pass a url like http://localhost:8000/items/3.5, it will return a error message shown below.

{
  "detail": [
    {
      "loc": [
        "path",
        "item_id"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}

The order of the path parameters matters. If you have conflicting path parameters, the one that is defined first will take precedence. Whenever you send a request to an api server, it will go down the list of all of our path operations, and it will find the first match and as soon as the match is found, it will stop running the code.


@myApp.get("/items/{item_id}")
def read_item(item_id: int):
    return {"message": f"Item with id {item_id}"}

@myApp.get("/items/all-items")
def read_all_items():
    return {"message": "All items"}

Now if we send a request to http://localhost:8000/items/all-items, it will return an error message shown below.

{
  "detail": [
    {
      "loc": [
        "path",
        "item_id"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}

It is because the first path operation is also executed for the all-items path. So, we need to make sure that the more specific path operations are defined first. Therefore, to make it work, we need to change the order of the path operations.

@myApp.get("/items/all-items")
def read_all_items():
    return {"message": "All items"}

@myApp.get("/items/{item_id}")
def read_item(item_id: int):
    return {"message": f"Item with id {item_id}"}

Predefined path

We define values with a enum structure. It will be used to validate the path parameters.

from enum import Enum

class ModelName(str, Enum):
    anything1 = "anything1"
    anything2 = "anything2"
    anything3 = "anything3"

@myApp.get("/items/type/{model_type}")
def item_type(model_type: ModelName):
    return {"message": f"Item with type {model_type}"}

If any wrong type is entered, it will return an error message shown below.

{
  "detail": [
    {
      "loc": [
        "path",
        "model_type"
      ],
      "msg": "value is not a valid enumeration member; permitted: 'anything1', 'anything2', 'anything3'",
      "type": "type_error.enum",
      "ctx": {
          "enum_values": [
          "anything1",
          "anything2",
          "anything3"
          ]
      }
    }
  ]
}

Query Parameters

Query parameters are the parameters that are passed after the question mark in the URL. For example, http://localhost:8000/items/?item_id=3. Any function parameter that are not part of the path parameters are query parameters.

@myApp.get("/items/all-items")
def read_all_items(item_name: str, item_size: int):
    return {"message": f"Item with name {item_name} and size {item_size}"}

# go to http://localhost:8000/docs
# click on the "Try it out" button
# enter the values in the query parameters, name="Display" and size=29
# click on the "Execute" button
{
  "message": "Item with name Display and size 29"
}

Default query parameters

To provide a default value for a query parameter, we can use the Query function from fastapi.

from fastapi import Query

@myApp.get("/items/all-items")
def read_all_items(item_name: str = Query("default name", min_length=3, max_length=15)):
    return {"message": f"Item with name {item_name}"}
{
  "message": "Item with name default name"
}

or you can pass them as arguments to the function.

@myApp.get("/items/all-items")
def read_all_items(item_name: str = "default name", item_size: int = 10):
    return {"message": f"Item with name {item_name} and size {item_size}"}
{
  "message": "Item with name default name and size 10"
}

Optional query parameters

To make a query parameter optional, we can use the Optional function from fastapi.

from typing import Optional

@myApp.get("/items/all-items")
# here item_size is an optional parameter
def read_all_items(item_name: str = "default name", item_size: Optional[int] = None):
    return {"message": f"Item with name {item_name} and size {item_size}"}
{
  "message": "Item with name default name and size None"
}

Querry and Path parameters can be combined.

# a specific item with a specific user review
@myApp.get("/items/{item_id}/users/{user_id}")
# item_id and user_id are path parameters
# review is a query parameter
def get_review(item_id: int, user_id: int, review: Optional[str] = None):
    return {"message": f"Item with id {item_id} and user id {user_id} has review {review}"}
{
  "message": "Item with id 3 and user id 4 has review None"
}