main a6b31dba7c8f cached
97 files
75.7 KB
21.0k tokens
229 symbols
1 requests
Download .txt
Repository: PacktPublishing/Building-Python-Web-APIs-with-FastAPI
Branch: main
Commit: a6b31dba7c8f
Files: 97
Total size: 75.7 KB

Directory structure:
gitextract_vyvw9g85/

├── .gitignore
├── LICENSE
├── README.md
├── ch01/
│   ├── Dockerfile
│   ├── hello.py
│   └── todos/
│       ├── api.py
│       └── requirements.txt
├── ch02/
│   └── todos/
│       ├── api.py
│       ├── model.py
│       ├── requirements.txt
│       └── todo.py
├── ch03/
│   └── todos/
│       ├── api.py
│       ├── model.py
│       ├── requirements.txt
│       └── todo.py
├── ch04/
│   └── todos/
│       ├── api.py
│       ├── model.py
│       ├── requirements.txt
│       ├── templates/
│       │   ├── home.html
│       │   └── todo.html
│       └── todo.py
├── ch05/
│   └── planner/
│       ├── database/
│       │   └── __init__.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── requirements.txt
│       └── routes/
│           ├── __init__.py
│           ├── events.py
│           └── users.py
├── ch06/
│   └── planner/
│       ├── database/
│       │   ├── __init__.py
│       │   └── connection.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── requirements.txt
│       └── routes/
│           ├── __init__.py
│           ├── events.py
│           └── users.py
├── ch07/
│   └── planner/
│       ├── auth/
│       │   ├── __init__.py
│       │   ├── authenticate.py
│       │   ├── hash_password.py
│       │   └── jwt_handler.py
│       ├── database/
│       │   ├── __init__.py
│       │   └── connection.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── requirements.txt
│       └── routes/
│           ├── __init__.py
│           ├── events.py
│           └── users.py
├── ch08/
│   └── planner/
│       ├── auth/
│       │   ├── __init__.py
│       │   ├── authenticate.py
│       │   ├── hash_password.py
│       │   └── jwt_handler.py
│       ├── database/
│       │   ├── __init__.py
│       │   └── connection.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── pytest.ini
│       ├── requirements.txt
│       ├── routes/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       └── tests/
│           ├── conftest.py
│           ├── test_arthmetic_operations.py
│           ├── test_fixture.py
│           ├── test_login.py
│           └── test_routes.py
└── ch09/
    └── planner/
        ├── .dockerignore
        ├── Dockerfile
        ├── auth/
        │   ├── __init__.py
        │   ├── authenticate.py
        │   ├── hash_password.py
        │   └── jwt_handler.py
        ├── database/
        │   ├── __init__.py
        │   └── connection.py
        ├── docker-compose.yml
        ├── main.py
        ├── models/
        │   ├── __init__.py
        │   ├── events.py
        │   └── users.py
        ├── pytest.ini
        ├── requirements.txt
        ├── routes/
        │   ├── __init__.py
        │   ├── events.py
        │   └── users.py
        └── tests/
            ├── conftest.py
            ├── test_arthmetic_operations.py
            ├── test_fixture.py
            ├── test_login.py
            └── test_routes.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
venv
.env


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Packt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================



# Building Python Web APIs with FastAPI

<a href="https://www.packtpub.com/product/building-python-web-apis-with-fastapi/9781801076630?utm_source=github&utm_medium=repository&utm_campaign=9781801076630"><img src="https://static.packt-cdn.com/products/9781801076630/cover/smaller" alt="Building Python Web APIs with FastAPI" height="256px" align="right"></a>

This is the code repository for [Building Python Web APIs with FastAPI](https://www.packtpub.com/product/building-python-web-apis-with-fastapi/9781801076630?utm_source=github&utm_medium=repository&utm_campaign=9781801076630), published by Packt.

**A fast-paced guide to building high-performance, robust web APIs with very little boilerplate code**

## What is this book about?
RESTful web services are commonly used to create APIs for web-based applications owing to their light weight and high scalability. This book will show you how FastAPI, a high-performance web framework for building RESTful APIs in Python, allows you to build robust web APIs that are simple and intuitive and makes it easy to build quickly with very little boilerplate code.

This book covers the following exciting features:
* Set up a FastAPI application that is fully functional and secure
* Understand how to handle errors from requests and send proper responses in FastAPI
* Integrate and connect your application to a SQL and NoSQL (MongoDB) database
* Perform CRUD operations using SQL and FastAPI
* Manage concurrency in FastAPI applications

If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1801076634) today!

<a href="https://www.packtpub.com/?utm_source=github&utm_medium=banner&utm_campaign=GitHubBanner"><img src="https://raw.githubusercontent.com/PacktPublishing/GitHub/master/GitHub.png" 
alt="https://www.packtpub.com/" border="5" /></a>


## Instructions and Navigations
All of the code is organized into folders. For example, Chapter05.

The code will look like the following:
```
from pydantic import BaseModel
from typing import List
class Event(BaseModel):
   id: int
   title: str
   image: str
   description: str
   tags: List[str]
   location: str  
```

**Following is what you need for this book:**

This book is for Python developers who want to learn FastAPI in a pragmatic way to create robust web APIs with ease. If you are a Django or Flask developer looking to try something new that's faster, more efficient, and produces fewer bugs, this FastAPI Python book is for you. The book assumes intermediate-level knowledge of Python programming.

With the following software and hardware list you can run all code files present in the book (Chapter 1-09).

### Software and Hardware List

| Chapter  | Software required                   | OS required                        |
| -------- | ------------------------------------| -----------------------------------|
| 1-09     | Python 3.10                         | Windows, Mac OS X, and Linux       |
| 1-09     | Git 2.36.0                          | Windows, Mac OS X, and Linux       |



We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://packt.link/qqhpc).


### Related products <Other books you may enjoy>
* Python Web Development with Sanic [[Packt]](https://www.packtpub.com/product/python-web-development-with-sanic/9781801814416?_ga=2.134911217.1837201707.1657723916-1157268863.1584421665&utm_source=github&utm_medium=repository&utm_campaign=9781801814416) [[Amazon]](https://www.amazon.com/dp/1801814414)

* Becoming an Enterprise Django Developer [[Packt]](https://www.packtpub.com/product/becoming-an-enterprise-django-developer/9781801073639?_ga=2.127463693.1837201707.1657723916-1157268863.1584421665&utm_source=github&utm_medium=repository&utm_campaign=9781801073639) [[Amazon]](https://www.amazon.com/dp/1801073635)

## Errata 
 * **Use port 8000 as per the GitHub repository examples.**
 * Page 14 (Code Snippet 1 line 1):  **FROM PYTHON:3.8** _should be_ **FROM python:3.8**
 * Page 15 (last line):  **FROM PYTHON:3.8** _should be_ **(venv)$ uvicorn api:app --port 8080 --reload**
 * Page 10,11,12 :  Page 10 should display the picture of page 11. Page 11 should display the picture of page 12. Page 12 should display the picture of page 10.
 * Page 24, (Code snippet 2 Line 1):
       ```from Pydantic import BaseModel```
       _should be_
       ```from pydantic import BaseModel```
    * Page 24, (Code snippet 2 Line 2):
       ```class Todo(BaseMode):```
       _should be_
       ```class Todo(BaseModel):```
 * Page 24, **Let’s go ahead and use the model in our POST route. In api.py, import the model** _should be_ **Let’s go ahead and use the model in our POST route. In todo.py, import the model**
 
## Get to Know the Author
**Abdulazeez Abdulazeez Adeshina**
is a skilled Python developer, backend software engineer, and technical writer, with a wide range of technical skill sets in his arsenal. His background has led him to build command-line applications, backend applications in FastAPI, and algorithm-based treasure-hunting tools. He also enjoys teaching Python and solving mathematical-oriented problems through his blog. Abdulazeez is currently in his penultimate year of a water resources and environmental engineering program. His work experience as a guest technical author includes the likes of Auth0, LogRocket, Okteto, and TestDriven.




### Download a free PDF

 <i>If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.<br>Simply click on the link to claim your free PDF.</i>
<p align="center"> <a href="https://packt.link/free-ebook/9781801076630">https://packt.link/free-ebook/9781801076630 </a> </p>


================================================
FILE: ch01/Dockerfile
================================================
FROM PYTHON:3.8
# Set working directory to /usr/src/app
WORKDIR /usr/src/app
# Copy the contents of the current local directory into the container’s working directory
ADD . /usr/src/app
# Run a command
CMD [“python”, “hello.py”]


================================================
FILE: ch01/hello.py
================================================
print("Hello!")


================================================
FILE: ch01/todos/api.py
================================================
from fastapi import FastAPI

app = FastAPI()


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


================================================
FILE: ch01/todos/requirements.txt
================================================
fastapi
uvicorn


================================================
FILE: ch02/todos/api.py
================================================
from fastapi import FastAPI

from todo import todo_router

app = FastAPI()


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


app.include_router(todo_router)


================================================
FILE: ch02/todos/model.py
================================================
from pydantic import BaseModel


class Todo(BaseModel):
    id: int
    item: str

    class Config:
        schema_extra = {
            "example": {
                "id": 1,
                "item": "Example Schema!"
            }
        }


class TodoItem(BaseModel):
    item: str

    class Config:
        schema_extra = {
            "example": {
                "item": "Read the next chapter of the book"
            }
        }


================================================
FILE: ch02/todos/requirements.txt
================================================
fastapi==0.70.0
uvicorn==0.15.0


================================================
FILE: ch02/todos/todo.py
================================================
from fastapi import APIRouter, Path

from model import Todo, TodoItem

todo_router = APIRouter()

todo_list = []


@todo_router.post("/todo")
async def add_todo(todo: Todo) -> dict:
    todo_list.append(todo)
    return {
        "message": "Todo added successfully."
    }


@todo_router.get("/todo")
async def retrieve_todo() -> dict:
    return {
        "todos": todo_list
    }


@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."
    }


@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:
    for todo in todo_list:
        if todo.id == todo_id:
            todo.item = todo_data.item
            return {
                "message": "Todo updated successfully."
            }
    return {
        "message": "Todo with supplied ID doesn't exist."
    }


@todo_router.delete("/todo/{todo_id}")
async def delete_single_todo(todo_id: int) -> dict:
    for index in range(len(todo_list)):
        todo = todo_list[index]
        if todo.id == todo_id:
            todo_list.pop(index)
            return {
                "message": "Todo deleted successfully."
            }
    return {
        "message": "Todo with supplied ID doesn't exist."
    }


@todo_router.delete("/todo")
async def delete_all_todo() -> dict:
    todo_list.clear()
    return {
        "message": "Todos deleted successfully."
    }


================================================
FILE: ch03/todos/api.py
================================================
from fastapi import FastAPI

from todo import todo_router

app = FastAPI()


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


app.include_router(todo_router)


================================================
FILE: ch03/todos/model.py
================================================
from typing import List

from pydantic import BaseModel


class Todo(BaseModel):
    id: int
    item: str

    class Config:
        schema_extra = {
            "example": {
                "id": 1,
                "item": "Example schema!"
            }
        }


class TodoItem(BaseModel):
    item: str

    class Config:
        schema_extra = {
            "example": {
                "item": "Read the next chapter of the book"
            }
        }


class TodoItems(BaseModel):
    todos: List[TodoItem]

    class Config:
        schema_extra = {
            "example": {
                "todos": [
                    {
                        "item": "Example schema 1!"
                    },
                    {
                        "item": "Example schema 2!"
                    }
                ]
            }
        }


================================================
FILE: ch03/todos/requirements.txt
================================================
fastapi==0.70.0
uvicorn==0.15.0


================================================
FILE: ch03/todos/todo.py
================================================
from fastapi import APIRouter, Path, HTTPException, status
from model import Todo, TodoItem, TodoItems

todo_router = APIRouter()

todo_list = []


@todo_router.post("/todo", status_code=201)
async def add_todo(todo: Todo) -> dict:
    todo_list.append(todo)
    return {
        "message": "Todo added successfully."
    }


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


@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
            }
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )


@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:
    for todo in todo_list:
        if todo.id == todo_id:
            todo.item = todo_data.item
            return {
                "message": "Todo updated successfully."
            }

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )


@todo_router.delete("/todo/{todo_id}")
async def delete_single_todo(todo_id: int) -> dict:
    for index in range(len(todo_list)):
        todo = todo_list[index]
        if todo.id == todo_id:
            todo_list.pop(index)
            return {
                "message": "Todo deleted successfully."
            }
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )


