Use FastAPI to build web services in Python

FastAPI is a modern Python web framework that leverage the latest Python improvement in asyncio. In this article you will see how to set up a container based development environment and implement a small web service with FastAPI.

Getting Started

The development environment can be set up using the Fedora container image. The following Dockerfile prepares the container image with FastAPI, Uvicorn and aiofiles.

FROM fedora:32
RUN dnf install -y python-pip \
    && dnf clean all \
    && pip install fastapi uvicorn aiofiles
WORKDIR /srv
CMD ["uvicorn", "main:app", "--reload"]

After saving this Dockerfile in your working directory, build the container image using podman.

$ podman build -t fastapi .
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/fastapi latest 01e974cabe8b 18 seconds ago 326 MB

Now let’s create a basic FastAPI program and run it using that container image.

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello Fedora Magazine!"}

Save that source code in a main.py file and then run the following command to execute it:

$ podman run --rm -v $PWD:/srv:z -p 8000:8000 --name fastapi -d fastapi
$ curl http://127.0.0.1:8000
{"message":"Hello Fedora Magazine!"

You now have a running web service using FastAPI. Any changes to main.py will be automatically reloaded. For example, try changing the “Hello Fedora Magazine!” message.

To stop the application, run the following command.

$ podman stop fastapi

Building a small web service

To really see the benefits of FastAPI and the performance improvement it brings (see comparison with other Python web frameworks), let’s build an application that manipulates some I/O. You can use the output of the dnf history command as data for that application.

First, save the output of that command in a file.

$ dnf history | tail --lines=+3 > history.txt

The command is using tail to remove the headers of dnf history which are not needed by the application. Each dnf transaction can be represented with the following information:

  • id : number of the transaction (increments every time a new transaction is run)
  • command : the dnf command run during the transaction
  • date: the date and time the transaction happened

Next, modify the main.py file to add that data structure to the application.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class DnfTransaction(BaseModel):
    id: int
    command: str
    date: str

FastAPI comes with the pydantic library which allow you to easily build data classes and benefit from type annotation to validate your data.

Now, continue building the application by adding a function that will read the data from the history.txt file.

import aiofiles

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class DnfTransaction(BaseModel):
    id: int
    command: str
    date: str


async def read_history():
    transactions = []
    async with aiofiles.open("history.txt") as f:
        async for line in f:
            transactions.append(DnfTransaction(                                                          
                id=line.split("|")[0].strip(" "),
                command=line.split("|")[1].strip(" "),
                date=line.split("|")[2].strip(" ")))
    return transactions

This function makes use of the aiofiles library which provides an asyncio API to manipulate files in Python. This means that opening and reading the file will not block other requests made to the server.

Finally, change the root function to return the data stored in the transactions list.

@app.get("/")
async def read_root():
    return await read_history()

To see the output of the application, run the following command

$ curl http://127.0.0.1:8000 | python -m json.tool
[
{
"id": 103,
"command": "update",
"date": "2020-05-25 08:35"
},
{
"id": 102,
"command": "update",
"date": "2020-05-23 15:46"
},
{
"id": 101,
"command": "update",
"date": "2020-05-22 11:32"
},
....
]

Conclusion

FastAPI is gaining a lot a popularity in the Python web framework ecosystem because it offers a simple way to build web services using asyncio. You can find more information about FastAPI in the documentation.

The code of this article is available in this GitHub repository.


Photo by Jan Kubita on Unsplash.

For Developers Using Software

9 Comments

  1. Mehdi

    Does not work on local machine.

    #ERROR: Error loading ASGI app. Could not import module “main”.
    Command used is:

    uvicorn main:app –reload

    And I have installed everything:
    pip3 install uvicorn fastapi aiofiles

    • Mehdi

      Turns out the file name has to be called main.py as mentioned in the post. I was using a different name. Solved.

    • Eric L.

      You probably haven’t created the

      main.py

      in the current directory.

  2. baoboa

    Very useful, thanks for this post.

  3. pr0PM

    Hi,
    Got everything working until

    podman run --rm -v $PWD:/srv:z -p 8000:8000 --name fastapi -d fastapi

    which gives

    Error: executable file not found in $PATH: No such file or directory: OCI runtime command not found error
  4. Vaclav

    Awesome post! Thanks. It works fine without podman /docker container only.
    Podman build fails at RUN:
    “Error: error building at STEP “RUN dnf install -y python pip && dnf clean all && pip install fastapi uvicorn aiofiles”: error while running runtime: exit status 127

    There could be a typo – check: dnf search python-pip
    I suggest “python pip”

    • Yeah it looks like you are missing “-” in the RUN command which should be “RUN dnf install -y python-pip ….”

  5. better way are using H2o https://h2o.examp1e.net/

  6. jan

    Thanks for the introduction.
    Today I was playing with this example on OSX and docker and I figured that one needs to rewrite the last line in the Dockerfile to make uvicorn accessible outside of the container, otherwise it’s only bounded to localhost inside the container and one can only curl within the container.

    CMD [“uvicorn”, “main:app”, “–host”, “0.0.0.0”, “–reload”]

    I thought this must be the same for podman but it’s not.
    Any ideas why that is?

Comments are Closed

The opinions expressed on this website are those of each author, not of the author's employer or of Red Hat. Fedora Magazine aspires to publish all content under a Creative Commons license but may not be able to do so in all cases. You are responsible for ensuring that you have the necessary permission to reuse any work on this site. The Fedora logo is a trademark of Red Hat, Inc. Terms and Conditions