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 Building Python Web APIs with FastAPI 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! https://www.packtpub.com/ ## 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 * 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 If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.

https://packt.link/free-ebook/9781801076630

================================================ 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 ================================================ Packt Todo Application
{% block todo_container %}{% endblock %}
================================================ FILE: ch04/todos/templates/todo.html ================================================ {% extends "home.html" %} {% block todo_container %}

{% if todo %}

Todo ID: {{ todo.id }}

Item: {{ todo.item }}

{% else %}

Todos


{% endif %}
{% 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