FastAPI-tutorial

86 阅读4分钟

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}