@todo_router.delete("/todo")
async def delete_all_todo() -> dict:
    todo_list.clear()
    return {
        "message": "Todos deleted successfully."
    }


================================================
FILE: ch04/todos/api.py
================================================
from fastapi import FastAPI

from todo import todo_router

app = FastAPI()


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


app.include_router(todo_router)


================================================
FILE: ch04/todos/model.py
================================================
from typing import List, Optional

from fastapi import Form
from pydantic import BaseModel


class Todo(BaseModel):
    id: Optional[int]
    item: str

    @classmethod
    def as_form(
            cls,
            item: str = Form(...)
    ):
        return cls(item=item)

    class Config:
        schema_extra = {
            "example": {
                "id": 1,
                "item": "Example schema!"
            }
        }


class TodoItem(BaseModel):
    item: str

    class Config:
        schema_extra = {
            "example": {
                "item": "Read the next chapter of the book"
            }
        }


class TodoItems(BaseModel):
    todos: List[TodoItem]

    class Config:
        schema_extra = {
            "example": {
                "todos": [
                    {
                        "item": "Example schema 1!"
                    },
                    {
                        "item": "Example schema 2!"
                    }
                ]
            }
        }


================================================
FILE: ch04/todos/requirements.txt
================================================
fastapi==0.70.0
uvicorn==0.15.0
jinja2 == 3.1.2
python-multipart


================================================
FILE: ch04/todos/templates/home.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="IE=edge" http-equiv="X-UA-Compatible">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <title>Packt Todo Application</title>
    <link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
          integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" rel="stylesheet">
    <link crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
          integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" rel="stylesheet">
</head>
<body>
<header>
    <nav class="navar">
        <div class="container-fluid">
            <center>
                <h1>Packt Todo Application</h1>
            </center>
        </div>
    </nav>
</header>
<div class="container-fluid">
    {% block todo_container %}{% endblock %}
</div>
</body>
</html>

================================================
FILE: ch04/todos/templates/todo.html
================================================
{% extends "home.html" %}

{% block todo_container %}
<main class="container">
    <hr>

    <section class="container-fluid">
        <form method="post">
            <div class="col-auto">
                <div class="input-group mb-3">
                    <input aria-describedby="button-addon2" aria-label="Add a todo" class="form-control" name="item"
                           placeholder="Purchase Packt's Python workshop course" type="text"
                           value="{{ item }}"/>
                    <button class="btn btn-outline-primary" data-mdb-ripple-color="dark" id="button-addon2"
                            type="submit">
                        Add Todo
                    </button>
                </div>
            </div>
        </form>
    </section>


    {% if todo %}
    <article class="card container-fluid">
        <br/>
        <h4>Todo ID: {{ todo.id }} </h4>
        <p>
            <strong>
                Item: {{ todo.item }}
            </strong>
        </p>
    </article>
    {% else %}
    <section class="container-fluid">
        <h2 align="center">Todos</h2>
        <br>
        <div class="card">
            <ul class="list-group list-group-flush">
                {% for todo in todos %}
                <li class="list-group-item">
                    {{ loop.index }}. <a href="/todo/{{ loop.index }}"> {{ todo.item }} </a>
                </li>
                {% endfor %}
            </ul>
        </div>
        {% endif %}
    </section>
</main>
{% endblock %}

================================================
FILE: ch04/todos/todo.py
================================================
from fastapi import APIRouter, Path, HTTPException, status, Request, Depends
from fastapi.templating import Jinja2Templates

from model import Todo, TodoItem, TodoItems

todo_router = APIRouter()

todo_list = []

templates = Jinja2Templates(directory="templates/")


@todo_router.post("/todo")
async def add_todo(request: Request, todo: Todo = Depends(Todo.as_form)):
    todo.id = len(todo_list) + 1
    todo_list.append(todo)
    return templates.TemplateResponse("todo.html",
                                      {
                                          "request": request,
                                          "todos": todo_list
                                      })


@todo_router.get("/todo", response_model=TodoItems)
async def retrieve_todo(request: Request):
    return templates.TemplateResponse("todo.html", {
        "request": request,
        "todos": todo_list
    })


@todo_router.get("/todo/{todo_id}")
async def get_single_todo(request: Request, todo_id: int = Path(..., title="The ID of the todo to retrieve.")):
    for todo in todo_list:
        if todo.id == todo_id:
            return templates.TemplateResponse("todo.html", {
                "request": request,
                "todo": todo
            })
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )


@todo_router.put("/todo/{todo_id}")
async def update_todo(request: Request, todo_data: TodoItem,
                      todo_id: int = Path(..., title="The ID of the todo to be updated.")) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            todo.item = todo_data.item
            return {
                "message": "Todo updated successfully."
            }

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )


@todo_router.delete("/todo/{todo_id}")
async def delete_single_todo(request: Request, todo_id: int) -> dict:
    for index in range(len(todo_list)):
        todo = todo_list[index]
        if todo.id == todo_id:
            todo_list.pop(index)
            return {
                "message": "Todo deleted successfully."
            }
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist",
    )


@todo_router.delete("/todo")
async def delete_all_todo() -> dict:
    todo_list.clear()
    return {
        "message": "Todos deleted successfully."
    }


================================================
FILE: ch05/planner/database/__init__.py
================================================


================================================
FILE: ch05/planner/main.py
================================================
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

from routes.users import user_router
from routes.events import event_router

import uvicorn

app = FastAPI()

# Register routes

app.include_router(user_router,  prefix="/user")
app.include_router(event_router, prefix="/event")


@app.get("/")
async def home():
    return RedirectResponse(url="/event/")

if __name__ == '__main__':
    uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)

================================================
FILE: ch05/planner/models/__init__.py
================================================


================================================
FILE: ch05/planner/models/events.py
================================================
from typing import List

from pydantic import BaseModel


class Event(BaseModel):
    id: int
    title: str
    image: str
    description: str
    tags: List[str]
    location: str

    class Config:
        schema_extra = {
            "example": {
                "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"
            }
        }


================================================
FILE: ch05/planner/models/users.py
================================================
from pydantic import BaseModel, EmailStr


class User(BaseModel):
    email: EmailStr
    password: str

    class Config:
        schema_extra = {
            "example": {
                "email": "fastapi@packt.com",
                "password": "strong!!!",
            }
        }


class UserSignIn(BaseModel):
    email: EmailStr
    password: str

    schema_extra = {
        "example": {
            "email": "fastapi@packt.com",
            "password": "strong!!!"
        }
    }


================================================
FILE: ch05/planner/requirements.txt
================================================
anyio==3.5.0
asgiref==3.5.0
bcrypt==3.2.0
beanie==1.10.4
cffi==1.15.0
click==8.0.4
dnspython==2.2.0
email-validator==1.1.3
fastapi==0.74.1
h11==0.13.0
idna==3.3
Jinja2==3.0.3
MarkupSafe==2.1.0
motor==2.5.1
multidict==6.0.2
passlib==1.7.4
pycparser==2.21
pydantic==1.9.0
PyJWT==2.3.0
pymongo==3.12.3
python-dotenv==0.20.0
python-multipart==0.0.5
six==1.16.0
sniffio==1.2.0
SQLAlchemy==1.4.32
sqlalchemy2-stubs==0.0.2a20
sqlmodel==0.0.6
starlette==0.17.1
toml==0.10.2
typing_extensions==4.1.1
uvicorn==0.17.5
yarl==1.7.2


================================================
FILE: ch05/planner/routes/__init__.py
================================================


================================================
FILE: ch05/planner/routes/events.py
================================================
from typing import List

from fastapi import APIRouter, Body, HTTPException, status
from models.events import Event

event_router = APIRouter(
    tags=["Events"]
)

events = []


@event_router.get("/", response_model=List[Event])
async def retrieve_all_events() -> List[Event]:
    return events


@event_router.get("/{id}", response_model=Event)
async def retrieve_event(id: int) -> Event:
    for event in events:
        if event.id == id:
            return event
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Event with supplied ID does not exist"
    )


@event_router.post("/new")
async def create_event(body: Event = Body(...)) -> dict:
    events.append(body)
    return {
        "message": "Event created successfully"
    }


@event_router.delete("/{id}")
async def delete_event(id: int) -> dict:
    for event in events:
        if event.id == id:
            events.remove(event)
            return {
                "message": "Event deleted successfully"
            }

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Event with supplied ID does not exist"
    )


================================================
FILE: ch05/planner/routes/users.py
================================================
from fastapi import APIRouter, HTTPException, status

from models.users import User, UserSignIn

user_router = APIRouter(
    tags=["User"],
)

users = {}


@user_router.post("/signup")
async def sign_user_up(data: User) -> dict:
    if data.email in users:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with supplied username exists"
        )

    users[data.email] = data

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


@user_router.post("/signin")
async def sign_user_in(user: UserSignIn) -> dict:
    if user.email not in users:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User does not exist"
        )

    if users[user.email].password != user.password:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Wrong credential passed"
        )
    return {
        "message": "User signed in successfully"
    }


================================================
FILE: ch06/planner/database/__init__.py
================================================


================================================
FILE: ch06/planner/database/connection.py
================================================
from typing import Any, List, Optional

from beanie import init_beanie, PydanticObjectId
from models.events import Event
from models.users import User
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseSettings, BaseModel


class Settings(BaseSettings):
    DATABASE_URL: Optional[str] = None

    async def initialize_database(self):
        client = AsyncIOMotorClient(self.DATABASE_URL)
        await init_beanie(database=client.get_default_database(),
                          document_models=[Event, User])

    class Config:
        env_file = ".env"


class Database:
    def __init__(self, model):
        self.model = model

    async def save(self, document) -> None:
        await document.create()
        return

    async def get(self, id: PydanticObjectId) -> Any:
        doc = await self.model.get(id)
        if doc:
            return doc
        return False

    async def get_all(self) -> List[Any]:
        docs = await self.model.find_all().to_list()
        return docs

    async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
        doc_id = id
        des_body = body.dict()

        des_body = {k: v for k, v in des_body.items() if v is not None}
        update_query = {"$set": {
            field: value for field, value in des_body.items()
        }}

        doc = await self.get(doc_id)
        if not doc:
            return False
        await doc.update(update_query)
        return doc

    async def delete(self, id: PydanticObjectId) -> bool:
        doc = await self.get(id)
        if not doc:
            return False
        await doc.delete()
        return True


================================================
FILE: ch06/planner/main.py
================================================
import uvicorn
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

from database.connection import Settings
from routes.events import event_router
from routes.users import user_router

app = FastAPI()

settings = Settings()

# Register routes

app.include_router(user_router, prefix="/user")
app.include_router(event_router, prefix="/event")


@app.on_event("startup")
async def init_db():
    await settings.initialize_database()


@app.get("/")
async def home():
    return RedirectResponse(url="/event/")


if __name__ == '__main__':
    uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)


================================================
FILE: ch06/planner/models/__init__.py
================================================


================================================
FILE: ch06/planner/models/events.py
================================================
from typing import Optional, List

from beanie import Document
from pydantic import BaseModel


class Event(Document):
    title: str
    image: str
    description: str
    tags: List[str]
    location: str

    class Config:
        schema_extra = {
            "example": {
                "title": "FastAPI BookLaunch",
                "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"
            }
        }

    class Settings:
        name = "events"


class EventUpdate(BaseModel):
    title: Optional[str]
    image: Optional[str]
    description: Optional[str]
    tags: Optional[List[str]]
    location: Optional[str]

    class Config:
        schema_extra = {
            "example": {
                "title": "FastAPI BookLaunch",
                "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"
            }
        }


================================================
FILE: ch06/planner/models/users.py
================================================
from beanie import Document

from pydantic import BaseModel, EmailStr


class User(Document):
    email: EmailStr
    password: str

    class Settings:
        name = "users"

    class Config:
        schema_extra = {
            "example": {
                "email": "fastapi@packt.com",
                "password": "strong!!!",
            }
        }


class UserSignIn(BaseModel):
    email: EmailStr
    password: str


