← Web apps with Flask

The same API in FastAPI

Build the club API again in FastAPI, where each input's type is checked and converted for you, and wrong input gets a clear error reply.

FastAPI is a newer Python library for APIs, used by companies big and small. Its trick: the types you write are checked for you.

In Flask, a number in the address arrives as text, and you check and convert it yourself. FastAPI reads the type you give each input (age: int) and does it for you: it turns "12" into 12, and replies with an error explaining the problem when someone sends twelve. Routes are written @api.get("/address"), and uvicorn.run(api) starts the app (here, in the App tab).

PredictRead the code and say what it will do before you run anything. A wrong guess is fine: it shows you what to look for.

Predict

api.py
from fastapi import FastAPI
import uvicorn

api = FastAPI()


@api.get("/next-year")
def next_year(age: int):
    return {"next": age + 1}


uvicorn.run(api)

age: int says what type age must be. What happens to a value that is not a whole number?

Someone visits /next-year?age=twelve. What do they get?

RunRun it and compare the result with your prediction.

Run

Run it (FastAPI takes a moment to load the first time) and try the addresses on the home page. In members(name: str = ""), a parameter that is not in the route's address comes from the query string, and = "" makes it optional. In /members/{group}, curly brackets mark the part that changes (Flask uses angle brackets).

api.py
import csv
from fastapi import FastAPI
import uvicorn

api = FastAPI()


def load_members():
    with open("members.csv") as f:
        return list(csv.DictReader(f))


@api.get("/")
def home():
    return {"message": "Chess club API. Try /members, /members?name=ad and /members/Players"}


@api.get("/members")
def members(name: str = ""):
    found = []
    for m in load_members():
        if name.lower() in m["name"].lower():
            found.append(m)
    return {"members": found}


@api.get("/members/{group}")
def group_members(group: str):
    found = []
    for m in load_members():
        if m["group"] == group:
            found.append(m)
    return {"group": group, "count": len(found), "members": found}


uvicorn.run(api)
Try it yourselfYour edits are saved in this browser.

InvestigateTrace how each line of code connects to what you see.

Investigate

api.py
import csv
from fastapi import FastAPI
import uvicorn

api = FastAPI()


def load_members():
    with open("members.csv") as f:
        return list(csv.DictReader(f))


@api.get("/")
def home():
    return {"message": "Chess club API. Try /members, /members?name=ad and /members/Players"}


@api.get("/members")
def members(name: str = ""):
    found = []
    for m in load_members():
        if name.lower() in m["name"].lower():
            found.append(m)
    return {"members": found}


@api.get("/members/{group}")
def group_members(group: str):
    found = []
    for m in load_members():
        if m["group"] == group:
            found.append(m)
    return {"group": group, "count": len(found), "members": found}


uvicorn.run(api)

Where an input comes from.

Compared with Flask.

A number that arrived as text.

Where does name come from in members(name: str = "")?

Compared with Flask's request.args.get("name", ""), what did FastAPI save you?

/next-year?age=12 gives 500 Internal Server Error instead of {"next": 13}. Click the line that is wrong.

ModifyChange the code on purpose and check the result.

Modify

Add an optional min_wins input that FastAPI checks as a whole number. Try /results?min_wins=lots too.

Need a hint?

def results(min_wins: int = 0):, and only add rows whose wins are at least min_wins. Try /results?min_wins=3, then /results?min_wins=lots.

Add /leader, which sends the player with the most wins.

Need a hint?

@api.get("/leader"), with the biggest-so-far loop from Data detective or Lesson 5.

Try it yourselfYour edits are saved in this browser.

Let /results?min_wins=3 send only players with at least 3 wins (checked as a whole number).

    Run your program. This ticks when your code does all of the above.

    Add /leader, which sends the player with the most wins.

      Run your program. This ticks when your code does all of the above.

      MakeBuild something new using what you have learned. Expect a few tries; that is normal.

      An API of your own

      Make a small API with at least one int or float input: minutes to seconds, a times table, a dice roller with a number of sides. Test it with a good value and a wrong one.

      Try it yourselfYour edits are saved in this browser.

      Make an API of your own with at least one int or float input, and test it with a wrong value.

        Run your program. This ticks when your code does all of the above.

        Info

        Where this leads

        On a computer, FastAPI also makes a documentation page at /docs from your types, where you can try every route (it needs a computer: the playground cannot show it). The checks come from a library called Pydantic, which can also check whole JSON bodies sent to an API.

        Which would you choose for a website with pages and forms, and which for an API that apps use?

        For teachers

        An optional lesson. Students rebuild the club API in FastAPI, see typed inputs checked and converted for them (with 422 replies for bad input), then add a checked optional input, a leader route and an API of their own. About 45 minutes.

        Open the teacher guide: answers, solutions, slides and a worksheet.

        Students will be able to

        Before class

        Optional: not needed for completing the course. Read the unit primer first. FastAPI routes are @api.get("/address"); parameters in the address use curly brackets (/members/{group}), and other parameters come from the query string (?name=ad), optional when they have a default. The type after each parameter (age: int) makes FastAPI convert the value and reply 422 with an explanation when it cannot, before the function runs (Pydantic does the checking). Without a type, the value is text (the bug task: "12" + 1 is an error, 500). uvicorn.run(api) starts the app; in the playground it shows in the App tab, on a computer pip install fastapi uvicorn and python app.py serve it at http://127.0.0.1:8000, with an automatic documentation page at /docs (not available in the playground). No async, dependency injection or Pydantic models in this lesson.

        Running the lesson

        1. Predict: compare with Flask, where you would write int() and try/except yourself.
        2. Run: try /members?name=ad, then /members/Players, then /members/players.
        3. Modify: send min_wins=lots and read the 422 reply together.
        4. Info: list what each tool is good for on the board.

        The hard part (on purpose)

        Reading the 422 reply. Have students read the error JSON aloud and find the field it names before changing anything.

        When students get stuck

        Stretch and support

        Stretch: Return an error of your own for a sensible-looking but wrong value (from fastapi import HTTPException; raise HTTPException(404, "No such player")).

        Support: Give students the min_wins parameter line, and ask them only to add the if in the loop.

        Where it leads

        The end of the course. Next steps: SQLite for storage (sqlite3 is built into Python), hosting a Flask or FastAPI app (with a teacher), or the planned Machine learning with Gradio course.

        What each step is for

        Now you can
        You reached the endBack to Web apps with Flask