├── ch01
├── hello.py
├── todos
│ ├── requirements.txt
│ └── api.py
└── Dockerfile
├── ch05
└── planner
│ ├── database
│ └── __init__.py
│ ├── models
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── routes
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── main.py
│ └── requirements.txt
├── ch06
└── planner
│ ├── database
│ ├── __init__.py
│ └── connection.py
│ ├── models
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── routes
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── requirements.txt
│ └── main.py
├── ch07
└── planner
│ ├── auth
│ ├── __init__.py
│ ├── hash_password.py
│ ├── authenticate.py
│ └── jwt_handler.py
│ ├── database
│ ├── __init__.py
│ └── connection.py
│ ├── models
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── routes
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── requirements.txt
│ └── main.py
├── ch08
└── planner
│ ├── auth
│ ├── __init__.py
│ ├── hash_password.py
│ ├── authenticate.py
│ └── jwt_handler.py
│ ├── database
│ ├── __init__.py
│ └── connection.py
│ ├── models
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── routes
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── pytest.ini
│ ├── tests
│ ├── test_arthmetic_operations.py
│ ├── test_fixture.py
│ ├── conftest.py
│ ├── test_login.py
│ └── test_routes.py
│ ├── main.py
│ └── requirements.txt
├── ch09
└── planner
│ ├── auth
│ ├── __init__.py
│ ├── hash_password.py
│ ├── authenticate.py
│ └── jwt_handler.py
│ ├── database
│ ├── __init__.py
│ └── connection.py
│ ├── models
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── routes
│ ├── __init__.py
│ ├── users.py
│ └── events.py
│ ├── .dockerignore
│ ├── pytest.ini
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── docker-compose.yml
│ ├── tests
│ ├── test_arthmetic_operations.py
│ ├── test_fixture.py
│ ├── conftest.py
│ ├── test_login.py
│ └── test_routes.py
│ └── main.py
├── .gitignore
├── ch02
└── todos
│ ├── requirements.txt
│ ├── api.py
│ ├── model.py
│ └── todo.py
├── ch03
└── todos
│ ├── requirements.txt
│ ├── api.py
│ ├── model.py
│ └── todo.py
├── ch04
└── todos
│ ├── requirements.txt
│ ├── api.py
│ ├── templates
│ ├── home.html
│ └── todo.html
│ ├── model.py
│ └── todo.py
├── LICENSE
└── README.md
/ch01/hello.py:
--------------------------------------------------------------------------------
1 | print("Hello!")
2 |
--------------------------------------------------------------------------------
/ch05/planner/database/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch05/planner/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch05/planner/routes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch06/planner/database/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch06/planner/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch06/planner/routes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch07/planner/auth/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch07/planner/database/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch07/planner/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch07/planner/routes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch08/planner/auth/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch08/planner/database/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch08/planner/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch08/planner/routes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch09/planner/auth/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch09/planner/database/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch09/planner/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ch09/planner/routes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | venv
3 | .env
4 |
--------------------------------------------------------------------------------
/ch09/planner/.dockerignore:
--------------------------------------------------------------------------------
1 | venv
2 | .env
3 | .git
--------------------------------------------------------------------------------
/ch01/todos/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi
2 | uvicorn
3 |
--------------------------------------------------------------------------------
/ch08/planner/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | asyncio_mode=auto
3 |
--------------------------------------------------------------------------------
/ch09/planner/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | asyncio_mode=auto
3 |
--------------------------------------------------------------------------------
/ch02/todos/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi==0.70.0
2 | uvicorn==0.15.0
3 |
--------------------------------------------------------------------------------
/ch03/todos/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi==0.70.0
2 | uvicorn==0.15.0
3 |
--------------------------------------------------------------------------------
/ch04/todos/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi==0.70.0
2 | uvicorn==0.15.0
3 | jinja2 == 3.1.2
4 | python-multipart
5 |
--------------------------------------------------------------------------------
/ch01/todos/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | app = FastAPI()
4 |
5 |
6 | @app.get("/")
7 | async def welcome() -> dict:
8 | return {
9 | "message": "Hello World"
10 | }
11 |
--------------------------------------------------------------------------------
/ch01/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM PYTHON:3.8
2 | # Set working directory to /usr/src/app
3 | WORKDIR /usr/src/app
4 | # Copy the contents of the current local directory into the container’s working directory
5 | ADD . /usr/src/app
6 | # Run a command
7 | CMD [“python”, “hello.py”]
8 |
--------------------------------------------------------------------------------
/ch09/planner/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10
2 |
3 | WORKDIR /app
4 |
5 | ADD requirements.txt /app/requirements.txt
6 |
7 | RUN pip install --upgrade pip && pip install -r /app/requirements.txt
8 |
9 | EXPOSE 8080
10 |
11 | COPY ./ /app
12 |
13 | CMD ["python", "main.py"]
14 |
--------------------------------------------------------------------------------
/ch02/todos/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | from todo import todo_router
4 |
5 | app = FastAPI()
6 |
7 |
8 | @app.get("/")
9 | async def welcome() -> dict:
10 | return {
11 | "message": "Hello World"
12 | }
13 |
14 |
15 | app.include_router(todo_router)
16 |
--------------------------------------------------------------------------------
/ch03/todos/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | from todo import todo_router
4 |
5 | app = FastAPI()
6 |
7 |
8 | @app.get("/")
9 | async def welcome() -> dict:
10 | return {
11 | "message": "Hello World"
12 | }
13 |
14 |
15 | app.include_router(todo_router)
16 |
--------------------------------------------------------------------------------
/ch04/todos/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | from todo import todo_router
4 |
5 | app = FastAPI()
6 |
7 |
8 | @app.get("/")
9 | async def welcome() -> dict:
10 | return {
11 | "message": "Hello World"
12 | }
13 |
14 |
15 | app.include_router(todo_router)
16 |
--------------------------------------------------------------------------------
/ch09/planner/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi==0.78.0
2 | bcrypt==3.2.2
3 | beanie==1.11.1
4 | email-validator==1.2.1
5 | httpx==0.22.0
6 | Jinja2==3.0.3
7 | motor==2.5.1
8 | passlib==1.7.4
9 | pytest==7.1.2
10 | python-multipart==.0.0.5
11 | python-dotenv==0.20.0
12 | python-jose==3.3.0
13 | sqlmodel==0.0.6
14 | uvicorn==0.17.6
15 |
16 |
--------------------------------------------------------------------------------
/ch09/planner/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | api:
5 | build: .
6 | image: event-planner-api:latest
7 | ports:
8 | - "8080:8080"
9 | env_file:
10 | - .env.prod
11 |
12 | database:
13 | image: mongo
14 | ports:
15 | - "27017"
16 | volumes:
17 | - data:/data/db
18 |
19 | volumes:
20 | data:
21 |
--------------------------------------------------------------------------------
/ch07/planner/auth/hash_password.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
4 |
5 |
6 | class HashPassword:
7 | def create_hash(self, password: str):
8 | return pwd_context.hash(password)
9 |
10 | def verify_hash(self, plain_password: str, hashed_password: str):
11 | return pwd_context.verify(plain_password, hashed_password)
12 |
--------------------------------------------------------------------------------
/ch08/planner/auth/hash_password.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
4 |
5 |
6 | class HashPassword:
7 | def create_hash(self, password: str) -> str:
8 | return pwd_context.hash(password)
9 |
10 | def verify_hash(self, plain_password: str, hashed_password: str) -> bool:
11 | return pwd_context.verify(plain_password, hashed_password)
12 |
--------------------------------------------------------------------------------
/ch09/planner/auth/hash_password.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
4 |
5 |
6 | class HashPassword:
7 | def create_hash(self, password: str) -> str:
8 | return pwd_context.hash(password)
9 |
10 | def verify_hash(self, plain_password: str, hashed_password: str) -> bool:
11 | return pwd_context.verify(plain_password, hashed_password)
12 |
--------------------------------------------------------------------------------
/ch06/planner/models/users.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class User(Document):
7 | email: EmailStr
8 | password: str
9 |
10 | class Settings:
11 | name = "users"
12 |
13 | class Config:
14 | schema_extra = {
15 | "example": {
16 | "email": "fastapi@packt.com",
17 | "password": "strong!!!",
18 | }
19 | }
20 |
21 |
22 | class UserSignIn(BaseModel):
23 | email: EmailStr
24 | password: str
25 |
--------------------------------------------------------------------------------
/ch02/todos/model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Todo(BaseModel):
5 | id: int
6 | item: str
7 |
8 | class Config:
9 | schema_extra = {
10 | "example": {
11 | "id": 1,
12 | "item": "Example Schema!"
13 | }
14 | }
15 |
16 |
17 | class TodoItem(BaseModel):
18 | item: str
19 |
20 | class Config:
21 | schema_extra = {
22 | "example": {
23 | "item": "Read the next chapter of the book"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ch07/planner/models/users.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class User(Document):
7 | email: EmailStr
8 | password: str
9 |
10 | class Collection:
11 | name = "users"
12 |
13 | class Config:
14 | schema_extra = {
15 | "example": {
16 | "email": "fastapi@packt.com",
17 | "password": "strong!!!"
18 | }
19 | }
20 |
21 |
22 | class TokenResponse(BaseModel):
23 | access_token: str
24 | token_type: str
25 |
--------------------------------------------------------------------------------
/ch08/planner/models/users.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class User(Document):
7 | email: EmailStr
8 | password: str
9 |
10 | class Settings:
11 | name = "users"
12 |
13 | class Config:
14 | schema_extra = {
15 | "example": {
16 | "email": "fastapi@packt.com",
17 | "password": "strong!!!"
18 | }
19 | }
20 |
21 |
22 | class TokenResponse(BaseModel):
23 | access_token: str
24 | token_type: str
25 |
--------------------------------------------------------------------------------
/ch09/planner/models/users.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 | from pydantic import BaseModel, EmailStr
4 |
5 |
6 | class User(Document):
7 | email: EmailStr
8 | password: str
9 |
10 | class Settings:
11 | name = "users"
12 |
13 | class Config:
14 | schema_extra = {
15 | "example": {
16 | "email": "fastapi@packt.com",
17 | "password": "strong!!!"
18 | }
19 | }
20 |
21 |
22 | class TokenResponse(BaseModel):
23 | access_token: str
24 | token_type: str
25 |
--------------------------------------------------------------------------------
/ch05/planner/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 | from fastapi.responses import RedirectResponse
3 |
4 | from routes.users import user_router
5 | from routes.events import event_router
6 |
7 | import uvicorn
8 |
9 | app = FastAPI()
10 |
11 | # Register routes
12 |
13 | app.include_router(user_router, prefix="/user")
14 | app.include_router(event_router, prefix="/event")
15 |
16 |
17 | @app.get("/")
18 | async def home():
19 | return RedirectResponse(url="/event/")
20 |
21 | if __name__ == '__main__':
22 | uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
--------------------------------------------------------------------------------
/ch07/planner/auth/authenticate.py:
--------------------------------------------------------------------------------
1 | from auth.jwt_handler import verify_access_token
2 | from fastapi import Depends, HTTPException, status
3 | from fastapi.security import OAuth2PasswordBearer
4 |
5 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")
6 |
7 |
8 | async def authenticate(token: str = Depends(oauth2_scheme)) -> str:
9 | if not token:
10 | raise HTTPException(
11 | status_code=status.HTTP_403_FORBIDDEN,
12 | detail="Sign in for access"
13 | )
14 |
15 | decoded_token = verify_access_token(token)
16 | return decoded_token["user"]
17 |
--------------------------------------------------------------------------------
/ch08/planner/auth/authenticate.py:
--------------------------------------------------------------------------------
1 | from auth.jwt_handler import verify_access_token
2 | from fastapi import Depends, HTTPException, status
3 | from fastapi.security import OAuth2PasswordBearer
4 |
5 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")
6 |
7 |
8 | async def authenticate(token: str = Depends(oauth2_scheme)) -> str:
9 | if not token:
10 | raise HTTPException(
11 | status_code=status.HTTP_403_FORBIDDEN,
12 | detail="Sign in for access"
13 | )
14 |
15 | decoded_token = await verify_access_token(token)
16 | return decoded_token["user"]
17 |
--------------------------------------------------------------------------------
/ch09/planner/auth/authenticate.py:
--------------------------------------------------------------------------------
1 | from auth.jwt_handler import verify_access_token
2 | from fastapi import Depends, HTTPException, status
3 | from fastapi.security import OAuth2PasswordBearer
4 |
5 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")
6 |
7 |
8 | async def authenticate(token: str = Depends(oauth2_scheme)) -> str:
9 | if not token:
10 | raise HTTPException(
11 | status_code=status.HTTP_403_FORBIDDEN,
12 | detail="Sign in for access"
13 | )
14 |
15 | decoded_token = await verify_access_token(token)
16 | return decoded_token["user"]
17 |
--------------------------------------------------------------------------------
/ch05/planner/models/users.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, EmailStr
2 |
3 |
4 | class User(BaseModel):
5 | email: EmailStr
6 | password: str
7 |
8 | class Config:
9 | schema_extra = {
10 | "example": {
11 | "email": "fastapi@packt.com",
12 | "password": "strong!!!",
13 | }
14 | }
15 |
16 |
17 | class UserSignIn(BaseModel):
18 | email: EmailStr
19 | password: str
20 |
21 | schema_extra = {
22 | "example": {
23 | "email": "fastapi@packt.com",
24 | "password": "strong!!!"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ch08/planner/tests/test_arthmetic_operations.py:
--------------------------------------------------------------------------------
1 | def add(a: int, b: int) -> int:
2 | return a + b
3 |
4 |
5 | def subtract(a: int, b: int) -> int:
6 | return b - a
7 |
8 |
9 | def multiply(a: int, b: int) -> int:
10 | return a * b
11 |
12 |
13 | def divide(a: int, b: int) -> int:
14 | return b // a
15 |
16 |
17 | def test_add() -> None:
18 | assert add(1, 1) == 2
19 |
20 |
21 | def test_subtract() -> None:
22 | assert subtract(2, 5) == 3
23 |
24 |
25 | def test_multiply() -> None:
26 | assert multiply(10, 10) == 100
27 |
28 |
29 | def test_divide() -> None:
30 | assert divide(25, 100) == 4
31 |
--------------------------------------------------------------------------------
/ch09/planner/tests/test_arthmetic_operations.py:
--------------------------------------------------------------------------------
1 | def add(a: int, b: int) -> int:
2 | return a + b
3 |
4 |
5 | def subtract(a: int, b: int) -> int:
6 | return b - a
7 |
8 |
9 | def multiply(a: int, b: int) -> int:
10 | return a * b
11 |
12 |
13 | def divide(a: int, b: int) -> int:
14 | return b // a
15 |
16 |
17 | def test_add() -> None:
18 | assert add(1, 1) == 2
19 |
20 |
21 | def test_subtract() -> None:
22 | assert subtract(2, 5) == 3
23 |
24 |
25 | def test_multiply() -> None:
26 | assert multiply(10, 10) == 100
27 |
28 |
29 | def test_divide() -> None:
30 | assert divide(25, 100) == 4
31 |
--------------------------------------------------------------------------------
/ch08/planner/tests/test_fixture.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | # Fixture is defined.
4 | from models.events import EventUpdate
5 |
6 |
7 | @pytest.fixture
8 | def event() -> EventUpdate:
9 | return EventUpdate(
10 | title="FastAPI Book Launch",
11 | image="https://packt.com/fastapi.png",
12 | description="We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
13 | tags=["python", "fastapi", "book", "launch"],
14 | location="Google Meet"
15 | )
16 |
17 |
18 | def test_event_name(event: EventUpdate) -> None:
19 | assert event.title == "FastAPI Book Launch"
20 |
--------------------------------------------------------------------------------
/ch09/planner/tests/test_fixture.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | # Fixture is defined.
4 | from models.events import EventUpdate
5 |
6 |
7 | @pytest.fixture
8 | def event() -> EventUpdate:
9 | return EventUpdate(
10 | title="FastAPI Book Launch",
11 | image="https://packt.com/fastapi.png",
12 | description="We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
13 | tags=["python", "fastapi", "book", "launch"],
14 | location="Google Meet"
15 | )
16 |
17 |
18 | def test_event_name(event: EventUpdate) -> None:
19 | assert event.title == "FastAPI Book Launch"
20 |
--------------------------------------------------------------------------------
/ch05/planner/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==3.5.0
2 | asgiref==3.5.0
3 | bcrypt==3.2.0
4 | beanie==1.10.4
5 | cffi==1.15.0
6 | click==8.0.4
7 | dnspython==2.2.0
8 | email-validator==1.1.3
9 | fastapi==0.74.1
10 | h11==0.13.0
11 | idna==3.3
12 | Jinja2==3.0.3
13 | MarkupSafe==2.1.0
14 | motor==2.5.1
15 | multidict==6.0.2
16 | passlib==1.7.4
17 | pycparser==2.21
18 | pydantic==1.9.0
19 | PyJWT==2.3.0
20 | pymongo==3.12.3
21 | python-dotenv==0.20.0
22 | python-multipart==0.0.5
23 | six==1.16.0
24 | sniffio==1.2.0
25 | SQLAlchemy==1.4.32
26 | sqlalchemy2-stubs==0.0.2a20
27 | sqlmodel==0.0.6
28 | starlette==0.17.1
29 | toml==0.10.2
30 | typing_extensions==4.1.1
31 | uvicorn==0.17.5
32 | yarl==1.7.2
33 |
--------------------------------------------------------------------------------
/ch06/planner/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==3.5.0
2 | asgiref==3.5.0
3 | bcrypt==3.2.0
4 | beanie==1.10.4
5 | cffi==1.15.0
6 | click==8.0.4
7 | dnspython==2.2.0
8 | email-validator==1.1.3
9 | fastapi==0.74.1
10 | h11==0.13.0
11 | idna==3.3
12 | Jinja2==3.0.3
13 | MarkupSafe==2.1.0
14 | motor==2.5.1
15 | multidict==6.0.2
16 | passlib==1.7.4
17 | pycparser==2.21
18 | pydantic==1.9.0
19 | PyJWT==2.3.0
20 | pymongo==3.12.3
21 | python-dotenv==0.20.0
22 | python-multipart==0.0.5
23 | six==1.16.0
24 | sniffio==1.2.0
25 | SQLAlchemy==1.4.32
26 | sqlalchemy2-stubs==0.0.2a20
27 | sqlmodel==0.0.6
28 | starlette==0.17.1
29 | toml==0.10.2
30 | typing_extensions==4.1.1
31 | uvicorn==0.17.5
32 | yarl==1.7.2
33 |
--------------------------------------------------------------------------------
/ch05/planner/models/events.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | class Event(BaseModel):
7 | id: int
8 | title: str
9 | image: str
10 | description: str
11 | tags: List[str]
12 | location: str
13 |
14 | class Config:
15 | schema_extra = {
16 | "example": {
17 | "title": "FastAPI Book Launch",
18 | "image": "https://linktomyimage.com/image.png",
19 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
20 | "tags": ["python", "fastapi", "book", "launch"],
21 | "location": "Google Meet"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ch07/planner/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==3.5.0
2 | asgiref==3.5.0
3 | bcrypt==3.2.0
4 | beanie==1.10.4
5 | cffi==1.15.0
6 | click==8.0.4
7 | cryptography==36.0.2
8 | dnspython==2.2.0
9 | ecdsa==0.17.0
10 | email-validator==1.1.3
11 | fastapi==0.74.1
12 | h11==0.13.0
13 | idna==3.3
14 | Jinja2==3.0.3
15 | MarkupSafe==2.1.0
16 | motor==2.5.1
17 | multidict==6.0.2
18 | passlib==1.7.4
19 | pyasn1==0.4.8
20 | pycparser==2.21
21 | pydantic==1.9.0
22 | pymongo==3.12.3
23 | python-dotenv==0.20.0
24 | python-jose==3.3.0
25 | python-multipart==0.0.5
26 | rsa==4.8
27 | six==1.16.0
28 | sniffio==1.2.0
29 | SQLAlchemy==1.4.32
30 | sqlalchemy2-stubs==0.0.2a20
31 | sqlmodel==0.0.6
32 | starlette==0.17.1
33 | toml==0.10.2
34 | typing_extensions==4.1.1
35 | uvicorn==0.17.5
36 | yarl==1.7.2
37 |
--------------------------------------------------------------------------------
/ch06/planner/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 | from fastapi import FastAPI
3 | from fastapi.responses import RedirectResponse
4 |
5 | from database.connection import Settings
6 | from routes.events import event_router
7 | from routes.users import user_router
8 |
9 | app = FastAPI()
10 |
11 | settings = Settings()
12 |
13 | # Register routes
14 |
15 | app.include_router(user_router, prefix="/user")
16 | app.include_router(event_router, prefix="/event")
17 |
18 |
19 | @app.on_event("startup")
20 | async def init_db():
21 | await settings.initialize_database()
22 |
23 |
24 | @app.get("/")
25 | async def home():
26 | return RedirectResponse(url="/event/")
27 |
28 |
29 | if __name__ == '__main__':
30 | uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
31 |
--------------------------------------------------------------------------------
/ch09/planner/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import httpx
4 | import pytest
5 | from database.connection import Settings
6 | from main import app
7 | from models.events import Event
8 | from models.users import User
9 |
10 |
11 | @pytest.fixture(scope="session")
12 | def event_loop():
13 | loop = asyncio.get_event_loop()
14 | yield loop
15 | loop.close()
16 |
17 |
18 | async def init_db():
19 | test_settings = Settings()
20 | test_settings.DATABASE_URL = "mongodb://localhost:27017/testdb"
21 |
22 | await test_settings.initialize_database()
23 |
24 |
25 | @pytest.fixture(scope="session")
26 | async def default_client():
27 | await init_db()
28 | async with httpx.AsyncClient(app=app, base_url="http://app") as client:
29 | yield client
30 |
31 | # Clean up resources
32 | await Event.find_all().delete()
33 | await User.find_all().delete()
34 |
--------------------------------------------------------------------------------
/ch08/planner/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import httpx
4 | import pytest
5 |
6 | from database.connection import Settings
7 | from main import app
8 | from models.events import Event
9 | from models.users import User
10 |
11 |
12 | @pytest.fixture(scope="session")
13 | def event_loop():
14 | loop = asyncio.get_event_loop()
15 | yield loop
16 | loop.close()
17 |
18 |
19 | async def init_db():
20 | test_settings = Settings()
21 | test_settings.DATABASE_URL = "mongodb://localhost:27017/testdb"
22 |
23 | await test_settings.initialize_database()
24 |
25 |
26 | @pytest.fixture(scope="session")
27 | async def default_client():
28 | await init_db()
29 | async with httpx.AsyncClient(app=app, base_url="http://app") as client:
30 | yield client
31 |
32 | # Clean up resources
33 | await Event.find_all().delete()
34 | await User.find_all().delete()
35 |
--------------------------------------------------------------------------------
/ch03/todos/model.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | class Todo(BaseModel):
7 | id: int
8 | item: str
9 |
10 | class Config:
11 | schema_extra = {
12 | "example": {
13 | "id": 1,
14 | "item": "Example schema!"
15 | }
16 | }
17 |
18 |
19 | class TodoItem(BaseModel):
20 | item: str
21 |
22 | class Config:
23 | schema_extra = {
24 | "example": {
25 | "item": "Read the next chapter of the book"
26 | }
27 | }
28 |
29 |
30 | class TodoItems(BaseModel):
31 | todos: List[TodoItem]
32 |
33 | class Config:
34 | schema_extra = {
35 | "example": {
36 | "todos": [
37 | {
38 | "item": "Example schema 1!"
39 | },
40 | {
41 | "item": "Example schema 2!"
42 | }
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ch07/planner/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 | from fastapi import FastAPI
3 | from fastapi.responses import RedirectResponse
4 | from fastapi.middleware.cors import CORSMiddleware
5 |
6 | from database.connection import Settings
7 | from routes.events import event_router
8 | from routes.users import user_router
9 |
10 | app = FastAPI()
11 |
12 | settings = Settings()
13 |
14 | # register origins
15 |
16 | origins = ["*"]
17 |
18 | app.add_middleware(
19 | CORSMiddleware,
20 | allow_origins=origins,
21 | allow_credentials=True,
22 | allow_methods=["*"],
23 | allow_headers=["*"],
24 | )
25 |
26 | # Register routes
27 |
28 | app.include_router(user_router, prefix="/user")
29 | app.include_router(event_router, prefix="/event")
30 |
31 |
32 | @app.on_event("startup")
33 | async def init_db():
34 | await settings.initialize_database()
35 |
36 |
37 | @app.get("/")
38 | async def home():
39 | return RedirectResponse(url="/event/")
40 |
41 |
42 | if __name__ == '__main__':
43 | uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
44 |
--------------------------------------------------------------------------------
/ch08/planner/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 | from fastapi import FastAPI
3 | from fastapi.responses import RedirectResponse
4 | from fastapi.middleware.cors import CORSMiddleware
5 |
6 | from database.connection import Settings
7 | from routes.events import event_router
8 | from routes.users import user_router
9 |
10 | app = FastAPI()
11 |
12 | settings = Settings()
13 |
14 |
15 | # register origins
16 |
17 | origins = ["*"]
18 |
19 | app.add_middleware(
20 | CORSMiddleware,
21 | allow_origins=origins,
22 | allow_credentials=True,
23 | allow_methods=["*"],
24 | allow_headers=["*"],
25 | )
26 |
27 | # Register routes
28 |
29 | app.include_router(user_router, prefix="/user")
30 | app.include_router(event_router, prefix="/event")
31 |
32 |
33 | @app.on_event("startup")
34 | async def init_db():
35 | await settings.initialize_database()
36 |
37 |
38 | @app.get("/")
39 | async def home():
40 | return RedirectResponse(url="/event/")
41 |
42 |
43 | if __name__ == '__main__':
44 | uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
45 |
--------------------------------------------------------------------------------
/ch09/planner/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 | from fastapi.middleware.cors import CORSMiddleware
3 | from fastapi.responses import RedirectResponse
4 | from database.connection import Settings
5 |
6 | from routes.users import user_router
7 | from routes.events import event_router
8 |
9 | import uvicorn
10 |
11 | app = FastAPI()
12 |
13 | settings = Settings()
14 |
15 |
16 | # register origins
17 |
18 | origins = ["*"]
19 |
20 | app.add_middleware(
21 | CORSMiddleware,
22 | allow_origins=origins,
23 | allow_credentials=True,
24 | allow_methods=["*"],
25 | allow_headers=["*"],
26 | )
27 |
28 |
29 | # Register routes
30 |
31 | app.include_router(user_router, prefix="/user")
32 | app.include_router(event_router, prefix="/event")
33 |
34 |
35 | @app.on_event("startup")
36 | async def init_db():
37 | await settings.initialize_database()
38 |
39 |
40 | @app.get("/")
41 | async def home():
42 | return RedirectResponse(url="/event/")
43 |
44 | if __name__ == '__main__':
45 | uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
--------------------------------------------------------------------------------
/ch08/planner/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==3.5.0
2 | asgi-lifespan==1.0.1
3 | asgiref==3.5.0
4 | attrs==21.4.0
5 | bcrypt==3.2.2
6 | beanie==1.11.0
7 | certifi==2021.10.8
8 | cffi==1.15.0
9 | charset-normalizer==2.0.12
10 | click==8.0.4
11 | coverage==6.3.3
12 | cryptography==36.0.2
13 | dnspython==2.2.1
14 | ecdsa==0.17.0
15 | email-validator==1.1.3
16 | fastapi==0.77.1
17 | h11==0.12.0
18 | httpcore==0.14.7
19 | httpx==0.22.0
20 | idna==3.3
21 | iniconfig==1.1.1
22 | Jinja2==3.0.3
23 | MarkupSafe==2.1.0
24 | motor==3.0.0
25 | multidict==6.0.2
26 | packaging==21.3
27 | passlib==1.7.4
28 | pluggy==1.0.0
29 | py==1.11.0
30 | pyasn1==0.4.8
31 | pycparser==2.21
32 | pydantic==1.9.0
33 | pymongo==4.1.1
34 | pyparsing==3.0.9
35 | pytest==7.1.2
36 | pytest-asyncio==0.18.3
37 | python-dotenv==0.20.0
38 | python-jose==3.3.0
39 | python-multipart==0.0.5
40 | rfc3986==1.5.0
41 | rsa==4.8
42 | six==1.16.0
43 | sniffio==1.2.0
44 | SQLAlchemy==1.4.32
45 | sqlalchemy2-stubs==0.0.2a20
46 | sqlmodel==0.0.6
47 | starlette==0.19.1
48 | toml==0.10.2
49 | tomli==2.0.1
50 | typing_extensions==4.1.1
51 | uvicorn==0.17.6
52 | yarl==1.7.2
53 |
--------------------------------------------------------------------------------
/ch04/todos/templates/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Packt Todo Application
8 |
10 |
12 |
13 |
14 |
23 |
24 | {% block todo_container %}{% endblock %}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ch05/planner/routes/users.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, HTTPException, status
2 |
3 | from models.users import User, UserSignIn
4 |
5 | user_router = APIRouter(
6 | tags=["User"],
7 | )
8 |
9 | users = {}
10 |
11 |
12 | @user_router.post("/signup")
13 | async def sign_user_up(data: User) -> dict:
14 | if data.email in users:
15 | raise HTTPException(
16 | status_code=status.HTTP_409_CONFLICT,
17 | detail="User with supplied username exists"
18 | )
19 |
20 | users[data.email] = data
21 |
22 | return {
23 | "message": "User successfully registered!"
24 | }
25 |
26 |
27 | @user_router.post("/signin")
28 | async def sign_user_in(user: UserSignIn) -> dict:
29 | if user.email not in users:
30 | raise HTTPException(
31 | status_code=status.HTTP_404_NOT_FOUND,
32 | detail="User does not exist"
33 | )
34 |
35 | if users[user.email].password != user.password:
36 | raise HTTPException(
37 | status_code=status.HTTP_403_FORBIDDEN,
38 | detail="Wrong credential passed"
39 | )
40 | return {
41 | "message": "User signed in successfully"
42 | }
43 |
--------------------------------------------------------------------------------
/ch04/todos/model.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 |
3 | from fastapi import Form
4 | from pydantic import BaseModel
5 |
6 |
7 | class Todo(BaseModel):
8 | id: Optional[int]
9 | item: str
10 |
11 | @classmethod
12 | def as_form(
13 | cls,
14 | item: str = Form(...)
15 | ):
16 | return cls(item=item)
17 |
18 | class Config:
19 | schema_extra = {
20 | "example": {
21 | "id": 1,
22 | "item": "Example schema!"
23 | }
24 | }
25 |
26 |
27 | class TodoItem(BaseModel):
28 | item: str
29 |
30 | class Config:
31 | schema_extra = {
32 | "example": {
33 | "item": "Read the next chapter of the book"
34 | }
35 | }
36 |
37 |
38 | class TodoItems(BaseModel):
39 | todos: List[TodoItem]
40 |
41 | class Config:
42 | schema_extra = {
43 | "example": {
44 | "todos": [
45 | {
46 | "item": "Example schema 1!"
47 | },
48 | {
49 | "item": "Example schema 2!"
50 | }
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ch08/planner/tests/test_login.py:
--------------------------------------------------------------------------------
1 | import httpx
2 | import pytest
3 |
4 |
5 | @pytest.mark.asyncio
6 | async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
7 | payload = {
8 | "email": "testuser@packt.com",
9 | "password": "testpassword",
10 | }
11 |
12 | headers = {
13 | "accept": "application/json",
14 | "Content-Type": "application/json"
15 | }
16 |
17 | test_response = {
18 | "message": "User created successfully"
19 | }
20 |
21 | response = await default_client.post("/user/signup", json=payload, headers=headers)
22 |
23 | assert response.status_code == 200
24 | assert response.json() == test_response
25 |
26 |
27 | @pytest.mark.asyncio
28 | async def test_sign_user_in(default_client: httpx.AsyncClient) -> None:
29 | payload = {
30 | "username": "testuser@packt.com",
31 | "password": "testpassword"
32 | }
33 |
34 | headers = {
35 | "accept": "application/json",
36 | "Content-Type": "application/x-www-form-urlencoded"
37 | }
38 |
39 | response = await default_client.post("/user/signin", data=payload, headers=headers)
40 |
41 | assert response.status_code == 200
42 | assert response.json()["token_type"] == "Bearer"
43 |
--------------------------------------------------------------------------------
/ch09/planner/tests/test_login.py:
--------------------------------------------------------------------------------
1 | import httpx
2 | import pytest
3 |
4 |
5 | @pytest.mark.asyncio
6 | async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
7 | payload = {
8 | "email": "testuser@packt.com",
9 | "password": "testpassword",
10 | }
11 |
12 | headers = {
13 | "accept": "application/json",
14 | "Content-Type": "application/json"
15 | }
16 |
17 | test_response = {
18 | "message": "User created successfully"
19 | }
20 |
21 | response = await default_client.post("/user/signup", json=payload, headers=headers)
22 |
23 | assert response.status_code == 200
24 | assert response.json() == test_response
25 |
26 |
27 | @pytest.mark.asyncio
28 | async def test_sign_user_in(default_client: httpx.AsyncClient) -> None:
29 | payload = {
30 | "username": "testuser@packt.com",
31 | "password": "testpassword"
32 | }
33 |
34 | headers = {
35 | "accept": "application/json",
36 | "Content-Type": "application/x-www-form-urlencoded"
37 | }
38 |
39 | response = await default_client.post("/user/signin", data=payload, headers=headers)
40 |
41 | assert response.status_code == 200
42 | assert response.json()["token_type"] == "Bearer"
43 |
--------------------------------------------------------------------------------
/ch07/planner/auth/jwt_handler.py:
--------------------------------------------------------------------------------
1 | import time
2 | from datetime import datetime
3 |
4 | from database.connection import Settings
5 | from fastapi import HTTPException, status
6 | from jose import jwt, JWTError
7 |
8 | settings = Settings()
9 |
10 |
11 | def create_access_token(user: str):
12 | payload = {
13 | "user": user,
14 | "expires": time.time() + 3600
15 | }
16 |
17 | token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
18 | return token
19 |
20 |
21 | def verify_access_token(token: str):
22 | try:
23 | data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
24 |
25 | expire = data.get("expires")
26 |
27 | if expire is None:
28 | raise HTTPException(
29 | status_code=status.HTTP_400_BAD_REQUEST,
30 | detail="No access token supplied"
31 | )
32 | if datetime.utcnow() > datetime.utcfromtimestamp(expire):
33 | raise HTTPException(
34 | status_code=status.HTTP_403_FORBIDDEN,
35 | detail="Token expired!"
36 | )
37 | return data
38 |
39 | except JWTError:
40 | raise HTTPException(
41 | status_code=status.HTTP_400_BAD_REQUEST,
42 | detail="Invalid token"
43 | )
44 |
--------------------------------------------------------------------------------
/ch06/planner/routes/users.py:
--------------------------------------------------------------------------------
1 | from database.connection import Database
2 | from fastapi import APIRouter, HTTPException, status
3 | from models.users import User, UserSignIn
4 |
5 | user_router = APIRouter(
6 | tags=["User"],
7 | )
8 |
9 | user_database = Database(User)
10 |
11 |
12 | @user_router.post("/signup")
13 | async def sign_user_up(user: User) -> dict:
14 | user_exist = await User.find_one(User.email == user.email)
15 |
16 | if user_exist:
17 | raise HTTPException(
18 | status_code=status.HTTP_409_CONFLICT,
19 | detail="User with email provided exists already."
20 | )
21 | await user_database.save(user)
22 | return {
23 | "message": "User created successfully"
24 | }
25 |
26 |
27 | @user_router.post("/signin")
28 | async def sign_user_in(user: UserSignIn) -> dict:
29 | user_exist = await User.find_one(User.email == user.email)
30 | if not user_exist:
31 | raise HTTPException(
32 | status_code=status.HTTP_404_NOT_FOUND,
33 | detail="User with email does not exist."
34 | )
35 | if user_exist.password == user.password:
36 | return {
37 | "message": "User signed in successfully."
38 | }
39 |
40 | raise HTTPException(
41 | status_code=status.HTTP_401_UNAUTHORIZED,
42 | detail="Invalid details passed."
43 | )
44 |
--------------------------------------------------------------------------------
/ch05/planner/routes/events.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from fastapi import APIRouter, Body, HTTPException, status
4 | from models.events import Event
5 |
6 | event_router = APIRouter(
7 | tags=["Events"]
8 | )
9 |
10 | events = []
11 |
12 |
13 | @event_router.get("/", response_model=List[Event])
14 | async def retrieve_all_events() -> List[Event]:
15 | return events
16 |
17 |
18 | @event_router.get("/{id}", response_model=Event)
19 | async def retrieve_event(id: int) -> Event:
20 | for event in events:
21 | if event.id == id:
22 | return event
23 | raise HTTPException(
24 | status_code=status.HTTP_404_NOT_FOUND,
25 | detail="Event with supplied ID does not exist"
26 | )
27 |
28 |
29 | @event_router.post("/new")
30 | async def create_event(body: Event = Body(...)) -> dict:
31 | events.append(body)
32 | return {
33 | "message": "Event created successfully"
34 | }
35 |
36 |
37 | @event_router.delete("/{id}")
38 | async def delete_event(id: int) -> dict:
39 | for event in events:
40 | if event.id == id:
41 | events.remove(event)
42 | return {
43 | "message": "Event deleted successfully"
44 | }
45 |
46 | raise HTTPException(
47 | status_code=status.HTTP_404_NOT_FOUND,
48 | detail="Event with supplied ID does not exist"
49 | )
50 |
--------------------------------------------------------------------------------
/ch06/planner/models/events.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, List
2 |
3 | from beanie import Document
4 | from pydantic import BaseModel
5 |
6 |
7 | class Event(Document):
8 | title: str
9 | image: str
10 | description: str
11 | tags: List[str]
12 | location: str
13 |
14 | class Config:
15 | schema_extra = {
16 | "example": {
17 | "title": "FastAPI BookLaunch",
18 | "image": "https://linktomyimage.com/image.png",
19 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
20 | "tags": ["python", "fastapi", "book", "launch"],
21 | "location": "Google Meet"
22 | }
23 | }
24 |
25 | class Settings:
26 | name = "events"
27 |
28 |
29 | class EventUpdate(BaseModel):
30 | title: Optional[str]
31 | image: Optional[str]
32 | description: Optional[str]
33 | tags: Optional[List[str]]
34 | location: Optional[str]
35 |
36 | class Config:
37 | schema_extra = {
38 | "example": {
39 | "title": "FastAPI BookLaunch",
40 | "image": "https://linktomyimage.com/image.png",
41 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
42 | "tags": ["python", "fastapi", "book", "launch"],
43 | "location": "Google Meet"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ch07/planner/models/events.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, List
2 |
3 | from beanie import Document
4 | from pydantic import BaseModel
5 |
6 |
7 | class Event(Document):
8 | creator: Optional[str]
9 | title: str
10 | image: str
11 | description: str
12 | tags: List[str]
13 | location: str
14 |
15 | class Config:
16 | schema_extra = {
17 | "example": {
18 | "title": "FastAPI BookLaunch",
19 | "image": "https://linktomyimage.com/image.png",
20 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
21 | "tags": ["python", "fastapi", "book", "launch"],
22 | "location": "Google Meet"
23 | }
24 | }
25 |
26 | class Collection:
27 | name = "events"
28 |
29 |
30 | class EventUpdate(BaseModel):
31 | title: Optional[str]
32 | image: Optional[str]
33 | description: Optional[str]
34 | tags: Optional[List[str]]
35 | location: Optional[str]
36 |
37 | class Config:
38 | schema_extra = {
39 | "example": {
40 | "title": "FastAPI BookLaunch",
41 | "image": "https://linktomyimage.com/image.png",
42 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
43 | "tags": ["python", "fastapi", "book", "launch"],
44 | "location": "Google Meet"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ch08/planner/models/events.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, List
2 |
3 | from beanie import Document
4 | from pydantic import BaseModel
5 |
6 |
7 | class Event(Document):
8 | creator: Optional[str]
9 | title: str
10 | image: str
11 | description: str
12 | tags: List[str]
13 | location: str
14 |
15 | class Config:
16 | schema_extra = {
17 | "example": {
18 | "title": "FastAPI Book Launch",
19 | "image": "https://linktomyimage.com/image.png",
20 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
21 | "tags": ["python", "fastapi", "book", "launch"],
22 | "location": "Google Meet"
23 | }
24 | }
25 |
26 | class Settings:
27 | name = "events"
28 |
29 |
30 | class EventUpdate(BaseModel):
31 | title: Optional[str]
32 | image: Optional[str]
33 | description: Optional[str]
34 | tags: Optional[List[str]]
35 | location: Optional[str]
36 |
37 | class Config:
38 | schema_extra = {
39 | "example": {
40 | "title": "FastAPI BookLaunch",
41 | "image": "https://linktomyimage.com/image.png",
42 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
43 | "tags": ["python", "fastapi", "book", "launch"],
44 | "location": "Google Meet"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ch09/planner/models/events.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, List
2 |
3 | from beanie import Document
4 | from pydantic import BaseModel
5 |
6 |
7 | class Event(Document):
8 | creator: Optional[str]
9 | title: str
10 | image: str
11 | description: str
12 | tags: List[str]
13 | location: str
14 |
15 | class Config:
16 | schema_extra = {
17 | "example": {
18 | "title": "FastAPI Book Launch",
19 | "image": "https://linktomyimage.com/image.png",
20 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
21 | "tags": ["python", "fastapi", "book", "launch"],
22 | "location": "Google Meet"
23 | }
24 | }
25 |
26 | class Settings:
27 | name = "events"
28 |
29 |
30 | class EventUpdate(BaseModel):
31 | title: Optional[str]
32 | image: Optional[str]
33 | description: Optional[str]
34 | tags: Optional[List[str]]
35 | location: Optional[str]
36 |
37 | class Config:
38 | schema_extra = {
39 | "example": {
40 | "title": "FastAPI BookLaunch",
41 | "image": "https://linktomyimage.com/image.png",
42 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
43 | "tags": ["python", "fastapi", "book", "launch"],
44 | "location": "Google Meet"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ch08/planner/auth/jwt_handler.py:
--------------------------------------------------------------------------------
1 | import time
2 | from datetime import datetime
3 |
4 | from database.connection import Settings
5 | from fastapi import HTTPException, status
6 | from jose import jwt, JWTError
7 | from models.users import User
8 |
9 | settings = Settings()
10 |
11 |
12 | def create_access_token(user: str) -> str:
13 | payload = {
14 | "user": user,
15 | "expires": time.time() + 3600
16 | }
17 |
18 | token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
19 | return token
20 |
21 |
22 | async def verify_access_token(token: str) -> dict:
23 | try:
24 | data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
25 |
26 | expire = data.get("expires")
27 |
28 | if expire is None:
29 | raise HTTPException(
30 | status_code=status.HTTP_400_BAD_REQUEST,
31 | detail="No access token supplied"
32 | )
33 | if datetime.utcnow() > datetime.utcfromtimestamp(expire):
34 | raise HTTPException(
35 | status_code=status.HTTP_403_FORBIDDEN,
36 | detail="Token expired!"
37 | )
38 | user_exist = await User.find_one(User.email == data["user"])
39 | if not user_exist:
40 | raise HTTPException(
41 | status_code=status.HTTP_400_BAD_REQUEST,
42 | detail="Invalid token"
43 | )
44 |
45 | return data
46 |
47 | except JWTError:
48 | raise HTTPException(
49 | status_code=status.HTTP_400_BAD_REQUEST,
50 | detail="Invalid token"
51 | )
52 |
--------------------------------------------------------------------------------
/ch09/planner/auth/jwt_handler.py:
--------------------------------------------------------------------------------
1 | import time
2 | from datetime import datetime
3 |
4 | from database.connection import Settings
5 | from fastapi import HTTPException, status
6 | from jose import jwt, JWTError
7 | from models.users import User
8 |
9 | settings = Settings()
10 |
11 |
12 | def create_access_token(user: str) -> str:
13 | payload = {
14 | "user": user,
15 | "expires": time.time() + 3600
16 | }
17 |
18 | token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
19 | return token
20 |
21 |
22 | async def verify_access_token(token: str) -> dict:
23 | try:
24 | data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
25 |
26 | expire = data.get("expires")
27 |
28 | if expire is None:
29 | raise HTTPException(
30 | status_code=status.HTTP_400_BAD_REQUEST,
31 | detail="No access token supplied"
32 | )
33 | if datetime.utcnow() > datetime.utcfromtimestamp(expire):
34 | raise HTTPException(
35 | status_code=status.HTTP_403_FORBIDDEN,
36 | detail="Token expired!"
37 | )
38 | user_exist = await User.find_one(User.email == data["user"])
39 | if not user_exist:
40 | raise HTTPException(
41 | status_code=status.HTTP_400_BAD_REQUEST,
42 | detail="Invalid token"
43 | )
44 |
45 | return data
46 |
47 | except JWTError:
48 | raise HTTPException(
49 | status_code=status.HTTP_400_BAD_REQUEST,
50 | detail="Invalid token"
51 | )
52 |
--------------------------------------------------------------------------------
/ch04/todos/templates/todo.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block todo_container %}
4 |
5 |
6 |
7 |
22 |
23 |
24 | {% if todo %}
25 |
26 |
27 | Todo ID: {{ todo.id }}
28 |
29 |
30 | Item: {{ todo.item }}
31 |
32 |
33 |
34 | {% else %}
35 |
36 | Todos
37 |
38 |
39 |
40 | {% for todo in todos %}
41 | -
42 | {{ loop.index }}. {{ todo.item }}
43 |
44 | {% endfor %}
45 |
46 |
47 | {% endif %}
48 |
49 |
50 | {% endblock %}
--------------------------------------------------------------------------------
/ch07/planner/routes/users.py:
--------------------------------------------------------------------------------
1 | from auth.hash_password import HashPassword
2 | from auth.jwt_handler import create_access_token
3 | from database.connection import Database
4 | from fastapi import APIRouter, Depends, HTTPException, status
5 | from fastapi.security import OAuth2PasswordRequestForm
6 | from models.users import User, TokenResponse
7 |
8 | user_router = APIRouter(
9 | tags=["User"],
10 | )
11 |
12 | user_database = Database(User)
13 | hash_password = HashPassword()
14 |
15 |
16 | @user_router.post("/signup")
17 | async def sign_user_up(user: User) -> dict:
18 | user_exist = await User.find_one(User.email == user.email)
19 |
20 | if user_exist:
21 | raise HTTPException(
22 | status_code=status.HTTP_409_CONFLICT,
23 | detail="User with email provided exists already."
24 | )
25 | hashed_password = hash_password.create_hash(user.password)
26 | user.password = hashed_password
27 | await user_database.save(user)
28 | return {
29 | "message": "User created successfully"
30 | }
31 |
32 |
33 | @user_router.post("/signin", response_model=TokenResponse)
34 | async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:
35 | user_exist = await User.find_one(User.email == user.username)
36 | if not user_exist:
37 | raise HTTPException(
38 | status_code=status.HTTP_404_NOT_FOUND,
39 | detail="User with email does not exist."
40 | )
41 | if hash_password.verify_hash(user.password, user_exist.password):
42 | access_token = create_access_token(user_exist.email)
43 | return {
44 | "access_token": access_token,
45 | "token_type": "Bearer"
46 | }
47 |
48 | raise HTTPException(
49 | status_code=status.HTTP_401_UNAUTHORIZED,
50 | detail="Invalid details passed."
51 | )
52 |
--------------------------------------------------------------------------------
/ch08/planner/routes/users.py:
--------------------------------------------------------------------------------
1 | from auth.hash_password import HashPassword
2 | from auth.jwt_handler import create_access_token
3 | from database.connection import Database
4 | from fastapi import APIRouter, Depends, HTTPException, status
5 | from fastapi.security import OAuth2PasswordRequestForm
6 | from models.users import User, TokenResponse
7 |
8 | user_router = APIRouter(
9 | tags=["User"],
10 | )
11 |
12 | user_database = Database(User)
13 | hash_password = HashPassword()
14 |
15 |
16 | @user_router.post("/signup")
17 | async def sign_user_up(user: User) -> dict:
18 | user_exist = await User.find_one(User.email == user.email)
19 |
20 | if user_exist:
21 | raise HTTPException(
22 | status_code=status.HTTP_409_CONFLICT,
23 | detail="User with email provided exists already."
24 | )
25 | hashed_password = hash_password.create_hash(user.password)
26 | user.password = hashed_password
27 | await user_database.save(user)
28 | return {
29 | "message": "User created successfully"
30 | }
31 |
32 |
33 | @user_router.post("/signin", response_model=TokenResponse)
34 | async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:
35 | user_exist = await User.find_one(User.email == user.username)
36 | if not user_exist:
37 | raise HTTPException(
38 | status_code=status.HTTP_404_NOT_FOUND,
39 | detail="User with email does not exist."
40 | )
41 | if hash_password.verify_hash(user.password, user_exist.password):
42 | access_token = create_access_token(user_exist.email)
43 | return {
44 | "access_token": access_token,
45 | "token_type": "Bearer"
46 | }
47 |
48 | raise HTTPException(
49 | status_code=status.HTTP_401_UNAUTHORIZED,
50 | detail="Invalid details passed."
51 | )
52 |
--------------------------------------------------------------------------------
/ch09/planner/routes/users.py:
--------------------------------------------------------------------------------
1 | from auth.hash_password import HashPassword
2 | from auth.jwt_handler import create_access_token
3 | from database.connection import Database
4 | from fastapi import APIRouter, Depends, HTTPException, status
5 | from fastapi.security import OAuth2PasswordRequestForm
6 | from models.users import User, TokenResponse
7 |
8 | user_router = APIRouter(
9 | tags=["User"],
10 | )
11 |
12 | user_database = Database(User)
13 | hash_password = HashPassword()
14 |
15 |
16 | @user_router.post("/signup")
17 | async def sign_user_up(user: User) -> dict:
18 | user_exist = await User.find_one(User.email == user.email)
19 |
20 | if user_exist:
21 | raise HTTPException(
22 | status_code=status.HTTP_409_CONFLICT,
23 | detail="User with email provided exists already."
24 | )
25 | hashed_password = hash_password.create_hash(user.password)
26 | user.password = hashed_password
27 | await user_database.save(user)
28 | return {
29 | "message": "User created successfully"
30 | }
31 |
32 |
33 | @user_router.post("/signin", response_model=TokenResponse)
34 | async def sign_user_in(user: OAuth2PasswordRequestForm = Depends()) -> dict:
35 | user_exist = await User.find_one(User.email == user.username)
36 | if not user_exist:
37 | raise HTTPException(
38 | status_code=status.HTTP_404_NOT_FOUND,
39 | detail="User with email does not exist."
40 | )
41 | if hash_password.verify_hash(user.password, user_exist.password):
42 | access_token = create_access_token(user_exist.email)
43 | return {
44 | "access_token": access_token,
45 | "token_type": "Bearer"
46 | }
47 |
48 | raise HTTPException(
49 | status_code=status.HTTP_401_UNAUTHORIZED,
50 | detail="Invalid details passed."
51 | )
52 |
--------------------------------------------------------------------------------
/ch07/planner/database/connection.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from beanie import init_beanie, PydanticObjectId
4 | from models.events import Event
5 | from models.users import User
6 | from motor.motor_asyncio import AsyncIOMotorClient
7 | from pydantic import BaseSettings, BaseModel
8 |
9 |
10 | class Settings(BaseSettings):
11 | DATABASE_URL: Optional[str] = None
12 | SECRET_KEY: Optional[str] = None
13 |
14 | async def initialize_database(self):
15 | client = AsyncIOMotorClient(self.DATABASE_URL)
16 | await init_beanie(database=client.get_default_database(),
17 | document_models=[Event, User])
18 |
19 | class Config:
20 | env_file = ".env"
21 |
22 |
23 | class Database:
24 | def __init__(self, model):
25 | self.model = model
26 |
27 | async def save(self, document):
28 | await document.create()
29 | return
30 |
31 | async def get(self, id: PydanticObjectId):
32 | doc = await self.model.get(id)
33 | if doc:
34 | return doc
35 | return False
36 |
37 | async def get_all(self):
38 | docs = await self.model.find_all().to_list()
39 | return docs
40 |
41 | async def update(self, id: PydanticObjectId, body: BaseModel):
42 | doc_id = id
43 | des_body = body.dict()
44 |
45 | des_body = {k: v for k, v in des_body.items() if v is not None}
46 | update_query = {"$set": {
47 | field: value for field, value in des_body.items()
48 | }}
49 |
50 | doc = await self.get(doc_id)
51 | if not doc:
52 | return False
53 | await doc.update(update_query)
54 | return doc
55 |
56 | async def delete(self, id: PydanticObjectId):
57 | doc = await self.get(id)
58 | if not doc:
59 | return False
60 | await doc.delete()
61 | return True
62 |
--------------------------------------------------------------------------------
/ch06/planner/database/connection.py:
--------------------------------------------------------------------------------
1 | from typing import Any, List, Optional
2 |
3 | from beanie import init_beanie, PydanticObjectId
4 | from models.events import Event
5 | from models.users import User
6 | from motor.motor_asyncio import AsyncIOMotorClient
7 | from pydantic import BaseSettings, BaseModel
8 |
9 |
10 | class Settings(BaseSettings):
11 | DATABASE_URL: Optional[str] = None
12 |
13 | async def initialize_database(self):
14 | client = AsyncIOMotorClient(self.DATABASE_URL)
15 | await init_beanie(database=client.get_default_database(),
16 | document_models=[Event, User])
17 |
18 | class Config:
19 | env_file = ".env"
20 |
21 |
22 | class Database:
23 | def __init__(self, model):
24 | self.model = model
25 |
26 | async def save(self, document) -> None:
27 | await document.create()
28 | return
29 |
30 | async def get(self, id: PydanticObjectId) -> Any:
31 | doc = await self.model.get(id)
32 | if doc:
33 | return doc
34 | return False
35 |
36 | async def get_all(self) -> List[Any]:
37 | docs = await self.model.find_all().to_list()
38 | return docs
39 |
40 | async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
41 | doc_id = id
42 | des_body = body.dict()
43 |
44 | des_body = {k: v for k, v in des_body.items() if v is not None}
45 | update_query = {"$set": {
46 | field: value for field, value in des_body.items()
47 | }}
48 |
49 | doc = await self.get(doc_id)
50 | if not doc:
51 | return False
52 | await doc.update(update_query)
53 | return doc
54 |
55 | async def delete(self, id: PydanticObjectId) -> bool:
56 | doc = await self.get(id)
57 | if not doc:
58 | return False
59 | await doc.delete()
60 | return True
61 |
--------------------------------------------------------------------------------
/ch08/planner/database/connection.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Any, List
2 |
3 | from beanie import init_beanie, PydanticObjectId
4 | from models.events import Event
5 | from models.users import User
6 | from motor.motor_asyncio import AsyncIOMotorClient
7 | from pydantic import BaseSettings, BaseModel
8 |
9 |
10 | class Settings(BaseSettings):
11 | DATABASE_URL: Optional[str] = None
12 | SECRET_KEY: Optional[str] = "default"
13 |
14 | async def initialize_database(self):
15 | client = AsyncIOMotorClient(self.DATABASE_URL)
16 | await init_beanie(database=client.get_default_database(),
17 | document_models=[Event, User])
18 |
19 | class Config:
20 | env_file = ".env"
21 |
22 |
23 | class Database:
24 | def __init__(self, model):
25 | self.model = model
26 |
27 | async def save(self, document):
28 | await document.create()
29 | return
30 |
31 | async def get(self, id: PydanticObjectId) -> bool:
32 | doc = await self.model.get(id)
33 | if doc:
34 | return doc
35 | return False
36 |
37 | async def get_all(self) -> List[Any]:
38 | docs = await self.model.find_all().to_list()
39 | return docs
40 |
41 | async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
42 | doc_id = id
43 | des_body = body.dict()
44 |
45 | des_body = {k: v for k, v in des_body.items() if v is not None}
46 | update_query = {"$set": {
47 | field: value for field, value in des_body.items()
48 | }}
49 |
50 | doc = await self.get(doc_id)
51 | if not doc:
52 | return False
53 | await doc.update(update_query)
54 | return doc
55 |
56 | async def delete(self, id: PydanticObjectId) -> bool:
57 | doc = await self.get(id)
58 | if not doc:
59 | return False
60 | await doc.delete()
61 | return True
62 |
--------------------------------------------------------------------------------
/ch09/planner/database/connection.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Any, List
2 |
3 | from beanie import init_beanie, PydanticObjectId
4 | from models.events import Event
5 | from models.users import User
6 | from motor.motor_asyncio import AsyncIOMotorClient
7 | from pydantic import BaseSettings, BaseModel
8 |
9 |
10 | class Settings(BaseSettings):
11 | DATABASE_URL: Optional[str] = None
12 | SECRET_KEY: Optional[str] = "default"
13 |
14 | async def initialize_database(self):
15 | client = AsyncIOMotorClient(self.DATABASE_URL)
16 | await init_beanie(database=client.get_default_database(),
17 | document_models=[Event, User])
18 |
19 | class Config:
20 | env_file = ".env"
21 |
22 |
23 | class Database:
24 | def __init__(self, model):
25 | self.model = model
26 |
27 | async def save(self, document):
28 | await document.create()
29 | return
30 |
31 | async def get(self, id: PydanticObjectId) -> bool:
32 | doc = await self.model.get(id)
33 | if doc:
34 | return doc
35 | return False
36 |
37 | async def get_all(self) -> List[Any]:
38 | docs = await self.model.find_all().to_list()
39 | return docs
40 |
41 | async def update(self, id: PydanticObjectId, body: BaseModel) -> Any:
42 | doc_id = id
43 | des_body = body.dict()
44 |
45 | des_body = {k: v for k, v in des_body.items() if v is not None}
46 | update_query = {"$set": {
47 | field: value for field, value in des_body.items()
48 | }}
49 |
50 | doc = await self.get(doc_id)
51 | if not doc:
52 | return False
53 | await doc.update(update_query)
54 | return doc
55 |
56 | async def delete(self, id: PydanticObjectId) -> bool:
57 | doc = await self.get(id)
58 | if not doc:
59 | return False
60 | await doc.delete()
61 | return True
62 |
--------------------------------------------------------------------------------
/ch06/planner/routes/events.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from beanie import PydanticObjectId
4 | from database.connection import Database
5 | from fastapi import APIRouter, HTTPException, status
6 | from models.events import Event, EventUpdate
7 |
8 | event_router = APIRouter(
9 | tags=["Events"]
10 | )
11 |
12 | event_database = Database(Event)
13 |
14 |
15 | @event_router.get("/", response_model=List[Event])
16 | async def retrieve_all_events() -> List[Event]:
17 | events = await event_database.get_all()
18 | return events
19 |
20 |
21 | @event_router.get("/{id}", response_model=Event)
22 | async def retrieve_event(id: PydanticObjectId) -> Event:
23 | event = await event_database.get(id)
24 | if not event:
25 | raise HTTPException(
26 | status_code=status.HTTP_404_NOT_FOUND,
27 | detail="Event with supplied ID does not exist"
28 | )
29 | return event
30 |
31 |
32 | @event_router.post("/new")
33 | async def create_event(body: Event) -> dict:
34 | await event_database.save(body)
35 | return {
36 | "message": "Event created successfully"
37 | }
38 |
39 |
40 | @event_router.put("/{id}", response_model=Event)
41 | async def update_event(id: PydanticObjectId, body: EventUpdate) -> Event:
42 | updated_event = await event_database.update(id, body)
43 | if not updated_event:
44 | raise HTTPException(
45 | status_code=status.HTTP_404_NOT_FOUND,
46 | detail="Event with supplied ID does not exist"
47 | )
48 | return updated_event
49 |
50 |
51 | @event_router.delete("/{id}")
52 | async def delete_event(id: PydanticObjectId) -> dict:
53 | event = await event_database.delete(id)
54 | if not event:
55 | raise HTTPException(
56 | status_code=status.HTTP_404_NOT_FOUND,
57 | detail="Event with supplied ID does not exist"
58 | )
59 | return {
60 | "message": "Event deleted successfully."
61 | }
62 |
--------------------------------------------------------------------------------
/ch02/todos/todo.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, Path
2 |
3 | from model import Todo, TodoItem
4 |
5 | todo_router = APIRouter()
6 |
7 | todo_list = []
8 |
9 |
10 | @todo_router.post("/todo")
11 | async def add_todo(todo: Todo) -> dict:
12 | todo_list.append(todo)
13 | return {
14 | "message": "Todo added successfully."
15 | }
16 |
17 |
18 | @todo_router.get("/todo")
19 | async def retrieve_todo() -> dict:
20 | return {
21 | "todos": todo_list
22 | }
23 |
24 |
25 | @todo_router.get("/todo/{todo_id}")
26 | async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to retrieve.")) -> dict:
27 | for todo in todo_list:
28 | if todo.id == todo_id:
29 | return {
30 | "todo": todo
31 | }
32 | return {
33 | "message": "Todo with supplied ID doesn't exist."
34 | }
35 |
36 |
37 | @todo_router.put("/todo/{todo_id}")
38 | async def update_todo(todo_data: TodoItem, todo_id: int = Path(..., title="The ID of the todo to be updated.")) -> dict:
39 | for todo in todo_list:
40 | if todo.id == todo_id:
41 | todo.item = todo_data.item
42 | return {
43 | "message": "Todo updated successfully."
44 | }
45 | return {
46 | "message": "Todo with supplied ID doesn't exist."
47 | }
48 |
49 |
50 | @todo_router.delete("/todo/{todo_id}")
51 | async def delete_single_todo(todo_id: int) -> dict:
52 | for index in range(len(todo_list)):
53 | todo = todo_list[index]
54 | if todo.id == todo_id:
55 | todo_list.pop(index)
56 | return {
57 | "message": "Todo deleted successfully."
58 | }
59 | return {
60 | "message": "Todo with supplied ID doesn't exist."
61 | }
62 |
63 |
64 | @todo_router.delete("/todo")
65 | async def delete_all_todo() -> dict:
66 | todo_list.clear()
67 | return {
68 | "message": "Todos deleted successfully."
69 | }
70 |
--------------------------------------------------------------------------------
/ch03/todos/todo.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, Path, HTTPException, status
2 | from model import Todo, TodoItem, TodoItems
3 |
4 | todo_router = APIRouter()
5 |
6 | todo_list = []
7 |
8 |
9 | @todo_router.post("/todo", status_code=201)
10 | async def add_todo(todo: Todo) -> dict:
11 | todo_list.append(todo)
12 | return {
13 | "message": "Todo added successfully."
14 | }
15 |
16 |
17 | @todo_router.get("/todo", response_model=TodoItems)
18 | async def retrieve_todo() -> dict:
19 | return {
20 | "todos": todo_list
21 | }
22 |
23 |
24 | @todo_router.get("/todo/{todo_id}")
25 | async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to retrieve.")) -> dict:
26 | for todo in todo_list:
27 | if todo.id == todo_id:
28 | return {
29 | "todo": todo
30 | }
31 | raise HTTPException(
32 | status_code=status.HTTP_404_NOT_FOUND,
33 | detail="Todo with supplied ID doesn't exist",
34 | )
35 |
36 |
37 | @todo_router.put("/todo/{todo_id}")
38 | async def update_todo(todo_data: TodoItem, todo_id: int = Path(..., title="The ID of the todo to be updated.")) -> dict:
39 | for todo in todo_list:
40 | if todo.id == todo_id:
41 | todo.item = todo_data.item
42 | return {
43 | "message": "Todo updated successfully."
44 | }
45 |
46 | raise HTTPException(
47 | status_code=status.HTTP_404_NOT_FOUND,
48 | detail="Todo with supplied ID doesn't exist",
49 | )
50 |
51 |
52 | @todo_router.delete("/todo/{todo_id}")
53 | async def delete_single_todo(todo_id: int) -> dict:
54 | for index in range(len(todo_list)):
55 | todo = todo_list[index]
56 | if todo.id == todo_id:
57 | todo_list.pop(index)
58 | return {
59 | "message": "Todo deleted successfully."
60 | }
61 | raise HTTPException(
62 | status_code=status.HTTP_404_NOT_FOUND,
63 | detail="Todo with supplied ID doesn't exist",
64 | )
65 |
66 |
67 | @todo_router.delete("/todo")
68 | async def delete_all_todo() -> dict:
69 | todo_list.clear()
70 | return {
71 | "message": "Todos deleted successfully."
72 | }
73 |
--------------------------------------------------------------------------------
/ch07/planner/routes/events.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from auth.authenticate import authenticate
4 | from beanie import PydanticObjectId
5 | from database.connection import Database
6 | from fastapi import APIRouter, Depends, HTTPException, status
7 | from models.events import Event, EventUpdate
8 |
9 | event_router = APIRouter(
10 | tags=["Events"]
11 | )
12 |
13 | event_database = Database(Event)
14 |
15 |
16 | @event_router.get("/", response_model=List[Event])
17 | async def retrieve_all_events() -> List[Event]:
18 | events = await event_database.get_all()
19 | return events
20 |
21 |
22 | @event_router.get("/{id}", response_model=Event)
23 | async def retrieve_event(id: PydanticObjectId) -> Event:
24 | event = await event_database.get(id)
25 | if not event:
26 | raise HTTPException(
27 | status_code=status.HTTP_404_NOT_FOUND,
28 | detail="Event with supplied ID does not exist"
29 | )
30 | return event
31 |
32 |
33 | @event_router.post("/new")
34 | async def create_event(body: Event, user: str = Depends(authenticate)) -> dict:
35 | body.creator = user
36 | await event_database.save(body)
37 | return {
38 | "message": "Event created successfully"
39 | }
40 |
41 |
42 | @event_router.put("/{id}", response_model=Event)
43 | async def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:
44 | event = await event_database.get(id)
45 | if event.creator != user:
46 | raise HTTPException(
47 | status_code=status.HTTP_400_BAD_REQUEST,
48 | detail="Operation not allowed"
49 | )
50 | updated_event = await event_database.update(id, body)
51 | if not updated_event:
52 | raise HTTPException(
53 | status_code=status.HTTP_404_NOT_FOUND,
54 | detail="Event with supplied ID does not exist"
55 | )
56 | return updated_event
57 |
58 |
59 | @event_router.delete("/{id}")
60 | async def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:
61 | event = await event_database.get(id)
62 | if not event:
63 | raise HTTPException(
64 | status_code=status.HTTP_404_NOT_FOUND,
65 | detail="Event not found"
66 | )
67 | if event.creator != user:
68 | raise HTTPException(
69 | status_code=status.HTTP_400_BAD_REQUEST,
70 | detail="Operation not allowed"
71 | )
72 | event = await event_database.delete(id)
73 |
74 | return {
75 | "message": "Event deleted successfully."
76 | }
77 |
--------------------------------------------------------------------------------
/ch08/planner/routes/events.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from auth.authenticate import authenticate
4 | from beanie import PydanticObjectId
5 | from database.connection import Database
6 | from fastapi import APIRouter, Depends, HTTPException, status
7 | from models.events import Event, EventUpdate
8 |
9 | event_router = APIRouter(
10 | tags=["Events"]
11 | )
12 |
13 | event_database = Database(Event)
14 |
15 |
16 | @event_router.get("/", response_model=List[Event])
17 | async def retrieve_all_events() -> List[Event]:
18 | events = await event_database.get_all()
19 | return events
20 |
21 |
22 | @event_router.get("/{id}", response_model=Event)
23 | async def retrieve_event(id: PydanticObjectId) -> Event:
24 | event = await event_database.get(id)
25 | if not event:
26 | raise HTTPException(
27 | status_code=status.HTTP_404_NOT_FOUND,
28 | detail="Event with supplied ID does not exist"
29 | )
30 | return event
31 |
32 |
33 | @event_router.post("/new")
34 | async def create_event(body: Event, user: str = Depends(authenticate)) -> dict:
35 | body.creator = user
36 | await event_database.save(body)
37 | return {
38 | "message": "Event created successfully"
39 | }
40 |
41 |
42 | @event_router.put("/{id}", response_model=Event)
43 | async def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:
44 | event = await event_database.get(id)
45 | if event.creator != user:
46 | raise HTTPException(
47 | status_code=status.HTTP_400_BAD_REQUEST,
48 | detail="Operation not allowed"
49 | )
50 | updated_event = await event_database.update(id, body)
51 | if not updated_event:
52 | raise HTTPException(
53 | status_code=status.HTTP_404_NOT_FOUND,
54 | detail="Event with supplied ID does not exist"
55 | )
56 | return updated_event
57 |
58 |
59 | @event_router.delete("/{id}")
60 | async def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:
61 | event = await event_database.get(id)
62 | if event.creator != user:
63 | raise HTTPException(
64 | status_code=status.HTTP_404_NOT_FOUND,
65 | detail="Event not found"
66 | )
67 | if not event:
68 | raise HTTPException(
69 | status_code=status.HTTP_404_NOT_FOUND,
70 | detail="Event with supplied ID does not exist"
71 | )
72 | await event_database.delete(id)
73 |
74 | return {
75 | "message": "Event deleted successfully."
76 | }
77 |
--------------------------------------------------------------------------------
/ch09/planner/routes/events.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from auth.authenticate import authenticate
4 | from beanie import PydanticObjectId
5 | from database.connection import Database
6 | from fastapi import APIRouter, Depends, HTTPException, status
7 | from models.events import Event, EventUpdate
8 |
9 | event_router = APIRouter(
10 | tags=["Events"]
11 | )
12 |
13 | event_database = Database(Event)
14 |
15 |
16 | @event_router.get("/", response_model=List[Event])
17 | async def retrieve_all_events() -> List[Event]:
18 | events = await event_database.get_all()
19 | return events
20 |
21 |
22 | @event_router.get("/{id}", response_model=Event)
23 | async def retrieve_event(id: PydanticObjectId) -> Event:
24 | event = await event_database.get(id)
25 | if not event:
26 | raise HTTPException(
27 | status_code=status.HTTP_404_NOT_FOUND,
28 | detail="Event with supplied ID does not exist"
29 | )
30 | return event
31 |
32 |
33 | @event_router.post("/new")
34 | async def create_event(body: Event, user: str = Depends(authenticate)) -> dict:
35 | body.creator = user
36 | await event_database.save(body)
37 | return {
38 | "message": "Event created successfully"
39 | }
40 |
41 |
42 | @event_router.put("/{id}", response_model=Event)
43 | async def update_event(id: PydanticObjectId, body: EventUpdate, user: str = Depends(authenticate)) -> Event:
44 | event = await event_database.get(id)
45 | if event.creator != user:
46 | raise HTTPException(
47 | status_code=status.HTTP_400_BAD_REQUEST,
48 | detail="Operation not allowed"
49 | )
50 | updated_event = await event_database.update(id, body)
51 | if not updated_event:
52 | raise HTTPException(
53 | status_code=status.HTTP_404_NOT_FOUND,
54 | detail="Event with supplied ID does not exist"
55 | )
56 | return updated_event
57 |
58 |
59 | @event_router.delete("/{id}")
60 | async def delete_event(id: PydanticObjectId, user: str = Depends(authenticate)) -> dict:
61 | event = await event_database.get(id)
62 | if event.creator != user:
63 | raise HTTPException(
64 | status_code=status.HTTP_400_BAD_REQUEST,
65 | detail="Operation not allowed"
66 | )
67 | if not event:
68 | raise HTTPException(
69 | status_code=status.HTTP_404_NOT_FOUND,
70 | detail="Event with supplied ID does not exist"
71 | )
72 | await event_database.delete(id)
73 |
74 | return {
75 | "message": "Event deleted successfully."
76 | }
77 |
--------------------------------------------------------------------------------
/ch04/todos/todo.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, Path, HTTPException, status, Request, Depends
2 | from fastapi.templating import Jinja2Templates
3 |
4 | from model import Todo, TodoItem, TodoItems
5 |
6 | todo_router = APIRouter()
7 |
8 | todo_list = []
9 |
10 | templates = Jinja2Templates(directory="templates/")
11 |
12 |
13 | @todo_router.post("/todo")
14 | async def add_todo(request: Request, todo: Todo = Depends(Todo.as_form)):
15 | todo.id = len(todo_list) + 1
16 | todo_list.append(todo)
17 | return templates.TemplateResponse("todo.html",
18 | {
19 | "request": request,
20 | "todos": todo_list
21 | })
22 |
23 |
24 | @todo_router.get("/todo", response_model=TodoItems)
25 | async def retrieve_todo(request: Request):
26 | return templates.TemplateResponse("todo.html", {
27 | "request": request,
28 | "todos": todo_list
29 | })
30 |
31 |
32 | @todo_router.get("/todo/{todo_id}")
33 | async def get_single_todo(request: Request, todo_id: int = Path(..., title="The ID of the todo to retrieve.")):
34 | for todo in todo_list:
35 | if todo.id == todo_id:
36 | return templates.TemplateResponse("todo.html", {
37 | "request": request,
38 | "todo": todo
39 | })
40 | raise HTTPException(
41 | status_code=status.HTTP_404_NOT_FOUND,
42 | detail="Todo with supplied ID doesn't exist",
43 | )
44 |
45 |
46 | @todo_router.put("/todo/{todo_id}")
47 | async def update_todo(request: Request, todo_data: TodoItem,
48 | todo_id: int = Path(..., title="The ID of the todo to be updated.")) -> dict:
49 | for todo in todo_list:
50 | if todo.id == todo_id:
51 | todo.item = todo_data.item
52 | return {
53 | "message": "Todo updated successfully."
54 | }
55 |
56 | raise HTTPException(
57 | status_code=status.HTTP_404_NOT_FOUND,
58 | detail="Todo with supplied ID doesn't exist",
59 | )
60 |
61 |
62 | @todo_router.delete("/todo/{todo_id}")
63 | async def delete_single_todo(request: Request, todo_id: int) -> dict:
64 | for index in range(len(todo_list)):
65 | todo = todo_list[index]
66 | if todo.id == todo_id:
67 | todo_list.pop(index)
68 | return {
69 | "message": "Todo deleted successfully."
70 | }
71 | raise HTTPException(
72 | status_code=status.HTTP_404_NOT_FOUND,
73 | detail="Todo with supplied ID doesn't exist",
74 | )
75 |
76 |
77 | @todo_router.delete("/todo")
78 | async def delete_all_todo() -> dict:
79 | todo_list.clear()
80 | return {
81 | "message": "Todos deleted successfully."
82 | }
83 |
--------------------------------------------------------------------------------
/ch08/planner/tests/test_routes.py:
--------------------------------------------------------------------------------
1 | import httpx
2 | import pytest
3 |
4 | from auth.jwt_handler import create_access_token
5 | from models.events import Event
6 |
7 |
8 | @pytest.fixture(scope="module")
9 | async def access_token() -> str:
10 | return create_access_token("testuser@packt.com")
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | async def mock_event() -> Event:
15 | new_event = Event(
16 | creator="testuser@packt.com",
17 | title="FastAPI Book Launch",
18 | image="https://linktomyimage.com/image.png",
19 | description="We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
20 | tags=["python", "fastapi", "book", "launch"],
21 | location="Google Meet"
22 | )
23 |
24 | await Event.insert_one(new_event)
25 |
26 | yield new_event
27 |
28 |
29 | @pytest.mark.asyncio
30 | async def test_get_events(default_client: httpx.AsyncClient, mock_event: Event) -> None:
31 | response = await default_client.get("/event/")
32 |
33 | assert response.status_code == 200
34 | assert response.json()[0]["_id"] == str(mock_event.id)
35 |
36 |
37 | @pytest.mark.asyncio
38 | async def test_get_event(default_client: httpx.AsyncClient, mock_event: Event) -> None:
39 | url = f"/event/{str(mock_event.id)}"
40 | response = await default_client.get(url)
41 |
42 | assert response.status_code == 200
43 | assert response.json()["creator"] == mock_event.creator
44 | assert response.json()["_id"] == str(mock_event.id)
45 |
46 |
47 | @pytest.mark.asyncio
48 | async def test_post_event(default_client: httpx.AsyncClient, access_token: str) -> None:
49 | payload = {
50 | "title": "FastAPI Book Launch",
51 | "image": "https://linktomyimage.com/image.png",
52 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
53 | "tags": [
54 | "python",
55 | "fastapi",
56 | "book",
57 | "launch"
58 | ],
59 | "location": "Google Meet",
60 | }
61 |
62 | headers = {
63 | "Content-Type": "application/json",
64 | "Authorization": f"Bearer {access_token}"
65 | }
66 |
67 | test_response = {
68 | "message": "Event created successfully"
69 | }
70 |
71 | response = await default_client.post("/event/new", json=payload, headers=headers)
72 |
73 | assert response.status_code == 200
74 | assert response.json() == test_response
75 |
76 |
77 | @pytest.mark.asyncio
78 | async def test_get_events_count(default_client: httpx.AsyncClient) -> None:
79 | response = await default_client.get("/event/")
80 |
81 | events = response.json()
82 |
83 | assert response.status_code == 200
84 | assert len(events) == 2
85 |
86 |
87 | @pytest.mark.asyncio
88 | async def test_update_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
89 | test_payload = {
90 | "title": "Updated FastAPI event"
91 | }
92 |
93 | headers = {
94 | "Content-Type": "application/json",
95 | "Authorization": f"Bearer {access_token}"
96 | }
97 |
98 | url = f"/event/{str(mock_event.id)}"
99 |
100 | response = await default_client.put(url, json=test_payload, headers=headers)
101 |
102 | assert response.status_code == 200
103 | assert response.json()["title"] == test_payload["title"]
104 |
105 |
106 | @pytest.mark.asyncio
107 | async def test_delete_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
108 | test_response = {
109 | "message": "Event deleted successfully."
110 | }
111 |
112 | headers = {
113 | "Content-Type": "application/json",
114 | "Authorization": f"Bearer {access_token}"
115 | }
116 |
117 | url = f"/event/{mock_event.id}"
118 |
119 | response = await default_client.delete(url, headers=headers)
120 |
121 | assert response.status_code == 200
122 | assert response.json() == test_response
123 |
124 |
125 | @pytest.mark.asyncio
126 | async def test_get_event_again(default_client: httpx.AsyncClient, mock_event: Event) -> None:
127 | url = f"/event/{str(mock_event.id)}"
128 | response = await default_client.get(url)
129 |
130 | assert response.status_code == 404
131 |
--------------------------------------------------------------------------------
/ch09/planner/tests/test_routes.py:
--------------------------------------------------------------------------------
1 | import httpx
2 | import pytest
3 |
4 | from auth.jwt_handler import create_access_token
5 | from models.events import Event
6 |
7 |
8 | @pytest.fixture(scope="module")
9 | async def access_token() -> str:
10 | return create_access_token("testuser@packt.com")
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | async def mock_event() -> Event:
15 | new_event = Event(
16 | creator="testuser@packt.com",
17 | title="FastAPI Book Launch",
18 | image="https://linktomyimage.com/image.png",
19 | description="We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
20 | tags=["python", "fastapi", "book", "launch"],
21 | location="Google Meet"
22 | )
23 |
24 | await Event.insert_one(new_event)
25 |
26 | yield new_event
27 |
28 |
29 | @pytest.mark.asyncio
30 | async def test_get_events(default_client: httpx.AsyncClient, mock_event: Event) -> None:
31 | response = await default_client.get("/event/")
32 |
33 | assert response.status_code == 200
34 | assert response.json()[0]["_id"] == str(mock_event.id)
35 |
36 |
37 | @pytest.mark.asyncio
38 | async def test_get_event(default_client: httpx.AsyncClient, mock_event: Event) -> None:
39 | url = f"/event/{str(mock_event.id)}"
40 | response = await default_client.get(url)
41 |
42 | assert response.status_code == 200
43 | assert response.json()["creator"] == mock_event.creator
44 | assert response.json()["_id"] == str(mock_event.id)
45 |
46 |
47 | @pytest.mark.asyncio
48 | async def test_post_event(default_client: httpx.AsyncClient, access_token: str) -> None:
49 | payload = {
50 | "title": "FastAPI Book Launch",
51 | "image": "https://linktomyimage.com/image.png",
52 | "description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
53 | "tags": [
54 | "python",
55 | "fastapi",
56 | "book",
57 | "launch"
58 | ],
59 | "location": "Google Meet",
60 | }
61 |
62 | headers = {
63 | "Content-Type": "application/json",
64 | "Authorization": f"Bearer {access_token}"
65 | }
66 |
67 | test_response = {
68 | "message": "Event created successfully"
69 | }
70 |
71 | response = await default_client.post("/event/new", json=payload, headers=headers)
72 |
73 | assert response.status_code == 200
74 | assert response.json() == test_response
75 |
76 |
77 | @pytest.mark.asyncio
78 | async def test_get_events_count(default_client: httpx.AsyncClient) -> None:
79 | response = await default_client.get("/event/")
80 |
81 | events = response.json()
82 |
83 | assert response.status_code == 200
84 | assert len(events) == 2
85 |
86 |
87 | @pytest.mark.asyncio
88 | async def test_update_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
89 | test_payload = {
90 | "title": "Updated FastAPI event"
91 | }
92 |
93 | headers = {
94 | "Content-Type": "application/json",
95 | "Authorization": f"Bearer {access_token}"
96 | }
97 |
98 | url = f"/event/{str(mock_event.id)}"
99 |
100 | response = await default_client.put(url, json=test_payload, headers=headers)
101 |
102 | assert response.status_code == 200
103 | assert response.json()["title"] == test_payload["title"]
104 |
105 |
106 | @pytest.mark.asyncio
107 | async def test_delete_event(default_client: httpx.AsyncClient, mock_event: Event, access_token: str) -> None:
108 | test_response = {
109 | "message": "Event deleted successfully."
110 | }
111 |
112 | headers = {
113 | "Content-Type": "application/json",
114 | "Authorization": f"Bearer {access_token}"
115 | }
116 |
117 | url = f"/event/{mock_event.id}"
118 |
119 | response = await default_client.delete(url, headers=headers)
120 |
121 | assert response.status_code == 200
122 | assert response.json() == test_response
123 |
124 |
125 | @pytest.mark.asyncio
126 | async def test_get_event_again(default_client: httpx.AsyncClient, mock_event: Event) -> None:
127 | url = f"/event/{str(mock_event.id)}"
128 | response = await default_client.get(url)
129 |
130 | assert response.status_code == 404
131 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Building Python Web APIs with FastAPI
5 |
6 |
7 |
8 | 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.
9 |
10 | **A fast-paced guide to building high-performance, robust web APIs with very little boilerplate code**
11 |
12 | ## What is this book about?
13 | 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.
14 |
15 | This book covers the following exciting features:
16 | * Set up a FastAPI application that is fully functional and secure
17 | * Understand how to handle errors from requests and send proper responses in FastAPI
18 | * Integrate and connect your application to a SQL and NoSQL (MongoDB) database
19 | * Perform CRUD operations using SQL and FastAPI
20 | * Manage concurrency in FastAPI applications
21 |
22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1801076634) today!
23 |
24 |
26 |
27 |
28 | ## Instructions and Navigations
29 | All of the code is organized into folders. For example, Chapter05.
30 |
31 | The code will look like the following:
32 | ```
33 | from pydantic import BaseModel
34 | from typing import List
35 | class Event(BaseModel):
36 | id: int
37 | title: str
38 | image: str
39 | description: str
40 | tags: List[str]
41 | location: str
42 | ```
43 |
44 | **Following is what you need for this book:**
45 |
46 | 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.
47 |
48 | With the following software and hardware list you can run all code files present in the book (Chapter 1-09).
49 |
50 | ### Software and Hardware List
51 |
52 | | Chapter | Software required | OS required |
53 | | -------- | ------------------------------------| -----------------------------------|
54 | | 1-09 | Python 3.10 | Windows, Mac OS X, and Linux |
55 | | 1-09 | Git 2.36.0 | Windows, Mac OS X, and Linux |
56 |
57 |
58 |
59 | 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).
60 |
61 |
62 | ### Related products
63 | * 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)
64 |
65 | * 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)
66 |
67 | ## Errata
68 | * **Use port 8000 as per the GitHub repository examples.**
69 | * Page 14 (Code Snippet 1 line 1): **FROM PYTHON:3.8** _should be_ **FROM python:3.8**
70 | * Page 15 (last line): **FROM PYTHON:3.8** _should be_ **(venv)$ uvicorn api:app --port 8080 --reload**
71 | * 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.
72 | * Page 24, (Code snippet 2 Line 1):
73 | ```from Pydantic import BaseModel```
74 | _should be_
75 | ```from pydantic import BaseModel```
76 | * Page 24, (Code snippet 2 Line 2):
77 | ```class Todo(BaseMode):```
78 | _should be_
79 | ```class Todo(BaseModel):```
80 | * 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**
81 |
82 | ## Get to Know the Author
83 | **Abdulazeez Abdulazeez Adeshina**
84 | 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.
85 |
86 |
87 |
88 |
89 | ### Download a free PDF
90 |
91 | 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.
92 | https://packt.link/free-ebook/9781801076630
93 |
--------------------------------------------------------------------------------