================================================
FILE: ch06/planner/requirements.txt
================================================
anyio==3.5.0
asgiref==3.5.0
bcrypt==3.2.0
beanie==1.10.4
cffi==1.15.0
click==8.0.4
dnspython==2.2.0
email-validator==1.1.3
fastapi==0.74.1
h11==0.13.0
idna==3.3
Jinja2==3.0.3
MarkupSafe==2.1.0
motor==2.5.1
multidict==6.0.2
passlib==1.7.4
pycparser==2.21
pydantic==1.9.0
PyJWT==2.3.0
pymongo==3.12.3
python-dotenv==0.20.0
python-multipart==0.0.5
six==1.16.0
sniffio==1.2.0
SQLAlchemy==1.4.32
sqlalchemy2-stubs==0.0.2a20
sqlmodel==0.0.6
starlette==0.17.1
toml==0.10.2
typing_extensions==4.1.1
uvicorn==0.17.5
yarl==1.7.2


================================================
FILE: ch06/planner/routes/__init__.py
================================================


================================================
FILE: ch06/planner/routes/events.py
================================================
from typing import List

from beanie import PydanticObjectId
from database.connection import Database
from fastapi import APIRouter, HTTPException, status
from models.events import Event, EventUpdate

event_router = APIRouter(
    tags=["Events"]
)

event_database = Database(Event)


@event_router.get("/", response_model=List[Event])
async def retrieve_all_events() -> List[Event]:
    events = await event_database.get_all()
    return events


@event_router.get("/{id}", response_model=Event)
async def retrieve_event(id: PydanticObjectId) -> Event:
    event = await event_database.get(id)
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return event


@event_router.post("/new")
async def create_event(body: Event) -> dict:
    await event_database.save(body)
    return {
        "message": "Event created successfully"
    }


@event_router.put("/{id}", response_model=Event)
async def update_event(id: PydanticObjectId, body: EventUpdate) -> Event:
    updated_event = await event_database.update(id, body)
    if not updated_event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return updated_event


@event_router.delete("/{id}")
async def delete_event(id: PydanticObjectId) -> dict:
    event = await event_database.delete(id)
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return {
        "message": "Event deleted successfully."
    }


================================================
FILE: ch06/planner/routes/users.py
================================================
from database.connection import Database
from fastapi import APIRouter, HTTPException, status
from models.users import User, UserSignIn

user_router = APIRouter(
    tags=["User"],
)

user_database = Database(User)


@user_router.post("/signup")
async def sign_user_up(user: User) -> dict:
    user_exist = await User.find_one(User.email == user.email)

    if user_exist:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with email provided exists already."
        )
    await user_database.save(user)
    return {
        "message": "User created successfully"
    }


@user_router.post("/signin")
async def sign_user_in(user: UserSignIn) -> dict:
    user_exist = await User.find_one(User.email == user.email)
    if not user_exist:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User with email does not exist."
        )
    if user_exist.password == user.password:
        return {
            "message": "User signed in successfully."
        }

    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid details passed."
    )


================================================
FILE: ch07/planner/auth/__init__.py
================================================


================================================
FILE: ch07/planner/auth/authenticate.py
================================================
from auth.jwt_handler import verify_access_token
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")


async def authenticate(token: str = Depends(oauth2_scheme)) -> str:
    if not token:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Sign in for access"
        )

    decoded_token = verify_access_token(token)
    return decoded_token["user"]


================================================
FILE: ch07/planner/auth/hash_password.py
================================================
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


class HashPassword:
    def create_hash(self, password: str):
        return pwd_context.hash(password)

    def verify_hash(self, plain_password: str, hashed_password: str):
        return pwd_context.verify(plain_password, hashed_password)


================================================
FILE: ch07/planner/auth/jwt_handler.py
================================================
import time
from datetime import datetime

from database.connection import Settings
from fastapi import HTTPException, status
from jose import jwt, JWTError

settings = Settings()


def create_access_token(user: str):
    payload = {
        "user": user,
        "expires": time.time() + 3600
    }

    token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
    return token


def verify_access_token(token: str):
    try:
        data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])

        expire = data.get("expires")

        if expire is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="No access token supplied"
            )
        if datetime.utcnow() > datetime.utcfromtimestamp(expire):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Token expired!"
            )
        return data

    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid token"
        )


================================================
FILE: ch07/planner/database/__init__.py
================================================


================================================
FILE: ch07/planner/database/connection.py
================================================
from typing import Optional

from beanie import init_beanie, PydanticObjectId
from models.events import Event
from models.users import User
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseSettings, BaseModel


class Settings(BaseSettings):
    DATABASE_URL: Optional[str] = None
    SECRET_KEY: Optional[str] = None

    async def initialize_database(self):
        client = AsyncIOMotorClient(self.DATABASE_URL)
        await init_beanie(database=client.get_default_database(),
                          document_models=[Event, User])

    class Config:
        env_file = ".env"


class Database:
    def __init__(self, model):
        self.model = model

    async def save(self, document):
        await document.create()
        return

    async def get(self, id: PydanticObjectId):
        doc = await self.model.get(id)
        if doc:
            return doc
        return False

    async def get_all(self):
        docs = await self.model.find_all().to_list()
        return docs

    async def update(self, id: PydanticObjectId, body: BaseModel):
        doc_id = id
        des_body = body.dict()

        des_body = {k: v for k, v in des_body.items() if v is not None}
        update_query = {"$set": {
            field: value for field, value in des_body.items()
        }}

        doc = await self.get(doc_id)
        if not doc:
            return False
        await doc.update(update_query)
        return doc

    async def delete(self, id: PydanticObjectId):
        doc = await self.get(id)
        if not doc:
            return False
        await doc.delete()
        return True


================================================
FILE: ch07/planner/main.py
================================================
import uvicorn
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from fastapi.middleware.cors import CORSMiddleware

from database.connection import Settings
from routes.events import event_router
from routes.users import user_router

app = FastAPI()

settings = Settings()

# register origins

origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Register routes

app.include_router(user_router, prefix="/user")
app.include_router(event_router, prefix="/event")


@app.on_event("startup")
async def init_db():
    await settings.initialize_database()


@app.get("/")
async def home():
    return RedirectResponse(url="/event/")


if __name__ == '__main__':
    uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)


================================================
FILE: ch07/planner/models/__init__.py
================================================


================================================
FILE: ch07/planner/models/events.py
================================================
from typing import Optional, List

from beanie import Document
from pydantic import BaseModel


class Event(Document):
    creator: Optional[str]
    title: str
    image: str
    description: str
    tags: List[str]
    location: str

    class Config:
        schema_extra = {
            "example": {
                "title": "FastAPI BookLaunch",
                "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"
            }
        }

    class Collection:
        name = "events"


class EventUpdate(BaseModel):
    title: Optional[str]
    image: Optional[str]
    description: Optional[str]
    tags: Optional[List[str]]
    location: Optional[str]

    class Config:
        schema_extra = {
            "example": {
                "title": "FastAPI BookLaunch",
                "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"
            }
        }


================================================
FILE: ch07/planner/models/users.py
================================================
from beanie import Document

from pydantic import BaseModel, EmailStr


class User(Document):
    email: EmailStr
    password: str

    class Collection:
        name = "users"

    class Config:
        schema_extra = {
            "example": {
                "email": "fastapi@packt.com",
                "password": "strong!!!"
            }
        }


class TokenResponse(BaseModel):
    access_token: str
    token_type: str


================================================
FILE: ch07/planner/requirements.txt
================================================
anyio==3.5.0
asgiref==3.5.0
bcrypt==3.2.0
beanie==1.10.4
cffi==1.15.0
click==8.0.4
cryptography==36.0.2
dnspython==2.2.0
ecdsa==0.17.0
email-validator==1.1.3
fastapi==0.74.1
h11==0.13.0
idna==3.3
Jinja2==3.0.3
MarkupSafe==2.1.0
motor==2.5.1
multidict==6.0.2
passlib==1.7.4
pyasn1==0.4.8
pycparser==2.21
pydantic==1.9.0
pymongo==3.12.3
python-dotenv==0.20.0
python-jose==3.3.0
python-multipart==0.0.5
rsa==4.8
six==1.16.0
sniffio==1.2.0
SQLAlchemy==1.4.32
sqlalchemy2-stubs==0.0.2a20
sqlmodel==0.0.6
starlette==0.17.1
toml==0.10.2
typing_extensions==4.1.1
uvicorn==0.17.5
yarl==1.7.2


================================================
FILE: ch07/planner/routes/__init__.py
================================================


================================================
FILE: ch07/planner/routes/events.py
================================================
from typing import List

from auth.authenticate import authenticate
from beanie import PydanticObjectId
from database.connection import Database
from fastapi import APIRouter, Depends, HTTPException, status
from models.events import Event, EventUpdate

event_router = APIRouter(
    tags=["Events"]
)

event_database = Database(Event)


@event_router.get("/", response_model=List[Event])
async def retrieve_all_events() -> List[Event]:
    events = await event_database.get_all()
    return events


@event_router.get("/{id}", response_model=Event)
async def retrieve_event(id: PydanticObjectId) -> Event:
    event = await event_database.get(id)
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return event


@event_router.post("/new")
async def create_event(body: Event, user: str = Depends(authenticate)) -> dict:
    body.creator = user
    await event_database.save(body)
    return {
        "message": "Event created successfully"
    }


@event_router.put("/{id}", response_model=Event)
async def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:
    event = await event_database.get(id)
    if event.creator != user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Operation not allowed"
        )
    updated_event = await event_database.update(id, body)
    if not updated_event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return updated_event


@event_router.delete("/{id}")
async def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:
    event = await event_database.get(id)
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event not found"
        )
    if event.creator != user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Operation not allowed"
        )
    event = await event_database.delete(id)

    return {
        "message": "Event deleted successfully."
    }


================================================
FILE: ch07/planner/routes/users.py
================================================
from auth.hash_password import HashPassword
from auth.jwt_handler import create_access_token
from database.connection import Database
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from models.users import User, TokenResponse

user_router = APIRouter(
    tags=["User"],
)

user_database = Database(User)
hash_password = HashPassword()


@user_router.post("/signup")
async def sign_user_up(user: User) -> dict:
    user_exist = await User.find_one(User.email == user.email)

    if user_exist:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with email provided exists already."
        )
    hashed_password = hash_password.create_hash(user.password)
    user.password = hashed_password
    await user_database.save(user)
    return {
        "message": "User created successfully"
    }


@user_router.post("/signin", response_model=TokenResponse)
async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:
    user_exist = await User.find_one(User.email == user.username)
    if not user_exist:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User with email does not exist."
        )
    if hash_password.verify_hash(user.password, user_exist.password):
        access_token = create_access_token(user_exist.email)
        return {
            "access_token": access_token,
            "token_type": "Bearer"
        }

    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid details passed."
    )


================================================
FILE: ch08/planner/auth/__init__.py
================================================


================================================
FILE: ch08/planner/auth/authenticate.py
================================================
from auth.jwt_handler import verify_access_token
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")


async def authenticate(token: str = Depends(oauth2_scheme)) -> str:
    if not token:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Sign in for access"
        )

    decoded_token = await verify_access_token(token)
    return decoded_token["user"]


================================================
FILE: ch08/planner/auth/hash_password.py
================================================
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


class HashPassword:
    def create_hash(self, password: str) -> str:
        return pwd_context.hash(password)

    def verify_hash(self, plain_password: str, hashed_password: str) -> bool:
        return pwd_context.verify(plain_password, hashed_password)


================================================
FILE: ch08/planner/auth/jwt_handler.py
================================================
import time
from datetime import datetime

from database.connection import Settings
from fastapi import HTTPException, status
from jose import jwt, JWTError
from models.users import User

settings = Settings()


def create_access_token(user: str) -> str:
    payload = {
        "user": user,
        "expires": time.time() + 3600
    }

    token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
    return token


async def verify_access_token(token: str) -> dict:
    try:
        data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])

        expire = data.get("expires")

        if expire is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="No access token supplied"
            )
        if datetime.utcnow() > datetime.utcfromtimestamp(expire):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Token expired!"
            )
        user_exist = await User.find_one(User.email == data["user"])
        if not user_exist:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Invalid token"
            )

        return data

    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid token"
        )


================================================
FILE: ch08/planner/database/__init__.py
================================================


================================================
FILE: ch08/planner/database/connection.py
================================================
from typing import Optional, Any, List

from beanie import init_beanie, PydanticObjectId
from models.events import Event
from models.users import User
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseSettings, BaseModel


