Skip to content

Latest commit

 

History

History
548 lines (421 loc) · 12.1 KB

File metadata and controls

548 lines (421 loc) · 12.1 KB

Building Python Web APIs with FastAPI

Code and notes from studying Building Python Web APIs with FastAPI

Run the plannert project:

python.exe -m pip install --upgrade pip
cd planner
env/Scripts/activate
pip install -r requirements.txt
# python main.py
uvicorn api:app --port 8000 --reload

Run the todos project:

python.exe -m pip install --upgrade pip
cd todos
env/Scripts/activate
pip install -r requirements.txt
uvicorn api:app --port 8000 --reload

Chapter 1: Getting started

Create directory, create and activate a virtual environment, verify pip is installed:

pip install virtualenv
mkdir todos && cd todos
virtualenv env
env/Scripts/activate
python3 -m pip list

Deactivate the virtual environment:

deactivate

Install fastapi and create/freeze requirements.txt:

pip install fastapi
pip freeze > requirements.txt

To install packages from requirements.txt file we run:

pip install -r requirements.txt

Create api.py file:

from fastapi import FastAPI
app = FastAPI()

@app.get("/")
async def welcome() -> dict:
  return { "message": "Hello World" }

And run the API:

uvicorn api:app --port 8000 --reload

In the preceding command, uvicorn takes the following arguments:

  • file:instance: The file containing the instance of FastAPI and the name variable holding the FastAPI instance.
  • --port PORT: The port the application will be served on.
  • --reload: An optional argument included to restart the application on every file change.

Check the API:

curl http://127.0.0.1:8000/
{"message":"Hello World"}

Oops - remove __pycache__, add "pycache" to .gitignore and run:

git rm -r --cached __pycache__
git commit -a --amend --no-edit
git push --force

See:

Chapter 2: Routing in FastAPI

See todo.py for using APIRouter. See api.py for importing and including the router.

Run curl to POST data:

curl -X 'POST' \
  'http://127.0.0.1:8000/todo' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 1,
  "item": "First Todo is to finish this book!"
}'
{"message":"Todo added successfully"}

Run curl to update data:

curl -X 'POST' \
  'http://127.0.0.1:8000/todo' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 1,
  "item": "Example Schema!"
}'
{"message":"Todo added successfully"}

Run curl to get data:

curl -X 'GET' \
  'http://127.0.0.1:8000/todo' \
  -H 'accept: application/json'
{"message":"Hello World"}

Run curl to delete one todo item:

curl -X 'DELETE' \
  'http://127.0.0.1:8000/todo/1' \
  -H 'accept: application/json'
{"message":"Todo deleted successfully."}

Run curl to delete all todo items:

curl -X 'DELETE' \
  'http://127.0.0.1:8000/todo' \
  -H 'accept: application/json'
{"message":"Todos deleted successfully."}

See:

Validating request bodies using Pydantic models

See model.py for using Pydantic models.

See:

Path and query parameters

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to retrieve.")) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            return {
                "todo": todo
            }
    return {
        "message": "Todo with supplied ID doesn't exist."
    }
curl -X 'GET' \
  'http://127.0.0.1:8000/todo/2' \
  -H 'accept: application/json'
{"todo":{"id":2,"item":"Validation models help with input types"}}

See:

Documentation

Fast API exposes documentation in both ReDoc and interactive Swagger. Hit the urls:

You can define examples for the objects your API can receive:

class TodoItem(BaseModel):
  item: str
  model_config = {
    "json_schema_extra": {
      "examples": [
        {
          "item": "Todo item example",
        }
      ]
    }
  }

See:

Chapter 3: Response Models and Error Handling

Response models

Add model to model.py:

class TodoItem(BaseModel):
  item: str

class TodoItems(BaseModel):
  todos: List[TodoItem]
  model_config = {
    "json_schema_extra": {
      "examples": [
        {
          "todos": [
            {
              "item": "This todo will be retrieved without exposing my ID!"
            },
            {
              "item": "And so is this one."
            }
          ]
        }
      ]
    }
  }

Then change the route in todo.py, specify the type of the response will be response_model=TodoItems:

@todo_router.get("/todo", response_model=TodoItems)
async def retrieve_todos() -> dict:
  return {"todos": todo_list}

If you get the todos, you will see that the ID is not returned:

$ curl -X 'GET'   'http://127.0.0.1:8000/todo'   -H 'accept: application/json'
{"todos":[{"item":"First Todo is to finish this book!"}]}

Error handling

In todo.py file import HTTPException, status from fastapi and add exception:

@todo_router.put("/todo/{todo_id}")
async def update_todo(todo_data: TodoItem, todo_id: int = Path(..., title="The ID of the todo to be updated")) -> dict:
...
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )
$ curl -i -X 'GET'   'http://localhost:8000/todo/2'   -H 'accept: application/json'
HTTP/1.1 404 Not Found

{"detail":"Todo with supplied ID doesn't exist"}

Override the default HTTP status code

Edit file todo.py and set status_code=201 for the method:

@todo_router.post("/todo", status_code=201)
async def add_todo(todo: Todo) -> dict:
...

Chapter 4: Templating in FastAPI

Install jinja2:

pip install jinja2
pip install python-multipart
pip freeze > requirements.txt
pip install -r requirements.txt

Make templates directory and files:

mkdir templates
cd templates
touch {home,todo}.html

See:

Chapter 5: Structuring FastAPI Applications

Building an event planner with application structure to look like this:

