[
  {
    "path": ".gitignore",
    "content": ".idea\nvenv\n.env\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n\n\n# Building Python Web APIs with FastAPI\n\n<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>\n\nThis 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.\n\n**A fast-paced guide to building high-performance, robust web APIs with very little boilerplate code**\n\n## What is this book about?\nRESTful 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.\n\nThis book covers the following exciting features:\n* Set up a FastAPI application that is fully functional and secure\n* Understand how to handle errors from requests and send proper responses in FastAPI\n* Integrate and connect your application to a SQL and NoSQL (MongoDB) database\n* Perform CRUD operations using SQL and FastAPI\n* Manage concurrency in FastAPI applications\n\nIf you feel this book is for you, get your [copy](https://www.amazon.com/dp/1801076634) today!\n\n<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\" \nalt=\"https://www.packtpub.com/\" border=\"5\" /></a>\n\n\n## Instructions and Navigations\nAll of the code is organized into folders. For example, Chapter05.\n\nThe code will look like the following:\n```\nfrom pydantic import BaseModel\nfrom typing import List\nclass Event(BaseModel):\n   id: int\n   title: str\n   image: str\n   description: str\n   tags: List[str]\n   location: str  \n```\n\n**Following is what you need for this book:**\n\nThis 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.\n\nWith the following software and hardware list you can run all code files present in the book (Chapter 1-09).\n\n### Software and Hardware List\n\n| Chapter  | Software required                   | OS required                        |\n| -------- | ------------------------------------| -----------------------------------|\n| 1-09     | Python 3.10                         | Windows, Mac OS X, and Linux       |\n| 1-09     | Git 2.36.0                          | Windows, Mac OS X, and Linux       |\n\n\n\nWe 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).\n\n\n### Related products <Other books you may enjoy>\n* 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)\n\n* 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)\n\n## Errata \n * **Use port 8000 as per the GitHub repository examples.**\n * Page 14 (Code Snippet 1 line 1):  **FROM PYTHON:3.8** _should be_ **FROM python:3.8**\n * Page 15 (last line):  **FROM PYTHON:3.8** _should be_ **(venv)$ uvicorn api:app --port 8080 --reload**\n * 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.\n * Page 24, (Code snippet 2 Line 1):\n       ```from Pydantic import BaseModel```\n       _should be_\n       ```from pydantic import BaseModel```\n    * Page 24, (Code snippet 2 Line 2):\n       ```class Todo(BaseMode):```\n       _should be_\n       ```class Todo(BaseModel):```\n * 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**\n \n## Get to Know the Author\n**Abdulazeez Abdulazeez Adeshina**\nis 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.\n\n\n\n\n### Download a free PDF\n\n <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>\n<p align=\"center\"> <a href=\"https://packt.link/free-ebook/9781801076630\">https://packt.link/free-ebook/9781801076630 </a> </p>\n"
  },
  {
    "path": "ch01/Dockerfile",
    "content": "FROM PYTHON:3.8\n# Set working directory to /usr/src/app\nWORKDIR /usr/src/app\n# Copy the contents of the current local directory into the container’s working directory\nADD . /usr/src/app\n# Run a command\nCMD [“python”, “hello.py”]\n"
  },
  {
    "path": "ch01/hello.py",
    "content": "print(\"Hello!\")\n"
  },
  {
    "path": "ch01/todos/api.py",
    "content": "from fastapi import FastAPI\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n    return {\n        \"message\": \"Hello World\"\n    }\n"
  },
  {
    "path": "ch01/todos/requirements.txt",
    "content": "fastapi\nuvicorn\n"
  },
  {
    "path": "ch02/todos/api.py",
    "content": "from fastapi import FastAPI\n\nfrom todo import todo_router\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n    return {\n        \"message\": \"Hello World\"\n    }\n\n\napp.include_router(todo_router)\n"
  },
  {
    "path": "ch02/todos/model.py",
    "content": "from pydantic import BaseModel\n\n\nclass Todo(BaseModel):\n    id: int\n    item: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"id\": 1,\n                \"item\": \"Example Schema!\"\n            }\n        }\n\n\nclass TodoItem(BaseModel):\n    item: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"item\": \"Read the next chapter of the book\"\n            }\n        }\n"
  },
  {
    "path": "ch02/todos/requirements.txt",
    "content": "fastapi==0.70.0\nuvicorn==0.15.0\n"
  },
  {
    "path": "ch02/todos/todo.py",
    "content": "from fastapi import APIRouter, Path\n\nfrom model import Todo, TodoItem\n\ntodo_router = APIRouter()\n\ntodo_list = []\n\n\n@todo_router.post(\"/todo\")\nasync def add_todo(todo: Todo) -> dict:\n    todo_list.append(todo)\n    return {\n        \"message\": \"Todo added successfully.\"\n    }\n\n\n@todo_router.get(\"/todo\")\nasync def retrieve_todo() -> dict:\n    return {\n        \"todos\": todo_list\n    }\n\n\n@todo_router.get(\"/todo/{todo_id}\")\nasync def get_single_todo(todo_id: int = Path(..., title=\"The ID of the todo to retrieve.\")) -> dict:\n    for todo in todo_list:\n        if todo.id == todo_id:\n            return {\n                \"todo\": todo\n            }\n    return {\n        \"message\": \"Todo with supplied ID doesn't exist.\"\n    }\n\n\n@todo_router.put(\"/todo/{todo_id}\")\nasync def update_todo(todo_data: TodoItem, todo_id: int = Path(..., title=\"The ID of the todo to be updated.\")) -> dict:\n    for todo in todo_list:\n        if todo.id == todo_id:\n            todo.item = todo_data.item\n            return {\n                \"message\": \"Todo updated successfully.\"\n            }\n    return {\n        \"message\": \"Todo with supplied ID doesn't exist.\"\n    }\n\n\n@todo_router.delete(\"/todo/{todo_id}\")\nasync def delete_single_todo(todo_id: int) -> dict:\n    for index in range(len(todo_list)):\n        todo = todo_list[index]\n        if todo.id == todo_id:\n            todo_list.pop(index)\n            return {\n                \"message\": \"Todo deleted successfully.\"\n            }\n    return {\n        \"message\": \"Todo with supplied ID doesn't exist.\"\n    }\n\n\n@todo_router.delete(\"/todo\")\nasync def delete_all_todo() -> dict:\n    todo_list.clear()\n    return {\n        \"message\": \"Todos deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch03/todos/api.py",
    "content": "from fastapi import FastAPI\n\nfrom todo import todo_router\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n    return {\n        \"message\": \"Hello World\"\n    }\n\n\napp.include_router(todo_router)\n"
  },
  {
    "path": "ch03/todos/model.py",
    "content": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass Todo(BaseModel):\n    id: int\n    item: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"id\": 1,\n                \"item\": \"Example schema!\"\n            }\n        }\n\n\nclass TodoItem(BaseModel):\n    item: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"item\": \"Read the next chapter of the book\"\n            }\n        }\n\n\nclass TodoItems(BaseModel):\n    todos: List[TodoItem]\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"todos\": [\n                    {\n                        \"item\": \"Example schema 1!\"\n                    },\n                    {\n                        \"item\": \"Example schema 2!\"\n                    }\n                ]\n            }\n        }\n"
  },
  {
    "path": "ch03/todos/requirements.txt",
    "content": "fastapi==0.70.0\nuvicorn==0.15.0\n"
  },
  {
    "path": "ch03/todos/todo.py",
    "content": "from fastapi import APIRouter, Path, HTTPException, status\nfrom model import Todo, TodoItem, TodoItems\n\ntodo_router = APIRouter()\n\ntodo_list = []\n\n\n@todo_router.post(\"/todo\", status_code=201)\nasync def add_todo(todo: Todo) -> dict:\n    todo_list.append(todo)\n    return {\n        \"message\": \"Todo added successfully.\"\n    }\n\n\n@todo_router.get(\"/todo\", response_model=TodoItems)\nasync def retrieve_todo() -> dict:\n    return {\n        \"todos\": todo_list\n    }\n\n\n@todo_router.get(\"/todo/{todo_id}\")\nasync def get_single_todo(todo_id: int = Path(..., title=\"The ID of the todo to retrieve.\")) -> dict:\n    for todo in todo_list:\n        if todo.id == todo_id:\n            return {\n                \"todo\": todo\n            }\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Todo with supplied ID doesn't exist\",\n    )\n\n\n@todo_router.put(\"/todo/{todo_id}\")\nasync def update_todo(todo_data: TodoItem, todo_id: int = Path(..., title=\"The ID of the todo to be updated.\")) -> dict:\n    for todo in todo_list:\n        if todo.id == todo_id:\n            todo.item = todo_data.item\n            return {\n                \"message\": \"Todo updated successfully.\"\n            }\n\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Todo with supplied ID doesn't exist\",\n    )\n\n\n@todo_router.delete(\"/todo/{todo_id}\")\nasync def delete_single_todo(todo_id: int) -> dict:\n    for index in range(len(todo_list)):\n        todo = todo_list[index]\n        if todo.id == todo_id:\n            todo_list.pop(index)\n            return {\n                \"message\": \"Todo deleted successfully.\"\n            }\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Todo with supplied ID doesn't exist\",\n    )\n\n\n@todo_router.delete(\"/todo\")\nasync def delete_all_todo() -> dict:\n    todo_list.clear()\n    return {\n        \"message\": \"Todos deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch04/todos/api.py",
    "content": "from fastapi import FastAPI\n\nfrom todo import todo_router\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def welcome() -> dict:\n    return {\n        \"message\": \"Hello World\"\n    }\n\n\napp.include_router(todo_router)\n"
  },
  {
    "path": "ch04/todos/model.py",
    "content": "from typing import List, Optional\n\nfrom fastapi import Form\nfrom pydantic import BaseModel\n\n\nclass Todo(BaseModel):\n    id: Optional[int]\n    item: str\n\n    @classmethod\n    def as_form(\n            cls,\n            item: str = Form(...)\n    ):\n        return cls(item=item)\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"id\": 1,\n                \"item\": \"Example schema!\"\n            }\n        }\n\n\nclass TodoItem(BaseModel):\n    item: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"item\": \"Read the next chapter of the book\"\n            }\n        }\n\n\nclass TodoItems(BaseModel):\n    todos: List[TodoItem]\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"todos\": [\n                    {\n                        \"item\": \"Example schema 1!\"\n                    },\n                    {\n                        \"item\": \"Example schema 2!\"\n                    }\n                ]\n            }\n        }\n"
  },
  {
    "path": "ch04/todos/requirements.txt",
    "content": "fastapi==0.70.0\nuvicorn==0.15.0\njinja2 == 3.1.2\npython-multipart\n"
  },
  {
    "path": "ch04/todos/templates/home.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta content=\"IE=edge\" http-equiv=\"X-UA-Compatible\">\n    <meta content=\"width=device-width, initial-scale=1.0\" name=\"viewport\">\n    <title>Packt Todo Application</title>\n    <link crossorigin=\"anonymous\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css\"\n          integrity=\"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4\" rel=\"stylesheet\">\n    <link crossorigin=\"anonymous\" href=\"https://use.fontawesome.com/releases/v5.0.10/css/all.css\"\n          integrity=\"sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg\" rel=\"stylesheet\">\n</head>\n<body>\n<header>\n    <nav class=\"navar\">\n        <div class=\"container-fluid\">\n            <center>\n                <h1>Packt Todo Application</h1>\n            </center>\n        </div>\n    </nav>\n</header>\n<div class=\"container-fluid\">\n    {% block todo_container %}{% endblock %}\n</div>\n</body>\n</html>"
  },
  {
    "path": "ch04/todos/templates/todo.html",
    "content": "{% extends \"home.html\" %}\n\n{% block todo_container %}\n<main class=\"container\">\n    <hr>\n\n    <section class=\"container-fluid\">\n        <form method=\"post\">\n            <div class=\"col-auto\">\n                <div class=\"input-group mb-3\">\n                    <input aria-describedby=\"button-addon2\" aria-label=\"Add a todo\" class=\"form-control\" name=\"item\"\n                           placeholder=\"Purchase Packt's Python workshop course\" type=\"text\"\n                           value=\"{{ item }}\"/>\n                    <button class=\"btn btn-outline-primary\" data-mdb-ripple-color=\"dark\" id=\"button-addon2\"\n                            type=\"submit\">\n                        Add Todo\n                    </button>\n                </div>\n            </div>\n        </form>\n    </section>\n\n\n    {% if todo %}\n    <article class=\"card container-fluid\">\n        <br/>\n        <h4>Todo ID: {{ todo.id }} </h4>\n        <p>\n            <strong>\n                Item: {{ todo.item }}\n            </strong>\n        </p>\n    </article>\n    {% else %}\n    <section class=\"container-fluid\">\n        <h2 align=\"center\">Todos</h2>\n        <br>\n        <div class=\"card\">\n            <ul class=\"list-group list-group-flush\">\n                {% for todo in todos %}\n                <li class=\"list-group-item\">\n                    {{ loop.index }}. <a href=\"/todo/{{ loop.index }}\"> {{ todo.item }} </a>\n                </li>\n                {% endfor %}\n            </ul>\n        </div>\n        {% endif %}\n    </section>\n</main>\n{% endblock %}"
  },
  {
    "path": "ch04/todos/todo.py",
    "content": "from fastapi import APIRouter, Path, HTTPException, status, Request, Depends\nfrom fastapi.templating import Jinja2Templates\n\nfrom model import Todo, TodoItem, TodoItems\n\ntodo_router = APIRouter()\n\ntodo_list = []\n\ntemplates = Jinja2Templates(directory=\"templates/\")\n\n\n@todo_router.post(\"/todo\")\nasync def add_todo(request: Request, todo: Todo = Depends(Todo.as_form)):\n    todo.id = len(todo_list) + 1\n    todo_list.append(todo)\n    return templates.TemplateResponse(\"todo.html\",\n                                      {\n                                          \"request\": request,\n                                          \"todos\": todo_list\n                                      })\n\n\n@todo_router.get(\"/todo\", response_model=TodoItems)\nasync def retrieve_todo(request: Request):\n    return templates.TemplateResponse(\"todo.html\", {\n        \"request\": request,\n        \"todos\": todo_list\n    })\n\n\n@todo_router.get(\"/todo/{todo_id}\")\nasync def get_single_todo(request: Request, todo_id: int = Path(..., title=\"The ID of the todo to retrieve.\")):\n    for todo in todo_list:\n        if todo.id == todo_id:\n            return templates.TemplateResponse(\"todo.html\", {\n                \"request\": request,\n                \"todo\": todo\n            })\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Todo with supplied ID doesn't exist\",\n    )\n\n\n@todo_router.put(\"/todo/{todo_id}\")\nasync def update_todo(request: Request, todo_data: TodoItem,\n                      todo_id: int = Path(..., title=\"The ID of the todo to be updated.\")) -> dict:\n    for todo in todo_list:\n        if todo.id == todo_id:\n            todo.item = todo_data.item\n            return {\n                \"message\": \"Todo updated successfully.\"\n            }\n\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Todo with supplied ID doesn't exist\",\n    )\n\n\n@todo_router.delete(\"/todo/{todo_id}\")\nasync def delete_single_todo(request: Request, todo_id: int) -> dict:\n    for index in range(len(todo_list)):\n        todo = todo_list[index]\n        if todo.id == todo_id:\n            todo_list.pop(index)\n            return {\n                \"message\": \"Todo deleted successfully.\"\n            }\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Todo with supplied ID doesn't exist\",\n    )\n\n\n@todo_router.delete(\"/todo\")\nasync def delete_all_todo() -> dict:\n    todo_list.clear()\n    return {\n        \"message\": \"Todos deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch05/planner/database/__init__.py",
    "content": ""
  },
  {
    "path": "ch05/planner/main.py",
    "content": "from fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\n\nfrom routes.users import user_router\nfrom routes.events import event_router\n\nimport uvicorn\n\napp = FastAPI()\n\n# Register routes\n\napp.include_router(user_router,  prefix=\"/user\")\napp.include_router(event_router, prefix=\"/event\")\n\n\n@app.get(\"/\")\nasync def home():\n    return RedirectResponse(url=\"/event/\")\n\nif __name__ == '__main__':\n    uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8080, reload=True)"
  },
  {
    "path": "ch05/planner/models/__init__.py",
    "content": ""
  },
  {
    "path": "ch05/planner/models/events.py",
    "content": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass Event(BaseModel):\n    id: int\n    title: str\n    image: str\n    description: str\n    tags: List[str]\n    location: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI Book Launch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n"
  },
  {
    "path": "ch05/planner/models/users.py",
    "content": "from pydantic import BaseModel, EmailStr\n\n\nclass User(BaseModel):\n    email: EmailStr\n    password: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"email\": \"fastapi@packt.com\",\n                \"password\": \"strong!!!\",\n            }\n        }\n\n\nclass UserSignIn(BaseModel):\n    email: EmailStr\n    password: str\n\n    schema_extra = {\n        \"example\": {\n            \"email\": \"fastapi@packt.com\",\n            \"password\": \"strong!!!\"\n        }\n    }\n"
  },
  {
    "path": "ch05/planner/requirements.txt",
    "content": "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.3\nfastapi==0.74.1\nh11==0.13.0\nidna==3.3\nJinja2==3.0.3\nMarkupSafe==2.1.0\nmotor==2.5.1\nmultidict==6.0.2\npasslib==1.7.4\npycparser==2.21\npydantic==1.9.0\nPyJWT==2.3.0\npymongo==3.12.3\npython-dotenv==0.20.0\npython-multipart==0.0.5\nsix==1.16.0\nsniffio==1.2.0\nSQLAlchemy==1.4.32\nsqlalchemy2-stubs==0.0.2a20\nsqlmodel==0.0.6\nstarlette==0.17.1\ntoml==0.10.2\ntyping_extensions==4.1.1\nuvicorn==0.17.5\nyarl==1.7.2\n"
  },
  {
    "path": "ch05/planner/routes/__init__.py",
    "content": ""
  },
  {
    "path": "ch05/planner/routes/events.py",
    "content": "from typing import List\n\nfrom fastapi import APIRouter, Body, HTTPException, status\nfrom models.events import Event\n\nevent_router = APIRouter(\n    tags=[\"Events\"]\n)\n\nevents = []\n\n\n@event_router.get(\"/\", response_model=List[Event])\nasync def retrieve_all_events() -> List[Event]:\n    return events\n\n\n@event_router.get(\"/{id}\", response_model=Event)\nasync def retrieve_event(id: int) -> Event:\n    for event in events:\n        if event.id == id:\n            return event\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Event with supplied ID does not exist\"\n    )\n\n\n@event_router.post(\"/new\")\nasync def create_event(body: Event = Body(...)) -> dict:\n    events.append(body)\n    return {\n        \"message\": \"Event created successfully\"\n    }\n\n\n@event_router.delete(\"/{id}\")\nasync def delete_event(id: int) -> dict:\n    for event in events:\n        if event.id == id:\n            events.remove(event)\n            return {\n                \"message\": \"Event deleted successfully\"\n            }\n\n    raise HTTPException(\n        status_code=status.HTTP_404_NOT_FOUND,\n        detail=\"Event with supplied ID does not exist\"\n    )\n"
  },
  {
    "path": "ch05/planner/routes/users.py",
    "content": "from fastapi import APIRouter, HTTPException, status\n\nfrom models.users import User, UserSignIn\n\nuser_router = APIRouter(\n    tags=[\"User\"],\n)\n\nusers = {}\n\n\n@user_router.post(\"/signup\")\nasync def sign_user_up(data: User) -> dict:\n    if data.email in users:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=\"User with supplied username exists\"\n        )\n\n    users[data.email] = data\n\n    return {\n        \"message\": \"User successfully registered!\"\n    }\n\n\n@user_router.post(\"/signin\")\nasync def sign_user_in(user: UserSignIn) -> dict:\n    if user.email not in users:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"User does not exist\"\n        )\n\n    if users[user.email].password != user.password:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Wrong credential passed\"\n        )\n    return {\n        \"message\": \"User signed in successfully\"\n    }\n"
  },
  {
    "path": "ch06/planner/database/__init__.py",
    "content": ""
  },
  {
    "path": "ch06/planner/database/connection.py",
    "content": "from typing import Any, List, Optional\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event\nfrom models.users import User\nfrom motor.motor_asyncio import AsyncIOMotorClient\nfrom pydantic import BaseSettings, BaseModel\n\n\nclass Settings(BaseSettings):\n    DATABASE_URL: Optional[str] = None\n\n    async def initialize_database(self):\n        client = AsyncIOMotorClient(self.DATABASE_URL)\n        await init_beanie(database=client.get_default_database(),\n                          document_models=[Event, User])\n\n    class Config:\n        env_file = \".env\"\n\n\nclass Database:\n    def __init__(self, model):\n        self.model = model\n\n    async def save(self, document) -> None:\n        await document.create()\n        return\n\n    async def get(self, id: PydanticObjectId) -> Any:\n        doc = await self.model.get(id)\n        if doc:\n            return doc\n        return False\n\n    async def get_all(self) -> List[Any]:\n        docs = await self.model.find_all().to_list()\n        return docs\n\n    async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:\n        doc_id = id\n        des_body = body.dict()\n\n        des_body = {k: v for k, v in des_body.items() if v is not None}\n        update_query = {\"$set\": {\n            field: value for field, value in des_body.items()\n        }}\n\n        doc = await self.get(doc_id)\n        if not doc:\n            return False\n        await doc.update(update_query)\n        return doc\n\n    async def delete(self, id: PydanticObjectId) -> bool:\n        doc = await self.get(id)\n        if not doc:\n            return False\n        await doc.delete()\n        return True\n"
  },
  {
    "path": "ch06/planner/main.py",
    "content": "import uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\n\nfrom database.connection import Settings\nfrom routes.events import event_router\nfrom routes.users import user_router\n\napp = FastAPI()\n\nsettings = Settings()\n\n# Register routes\n\napp.include_router(user_router, prefix=\"/user\")\napp.include_router(event_router, prefix=\"/event\")\n\n\n@app.on_event(\"startup\")\nasync def init_db():\n    await settings.initialize_database()\n\n\n@app.get(\"/\")\nasync def home():\n    return RedirectResponse(url=\"/event/\")\n\n\nif __name__ == '__main__':\n    uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8080, reload=True)\n"
  },
  {
    "path": "ch06/planner/models/__init__.py",
    "content": ""
  },
  {
    "path": "ch06/planner/models/events.py",
    "content": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n    title: str\n    image: str\n    description: str\n    tags: List[str]\n    location: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI BookLaunch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n\n    class Settings:\n        name = \"events\"\n\n\nclass EventUpdate(BaseModel):\n    title: Optional[str]\n    image: Optional[str]\n    description: Optional[str]\n    tags: Optional[List[str]]\n    location: Optional[str]\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI BookLaunch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n"
  },
  {
    "path": "ch06/planner/models/users.py",
    "content": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    password: str\n\n    class Settings:\n        name = \"users\"\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"email\": \"fastapi@packt.com\",\n                \"password\": \"strong!!!\",\n            }\n        }\n\n\nclass UserSignIn(BaseModel):\n    email: EmailStr\n    password: str\n"
  },
  {
    "path": "ch06/planner/requirements.txt",
    "content": "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.3\nfastapi==0.74.1\nh11==0.13.0\nidna==3.3\nJinja2==3.0.3\nMarkupSafe==2.1.0\nmotor==2.5.1\nmultidict==6.0.2\npasslib==1.7.4\npycparser==2.21\npydantic==1.9.0\nPyJWT==2.3.0\npymongo==3.12.3\npython-dotenv==0.20.0\npython-multipart==0.0.5\nsix==1.16.0\nsniffio==1.2.0\nSQLAlchemy==1.4.32\nsqlalchemy2-stubs==0.0.2a20\nsqlmodel==0.0.6\nstarlette==0.17.1\ntoml==0.10.2\ntyping_extensions==4.1.1\nuvicorn==0.17.5\nyarl==1.7.2\n"
  },
  {
    "path": "ch06/planner/routes/__init__.py",
    "content": ""
  },
  {
    "path": "ch06/planner/routes/events.py",
    "content": "from typing import List\n\nfrom beanie import PydanticObjectId\nfrom database.connection import Database\nfrom fastapi import APIRouter, HTTPException, status\nfrom models.events import Event, EventUpdate\n\nevent_router = APIRouter(\n    tags=[\"Events\"]\n)\n\nevent_database = Database(Event)\n\n\n@event_router.get(\"/\", response_model=List[Event])\nasync def retrieve_all_events() -> List[Event]:\n    events = await event_database.get_all()\n    return events\n\n\n@event_router.get(\"/{id}\", response_model=Event)\nasync def retrieve_event(id: PydanticObjectId) -> Event:\n    event = await event_database.get(id)\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return event\n\n\n@event_router.post(\"/new\")\nasync def create_event(body: Event) -> dict:\n    await event_database.save(body)\n    return {\n        \"message\": \"Event created successfully\"\n    }\n\n\n@event_router.put(\"/{id}\", response_model=Event)\nasync def update_event(id: PydanticObjectId, body: EventUpdate) -> Event:\n    updated_event = await event_database.update(id, body)\n    if not updated_event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return updated_event\n\n\n@event_router.delete(\"/{id}\")\nasync def delete_event(id: PydanticObjectId) -> dict:\n    event = await event_database.delete(id)\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return {\n        \"message\": \"Event deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch06/planner/routes/users.py",
    "content": "from database.connection import Database\nfrom fastapi import APIRouter, HTTPException, status\nfrom models.users import User, UserSignIn\n\nuser_router = APIRouter(\n    tags=[\"User\"],\n)\n\nuser_database = Database(User)\n\n\n@user_router.post(\"/signup\")\nasync def sign_user_up(user: User) -> dict:\n    user_exist = await User.find_one(User.email == user.email)\n\n    if user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=\"User with email provided exists already.\"\n        )\n    await user_database.save(user)\n    return {\n        \"message\": \"User created successfully\"\n    }\n\n\n@user_router.post(\"/signin\")\nasync def sign_user_in(user: UserSignIn) -> dict:\n    user_exist = await User.find_one(User.email == user.email)\n    if not user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"User with email does not exist.\"\n        )\n    if user_exist.password == user.password:\n        return {\n            \"message\": \"User signed in successfully.\"\n        }\n\n    raise HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Invalid details passed.\"\n    )\n"
  },
  {
    "path": "ch07/planner/auth/__init__.py",
    "content": ""
  },
  {
    "path": "ch07/planner/auth/authenticate.py",
    "content": "from auth.jwt_handler import verify_access_token\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"/user/signin\")\n\n\nasync def authenticate(token: str = Depends(oauth2_scheme)) -> str:\n    if not token:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Sign in for access\"\n        )\n\n    decoded_token = verify_access_token(token)\n    return decoded_token[\"user\"]\n"
  },
  {
    "path": "ch07/planner/auth/hash_password.py",
    "content": "from passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass HashPassword:\n    def create_hash(self, password: str):\n        return pwd_context.hash(password)\n\n    def verify_hash(self, plain_password: str, hashed_password: str):\n        return pwd_context.verify(plain_password, hashed_password)\n"
  },
  {
    "path": "ch07/planner/auth/jwt_handler.py",
    "content": "import time\nfrom datetime import datetime\n\nfrom database.connection import Settings\nfrom fastapi import HTTPException, status\nfrom jose import jwt, JWTError\n\nsettings = Settings()\n\n\ndef create_access_token(user: str):\n    payload = {\n        \"user\": user,\n        \"expires\": time.time() + 3600\n    }\n\n    token = jwt.encode(payload, settings.SECRET_KEY, algorithm=\"HS256\")\n    return token\n\n\ndef verify_access_token(token: str):\n    try:\n        data = jwt.decode(token, settings.SECRET_KEY, algorithms=[\"HS256\"])\n\n        expire = data.get(\"expires\")\n\n        if expire is None:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"No access token supplied\"\n            )\n        if datetime.utcnow() > datetime.utcfromtimestamp(expire):\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=\"Token expired!\"\n            )\n        return data\n\n    except JWTError:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Invalid token\"\n        )\n"
  },
  {
    "path": "ch07/planner/database/__init__.py",
    "content": ""
  },
  {
    "path": "ch07/planner/database/connection.py",
    "content": "from typing import Optional\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event\nfrom models.users import User\nfrom motor.motor_asyncio import AsyncIOMotorClient\nfrom pydantic import BaseSettings, BaseModel\n\n\nclass Settings(BaseSettings):\n    DATABASE_URL: Optional[str] = None\n    SECRET_KEY: Optional[str] = None\n\n    async def initialize_database(self):\n        client = AsyncIOMotorClient(self.DATABASE_URL)\n        await init_beanie(database=client.get_default_database(),\n                          document_models=[Event, User])\n\n    class Config:\n        env_file = \".env\"\n\n\nclass Database:\n    def __init__(self, model):\n        self.model = model\n\n    async def save(self, document):\n        await document.create()\n        return\n\n    async def get(self, id: PydanticObjectId):\n        doc = await self.model.get(id)\n        if doc:\n            return doc\n        return False\n\n    async def get_all(self):\n        docs = await self.model.find_all().to_list()\n        return docs\n\n    async def update(self, id: PydanticObjectId, body: BaseModel):\n        doc_id = id\n        des_body = body.dict()\n\n        des_body = {k: v for k, v in des_body.items() if v is not None}\n        update_query = {\"$set\": {\n            field: value for field, value in des_body.items()\n        }}\n\n        doc = await self.get(doc_id)\n        if not doc:\n            return False\n        await doc.update(update_query)\n        return doc\n\n    async def delete(self, id: PydanticObjectId):\n        doc = await self.get(id)\n        if not doc:\n            return False\n        await doc.delete()\n        return True\n"
  },
  {
    "path": "ch07/planner/main.py",
    "content": "import uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom database.connection import Settings\nfrom routes.events import event_router\nfrom routes.users import user_router\n\napp = FastAPI()\n\nsettings = Settings()\n\n# register origins\n\norigins = [\"*\"]\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=origins,\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n# Register routes\n\napp.include_router(user_router, prefix=\"/user\")\napp.include_router(event_router, prefix=\"/event\")\n\n\n@app.on_event(\"startup\")\nasync def init_db():\n    await settings.initialize_database()\n\n\n@app.get(\"/\")\nasync def home():\n    return RedirectResponse(url=\"/event/\")\n\n\nif __name__ == '__main__':\n    uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8080, reload=True)\n"
  },
  {
    "path": "ch07/planner/models/__init__.py",
    "content": ""
  },
  {
    "path": "ch07/planner/models/events.py",
    "content": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n    creator: Optional[str]\n    title: str\n    image: str\n    description: str\n    tags: List[str]\n    location: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI BookLaunch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n\n    class Collection:\n        name = \"events\"\n\n\nclass EventUpdate(BaseModel):\n    title: Optional[str]\n    image: Optional[str]\n    description: Optional[str]\n    tags: Optional[List[str]]\n    location: Optional[str]\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI BookLaunch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n"
  },
  {
    "path": "ch07/planner/models/users.py",
    "content": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    password: str\n\n    class Collection:\n        name = \"users\"\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"email\": \"fastapi@packt.com\",\n                \"password\": \"strong!!!\"\n            }\n        }\n\n\nclass TokenResponse(BaseModel):\n    access_token: str\n    token_type: str\n"
  },
  {
    "path": "ch07/planner/requirements.txt",
    "content": "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\necdsa==0.17.0\nemail-validator==1.1.3\nfastapi==0.74.1\nh11==0.13.0\nidna==3.3\nJinja2==3.0.3\nMarkupSafe==2.1.0\nmotor==2.5.1\nmultidict==6.0.2\npasslib==1.7.4\npyasn1==0.4.8\npycparser==2.21\npydantic==1.9.0\npymongo==3.12.3\npython-dotenv==0.20.0\npython-jose==3.3.0\npython-multipart==0.0.5\nrsa==4.8\nsix==1.16.0\nsniffio==1.2.0\nSQLAlchemy==1.4.32\nsqlalchemy2-stubs==0.0.2a20\nsqlmodel==0.0.6\nstarlette==0.17.1\ntoml==0.10.2\ntyping_extensions==4.1.1\nuvicorn==0.17.5\nyarl==1.7.2\n"
  },
  {
    "path": "ch07/planner/routes/__init__.py",
    "content": ""
  },
  {
    "path": "ch07/planner/routes/events.py",
    "content": "from typing import List\n\nfrom auth.authenticate import authenticate\nfrom beanie import PydanticObjectId\nfrom database.connection import Database\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom models.events import Event, EventUpdate\n\nevent_router = APIRouter(\n    tags=[\"Events\"]\n)\n\nevent_database = Database(Event)\n\n\n@event_router.get(\"/\", response_model=List[Event])\nasync def retrieve_all_events() -> List[Event]:\n    events = await event_database.get_all()\n    return events\n\n\n@event_router.get(\"/{id}\", response_model=Event)\nasync def retrieve_event(id: PydanticObjectId) -> Event:\n    event = await event_database.get(id)\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return event\n\n\n@event_router.post(\"/new\")\nasync def create_event(body: Event, user: str = Depends(authenticate)) -> dict:\n    body.creator = user\n    await event_database.save(body)\n    return {\n        \"message\": \"Event created successfully\"\n    }\n\n\n@event_router.put(\"/{id}\", response_model=Event)\nasync def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:\n    event = await event_database.get(id)\n    if event.creator != user:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Operation not allowed\"\n        )\n    updated_event = await event_database.update(id, body)\n    if not updated_event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return updated_event\n\n\n@event_router.delete(\"/{id}\")\nasync def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:\n    event = await event_database.get(id)\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event not found\"\n        )\n    if event.creator != user:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Operation not allowed\"\n        )\n    event = await event_database.delete(id)\n\n    return {\n        \"message\": \"Event deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch07/planner/routes/users.py",
    "content": "from auth.hash_password import HashPassword\nfrom auth.jwt_handler import create_access_token\nfrom database.connection import Database\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordRequestForm\nfrom models.users import User, TokenResponse\n\nuser_router = APIRouter(\n    tags=[\"User\"],\n)\n\nuser_database = Database(User)\nhash_password = HashPassword()\n\n\n@user_router.post(\"/signup\")\nasync def sign_user_up(user: User) -> dict:\n    user_exist = await User.find_one(User.email == user.email)\n\n    if user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=\"User with email provided exists already.\"\n        )\n    hashed_password = hash_password.create_hash(user.password)\n    user.password = hashed_password\n    await user_database.save(user)\n    return {\n        \"message\": \"User created successfully\"\n    }\n\n\n@user_router.post(\"/signin\", response_model=TokenResponse)\nasync def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:\n    user_exist = await User.find_one(User.email == user.username)\n    if not user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"User with email does not exist.\"\n        )\n    if hash_password.verify_hash(user.password, user_exist.password):\n        access_token = create_access_token(user_exist.email)\n        return {\n            \"access_token\": access_token,\n            \"token_type\": \"Bearer\"\n        }\n\n    raise HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Invalid details passed.\"\n    )\n"
  },
  {
    "path": "ch08/planner/auth/__init__.py",
    "content": ""
  },
  {
    "path": "ch08/planner/auth/authenticate.py",
    "content": "from auth.jwt_handler import verify_access_token\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"/user/signin\")\n\n\nasync def authenticate(token: str = Depends(oauth2_scheme)) -> str:\n    if not token:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Sign in for access\"\n        )\n\n    decoded_token = await verify_access_token(token)\n    return decoded_token[\"user\"]\n"
  },
  {
    "path": "ch08/planner/auth/hash_password.py",
    "content": "from passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass HashPassword:\n    def create_hash(self, password: str) -> str:\n        return pwd_context.hash(password)\n\n    def verify_hash(self, plain_password: str, hashed_password: str) -> bool:\n        return pwd_context.verify(plain_password, hashed_password)\n"
  },
  {
    "path": "ch08/planner/auth/jwt_handler.py",
    "content": "import time\nfrom datetime import datetime\n\nfrom database.connection import Settings\nfrom fastapi import HTTPException, status\nfrom jose import jwt, JWTError\nfrom models.users import User\n\nsettings = Settings()\n\n\ndef create_access_token(user: str) -> str:\n    payload = {\n        \"user\": user,\n        \"expires\": time.time() + 3600\n    }\n\n    token = jwt.encode(payload, settings.SECRET_KEY, algorithm=\"HS256\")\n    return token\n\n\nasync def verify_access_token(token: str) -> dict:\n    try:\n        data = jwt.decode(token, settings.SECRET_KEY, algorithms=[\"HS256\"])\n\n        expire = data.get(\"expires\")\n\n        if expire is None:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"No access token supplied\"\n            )\n        if datetime.utcnow() > datetime.utcfromtimestamp(expire):\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=\"Token expired!\"\n            )\n        user_exist = await User.find_one(User.email == data[\"user\"])\n        if not user_exist:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"Invalid token\"\n            )\n\n        return data\n\n    except JWTError:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Invalid token\"\n        )\n"
  },
  {
    "path": "ch08/planner/database/__init__.py",
    "content": ""
  },
  {
    "path": "ch08/planner/database/connection.py",
    "content": "from typing import Optional, Any, List\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event\nfrom models.users import User\nfrom motor.motor_asyncio import AsyncIOMotorClient\nfrom pydantic import BaseSettings, BaseModel\n\n\nclass Settings(BaseSettings):\n    DATABASE_URL: Optional[str] = None\n    SECRET_KEY: Optional[str] = \"default\"\n\n    async def initialize_database(self):\n        client = AsyncIOMotorClient(self.DATABASE_URL)\n        await init_beanie(database=client.get_default_database(),\n                          document_models=[Event, User])\n\n    class Config:\n        env_file = \".env\"\n\n\nclass Database:\n    def __init__(self, model):\n        self.model = model\n\n    async def save(self, document):\n        await document.create()\n        return\n\n    async def get(self, id: PydanticObjectId) -> bool:\n        doc = await self.model.get(id)\n        if doc:\n            return doc\n        return False\n\n    async def get_all(self) -> List[Any]:\n        docs = await self.model.find_all().to_list()\n        return docs\n\n    async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:\n        doc_id = id\n        des_body = body.dict()\n\n        des_body = {k: v for k, v in des_body.items() if v is not None}\n        update_query = {\"$set\": {\n            field: value for field, value in des_body.items()\n        }}\n\n        doc = await self.get(doc_id)\n        if not doc:\n            return False\n        await doc.update(update_query)\n        return doc\n\n    async def delete(self, id: PydanticObjectId) -> bool:\n        doc = await self.get(id)\n        if not doc:\n            return False\n        await doc.delete()\n        return True\n"
  },
  {
    "path": "ch08/planner/main.py",
    "content": "import uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.responses import RedirectResponse\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom database.connection import Settings\nfrom routes.events import event_router\nfrom routes.users import user_router\n\napp = FastAPI()\n\nsettings = Settings()\n\n\n# register origins\n\norigins = [\"*\"]\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=origins,\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n# Register routes\n\napp.include_router(user_router, prefix=\"/user\")\napp.include_router(event_router, prefix=\"/event\")\n\n\n@app.on_event(\"startup\")\nasync def init_db():\n    await settings.initialize_database()\n\n\n@app.get(\"/\")\nasync def home():\n    return RedirectResponse(url=\"/event/\")\n\n\nif __name__ == '__main__':\n    uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8080, reload=True)\n"
  },
  {
    "path": "ch08/planner/models/__init__.py",
    "content": ""
  },
  {
    "path": "ch08/planner/models/events.py",
    "content": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n    creator: Optional[str]\n    title: str\n    image: str\n    description: str\n    tags: List[str]\n    location: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI Book Launch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n\n    class Settings:\n        name = \"events\"\n\n\nclass EventUpdate(BaseModel):\n    title: Optional[str]\n    image: Optional[str]\n    description: Optional[str]\n    tags: Optional[List[str]]\n    location: Optional[str]\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI BookLaunch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n"
  },
  {
    "path": "ch08/planner/models/users.py",
    "content": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    password: str\n\n    class Settings:\n        name = \"users\"\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"email\": \"fastapi@packt.com\",\n                \"password\": \"strong!!!\"\n            }\n        }\n\n\nclass TokenResponse(BaseModel):\n    access_token: str\n    token_type: str\n"
  },
  {
    "path": "ch08/planner/pytest.ini",
    "content": "[pytest]\nasyncio_mode=auto\n"
  },
  {
    "path": "ch08/planner/requirements.txt",
    "content": "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.15.0\ncharset-normalizer==2.0.12\nclick==8.0.4\ncoverage==6.3.3\ncryptography==36.0.2\ndnspython==2.2.1\necdsa==0.17.0\nemail-validator==1.1.3\nfastapi==0.77.1\nh11==0.12.0\nhttpcore==0.14.7\nhttpx==0.22.0\nidna==3.3\niniconfig==1.1.1\nJinja2==3.0.3\nMarkupSafe==2.1.0\nmotor==3.0.0\nmultidict==6.0.2\npackaging==21.3\npasslib==1.7.4\npluggy==1.0.0\npy==1.11.0\npyasn1==0.4.8\npycparser==2.21\npydantic==1.9.0\npymongo==4.1.1\npyparsing==3.0.9\npytest==7.1.2\npytest-asyncio==0.18.3\npython-dotenv==0.20.0\npython-jose==3.3.0\npython-multipart==0.0.5\nrfc3986==1.5.0\nrsa==4.8\nsix==1.16.0\nsniffio==1.2.0\nSQLAlchemy==1.4.32\nsqlalchemy2-stubs==0.0.2a20\nsqlmodel==0.0.6\nstarlette==0.19.1\ntoml==0.10.2\ntomli==2.0.1\ntyping_extensions==4.1.1\nuvicorn==0.17.6\nyarl==1.7.2\n"
  },
  {
    "path": "ch08/planner/routes/__init__.py",
    "content": ""
  },
  {
    "path": "ch08/planner/routes/events.py",
    "content": "from typing import List\n\nfrom auth.authenticate import authenticate\nfrom beanie import PydanticObjectId\nfrom database.connection import Database\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom models.events import Event, EventUpdate\n\nevent_router = APIRouter(\n    tags=[\"Events\"]\n)\n\nevent_database = Database(Event)\n\n\n@event_router.get(\"/\", response_model=List[Event])\nasync def retrieve_all_events() -> List[Event]:\n    events = await event_database.get_all()\n    return events\n\n\n@event_router.get(\"/{id}\", response_model=Event)\nasync def retrieve_event(id: PydanticObjectId) -> Event:\n    event = await event_database.get(id)\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return event\n\n\n@event_router.post(\"/new\")\nasync def create_event(body: Event, user: str = Depends(authenticate)) -> dict:\n    body.creator = user\n    await event_database.save(body)\n    return {\n        \"message\": \"Event created successfully\"\n    }\n\n\n@event_router.put(\"/{id}\", response_model=Event)\nasync def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:\n    event = await event_database.get(id)\n    if event.creator != user:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Operation not allowed\"\n        )\n    updated_event = await event_database.update(id, body)\n    if not updated_event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return updated_event\n\n\n@event_router.delete(\"/{id}\")\nasync def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:\n    event = await event_database.get(id)\n    if event.creator != user:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event not found\"\n        )\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    await event_database.delete(id)\n\n    return {\n        \"message\": \"Event deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch08/planner/routes/users.py",
    "content": "from auth.hash_password import HashPassword\nfrom auth.jwt_handler import create_access_token\nfrom database.connection import Database\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordRequestForm\nfrom models.users import User, TokenResponse\n\nuser_router = APIRouter(\n    tags=[\"User\"],\n)\n\nuser_database = Database(User)\nhash_password = HashPassword()\n\n\n@user_router.post(\"/signup\")\nasync def sign_user_up(user: User) -> dict:\n    user_exist = await User.find_one(User.email == user.email)\n\n    if user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=\"User with email provided exists already.\"\n        )\n    hashed_password = hash_password.create_hash(user.password)\n    user.password = hashed_password\n    await user_database.save(user)\n    return {\n        \"message\": \"User created successfully\"\n    }\n\n\n@user_router.post(\"/signin\", response_model=TokenResponse)\nasync def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:\n    user_exist = await User.find_one(User.email == user.username)\n    if not user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"User with email does not exist.\"\n        )\n    if hash_password.verify_hash(user.password, user_exist.password):\n        access_token = create_access_token(user_exist.email)\n        return {\n            \"access_token\": access_token,\n            \"token_type\": \"Bearer\"\n        }\n\n    raise HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Invalid details passed.\"\n    )\n"
  },
  {
    "path": "ch08/planner/tests/conftest.py",
    "content": "import asyncio\n\nimport httpx\nimport pytest\n\nfrom database.connection import Settings\nfrom main import app\nfrom models.events import Event\nfrom models.users import User\n\n\n@pytest.fixture(scope=\"session\")\ndef event_loop():\n    loop = asyncio.get_event_loop()\n    yield loop\n    loop.close()\n\n\nasync def init_db():\n    test_settings = Settings()\n    test_settings.DATABASE_URL = \"mongodb://localhost:27017/testdb\"\n\n    await test_settings.initialize_database()\n\n\n@pytest.fixture(scope=\"session\")\nasync def default_client():\n    await init_db()\n    async with httpx.AsyncClient(app=app, base_url=\"http://app\") as client:\n        yield client\n\n        # Clean up resources\n        await Event.find_all().delete()\n        await User.find_all().delete()\n"
  },
  {
    "path": "ch08/planner/tests/test_arthmetic_operations.py",
    "content": "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(a: int, b: int) -> int:\n    return a * b\n\n\ndef divide(a: int, b: int) -> int:\n    return b // a\n\n\ndef test_add() -> None:\n    assert add(1, 1) == 2\n\n\ndef test_subtract() -> None:\n    assert subtract(2, 5) == 3\n\n\ndef test_multiply() -> None:\n    assert multiply(10, 10) == 100\n\n\ndef test_divide() -> None:\n    assert divide(25, 100) == 4\n"
  },
  {
    "path": "ch08/planner/tests/test_fixture.py",
    "content": "import pytest\n\n# Fixture is defined.\nfrom models.events import EventUpdate\n\n\n@pytest.fixture\ndef event() -> EventUpdate:\n    return EventUpdate(\n        title=\"FastAPI Book Launch\",\n        image=\"https://packt.com/fastapi.png\",\n        description=\"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n        tags=[\"python\", \"fastapi\", \"book\", \"launch\"],\n        location=\"Google Meet\"\n    )\n\n\ndef test_event_name(event: EventUpdate) -> None:\n    assert event.title == \"FastAPI Book Launch\"\n"
  },
  {
    "path": "ch08/planner/tests/test_login.py",
    "content": "import httpx\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_sign_new_user(default_client: httpx.AsyncClient) -> None:\n    payload = {\n        \"email\": \"testuser@packt.com\",\n        \"password\": \"testpassword\",\n    }\n\n    headers = {\n        \"accept\": \"application/json\",\n        \"Content-Type\": \"application/json\"\n    }\n\n    test_response = {\n        \"message\": \"User created successfully\"\n    }\n\n    response = await default_client.post(\"/user/signup\", json=payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json() == test_response\n\n\n@pytest.mark.asyncio\nasync def test_sign_user_in(default_client: httpx.AsyncClient) -> None:\n    payload = {\n        \"username\": \"testuser@packt.com\",\n        \"password\": \"testpassword\"\n    }\n\n    headers = {\n        \"accept\": \"application/json\",\n        \"Content-Type\": \"application/x-www-form-urlencoded\"\n    }\n\n    response = await default_client.post(\"/user/signin\", data=payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json()[\"token_type\"] == \"Bearer\"\n"
  },
  {
    "path": "ch08/planner/tests/test_routes.py",
    "content": "import httpx\nimport pytest\n\nfrom auth.jwt_handler import create_access_token\nfrom models.events import Event\n\n\n@pytest.fixture(scope=\"module\")\nasync def access_token() -> str:\n    return create_access_token(\"testuser@packt.com\")\n\n\n@pytest.fixture(scope=\"module\")\nasync def mock_event() -> Event:\n    new_event = Event(\n        creator=\"testuser@packt.com\",\n        title=\"FastAPI Book Launch\",\n        image=\"https://linktomyimage.com/image.png\",\n        description=\"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n        tags=[\"python\", \"fastapi\", \"book\", \"launch\"],\n        location=\"Google Meet\"\n    )\n\n    await Event.insert_one(new_event)\n\n    yield new_event\n\n\n@pytest.mark.asyncio\nasync def test_get_events(default_client: httpx.AsyncClient, mock_event: Event) -> None:\n    response = await default_client.get(\"/event/\")\n\n    assert response.status_code == 200\n    assert response.json()[0][\"_id\"] == str(mock_event.id)\n\n\n@pytest.mark.asyncio\nasync def test_get_event(default_client: httpx.AsyncClient, mock_event: Event) -> None:\n    url = f\"/event/{str(mock_event.id)}\"\n    response = await default_client.get(url)\n\n    assert response.status_code == 200\n    assert response.json()[\"creator\"] == mock_event.creator\n    assert response.json()[\"_id\"] == str(mock_event.id)\n\n\n@pytest.mark.asyncio\nasync def test_post_event(default_client: httpx.AsyncClient, access_token: str) -> None:\n    payload = {\n        \"title\": \"FastAPI Book Launch\",\n        \"image\": \"https://linktomyimage.com/image.png\",\n        \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n        \"tags\": [\n            \"python\",\n            \"fastapi\",\n            \"book\",\n            \"launch\"\n        ],\n        \"location\": \"Google Meet\",\n    }\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": f\"Bearer {access_token}\"\n    }\n\n    test_response = {\n        \"message\": \"Event created successfully\"\n    }\n\n    response = await default_client.post(\"/event/new\", json=payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json() == test_response\n\n\n@pytest.mark.asyncio\nasync def test_get_events_count(default_client: httpx.AsyncClient) -> None:\n    response = await default_client.get(\"/event/\")\n\n    events = response.json()\n\n    assert response.status_code == 200\n    assert len(events) == 2\n\n\n@pytest.mark.asyncio\nasync def test_update_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:\n    test_payload = {\n        \"title\": \"Updated FastAPI event\"\n    }\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": f\"Bearer {access_token}\"\n    }\n\n    url = f\"/event/{str(mock_event.id)}\"\n\n    response = await default_client.put(url, json=test_payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json()[\"title\"] == test_payload[\"title\"]\n\n\n@pytest.mark.asyncio\nasync def test_delete_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:\n    test_response = {\n        \"message\": \"Event deleted successfully.\"\n    }\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": f\"Bearer {access_token}\"\n    }\n\n    url = f\"/event/{mock_event.id}\"\n\n    response = await default_client.delete(url, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json() == test_response\n\n\n@pytest.mark.asyncio\nasync def test_get_event_again(default_client: httpx.AsyncClient, mock_event: Event) -> None:\n    url = f\"/event/{str(mock_event.id)}\"\n    response = await default_client.get(url)\n\n    assert response.status_code == 404\n"
  },
  {
    "path": "ch09/planner/.dockerignore",
    "content": "venv\n.env\n.git"
  },
  {
    "path": "ch09/planner/Dockerfile",
    "content": "FROM python:3.10\n\nWORKDIR /app\n\nADD requirements.txt /app/requirements.txt\n\nRUN pip install --upgrade pip && pip install -r /app/requirements.txt\n\nEXPOSE 8080\n\nCOPY ./ /app\n\nCMD [\"python\", \"main.py\"]\n"
  },
  {
    "path": "ch09/planner/auth/__init__.py",
    "content": ""
  },
  {
    "path": "ch09/planner/auth/authenticate.py",
    "content": "from auth.jwt_handler import verify_access_token\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"/user/signin\")\n\n\nasync def authenticate(token: str = Depends(oauth2_scheme)) -> str:\n    if not token:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Sign in for access\"\n        )\n\n    decoded_token = await verify_access_token(token)\n    return decoded_token[\"user\"]\n"
  },
  {
    "path": "ch09/planner/auth/hash_password.py",
    "content": "from passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\n\nclass HashPassword:\n    def create_hash(self, password: str) -> str:\n        return pwd_context.hash(password)\n\n    def verify_hash(self, plain_password: str, hashed_password: str) -> bool:\n        return pwd_context.verify(plain_password, hashed_password)\n"
  },
  {
    "path": "ch09/planner/auth/jwt_handler.py",
    "content": "import time\nfrom datetime import datetime\n\nfrom database.connection import Settings\nfrom fastapi import HTTPException, status\nfrom jose import jwt, JWTError\nfrom models.users import User\n\nsettings = Settings()\n\n\ndef create_access_token(user: str) -> str:\n    payload = {\n        \"user\": user,\n        \"expires\": time.time() + 3600\n    }\n\n    token = jwt.encode(payload, settings.SECRET_KEY, algorithm=\"HS256\")\n    return token\n\n\nasync def verify_access_token(token: str) -> dict:\n    try:\n        data = jwt.decode(token, settings.SECRET_KEY, algorithms=[\"HS256\"])\n\n        expire = data.get(\"expires\")\n\n        if expire is None:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"No access token supplied\"\n            )\n        if datetime.utcnow() > datetime.utcfromtimestamp(expire):\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=\"Token expired!\"\n            )\n        user_exist = await User.find_one(User.email == data[\"user\"])\n        if not user_exist:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"Invalid token\"\n            )\n\n        return data\n\n    except JWTError:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Invalid token\"\n        )\n"
  },
  {
    "path": "ch09/planner/database/__init__.py",
    "content": ""
  },
  {
    "path": "ch09/planner/database/connection.py",
    "content": "from typing import Optional, Any, List\n\nfrom beanie import init_beanie, PydanticObjectId\nfrom models.events import Event\nfrom models.users import User\nfrom motor.motor_asyncio import AsyncIOMotorClient\nfrom pydantic import BaseSettings, BaseModel\n\n\nclass Settings(BaseSettings):\n    DATABASE_URL: Optional[str] = None\n    SECRET_KEY: Optional[str] = \"default\"\n\n    async def initialize_database(self):\n        client = AsyncIOMotorClient(self.DATABASE_URL)\n        await init_beanie(database=client.get_default_database(),\n                          document_models=[Event, User])\n\n    class Config:\n        env_file = \".env\"\n\n\nclass Database:\n    def __init__(self, model):\n        self.model = model\n\n    async def save(self, document):\n        await document.create()\n        return\n\n    async def get(self, id: PydanticObjectId) -> bool:\n        doc = await self.model.get(id)\n        if doc:\n            return doc\n        return False\n\n    async def get_all(self) -> List[Any]:\n        docs = await self.model.find_all().to_list()\n        return docs\n\n    async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:\n        doc_id = id\n        des_body = body.dict()\n\n        des_body = {k: v for k, v in des_body.items() if v is not None}\n        update_query = {\"$set\": {\n            field: value for field, value in des_body.items()\n        }}\n\n        doc = await self.get(doc_id)\n        if not doc:\n            return False\n        await doc.update(update_query)\n        return doc\n\n    async def delete(self, id: PydanticObjectId) -> bool:\n        doc = await self.get(id)\n        if not doc:\n            return False\n        await doc.delete()\n        return True\n"
  },
  {
    "path": "ch09/planner/docker-compose.yml",
    "content": "version: \"3\"\n\nservices:\n  api:\n    build: .\n    image: event-planner-api:latest\n    ports:\n      - \"8080:8080\"\n    env_file:\n      - .env.prod\n\n  database:\n    image: mongo\n    ports:\n      - \"27017\"\n    volumes:\n      - data:/data/db\n\nvolumes:\n  data:\n"
  },
  {
    "path": "ch09/planner/main.py",
    "content": "from fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import RedirectResponse\nfrom database.connection import Settings\n\nfrom routes.users import user_router\nfrom routes.events import event_router\n\nimport uvicorn\n\napp = FastAPI()\n\nsettings = Settings()\n\n\n# register origins\n\norigins = [\"*\"]\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=origins,\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n\n# Register routes\n\napp.include_router(user_router,  prefix=\"/user\")\napp.include_router(event_router, prefix=\"/event\")\n\n\n@app.on_event(\"startup\")\nasync def init_db():\n    await settings.initialize_database()\n\n\n@app.get(\"/\")\nasync def home():\n    return RedirectResponse(url=\"/event/\")\n\nif __name__ == '__main__':\n    uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8080, reload=True)"
  },
  {
    "path": "ch09/planner/models/__init__.py",
    "content": ""
  },
  {
    "path": "ch09/planner/models/events.py",
    "content": "from typing import Optional, List\n\nfrom beanie import Document\nfrom pydantic import BaseModel\n\n\nclass Event(Document):\n    creator: Optional[str]\n    title: str\n    image: str\n    description: str\n    tags: List[str]\n    location: str\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI Book Launch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n\n    class Settings:\n        name = \"events\"\n\n\nclass EventUpdate(BaseModel):\n    title: Optional[str]\n    image: Optional[str]\n    description: Optional[str]\n    tags: Optional[List[str]]\n    location: Optional[str]\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"title\": \"FastAPI BookLaunch\",\n                \"image\": \"https://linktomyimage.com/image.png\",\n                \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n                \"tags\": [\"python\", \"fastapi\", \"book\", \"launch\"],\n                \"location\": \"Google Meet\"\n            }\n        }\n"
  },
  {
    "path": "ch09/planner/models/users.py",
    "content": "from beanie import Document\n\nfrom pydantic import BaseModel, EmailStr\n\n\nclass User(Document):\n    email: EmailStr\n    password: str\n\n    class Settings:\n        name = \"users\"\n\n    class Config:\n        schema_extra = {\n            \"example\": {\n                \"email\": \"fastapi@packt.com\",\n                \"password\": \"strong!!!\"\n            }\n        }\n\n\nclass TokenResponse(BaseModel):\n    access_token: str\n    token_type: str\n"
  },
  {
    "path": "ch09/planner/pytest.ini",
    "content": "[pytest]\nasyncio_mode=auto\n"
  },
  {
    "path": "ch09/planner/requirements.txt",
    "content": "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.7.4\npytest==7.1.2\npython-multipart==.0.0.5\npython-dotenv==0.20.0\npython-jose==3.3.0\nsqlmodel==0.0.6\nuvicorn==0.17.6\n\n"
  },
  {
    "path": "ch09/planner/routes/__init__.py",
    "content": ""
  },
  {
    "path": "ch09/planner/routes/events.py",
    "content": "from typing import List\n\nfrom auth.authenticate import authenticate\nfrom beanie import PydanticObjectId\nfrom database.connection import Database\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom models.events import Event, EventUpdate\n\nevent_router = APIRouter(\n    tags=[\"Events\"]\n)\n\nevent_database = Database(Event)\n\n\n@event_router.get(\"/\", response_model=List[Event])\nasync def retrieve_all_events() -> List[Event]:\n    events = await event_database.get_all()\n    return events\n\n\n@event_router.get(\"/{id}\", response_model=Event)\nasync def retrieve_event(id: PydanticObjectId) -> Event:\n    event = await event_database.get(id)\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return event\n\n\n@event_router.post(\"/new\")\nasync def create_event(body: Event, user: str = Depends(authenticate)) -> dict:\n    body.creator = user\n    await event_database.save(body)\n    return {\n        \"message\": \"Event created successfully\"\n    }\n\n\n@event_router.put(\"/{id}\", response_model=Event)\nasync def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:\n    event = await event_database.get(id)\n    if event.creator != user:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Operation not allowed\"\n        )\n    updated_event = await event_database.update(id, body)\n    if not updated_event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    return updated_event\n\n\n@event_router.delete(\"/{id}\")\nasync def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:\n    event = await event_database.get(id)\n    if event.creator != user:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Operation not allowed\"\n        )\n    if not event:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Event with supplied ID does not exist\"\n        )\n    await event_database.delete(id)\n\n    return {\n        \"message\": \"Event deleted successfully.\"\n    }\n"
  },
  {
    "path": "ch09/planner/routes/users.py",
    "content": "from auth.hash_password import HashPassword\nfrom auth.jwt_handler import create_access_token\nfrom database.connection import Database\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom fastapi.security import OAuth2PasswordRequestForm\nfrom models.users import User, TokenResponse\n\nuser_router = APIRouter(\n    tags=[\"User\"],\n)\n\nuser_database = Database(User)\nhash_password = HashPassword()\n\n\n@user_router.post(\"/signup\")\nasync def sign_user_up(user: User) -> dict:\n    user_exist = await User.find_one(User.email == user.email)\n\n    if user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=\"User with email provided exists already.\"\n        )\n    hashed_password = hash_password.create_hash(user.password)\n    user.password = hashed_password\n    await user_database.save(user)\n    return {\n        \"message\": \"User created successfully\"\n    }\n\n\n@user_router.post(\"/signin\", response_model=TokenResponse)\nasync def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:\n    user_exist = await User.find_one(User.email == user.username)\n    if not user_exist:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"User with email does not exist.\"\n        )\n    if hash_password.verify_hash(user.password, user_exist.password):\n        access_token = create_access_token(user_exist.email)\n        return {\n            \"access_token\": access_token,\n            \"token_type\": \"Bearer\"\n        }\n\n    raise HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Invalid details passed.\"\n    )\n"
  },
  {
    "path": "ch09/planner/tests/conftest.py",
    "content": "import asyncio\n\nimport httpx\nimport pytest\nfrom database.connection import Settings\nfrom main import app\nfrom models.events import Event\nfrom models.users import User\n\n\n@pytest.fixture(scope=\"session\")\ndef event_loop():\n    loop = asyncio.get_event_loop()\n    yield loop\n    loop.close()\n\n\nasync def init_db():\n    test_settings = Settings()\n    test_settings.DATABASE_URL = \"mongodb://localhost:27017/testdb\"\n\n    await test_settings.initialize_database()\n\n\n@pytest.fixture(scope=\"session\")\nasync def default_client():\n    await init_db()\n    async with httpx.AsyncClient(app=app, base_url=\"http://app\") as client:\n        yield client\n\n        # Clean up resources\n        await Event.find_all().delete()\n        await User.find_all().delete()\n"
  },
  {
    "path": "ch09/planner/tests/test_arthmetic_operations.py",
    "content": "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(a: int, b: int) -> int:\n    return a * b\n\n\ndef divide(a: int, b: int) -> int:\n    return b // a\n\n\ndef test_add() -> None:\n    assert add(1, 1) == 2\n\n\ndef test_subtract() -> None:\n    assert subtract(2, 5) == 3\n\n\ndef test_multiply() -> None:\n    assert multiply(10, 10) == 100\n\n\ndef test_divide() -> None:\n    assert divide(25, 100) == 4\n"
  },
  {
    "path": "ch09/planner/tests/test_fixture.py",
    "content": "import pytest\n\n# Fixture is defined.\nfrom models.events import EventUpdate\n\n\n@pytest.fixture\ndef event() -> EventUpdate:\n    return EventUpdate(\n        title=\"FastAPI Book Launch\",\n        image=\"https://packt.com/fastapi.png\",\n        description=\"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n        tags=[\"python\", \"fastapi\", \"book\", \"launch\"],\n        location=\"Google Meet\"\n    )\n\n\ndef test_event_name(event: EventUpdate) -> None:\n    assert event.title == \"FastAPI Book Launch\"\n"
  },
  {
    "path": "ch09/planner/tests/test_login.py",
    "content": "import httpx\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_sign_new_user(default_client: httpx.AsyncClient) -> None:\n    payload = {\n        \"email\": \"testuser@packt.com\",\n        \"password\": \"testpassword\",\n    }\n\n    headers = {\n        \"accept\": \"application/json\",\n        \"Content-Type\": \"application/json\"\n    }\n\n    test_response = {\n        \"message\": \"User created successfully\"\n    }\n\n    response = await default_client.post(\"/user/signup\", json=payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json() == test_response\n\n\n@pytest.mark.asyncio\nasync def test_sign_user_in(default_client: httpx.AsyncClient) -> None:\n    payload = {\n        \"username\": \"testuser@packt.com\",\n        \"password\": \"testpassword\"\n    }\n\n    headers = {\n        \"accept\": \"application/json\",\n        \"Content-Type\": \"application/x-www-form-urlencoded\"\n    }\n\n    response = await default_client.post(\"/user/signin\", data=payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json()[\"token_type\"] == \"Bearer\"\n"
  },
  {
    "path": "ch09/planner/tests/test_routes.py",
    "content": "import httpx\nimport pytest\n\nfrom auth.jwt_handler import create_access_token\nfrom models.events import Event\n\n\n@pytest.fixture(scope=\"module\")\nasync def access_token() -> str:\n    return create_access_token(\"testuser@packt.com\")\n\n\n@pytest.fixture(scope=\"module\")\nasync def mock_event() -> Event:\n    new_event = Event(\n        creator=\"testuser@packt.com\",\n        title=\"FastAPI Book Launch\",\n        image=\"https://linktomyimage.com/image.png\",\n        description=\"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n        tags=[\"python\", \"fastapi\", \"book\", \"launch\"],\n        location=\"Google Meet\"\n    )\n\n    await Event.insert_one(new_event)\n\n    yield new_event\n\n\n@pytest.mark.asyncio\nasync def test_get_events(default_client: httpx.AsyncClient, mock_event: Event) -> None:\n    response = await default_client.get(\"/event/\")\n\n    assert response.status_code == 200\n    assert response.json()[0][\"_id\"] == str(mock_event.id)\n\n\n@pytest.mark.asyncio\nasync def test_get_event(default_client: httpx.AsyncClient, mock_event: Event) -> None:\n    url = f\"/event/{str(mock_event.id)}\"\n    response = await default_client.get(url)\n\n    assert response.status_code == 200\n    assert response.json()[\"creator\"] == mock_event.creator\n    assert response.json()[\"_id\"] == str(mock_event.id)\n\n\n@pytest.mark.asyncio\nasync def test_post_event(default_client: httpx.AsyncClient, access_token: str) -> None:\n    payload = {\n        \"title\": \"FastAPI Book Launch\",\n        \"image\": \"https://linktomyimage.com/image.png\",\n        \"description\": \"We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!\",\n        \"tags\": [\n            \"python\",\n            \"fastapi\",\n            \"book\",\n            \"launch\"\n        ],\n        \"location\": \"Google Meet\",\n    }\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": f\"Bearer {access_token}\"\n    }\n\n    test_response = {\n        \"message\": \"Event created successfully\"\n    }\n\n    response = await default_client.post(\"/event/new\", json=payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json() == test_response\n\n\n@pytest.mark.asyncio\nasync def test_get_events_count(default_client: httpx.AsyncClient) -> None:\n    response = await default_client.get(\"/event/\")\n\n    events = response.json()\n\n    assert response.status_code == 200\n    assert len(events) == 2\n\n\n@pytest.mark.asyncio\nasync def test_update_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:\n    test_payload = {\n        \"title\": \"Updated FastAPI event\"\n    }\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": f\"Bearer {access_token}\"\n    }\n\n    url = f\"/event/{str(mock_event.id)}\"\n\n    response = await default_client.put(url, json=test_payload, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json()[\"title\"] == test_payload[\"title\"]\n\n\n@pytest.mark.asyncio\nasync def test_delete_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:\n    test_response = {\n        \"message\": \"Event deleted successfully.\"\n    }\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": f\"Bearer {access_token}\"\n    }\n\n    url = f\"/event/{mock_event.id}\"\n\n    response = await default_client.delete(url, headers=headers)\n\n    assert response.status_code == 200\n    assert response.json() == test_response\n\n\n@pytest.mark.asyncio\nasync def test_get_event_again(default_client: httpx.AsyncClient, mock_event: Event) -> None:\n    url = f\"/event/{str(mock_event.id)}\"\n    response = await default_client.get(url)\n\n    assert response.status_code == 404\n"
  }
]