class Settings(BaseSettings):
    DATABASE_URL: Optional[str] = None
    SECRET_KEY: Optional[str] = "default"

    async def initialize_database(self):
        client = AsyncIOMotorClient(self.DATABASE_URL)
        await init_beanie(database=client.get_default_database(),
                          document_models=[Event, User])

    class Config:
        env_file = ".env"


class Database:
    def __init__(self, model):
        self.model = model

    async def save(self, document):
        await document.create()
        return

    async def get(self, id: PydanticObjectId) -> bool:
        doc = await self.model.get(id)
        if doc:
            return doc
        return False

    async def get_all(self) -> List[Any]:
        docs = await self.model.find_all().to_list()
        return docs

    async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
        doc_id = id
        des_body = body.dict()

        des_body = {k: v for k, v in des_body.items() if v is not None}
        update_query = {"$set": {
            field: value for field, value in des_body.items()
        }}

        doc = await self.get(doc_id)
        if not doc:
            return False
        await doc.update(update_query)
        return doc

    async def delete(self, id: PydanticObjectId) -> bool:
        doc = await self.get(id)
        if not doc:
            return False
        await doc.delete()
        return True


================================================
FILE: ch08/planner/main.py
================================================
import uvicorn
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from fastapi.middleware.cors import CORSMiddleware

from database.connection import Settings
from routes.events import event_router
from routes.users import user_router

app = FastAPI()

settings = Settings()


# register origins

origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Register routes

app.include_router(user_router, prefix="/user")
app.include_router(event_router, prefix="/event")


@app.on_event("startup")
async def init_db():
    await settings.initialize_database()


@app.get("/")
async def home():
    return RedirectResponse(url="/event/")


if __name__ == '__main__':
    uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)


================================================
FILE: ch08/planner/models/__init__.py
================================================


================================================
FILE: ch08/planner/models/events.py
================================================
from typing import Optional, List

from beanie import Document
from pydantic import BaseModel


class Event(Document):
    creator: Optional[str]
    title: str
    image: str
    description: str
    tags: List[str]
    location: str

    class Config:
        schema_extra = {
            "example": {
                "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"
            }
        }

    class Settings:
        name = "events"


class EventUpdate(BaseModel):
    title: Optional[str]
    image: Optional[str]
    description: Optional[str]
    tags: Optional[List[str]]
    location: Optional[str]

    class Config:
        schema_extra = {
            "example": {
                "title": "FastAPI BookLaunch",
                "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"
            }
        }


================================================
FILE: ch08/planner/models/users.py
================================================
from beanie import Document

from pydantic import BaseModel, EmailStr


class User(Document):
    email: EmailStr
    password: str

    class Settings:
        name = "users"

    class Config:
        schema_extra = {
            "example": {
                "email": "fastapi@packt.com",
                "password": "strong!!!"
            }
        }


class TokenResponse(BaseModel):
    access_token: str
    token_type: str


================================================
FILE: ch08/planner/pytest.ini
================================================
[pytest]
asyncio_mode=auto


================================================
FILE: ch08/planner/requirements.txt
================================================
anyio==3.5.0
asgi-lifespan==1.0.1
asgiref==3.5.0
attrs==21.4.0
bcrypt==3.2.2
beanie==1.11.0
certifi==2021.10.8
cffi==1.15.0
charset-normalizer==2.0.12
click==8.0.4
coverage==6.3.3
cryptography==36.0.2
dnspython==2.2.1
ecdsa==0.17.0
email-validator==1.1.3
fastapi==0.77.1
h11==0.12.0
httpcore==0.14.7
httpx==0.22.0
idna==3.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.1.0
motor==3.0.0
multidict==6.0.2
packaging==21.3
passlib==1.7.4
pluggy==1.0.0
py==1.11.0
pyasn1==0.4.8
pycparser==2.21
pydantic==1.9.0
pymongo==4.1.1
pyparsing==3.0.9
pytest==7.1.2
pytest-asyncio==0.18.3
python-dotenv==0.20.0
python-jose==3.3.0
python-multipart==0.0.5
rfc3986==1.5.0
rsa==4.8
six==1.16.0
sniffio==1.2.0
SQLAlchemy==1.4.32
sqlalchemy2-stubs==0.0.2a20
sqlmodel==0.0.6
starlette==0.19.1
toml==0.10.2
tomli==2.0.1
typing_extensions==4.1.1
uvicorn==0.17.6
yarl==1.7.2


================================================
FILE: ch08/planner/routes/__init__.py
================================================


================================================
FILE: ch08/planner/routes/events.py
================================================
from typing import List

from auth.authenticate import authenticate
from beanie import PydanticObjectId
from database.connection import Database
from fastapi import APIRouter, Depends, HTTPException, status
from models.events import Event, EventUpdate

event_router = APIRouter(
    tags=["Events"]
)

event_database = Database(Event)


@event_router.get("/", response_model=List[Event])
async def retrieve_all_events() -> List[Event]:
    events = await event_database.get_all()
    return events


@event_router.get("/{id}", response_model=Event)
async def retrieve_event(id: PydanticObjectId) -> Event:
    event = await event_database.get(id)
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return event


@event_router.post("/new")
async def create_event(body: Event, user: str = Depends(authenticate)) -> dict:
    body.creator = user
    await event_database.save(body)
    return {
        "message": "Event created successfully"
    }


@event_router.put("/{id}", response_model=Event)
async def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:
    event = await event_database.get(id)
    if event.creator != user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Operation not allowed"
        )
    updated_event = await event_database.update(id, body)
    if not updated_event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return updated_event


@event_router.delete("/{id}")
async def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:
    event = await event_database.get(id)
    if event.creator != user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event not found"
        )
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    await event_database.delete(id)

    return {
        "message": "Event deleted successfully."
    }


================================================
FILE: ch08/planner/routes/users.py
================================================
from auth.hash_password import HashPassword
from auth.jwt_handler import create_access_token
from database.connection import Database
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from models.users import User, TokenResponse

user_router = APIRouter(
    tags=["User"],
)

user_database = Database(User)
hash_password = HashPassword()


@user_router.post("/signup")
async def sign_user_up(user: User) -> dict:
    user_exist = await User.find_one(User.email == user.email)

    if user_exist:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with email provided exists already."
        )
    hashed_password = hash_password.create_hash(user.password)
    user.password = hashed_password
    await user_database.save(user)
    return {
        "message": "User created successfully"
    }


@user_router.post("/signin", response_model=TokenResponse)
async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:
    user_exist = await User.find_one(User.email == user.username)
    if not user_exist:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User with email does not exist."
        )
    if hash_password.verify_hash(user.password, user_exist.password):
        access_token = create_access_token(user_exist.email)
        return {
            "access_token": access_token,
            "token_type": "Bearer"
        }

    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid details passed."
    )


================================================
FILE: ch08/planner/tests/conftest.py
================================================
import asyncio

import httpx
import pytest

from database.connection import Settings
from main import app
from models.events import Event
from models.users import User


@pytest.fixture(scope="session")
def event_loop():
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()


async def init_db():
    test_settings = Settings()
    test_settings.DATABASE_URL = "mongodb://localhost:27017/testdb"

    await test_settings.initialize_database()


@pytest.fixture(scope="session")
async def default_client():
    await init_db()
    async with httpx.AsyncClient(app=app, base_url="http://app") as client:
        yield client

        # Clean up resources
        await Event.find_all().delete()
        await User.find_all().delete()


================================================
FILE: ch08/planner/tests/test_arthmetic_operations.py
================================================
def add(a: int, b: int) -> int:
    return a + b


def subtract(a: int, b: int) -> int:
    return b - a


def multiply(a: int, b: int) -> int:
    return a * b


def divide(a: int, b: int) -> int:
    return b // a


def test_add() -> None:
    assert add(1, 1) == 2


def test_subtract() -> None:
    assert subtract(2, 5) == 3


def test_multiply() -> None:
    assert multiply(10, 10) == 100


def test_divide() -> None:
    assert divide(25, 100) == 4


================================================
FILE: ch08/planner/tests/test_fixture.py
================================================
import pytest

# Fixture is defined.
from models.events import EventUpdate


@pytest.fixture
def event() -> EventUpdate:
    return EventUpdate(
        title="FastAPI Book Launch",
        image="https://packt.com/fastapi.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"
    )


def test_event_name(event: EventUpdate) -> None:
    assert event.title == "FastAPI Book Launch"


================================================
FILE: ch08/planner/tests/test_login.py
================================================
import httpx
import pytest


@pytest.mark.asyncio
async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
    payload = {
        "email": "testuser@packt.com",
        "password": "testpassword",
    }

    headers = {
        "accept": "application/json",
        "Content-Type": "application/json"
    }

    test_response = {
        "message": "User created successfully"
    }

    response = await default_client.post("/user/signup", json=payload, headers=headers)

    assert response.status_code == 200
    assert response.json() == test_response


@pytest.mark.asyncio
async def test_sign_user_in(default_client: httpx.AsyncClient) -> None:
    payload = {
        "username": "testuser@packt.com",
        "password": "testpassword"
    }

    headers = {
        "accept": "application/json",
        "Content-Type": "application/x-www-form-urlencoded"
    }

    response = await default_client.post("/user/signin", data=payload, headers=headers)

    assert response.status_code == 200
    assert response.json()["token_type"] == "Bearer"


================================================
FILE: ch08/planner/tests/test_routes.py
================================================
import httpx
import pytest

from auth.jwt_handler import create_access_token
from models.events import Event


@pytest.fixture(scope="module")
async def access_token() -> str:
    return create_access_token("testuser@packt.com")


@pytest.fixture(scope="module")
async def mock_event() -> Event:
    new_event = Event(
        creator="testuser@packt.com",
        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"
    )

    await Event.insert_one(new_event)

    yield new_event


@pytest.mark.asyncio
async def test_get_events(default_client: httpx.AsyncClient, mock_event: Event) -> None:
    response = await default_client.get("/event/")

    assert response.status_code == 200
    assert response.json()[0]["_id"] == str(mock_event.id)


@pytest.mark.asyncio
async def test_get_event(default_client: httpx.AsyncClient, mock_event: Event) -> None:
    url = f"/event/{str(mock_event.id)}"
    response = await default_client.get(url)

    assert response.status_code == 200
    assert response.json()["creator"] == mock_event.creator
    assert response.json()["_id"] == str(mock_event.id)


@pytest.mark.asyncio
async def test_post_event(default_client: httpx.AsyncClient, access_token: str) -> None:
    payload = {
        "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",
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}"
    }

    test_response = {
        "message": "Event created successfully"
    }

    response = await default_client.post("/event/new", json=payload, headers=headers)

    assert response.status_code == 200
    assert response.json() == test_response


@pytest.mark.asyncio
async def test_get_events_count(default_client: httpx.AsyncClient) -> None:
    response = await default_client.get("/event/")

    events = response.json()

    assert response.status_code == 200
    assert len(events) == 2


@pytest.mark.asyncio
async def test_update_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
    test_payload = {
        "title": "Updated FastAPI event"
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}"
    }

    url = f"/event/{str(mock_event.id)}"

    response = await default_client.put(url, json=test_payload, headers=headers)

    assert response.status_code == 200
    assert response.json()["title"] == test_payload["title"]


@pytest.mark.asyncio
async def test_delete_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
    test_response = {
        "message": "Event deleted successfully."
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}"
    }

    url = f"/event/{mock_event.id}"

    response = await default_client.delete(url, headers=headers)

    assert response.status_code == 200
    assert response.json() == test_response


@pytest.mark.asyncio
async def test_get_event_again(default_client: httpx.AsyncClient, mock_event: Event) -> None:
    url = f"/event/{str(mock_event.id)}"
    response = await default_client.get(url)

    assert response.status_code == 404


================================================
FILE: ch09/planner/.dockerignore
================================================
venv
.env
.git

================================================
FILE: ch09/planner/Dockerfile
================================================
FROM python:3.10

WORKDIR /app

ADD requirements.txt /app/requirements.txt

RUN pip install --upgrade pip && pip install -r /app/requirements.txt

EXPOSE 8080

COPY ./ /app

CMD ["python", "main.py"]


================================================
FILE: ch09/planner/auth/__init__.py
================================================


================================================
FILE: ch09/planner/auth/authenticate.py
================================================
from auth.jwt_handler import verify_access_token
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")


async def authenticate(token: str = Depends(oauth2_scheme)) -> str:
    if not token:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Sign in for access"
        )

    decoded_token = await verify_access_token(token)
    return decoded_token["user"]