planner/
  main.py
  database/
    __init__.py
    connection.py
  routes/
    __init__.py
    events.py
    users.py
  models/
    __init__.py
    events.py
    users.py

This is how the environment is initialized:

python.exe -m pip install --upgrade pip
pip install virtualenv
mkdir planner && cd planner
virtualenv env
env/Scripts/activate
pip install fastapi uvicorn "pydantic[email]"
pip install jinja2 python-multipart
pip freeze > requirements.txt
pip install -r requirements.txt
python3 -m pip list

To signup an user:

$ curl -i -X 'POST'   'http://127.0.0.1:8000/user/signup'   -H 'accept: application/json'   -H 'Content-Type: application/json'   -d '{
  "email": "[email protected]",
  "password": "Stro0ng!",
  "username": "FastPackt"
}'
HTTP/1.1 200 OK
date: Sat, 07 Oct 2023 03:35:40 GMT
server: uvicorn
content-length: 43
content-type: application/json

{"message":"User successfully registered!"}

Error signup an user:

$ curl -i -X 'POST'   'http://127.0.0.1:8000/user/signup'   -H 'accept: application/json'   -H 'Content-Type: application/json'   -d '{
  "email": "[email protected]",
  "password": "Stro0ng!",
  "username": "FastPackt"
}'
HTTP/1.1 409 Conflict
date: Sat, 07 Oct 2023 03:36:04 GMT
server: uvicorn
content-length: 47
content-type: application/json

{"detail":"User with supplied username exists"}

To signin an user:

$ curl -i -X 'POST'   'http://127.0.0.1:8000/user/signin'   -H 'accept: application/json'   -H 'Content-Type: application/json'   -d '{
  "email": "[email protected]",
  "password": "Stro0ng!"
}'
HTTP/1.1 200 OK
date: Sat, 07 Oct 2023 03:37:17 GMT
server: uvicorn
content-length: 41
content-type: application/json

{"message":"User signed in successfully"}

Error signin user:

$ curl -i -X 'POST'   'http://127.0.0.1:8000/user/signin'   -H 'accept: application/json'   -H 'Content-Type: application/json'   -d '{
  "email": "[email protected]",
  "password": "Stro0ng!"
}'
HTTP/1.1 404 Not Found
date: Sat, 07 Oct 2023 03:37:53 GMT
server: uvicorn
content-length: 32
content-type: application/json

{"detail":"User does not exist"}

Create event:

$ curl -i -X 'POST' 'http://127.0.0.1:8000/event/new' -H 'accept: application/json' -H 'Content-Type: application/json'   -d '{
  "id": 1,
  "title": "FastAPI Book Launch",
  "image": "https://linktomyimage.com/image.png",
  "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
  "tags": [
    "python",
    "fastapi",
    "book",
    "launch"
  ],
  "location": "Google Meet"
}'
HTTP/1.1 200 OK
date: Sat, 07 Oct 2023 03:46:44 GMT
server: uvicorn
content-length: 40
content-type: application/json

{"message":"Event created successfully"}

Get events:

$ curl -i -X 'GET' 'http://127.0.0.1:8000/event/' -H 'accept: application/json'
HTTP/1.1 200 OK
date: Sat, 07 Oct 2023 03:47:07 GMT
server: uvicorn
content-length: 288
content-type: application/json

[{"id":1,"title":"FastAPI Book Launch","image":"https://linktomyimage.com/image.png","description":"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!","tags":["python","fastapi","book","launch"],"location":"Google Meet"}]

Get one event by ID:

$ curl -i -X 'GET' 'http://127.0.0.1:8000/event/1' -H 'accept: application/json'
HTTP/1.1 200 OK
date: Sat, 07 Oct 2023 03:47:43 GMT
server: uvicorn
content-length: 286
content-type: application/json

{"id":1,"title":"FastAPI Book Launch","image":"https://linktomyimage.com/image.png","description":"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!","tags":["python","fastapi","book","launch"],"location":"Google Meet"}

Error get one event by ID:

$ curl -i -X 'GET' 'http://127.0.0.1:8000/event/0' -H 'accept: application/json'
HTTP/1.1 404 Not Found
date: Sat, 07 Oct 2023 03:48:13 GMT
server: uvicorn
content-length: 50
content-type: application/json

{"detail":"Event with supplied ID does not exist"}

Delete event by ID:

$ curl -i -X 'DELETE' \
  'http://127.0.0.1:8000/event/1' \
  -H 'accept: application/json'
HTTP/1.1 200 OK
date: Sat, 07 Oct 2023 03:49:08 GMT
server: uvicorn
content-length: 40
content-type: application/json

{"message":"Event deleted successfully"}

Error delete event by ID:

$ curl -i -X 'DELETE' 'http://127.0.0.1:8000/event/0' -H 'accept: application/json'
HTTP/1.1 404 Not Found
date: Sat, 07 Oct 2023 03:49:37 GMT
server: uvicorn
content-length: 50
content-type: application/json

{"detail":"Event with supplied ID does not exist"}

See:

Chapter 6: Connecting to a Database

Explains how to connect to a SQL database using SQLModel and a MongoDB database via Beanie.

Add dependencies:

cd planner
env/Scripts/activate # or bash: source env/Scripts/activate
pip install sqlmodel
pip freeze > requirements.txt
pip install -r requirements.txt
python3 -m pip list

Define SQL model:

from sqlmodel import Field, SQLModel, Optional

class Event(SQLModel, BaseModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
...

See: