This is the note of FAST API Tutorial, you may also refer to author's code2
Introduction
Setup the environment
Create and activate a virtual environment
python -m venv env
source ./env/bin/activate
Notice if you are using Mac(intel or m1), you may need to fix the # zsh: command not found: pythonby
echo "alias python=/usr/bin/python3" >> ~/.zshrc
echo 'export PATH="$(brew --prefix)/opt/python@3/libexec/bin:$PATH"' >> ~/.zshrc
Now create a requirement.txt file which lists what we gonna install under the path of the root directory by
fastapi
uvicorn[standard]
and run pip install -r requirement.txt .
You will see that everything is installed.
Create main.py
main.py whould be the base of your code. Building a simple FASTAPI app will all be happened here.
You may also try:
@app.post("/")
async def post():
return {"message": "hello from the post route"}
@app.put("/")
async def put():
return {"message": "hello from the put route"}
and the documentation is at http://localhost:8000/docs which would be great convenient for development.
Run the code
command of running the code: uvicorn main:app
Here main is the name of the python file and app is the name we initiate our code, say if we setup in hello.py and
world = FastAPI() # initiate our app
@world.get("/") # app.get() decorator
async def root(): # declare our root route
return {"message": "hello world"}
then we should use uvicorn hello:world to run the code.
Here are some other useful unicorn command flags:
# you can choose the port whatever you want, the default is 8000
uvicon main:app --port=8000
# no need to ctrl+C quit and restart the code after updating
# automatically restart the server
uvicon main:app --reload
For Mac, the command should be a little bit defferent:
python3 -m uvicorn main:app --reload
If everything works smoothly, you will see {"message":"hello world"} at the address of http://localhost:8000/ on your web browser.
Path parameters
For path parameters, if you go to somewhere like localhost:8000/123, you will then be directed to the page of {"detail":"Not Found"}. So if we want to add in functionaity where we can pass a path parameter in, this is the sort of thing where you could do to @app.get():
@app.get("/users")
async def list_users():
return {"message": "list users route"}
then if we go to http://localhost:8000/users, we will see {"message": "list users route"}, which is fine.
case 1
@app.get("/users/{user_id}")
async def get_user(user_id: str):
return {"user_id": user_id}
We may use this code to return what the user input in the path, and we can further modify for async def get_user(user_id: int) if we only want to accept integer.
input path -> http://localhost:8000/users/5
return -> {"user_id":5}
input path -> http://localhost:8000/users/djwe
return -> "msg":"Input should be a valid integer, unable to parse string as an integer"
We may also try this in http://localhost:8000/docs#/default/get_user_users__user_id__getand you will see user_id is not allowed to be a string in the parameters section.
case 2
Here is the sample of code:
@app.get("/users/{user_id}")
async def get_user(user_id: str):
return {"user_id": user_id}
@app.get("/users/me")
async def get_current_user():
return {"Message": "this is the current user"}
What we expect is it will output {"Message": "this is the current user"} after typing in http://localhost:8000/users/me, however the real output is {"user_id": "me"}. This is because python is going cycle through the code from top each time.
So if you have something specific, while the sturcture looks the same and allow a specific endpoint first before a dynamic endpoint, you have to put it before the dynamic endpoint. So the order is important.
case 3
We may use the code below as example of a more complicated case, that we want to return different value according to the input.
from enum import Enum
The Enum class provides basic functionality for creating enumeration types. An enumeration class can be created by inheriting the Enum class and defining enumeration constants.
class FoodEnum(str, Enum):
fruits = "fruits"
vegetables = "vegetables"
dairy = "dairy"
@app.get("/foods/{food_name}")
async def get_food(food_name: FoodEnum):
if food_name == FoodEnum.vegetables:
return {"food_name": food_name, "message": "you are healthy"}
if food_name.value == "fruits":
return {
"food_name": food_name,
"message": "you are still healthy, but like sweet things",
}
return {"food_name": food_name, "message": "i like chocolate milk"}
Query Parameters
A query parameter is an item that you're including in your function parameters that is not otherwise listed up.
# Firstly we create a database
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items")
async def list_items(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]
It doesn't actually let us know that there are query parameters.
case 1
Now if we want to make some parameters optional, we may use
from typing import Optional
@app.get("items/{item_id}")
async def list_items(item_id: str, q: Optional[str] = None, short: bool = False):
if q: # if the query is not none
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
If your python version is higher than 3.10, then you can use q: str | None = None instead of q: Optional[str] = None
case 2
@app.get("/users/{user_id}/items/{item_id}")
async def get_user_item(
user_id: int, item_id: str, q: Optional[str] = None, short: bool = False
):
item = {"item_id": item_id, "owner_id": user_id}
if q:
item.update({"q": q})
if not short:
item.update(
{
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut consectetur."
}
)
return item
case 3
There is a wired thing that in the code below, you can even have a required query parameters in item.
@app.get("items/{item_id}")
async def list_items(item_id: str, sample_query_param: str, q: Optional[str] = None, short: bool = False): # you can have required query parameters
item = {"item_id": item_id, "sample_query_param": sample_query_param} # declare item id
if q: # if the query is not none
item.update({"q":q}) # if we have the query, then we may update the query
if not short:
item.update(
{
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut consectetur."
}
)
return {"item_id": item_id}