================================================
FILE: ch09/planner/auth/hash_password.py
================================================
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


class HashPassword:
    def create_hash(self, password: str) -> str:
        return pwd_context.hash(password)

    def verify_hash(self, plain_password: str, hashed_password: str) -> bool:
        return pwd_context.verify(plain_password, hashed_password)


================================================
FILE: ch09/planner/auth/jwt_handler.py
================================================
import time
from datetime import datetime

from database.connection import Settings
from fastapi import HTTPException, status
from jose import jwt, JWTError
from models.users import User

settings = Settings()


def create_access_token(user: str) -> str:
    payload = {
        "user": user,
        "expires": time.time() + 3600
    }

    token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
    return token


async def verify_access_token(token: str) -> dict:
    try:
        data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])

        expire = data.get("expires")

        if expire is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="No access token supplied"
            )
        if datetime.utcnow() > datetime.utcfromtimestamp(expire):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Token expired!"
            )
        user_exist = await User.find_one(User.email == data["user"])
        if not user_exist:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Invalid token"
            )

        return data

    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid token"
        )


================================================
FILE: ch09/planner/database/__init__.py
================================================


================================================
FILE: ch09/planner/database/connection.py
================================================
from typing import Optional, Any, List

from beanie import init_beanie, PydanticObjectId
from models.events import Event
from models.users import User
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseSettings, BaseModel


class Settings(BaseSettings):
    DATABASE_URL: Optional[str] = None
    SECRET_KEY: Optional[str] = "default"

    async def initialize_database(self):
        client = AsyncIOMotorClient(self.DATABASE_URL)
        await init_beanie(database=client.get_default_database(),
                          document_models=[Event, User])

    class Config:
        env_file = ".env"


class Database:
    def __init__(self, model):
        self.model = model

    async def save(self, document):
        await document.create()
        return

    async def get(self, id: PydanticObjectId) -> bool:
        doc = await self.model.get(id)
        if doc:
            return doc
        return False

    async def get_all(self) -> List[Any]:
        docs = await self.model.find_all().to_list()
        return docs

    async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
        doc_id = id
        des_body = body.dict()

        des_body = {k: v for k, v in des_body.items() if v is not None}
        update_query = {"$set": {
            field: value for field, value in des_body.items()
        }}

        doc = await self.get(doc_id)
        if not doc:
            return False
        await doc.update(update_query)
        return doc

    async def delete(self, id: PydanticObjectId) -> bool:
        doc = await self.get(id)
        if not doc:
            return False
        await doc.delete()
        return True


================================================
FILE: ch09/planner/docker-compose.yml
================================================
version: "3"

services:
  api:
    build: .
    image: event-planner-api:latest
    ports:
      - "8080:8080"
    env_file:
      - .env.prod

  database:
    image: mongo
    ports:
      - "27017"
    volumes:
      - data:/data/db

volumes:
  data:


================================================
FILE: ch09/planner/main.py
================================================
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from database.connection import Settings

from routes.users import user_router
from routes.events import event_router

import uvicorn

app = FastAPI()

settings = Settings()


# register origins

origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# Register routes

app.include_router(user_router,  prefix="/user")
app.include_router(event_router, prefix="/event")


@app.on_event("startup")
async def init_db():
    await settings.initialize_database()


@app.get("/")
async def home():
    return RedirectResponse(url="/event/")

if __name__ == '__main__':
    uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)

================================================
FILE: ch09/planner/models/__init__.py
================================================


================================================
FILE: ch09/planner/models/events.py
================================================
from typing import Optional, List

from beanie import Document
from pydantic import BaseModel


class Event(Document):
    creator: Optional[str]
    title: str
    image: str
    description: str
    tags: List[str]
    location: str

    class Config:
        schema_extra = {
            "example": {
                "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"
            }
        }

    class Settings:
        name = "events"


class EventUpdate(BaseModel):
    title: Optional[str]
    image: Optional[str]
    description: Optional[str]
    tags: Optional[List[str]]
    location: Optional[str]

    class Config:
        schema_extra = {
            "example": {
                "title": "FastAPI BookLaunch",
                "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"
            }
        }


================================================
FILE: ch09/planner/models/users.py
================================================
from beanie import Document

from pydantic import BaseModel, EmailStr


class User(Document):
    email: EmailStr
    password: str

    class Settings:
        name = "users"

    class Config:
        schema_extra = {
            "example": {
                "email": "fastapi@packt.com",
                "password": "strong!!!"
            }
        }


class TokenResponse(BaseModel):
    access_token: str
    token_type: str


================================================
FILE: ch09/planner/pytest.ini
================================================
[pytest]
asyncio_mode=auto


================================================
FILE: ch09/planner/requirements.txt
================================================
fastapi==0.78.0
bcrypt==3.2.2
beanie==1.11.1
email-validator==1.2.1
httpx==0.22.0
Jinja2==3.0.3
motor==2.5.1
passlib==1.7.4
pytest==7.1.2
python-multipart==.0.0.5
python-dotenv==0.20.0
python-jose==3.3.0
sqlmodel==0.0.6
uvicorn==0.17.6



================================================
FILE: ch09/planner/routes/__init__.py
================================================


================================================
FILE: ch09/planner/routes/events.py
================================================
from typing import List

from auth.authenticate import authenticate
from beanie import PydanticObjectId
from database.connection import Database
from fastapi import APIRouter, Depends, HTTPException, status
from models.events import Event, EventUpdate

event_router = APIRouter(
    tags=["Events"]
)

event_database = Database(Event)


@event_router.get("/", response_model=List[Event])
async def retrieve_all_events() -> List[Event]:
    events = await event_database.get_all()
    return events


@event_router.get("/{id}", response_model=Event)
async def retrieve_event(id: PydanticObjectId) -> Event:
    event = await event_database.get(id)
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return event


@event_router.post("/new")
async def create_event(body: Event, user: str = Depends(authenticate)) -> dict:
    body.creator = user
    await event_database.save(body)
    return {
        "message": "Event created successfully"
    }


@event_router.put("/{id}", response_model=Event)
async def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:
    event = await event_database.get(id)
    if event.creator != user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Operation not allowed"
        )
    updated_event = await event_database.update(id, body)
    if not updated_event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    return updated_event


@event_router.delete("/{id}")
async def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:
    event = await event_database.get(id)
    if event.creator != user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Operation not allowed"
        )
    if not event:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Event with supplied ID does not exist"
        )
    await event_database.delete(id)

    return {
        "message": "Event deleted successfully."
    }


================================================
FILE: ch09/planner/routes/users.py
================================================
from auth.hash_password import HashPassword
from auth.jwt_handler import create_access_token
from database.connection import Database
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from models.users import User, TokenResponse

user_router = APIRouter(
    tags=["User"],
)

user_database = Database(User)
hash_password = HashPassword()


@user_router.post("/signup")
async def sign_user_up(user: User) -> dict:
    user_exist = await User.find_one(User.email == user.email)

    if user_exist:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with email provided exists already."
        )
    hashed_password = hash_password.create_hash(user.password)
    user.password = hashed_password
    await user_database.save(user)
    return {
        "message": "User created successfully"
    }


@user_router.post("/signin", response_model=TokenResponse)
async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:
    user_exist = await User.find_one(User.email == user.username)
    if not user_exist:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User with email does not exist."
        )
    if hash_password.verify_hash(user.password, user_exist.password):
        access_token = create_access_token(user_exist.email)
        return {
            "access_token": access_token,
            "token_type": "Bearer"
        }

    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid details passed."
    )


================================================
FILE: ch09/planner/tests/conftest.py
================================================
import asyncio

import httpx
import pytest
from database.connection import Settings
from main import app
from models.events import Event
from models.users import User


@pytest.fixture(scope="session")
def event_loop():
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()


async def init_db():
    test_settings = Settings()
    test_settings.DATABASE_URL = "mongodb://localhost:27017/testdb"

    await test_settings.initialize_database()


@pytest.fixture(scope="session")
async def default_client():
    await init_db()
    async with httpx.AsyncClient(app=app, base_url="http://app") as client:
        yield client

        # Clean up resources
        await Event.find_all().delete()
        await User.find_all().delete()


================================================
FILE: ch09/planner/tests/test_arthmetic_operations.py
================================================
def add(a: int, b: int) -> int:
    return a + b


def subtract(a: int, b: int) -> int:
    return b - a


def multiply(a: int, b: int) -> int:
    return a * b


def divide(a: int, b: int) -> int:
    return b // a


def test_add() -> None:
    assert add(1, 1) == 2


def test_subtract() -> None:
    assert subtract(2, 5) == 3


def test_multiply() -> None:
    assert multiply(10, 10) == 100


def test_divide() -> None:
    assert divide(25, 100) == 4


================================================
FILE: ch09/planner/tests/test_fixture.py
================================================
import pytest

# Fixture is defined.
from models.events import EventUpdate


@pytest.fixture
def event() -> EventUpdate:
    return EventUpdate(
        title="FastAPI Book Launch",
        image="https://packt.com/fastapi.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"
    )


def test_event_name(event: EventUpdate) -> None:
    assert event.title == "FastAPI Book Launch"


================================================
FILE: ch09/planner/tests/test_login.py
================================================
import httpx
import pytest


@pytest.mark.asyncio
async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
    payload = {
        "email": "testuser@packt.com",
        "password": "testpassword",
    }

    headers = {
        "accept": "application/json",
        "Content-Type": "application/json"
    }

    test_response = {
        "message": "User created successfully"
    }

    response = await default_client.post("/user/signup", json=payload, headers=headers)

    assert response.status_code == 200
    assert response.json() == test_response


@pytest.mark.asyncio
async def test_sign_user_in(default_client: httpx.AsyncClient) -> None:
    payload = {
        "username": "testuser@packt.com",
        "password": "testpassword"
    }

    headers = {
        "accept": "application/json",
        "Content-Type": "application/x-www-form-urlencoded"
    }

    response = await default_client.post("/user/signin", data=payload, headers=headers)

    assert response.status_code == 200
    assert response.json()["token_type"] == "Bearer"


================================================
FILE: ch09/planner/tests/test_routes.py
================================================
import httpx
import pytest

from auth.jwt_handler import create_access_token
from models.events import Event


@pytest.fixture(scope="module")
async def access_token() -> str:
    return create_access_token("testuser@packt.com")


@pytest.fixture(scope="module")
async def mock_event() -> Event:
    new_event = Event(
        creator="testuser@packt.com",
        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"
    )

    await Event.insert_one(new_event)

    yield new_event


@pytest.mark.asyncio
async def test_get_events(default_client: httpx.AsyncClient, mock_event: Event) -> None:
    response = await default_client.get("/event/")

    assert response.status_code == 200
    assert response.json()[0]["_id"] == str(mock_event.id)


@pytest.mark.asyncio
async def test_get_event(default_client: httpx.AsyncClient, mock_event: Event) -> None:
    url = f"/event/{str(mock_event.id)}"
    response = await default_client.get(url)

    assert response.status_code == 200
    assert response.json()["creator"] == mock_event.creator
    assert response.json()["_id"] == str(mock_event.id)


@pytest.mark.asyncio
async def test_post_event(default_client: httpx.AsyncClient, access_token: str) -> None:
    payload = {
        "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",
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}"
    }

    test_response = {
        "message": "Event created successfully"
    }

    response = await default_client.post("/event/new", json=payload, headers=headers)

    assert response.status_code == 200
    assert response.json() == test_response


@pytest.mark.asyncio
async def test_get_events_count(default_client: httpx.AsyncClient) -> None:
    response = await default_client.get("/event/")

    events = response.json()

    assert response.status_code == 200
    assert len(events) == 2


@pytest.mark.asyncio
async def test_update_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
    test_payload = {
        "title": "Updated FastAPI event"
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}"
    }

    url = f"/event/{str(mock_event.id)}"

    response = await default_client.put(url, json=test_payload, headers=headers)

    assert response.status_code == 200
    assert response.json()["title"] == test_payload["title"]


@pytest.mark.asyncio
async def test_delete_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
    test_response = {
        "message": "Event deleted successfully."
    }

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {access_token}"
    }

    url = f"/event/{mock_event.id}"

    response = await default_client.delete(url, headers=headers)

    assert response.status_code == 200
    assert response.json() == test_response


@pytest.mark.asyncio
async def test_get_event_again(default_client: httpx.AsyncClient, mock_event: Event) -> None:
    url = f"/event/{str(mock_event.id)}"
    response = await default_client.get(url)

    assert response.status_code == 404
Download .txt
gitextract_vyvw9g85/

├── .gitignore
├── LICENSE
├── README.md
├── ch01/
│   ├── Dockerfile
│   ├── hello.py
│   └── todos/
│       ├── api.py
│       └── requirements.txt
├── ch02/
│   └── todos/
│       ├── api.py
│       ├── model.py
│       ├── requirements.txt
│       └── todo.py
├── ch03/
│   └── todos/
│       ├── api.py
│       ├── model.py
│       ├── requirements.txt
│       └── todo.py
├── ch04/
│   └── todos/
│       ├── api.py
│       ├── model.py
│       ├── requirements.txt
│       ├── templates/
│       │   ├── home.html
│       │   └── todo.html
│       └── todo.py
├── ch05/
│   └── planner/
│       ├── database/
│       │   └── __init__.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── requirements.txt
│       └── routes/
│           ├── __init__.py
│           ├── events.py
│           └── users.py
├── ch06/
│   └── planner/
│       ├── database/
│       │   ├── __init__.py
│       │   └── connection.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── requirements.txt
│       └── routes/
│           ├── __init__.py
│           ├── events.py
│           └── users.py
├── ch07/
│   └── planner/
│       ├── auth/
│       │   ├── __init__.py
│       │   ├── authenticate.py
│       │   ├── hash_password.py
│       │   └── jwt_handler.py
│       ├── database/
│       │   ├── __init__.py
│       │   └── connection.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── requirements.txt
│       └── routes/
│           ├── __init__.py
│           ├── events.py
│           └── users.py
├── ch08/
│   └── planner/
│       ├── auth/
│       │   ├── __init__.py
│       │   ├── authenticate.py
│       │   ├── hash_password.py
│       │   └── jwt_handler.py
│       ├── database/
│       │   ├── __init__.py
│       │   └── connection.py
│       ├── main.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       ├── pytest.ini
│       ├── requirements.txt
│       ├── routes/
│       │   ├── __init__.py
│       │   ├── events.py
│       │   └── users.py
│       └── tests/
│           ├── conftest.py
│           ├── test_arthmetic_operations.py
│           ├── test_fixture.py
│           ├── test_login.py
│           └── test_routes.py
└── ch09/
    └── planner/
        ├── .dockerignore
        ├── Dockerfile
        ├── auth/
        │   ├── __init__.py
        │   ├── authenticate.py
        │   ├── hash_password.py
        │   └── jwt_handler.py
        ├── database/
        │   ├── __init__.py
        │   └── connection.py
        ├── docker-compose.yml
        ├── main.py
        ├── models/
        │   ├── __init__.py
        │   ├── events.py
        │   └── users.py
        ├── pytest.ini
        ├── requirements.txt
        ├── routes/
        │   ├── __init__.py
        │   ├── events.py
        │   └── users.py
        └── tests/
            ├── conftest.py
            ├── test_arthmetic_operations.py
            ├── test_fixture.py
            ├── test_login.py
            └── test_routes.py
Download .txt
SYMBOL INDEX (229 symbols across 58 files)

FILE: ch01/todos/api.py
  function welcome (line 7) | async def welcome() -> dict:

FILE: ch02/todos/api.py
  function welcome (line 9) | async def welcome() -> dict:

FILE: ch02/todos/model.py
  class Todo (line 4) | class Todo(BaseModel):
    class Config (line 8) | class Config:
  class TodoItem (line 17) | class TodoItem(BaseModel):
    class Config (line 20) | class Config:

FILE: ch02/todos/todo.py
  function add_todo (line 11) | async def add_todo(todo: Todo) -> dict:
  function retrieve_todo (line 19) | async def retrieve_todo() -> dict:
  function get_single_todo (line 26) | async def get_single_todo(todo_id: int = Path(..., title="The ID of the ...
  function update_todo (line 38) | async def update_todo(todo_data: TodoItem, todo_id: int = Path(..., titl...
  function delete_single_todo (line 51) | async def delete_single_todo(todo_id: int) -> dict:
  function delete_all_todo (line 65) | async def delete_all_todo() -> dict:

FILE: ch03/todos/api.py
  function welcome (line 9) | async def welcome() -> dict:

FILE: ch03/todos/model.py
  class Todo (line 6) | class Todo(BaseModel):
    class Config (line 10) | class Config:
  class TodoItem (line 19) | class TodoItem(BaseModel):
    class Config (line 22) | class Config:
  class TodoItems (line 30) | class TodoItems(BaseModel):
    class Config (line 33) | class Config:

FILE: ch03/todos/todo.py
  function add_todo (line 10) | async def add_todo(todo: Todo) -> dict:
  function retrieve_todo (line 18) | async def retrieve_todo() -> dict:
  function get_single_todo (line 25) | async def get_single_todo(todo_id: int = Path(..., title="The ID of the ...
  function update_todo (line 38) | async def update_todo(todo_data: TodoItem, todo_id: int = Path(..., titl...
  function delete_single_todo (line 53) | async def delete_single_todo(todo_id: int) -> dict:
  function delete_all_todo (line 68) | async def delete_all_todo() -> dict:

FILE: ch04/todos/api.py
  function welcome (line 9) | async def welcome() -> dict:

FILE: ch04/todos/model.py
  class Todo (line 7) | class Todo(BaseModel):
    method as_form (line 12) | def as_form(
    class Config (line 18) | class Config:
  class TodoItem (line 27) | class TodoItem(BaseModel):
    class Config (line 30) | class Config:
  class TodoItems (line 38) | class TodoItems(BaseModel):
    class Config (line 41) | class Config:

FILE: ch04/todos/todo.py
  function add_todo (line 14) | async def add_todo(request: Request, todo: Todo = Depends(Todo.as_form)):
  function retrieve_todo (line 25) | async def retrieve_todo(request: Request):
  function get_single_todo (line 33) | async def get_single_todo(request: Request, todo_id: int = Path(..., tit...
  function update_todo (line 47) | async def update_todo(request: Request, todo_data: TodoItem,
  function delete_single_todo (line 63) | async def delete_single_todo(request: Request, todo_id: int) -> dict:
  function delete_all_todo (line 78) | async def delete_all_todo() -> dict:

FILE: ch05/planner/main.py
  function home (line 18) | async def home():

FILE: ch05/planner/models/events.py
  class Event (line 6) | class Event(BaseModel):
    class Config (line 14) | class Config:

FILE: ch05/planner/models/users.py
  class User (line 4) | class User(BaseModel):
    class Config (line 8) | class Config:
  class UserSignIn (line 17) | class UserSignIn(BaseModel):

FILE: ch05/planner/routes/events.py
  function retrieve_all_events (line 14) | async def retrieve_all_events() -> List[Event]:
  function retrieve_event (line 19) | async def retrieve_event(id: int) -> Event:
  function create_event (line 30) | async def create_event(body: Event = Body(...)) -> dict:
  function delete_event (line 38) | async def delete_event(id: int) -> dict:

FILE: ch05/planner/routes/users.py
  function sign_user_up (line 13) | async def sign_user_up(data: User) -> dict:
  function sign_user_in (line 28) | async def sign_user_in(user: UserSignIn) -> dict:

FILE: ch06/planner/database/connection.py
  class Settings (line 10) | class Settings(BaseSettings):
    method initialize_database (line 13) | async def initialize_database(self):
    class Config (line 18) | class Config:
  class Database (line 22) | class Database:
    method __init__ (line 23) | def __init__(self, model):
    method save (line 26) | async def save(self, document) -> None:
    method get (line 30) | async def get(self, id: PydanticObjectId) -> Any:
    method get_all (line 36) | async def get_all(self) -> List[Any]:
    method update (line 40) | async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
    method delete (line 55) | async def delete(self, id: PydanticObjectId) -> bool:

FILE: ch06/planner/main.py
  function init_db (line 20) | async def init_db():
  function home (line 25) | async def home():

FILE: ch06/planner/models/events.py
  class Event (line 7) | class Event(Document):
    class Config (line 14) | class Config:
    class Settings (line 25) | class Settings:
  class EventUpdate (line 29) | class EventUpdate(BaseModel):
    class Config (line 36) | class Config:

FILE: ch06/planner/models/users.py
  class User (line 6) | class User(Document):
    class Settings (line 10) | class Settings:
    class Config (line 13) | class Config:
  class UserSignIn (line 22) | class UserSignIn(BaseModel):

FILE: ch06/planner/routes/events.py
  function retrieve_all_events (line 16) | async def retrieve_all_events() -> List[Event]:
  function retrieve_event (line 22) | async def retrieve_event(id: PydanticObjectId) -> Event:
  function create_event (line 33) | async def create_event(body: Event) -> dict:
  function update_event (line 41) | async def update_event(id: PydanticObjectId, body: EventUpdate) -> Event:
  function delete_event (line 52) | async def delete_event(id: PydanticObjectId) -> dict:

FILE: ch06/planner/routes/users.py
  function sign_user_up (line 13) | async def sign_user_up(user: User) -> dict:
  function sign_user_in (line 28) | async def sign_user_in(user: UserSignIn) -> dict:

FILE: ch07/planner/auth/authenticate.py
  function authenticate (line 8) | async def authenticate(token: str = Depends(oauth2_scheme)) -> str:

FILE: ch07/planner/auth/hash_password.py
  class HashPassword (line 6) | class HashPassword:
    method create_hash (line 7) | def create_hash(self, password: str):
    method verify_hash (line 10) | def verify_hash(self, plain_password: str, hashed_password: str):

FILE: ch07/planner/auth/jwt_handler.py
  function create_access_token (line 11) | def create_access_token(user: str):
  function verify_access_token (line 21) | def verify_access_token(token: str):

FILE: ch07/planner/database/connection.py
  class Settings (line 10) | class Settings(BaseSettings):
    method initialize_database (line 14) | async def initialize_database(self):
    class Config (line 19) | class Config:
  class Database (line 23) | class Database:
    method __init__ (line 24) | def __init__(self, model):
    method save (line 27) | async def save(self, document):
    method get (line 31) | async def get(self, id: PydanticObjectId):
    method get_all (line 37) | async def get_all(self):
    method update (line 41) | async def update(self, id: PydanticObjectId, body: BaseModel):
    method delete (line 56) | async def delete(self, id: PydanticObjectId):

FILE: ch07/planner/main.py
  function init_db (line 33) | async def init_db():
  function home (line 38) | async def home():

FILE: ch07/planner/models/events.py
  class Event (line 7) | class Event(Document):
    class Config (line 15) | class Config:
    class Collection (line 26) | class Collection:
  class EventUpdate (line 30) | class EventUpdate(BaseModel):
    class Config (line 37) | class Config:

FILE: ch07/planner/models/users.py
  class User (line 6) | class User(Document):
    class Collection (line 10) | class Collection:
    class Config (line 13) | class Config:
  class TokenResponse (line 22) | class TokenResponse(BaseModel):

FILE: ch07/planner/routes/events.py
  function retrieve_all_events (line 17) | async def retrieve_all_events() -> List[Event]:
  function retrieve_event (line 23) | async def retrieve_event(id: PydanticObjectId) -> Event:
  function create_event (line 34) | async def create_event(body: Event, user: str = Depends(authenticate)) -...
  function update_event (line 43) | async def update_event(id: PydanticObjectId, body: EventUpdate, user: st...
  function delete_event (line 60) | async def delete_event(id: PydanticObjectId, user: str = Depends(authent...

FILE: ch07/planner/routes/users.py
  function sign_user_up (line 17) | async def sign_user_up(user: User) -> dict:
  function sign_user_in (line 34) | async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> d...

FILE: ch08/planner/auth/authenticate.py
  function authenticate (line 8) | async def authenticate(token: str = Depends(oauth2_scheme)) -> str:

FILE: ch08/planner/auth/hash_password.py
  class HashPassword (line 6) | class HashPassword:
    method create_hash (line 7) | def create_hash(self, password: str) -> str:
    method verify_hash (line 10) | def verify_hash(self, plain_password: str, hashed_password: str) -> bool:

FILE: ch08/planner/auth/jwt_handler.py
  function create_access_token (line 12) | def create_access_token(user: str) -> str:
  function verify_access_token (line 22) | async def verify_access_token(token: str) -> dict:

FILE: ch08/planner/database/connection.py
  class Settings (line 10) | class Settings(BaseSettings):
    method initialize_database (line 14) | async def initialize_database(self):
    class Config (line 19) | class Config:
  class Database (line 23) | class Database:
    method __init__ (line 24) | def __init__(self, model):
    method save (line 27) | async def save(self, document):
    method get (line 31) | async def get(self, id: PydanticObjectId) -> bool:
    method get_all (line 37) | async def get_all(self) -> List[Any]:
    method update (line 41) | async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
    method delete (line 56) | async def delete(self, id: PydanticObjectId) -> bool:

FILE: ch08/planner/main.py
  function init_db (line 34) | async def init_db():
  function home (line 39) | async def home():

FILE: ch08/planner/models/events.py
  class Event (line 7) | class Event(Document):
    class Config (line 15) | class Config:
    class Settings (line 26) | class Settings:
  class EventUpdate (line 30) | class EventUpdate(BaseModel):
    class Config (line 37) | class Config:

FILE: ch08/planner/models/users.py
  class User (line 6) | class User(Document):
    class Settings (line 10) | class Settings:
    class Config (line 13) | class Config:
  class TokenResponse (line 22) | class TokenResponse(BaseModel):

FILE: ch08/planner/routes/events.py
  function retrieve_all_events (line 17) | async def retrieve_all_events() -> List[Event]:
  function retrieve_event (line 23) | async def retrieve_event(id: PydanticObjectId) -> Event:
  function create_event (line 34) | async def create_event(body: Event, user: str = Depends(authenticate)) -...
  function update_event (line 43) | async def update_event(id: PydanticObjectId, body: EventUpdate, user: st...
  function delete_event (line 60) | async def delete_event(id: PydanticObjectId, user: str = Depends(authent...

FILE: ch08/planner/routes/users.py
  function sign_user_up (line 17) | async def sign_user_up(user: User) -> dict:
  function sign_user_in (line 34) | async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> d...

FILE: ch08/planner/tests/conftest.py
  function event_loop (line 13) | def event_loop():
  function init_db (line 19) | async def init_db():
  function default_client (line 27) | async def default_client():

FILE: ch08/planner/tests/test_arthmetic_operations.py
  function add (line 1) | def add(a: int, b: int) -> int:
  function subtract (line 5) | def subtract(a: int, b: int) -> int:
  function multiply (line 9) | def multiply(a: int, b: int) -> int:
  function divide (line 13) | def divide(a: int, b: int) -> int:
  function test_add (line 17) | def test_add() -> None:
  function test_subtract (line 21) | def test_subtract() -> None:
  function test_multiply (line 25) | def test_multiply() -> None:
  function test_divide (line 29) | def test_divide() -> None:

FILE: ch08/planner/tests/test_fixture.py
  function event (line 8) | def event() -> EventUpdate:
  function test_event_name (line 18) | def test_event_name(event: EventUpdate) -> None:

FILE: ch08/planner/tests/test_login.py
  function test_sign_new_user (line 6) | async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
  function test_sign_user_in (line 28) | async def test_sign_user_in(default_client: httpx.AsyncClient) -> None:

FILE: ch08/planner/tests/test_routes.py
  function access_token (line 9) | async def access_token() -> str:
  function mock_event (line 14) | async def mock_event() -> Event:
  function test_get_events (line 30) | async def test_get_events(default_client: httpx.AsyncClient, mock_event:...
  function test_get_event (line 38) | async def test_get_event(default_client: httpx.AsyncClient, mock_event: ...
  function test_post_event (line 48) | async def test_post_event(default_client: httpx.AsyncClient, access_toke...
  function test_get_events_count (line 78) | async def test_get_events_count(default_client: httpx.AsyncClient) -> None:
  function test_update_event (line 88) | async def test_update_event(default_client: httpx.AsyncClient, mock_even...
  function test_delete_event (line 107) | async def test_delete_event(default_client: httpx.AsyncClient, mock_even...
  function test_get_event_again (line 126) | async def test_get_event_again(default_client: httpx.AsyncClient, mock_e...

FILE: ch09/planner/auth/authenticate.py
  function authenticate (line 8) | async def authenticate(token: str = Depends(oauth2_scheme)) -> str:

FILE: ch09/planner/auth/hash_password.py
  class HashPassword (line 6) | class HashPassword:
    method create_hash (line 7) | def create_hash(self, password: str) -> str:
    method verify_hash (line 10) | def verify_hash(self, plain_password: str, hashed_password: str) -> bool:

FILE: ch09/planner/auth/jwt_handler.py
  function create_access_token (line 12) | def create_access_token(user: str) -> str:
  function verify_access_token (line 22) | async def verify_access_token(token: str) -> dict:

FILE: ch09/planner/database/connection.py
  class Settings (line 10) | class Settings(BaseSettings):
    method initialize_database (line 14) | async def initialize_database(self):
    class Config (line 19) | class Config:
  class Database (line 23) | class Database:
    method __init__ (line 24) | def __init__(self, model):
    method save (line 27) | async def save(self, document):
    method get (line 31) | async def get(self, id: PydanticObjectId) -> bool:
    method get_all (line 37) | async def get_all(self) -> List[Any]:
    method update (line 41) | async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
    method delete (line 56) | async def delete(self, id: PydanticObjectId) -> bool:

FILE: ch09/planner/main.py
  function init_db (line 36) | async def init_db():
  function home (line 41) | async def home():

FILE: ch09/planner/models/events.py
  class Event (line 7) | class Event(Document):
    class Config (line 15) | class Config:
    class Settings (line 26) | class Settings:
  class EventUpdate (line 30) | class EventUpdate(BaseModel):
    class Config (line 37) | class Config:

FILE: ch09/planner/models/users.py
  class User (line 6) | class User(Document):
    class Settings (line 10) | class Settings:
    class Config (line 13) | class Config:
  class TokenResponse (line 22) | class TokenResponse(BaseModel):

FILE: ch09/planner/routes/events.py
  function retrieve_all_events (line 17) | async def retrieve_all_events() -> List[Event]:
  function retrieve_event (line 23) | async def retrieve_event(id: PydanticObjectId) -> Event:
  function create_event (line 34) | async def create_event(body: Event, user: str = Depends(authenticate)) -...
  function update_event (line 43) | async def update_event(id: PydanticObjectId, body: EventUpdate, user: st...
  function delete_event (line 60) | async def delete_event(id: PydanticObjectId, user: str = Depends(authent...

FILE: ch09/planner/routes/users.py
  function sign_user_up (line 17) | async def sign_user_up(user: User) -> dict:
  function sign_user_in (line 34) | async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> d...

FILE: ch09/planner/tests/conftest.py
  function event_loop (line 12) | def event_loop():
  function init_db (line 18) | async def init_db():
  function default_client (line 26) | async def default_client():

FILE: ch09/planner/tests/test_arthmetic_operations.py
  function add (line 1) | def add(a: int, b: int) -> int:
  function subtract (line 5) | def subtract(a: int, b: int) -> int:
  function multiply (line 9) | def multiply(a: int, b: int) -> int:
  function divide (line 13) | def divide(a: int, b: int) -> int:
  function test_add (line 17) | def test_add() -> None:
  function test_subtract (line 21) | def test_subtract() -> None:
  function test_multiply (line 25) | def test_multiply() -> None:
  function test_divide (line 29) | def test_divide() -> None:

FILE: ch09/planner/tests/test_fixture.py
  function event (line 8) | def event() -> EventUpdate:
  function test_event_name (line 18) | def test_event_name(event: EventUpdate) -> None:

FILE: ch09/planner/tests/test_login.py
  function test_sign_new_user (line 6) | async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
  function test_sign_user_in (line 28) | async def test_sign_user_in(default_client: httpx.AsyncClient) -> None:

FILE: ch09/planner/tests/test_routes.py
  function access_token (line 9) | async def access_token() -> str:
  function mock_event (line 14) | async def mock_event() -> Event:
  function test_get_events (line 30) | async def test_get_events(default_client: httpx.AsyncClient, mock_event:...
  function test_get_event (line 38) | async def test_get_event(default_client: httpx.AsyncClient, mock_event: ...
  function test_post_event (line 48) | async def test_post_event(default_client: httpx.AsyncClient, access_toke...
  function test_get_events_count (line 78) | async def test_get_events_count(default_client: httpx.AsyncClient) -> None:
  function test_update_event (line 88) | async def test_update_event(default_client: httpx.AsyncClient, mock_even...
  function test_delete_event (line 107) | async def test_delete_event(default_client: httpx.AsyncClient, mock_even...
  function test_get_event_again (line 126) | async def test_get_event_again(default_client: httpx.AsyncClient, mock_e...
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (87K chars).
[
  {
    "path": ".gitignore",
    "chars": 16,
    "preview": ".idea\nvenv\n.env\n"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2021 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 5744,
    "preview": "\n\n\n# Building Python Web APIs with FastAPI\n\n<a href=\"https://www.packtpub.com/product/building-python-web-apis-with-fast"
  },
  {
    "path": "ch01/Dockerfile",
    "chars": 229,
    "preview": "FROM PYTHON:3.8\n# Set working directory to /usr/src/app\nWORKDIR /usr/src/app\n# Copy the contents of the current local di"
  },
  {
    "path": "ch01/hello.py",
    "chars": 16,
    "preview": "print(\"Hello!\")\n"
  },
  {
    "path": "ch01/todos/api.py",
    "chars": 142,
    "preview": "from fastapi import FastAPI\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n    return {\n        \"message\""
  },
  {
    "path": "ch01/todos/requirements.txt",
    "chars": 16,
    "preview": "fastapi\nuvicorn\n"
  },
  {
    "path": "ch02/todos/api.py",
    "chars": 206,
    "preview": "from fastapi import FastAPI\n\nfrom todo import todo_router\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n"
  },
  {
    "path": "ch02/todos/model.py",
    "chars": 438,
    "preview": "from pydantic import BaseModel\n\n\nclass Todo(BaseModel):\n    id: int\n    item: str\n\n    class Config:\n        schema_extr"
  },
  {
    "path": "ch02/todos/requirements.txt",
    "chars": 32,
    "preview": "fastapi==0.70.0\nuvicorn==0.15.0\n"
  },
  {
    "path": "ch02/todos/todo.py",
    "chars": 1701,
    "preview": "from fastapi import APIRouter, Path\n\nfrom model import Todo, TodoItem\n\ntodo_router = APIRouter()\n\ntodo_list = []\n\n\n@todo"
  },
  {
    "path": "ch03/todos/api.py",
    "chars": 206,
    "preview": "from fastapi import FastAPI\n\nfrom todo import todo_router\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n"
  },
  {
    "path": "ch03/todos/model.py",
    "chars": 850,
    "preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass Todo(BaseModel):\n    id: int\n    item: str\n\n    class Co"
  },
  {
    "path": "ch03/todos/requirements.txt",
    "chars": 32,
    "preview": "fastapi==0.70.0\nuvicorn==0.15.0\n"
  },
  {
    "path": "ch03/todos/todo.py",
    "chars": 1943,
    "preview": "from fastapi import APIRouter, Path, HTTPException, status\nfrom model import Todo, TodoItem, TodoItems\n\ntodo_router = AP"
  },
  {
    "path": "ch04/todos/api.py",
    "chars": 206,
    "preview": "from fastapi import FastAPI\n\nfrom todo import todo_router\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n"
  },
  {
    "path": "ch04/todos/model.py",
    "chars": 1018,
    "preview": "from typing import List, Optional\n\nfrom fastapi import Form\nfrom pydantic import BaseModel\n\n\nclass Todo(BaseModel):\n    "
  },
  {
    "path": "ch04/todos/requirements.txt",
    "chars": 65,
    "preview": "fastapi==0.70.0\nuvicorn==0.15.0\njinja2 == 3.1.2\npython-multipart\n"
  },
  {
    "path": "ch04/todos/templates/home.html",
    "chars": 988,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta content=\"IE=edge\" http-equiv=\"X-UA-Compatib"
  },
  {
    "path": "ch04/todos/templates/todo.html",
    "chars": 1525,
    "preview": "{% extends \"home.html\" %}\n\n{% block todo_container %}\n<main class=\"container\">\n    <hr>\n\n    <section class=\"container-f"
  },
  {
    "path": "ch04/todos/todo.py",
    "chars": 2524,
    "preview": "from fastapi import APIRouter, Path, HTTPException, status, Request, Depends\nfrom fastapi.templating import Jinja2Templa"
  },
  {
    "path": "ch05/planner/database/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch05/planner/main.py",
    "chars": 476,
    "preview": "from fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\n\nfrom routes.users import user_router\nfrom ro"
  },
  {
    "path": "ch05/planner/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch05/planner/models/events.py",
    "chars": 646,
    "preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass Event(BaseModel):\n    id: int\n    title: str\n    image: "
  },
  {
    "path": "ch05/planner/models/users.py",
    "chars": 490,
    "preview": "from pydantic import BaseModel, EmailStr\n\n\nclass User(BaseModel):\n    email: EmailStr\n    password: str\n\n    class Confi"
  },
  {
    "path": "ch05/planner/requirements.txt",
    "chars": 519,
    "preview": "anyio==3.5.0\nasgiref==3.5.0\nbcrypt==3.2.0\nbeanie==1.10.4\ncffi==1.15.0\nclick==8.0.4\ndnspython==2.2.0\nemail-validator==1.1"
  },
  {
    "path": "ch05/planner/routes/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch05/planner/routes/events.py",
    "chars": 1162,
    "preview": "from typing import List\n\nfrom fastapi import APIRouter, Body, HTTPException, status\nfrom models.events import Event\n\neve"
  },
  {
    "path": "ch05/planner/routes/users.py",
    "chars": 1004,
    "preview": "from fastapi import APIRouter, HTTPException, status\n\nfrom models.users import User, UserSignIn\n\nuser_router = APIRouter"
  },
  {
    "path": "ch06/planner/database/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch06/planner/database/connection.py",
    "chars": 1652,
    "preview": "from typing import Any, List, Optional\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event"
  },
  {
    "path": "ch06/planner/main.py",
    "chars": 629,
    "preview": "import uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\n\nfrom database.connection impo"
  },
  {
    "path": "ch06/planner/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch06/planner/models/events.py",
    "chars": 1348,
    "preview": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n "
  },
  {
    "path": "ch06/planner/models/users.py",
    "chars": 425,
    "preview": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    pa"
  },
  {
    "path": "ch06/planner/requirements.txt",
    "chars": 519,
    "preview": "anyio==3.5.0\nasgiref==3.5.0\nbcrypt==3.2.0\nbeanie==1.10.4\ncffi==1.15.0\nclick==8.0.4\ndnspython==2.2.0\nemail-validator==1.1"
  },
  {
    "path": "ch06/planner/routes/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch06/planner/routes/events.py",
    "chars": 1704,
    "preview": "from typing import List\n\nfrom beanie import PydanticObjectId\nfrom database.connection import Database\nfrom fastapi impor"
  },
  {
    "path": "ch06/planner/routes/users.py",
    "chars": 1184,
    "preview": "from database.connection import Database\nfrom fastapi import APIRouter, HTTPException, status\nfrom models.users import U"
  },
  {
    "path": "ch07/planner/auth/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch07/planner/auth/authenticate.py",
    "chars": 512,
    "preview": "from auth.jwt_handler import verify_access_token\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.securit"
  },
  {
    "path": "ch07/planner/auth/hash_password.py",
    "chars": 352,
    "preview": "from passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass Hash"
  },
  {
    "path": "ch07/planner/auth/jwt_handler.py",
    "chars": 1111,
    "preview": "import time\nfrom datetime import datetime\n\nfrom database.connection import Settings\nfrom fastapi import HTTPException, s"
  },
  {
    "path": "ch07/planner/database/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch07/planner/database/connection.py",
    "chars": 1635,
    "preview": "from typing import Optional\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event\nfrom model"
  },
  {
    "path": "ch07/planner/main.py",
    "chars": 865,
    "preview": "import uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\nfrom fastapi.middleware.cors i"
  },
  {
    "path": "ch07/planner/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch07/planner/models/events.py",
    "chars": 1377,
    "preview": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n "
  },
  {
    "path": "ch07/planner/models/users.py",
    "chars": 433,
    "preview": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    pa"
  },
  {
    "path": "ch07/planner/requirements.txt",
    "chars": 583,
    "preview": "anyio==3.5.0\nasgiref==3.5.0\nbcrypt==3.2.0\nbeanie==1.10.4\ncffi==1.15.0\nclick==8.0.4\ncryptography==36.0.2\ndnspython==2.2.0"
  },
  {
    "path": "ch07/planner/routes/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch07/planner/routes/events.py",
    "chars": 2276,
    "preview": "from typing import List\n\nfrom auth.authenticate import authenticate\nfrom beanie import PydanticObjectId\nfrom database.co"
  },
  {
    "path": "ch07/planner/routes/users.py",
    "chars": 1643,
    "preview": "from auth.hash_password import HashPassword\nfrom auth.jwt_handler import create_access_token\nfrom database.connection im"
  },
  {
    "path": "ch08/planner/auth/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch08/planner/auth/authenticate.py",
    "chars": 518,
    "preview": "from auth.jwt_handler import verify_access_token\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.securit"
  },
  {
    "path": "ch08/planner/auth/hash_password.py",
    "chars": 367,
    "preview": "from passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass Hash"
  },
  {
    "path": "ch08/planner/auth/jwt_handler.py",
    "chars": 1402,
    "preview": "import time\nfrom datetime import datetime\n\nfrom database.connection import Settings\nfrom fastapi import HTTPException, s"
  },
  {
    "path": "ch08/planner/database/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch08/planner/database/connection.py",
    "chars": 1687,
    "preview": "from typing import Optional, Any, List\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event"
  },
  {
    "path": "ch08/planner/main.py",
    "chars": 866,
    "preview": "import uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\nfrom fastapi.middleware.cors i"
  },
  {
    "path": "ch08/planner/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch08/planner/models/events.py",
    "chars": 1376,
    "preview": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n "
  },
  {
    "path": "ch08/planner/models/users.py",
    "chars": 431,
    "preview": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    pa"
  },
  {
    "path": "ch08/planner/pytest.ini",
    "chars": 27,
    "preview": "[pytest]\nasyncio_mode=auto\n"
  },
  {
    "path": "ch08/planner/requirements.txt",
    "chars": 850,
    "preview": "anyio==3.5.0\nasgi-lifespan==1.0.1\nasgiref==3.5.0\nattrs==21.4.0\nbcrypt==3.2.2\nbeanie==1.11.0\ncertifi==2021.10.8\ncffi==1.1"
  },
  {
    "path": "ch08/planner/routes/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch08/planner/routes/events.py",
    "chars": 2282,
    "preview": "from typing import List\n\nfrom auth.authenticate import authenticate\nfrom beanie import PydanticObjectId\nfrom database.co"
  },
  {
    "path": "ch08/planner/routes/users.py",
    "chars": 1643,
    "preview": "from auth.hash_password import HashPassword\nfrom auth.jwt_handler import create_access_token\nfrom database.connection im"
  },
  {
    "path": "ch08/planner/tests/conftest.py",
    "chars": 747,
    "preview": "import asyncio\n\nimport httpx\nimport pytest\n\nfrom database.connection import Settings\nfrom main import app\nfrom models.ev"
  },
  {
    "path": "ch08/planner/tests/test_arthmetic_operations.py",
    "chars": 457,
    "preview": "def add(a: int, b: int) -> int:\n    return a + b\n\n\ndef subtract(a: int, b: int) -> int:\n    return b - a\n\n\ndef multiply("
  },
  {
    "path": "ch08/planner/tests/test_fixture.py",
    "chars": 559,
    "preview": "import pytest\n\n# Fixture is defined.\nfrom models.events import EventUpdate\n\n\n@pytest.fixture\ndef event() -> EventUpdate:"
  },
  {
    "path": "ch08/planner/tests/test_login.py",
    "chars": 1070,
    "preview": "import httpx\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_sign_new_user(default_client: httpx.AsyncClient) -> Non"
  },
  {
    "path": "ch08/planner/tests/test_routes.py",
    "chars": 3789,
    "preview": "import httpx\nimport pytest\n\nfrom auth.jwt_handler import create_access_token\nfrom models.events import Event\n\n\n@pytest.f"
  },
  {
    "path": "ch09/planner/.dockerignore",
    "chars": 14,
    "preview": "venv\n.env\n.git"
  },
  {
    "path": "ch09/planner/Dockerfile",
    "chars": 200,
    "preview": "FROM python:3.10\n\nWORKDIR /app\n\nADD requirements.txt /app/requirements.txt\n\nRUN pip install --upgrade pip && pip install"
  },
  {
    "path": "ch09/planner/auth/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch09/planner/auth/authenticate.py",
    "chars": 518,
    "preview": "from auth.jwt_handler import verify_access_token\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.securit"
  },
  {
    "path": "ch09/planner/auth/hash_password.py",
    "chars": 367,
    "preview": "from passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass Hash"
  },
  {
    "path": "ch09/planner/auth/jwt_handler.py",
    "chars": 1402,
    "preview": "import time\nfrom datetime import datetime\n\nfrom database.connection import Settings\nfrom fastapi import HTTPException, s"
  },
  {
    "path": "ch09/planner/database/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch09/planner/database/connection.py",
    "chars": 1687,
    "preview": "from typing import Optional, Any, List\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event"
  },
  {
    "path": "ch09/planner/docker-compose.yml",
    "chars": 253,
    "preview": "version: \"3\"\n\nservices:\n  api:\n    build: .\n    image: event-planner-api:latest\n    ports:\n      - \"8080:8080\"\n    env_f"
  },
  {
    "path": "ch09/planner/main.py",
    "chars": 867,
    "preview": "from fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import RedirectRes"
  },
  {
    "path": "ch09/planner/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch09/planner/models/events.py",
    "chars": 1376,
    "preview": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n "
  },
  {
    "path": "ch09/planner/models/users.py",
    "chars": 431,
    "preview": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    pa"
  },
  {
    "path": "ch09/planner/pytest.ini",
    "chars": 27,
    "preview": "[pytest]\nasyncio_mode=auto\n"
  },
  {
    "path": "ch09/planner/requirements.txt",
    "chars": 237,
    "preview": "fastapi==0.78.0\nbcrypt==3.2.2\nbeanie==1.11.1\nemail-validator==1.2.1\nhttpx==0.22.0\nJinja2==3.0.3\nmotor==2.5.1\npasslib==1."
  },
  {
    "path": "ch09/planner/routes/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ch09/planner/routes/events.py",
    "chars": 2290,
    "preview": "from typing import List\n\nfrom auth.authenticate import authenticate\nfrom beanie import PydanticObjectId\nfrom database.co"
  },
  {
    "path": "ch09/planner/routes/users.py",
    "chars": 1643,
    "preview": "from auth.hash_password import HashPassword\nfrom auth.jwt_handler import create_access_token\nfrom database.connection im"
  },
  {
    "path": "ch09/planner/tests/conftest.py",
    "chars": 746,
    "preview": "import asyncio\n\nimport httpx\nimport pytest\nfrom database.connection import Settings\nfrom main import app\nfrom models.eve"
  },
  {
    "path": "ch09/planner/tests/test_arthmetic_operations.py",
    "chars": 457,
    "preview": "def add(a: int, b: int) -> int:\n    return a + b\n\n\ndef subtract(a: int, b: int) -> int:\n    return b - a\n\n\ndef multiply("
  },
  {
    "path": "ch09/planner/tests/test_fixture.py",
    "chars": 559,
    "preview": "import pytest\n\n# Fixture is defined.\nfrom models.events import EventUpdate\n\n\n@pytest.fixture\ndef event() -> EventUpdate:"
  },
  {
    "path": "ch09/planner/tests/test_login.py",
    "chars": 1070,
    "preview": "import httpx\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_sign_new_user(default_client: httpx.AsyncClient) -> Non"
  },
  {
    "path": "ch09/planner/tests/test_routes.py",
    "chars": 3789,
    "preview": "import httpx\nimport pytest\n\nfrom auth.jwt_handler import create_access_token\nfrom models.events import Event\n\n\n@pytest.f"
  }
]

About this extraction

This page contains the full source code of the PacktPublishing/Building-Python-Web-APIs-with-FastAPI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (75.7 KB), approximately 21.0k tokens, and a symbol index with 229 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!