Workflows
Environment Setup
Install PDM
The dependency manager used in this project is pdm. To install it, run the following command:
Or, alternatively, other installation methods can be used.
Install Dependencies
The dependencies are broken into groups:
-
Default dependencies: required for the core functionality of the project in production.
-
Development dependencies: required for development, testing, and documentation.
The specified python version in pyproject.toml
is >=3.11
, and so a python 3.11 interpreter should be used.
Conda
To do so with conda:
$ conda search python | grep " 3\.\(10\|11\|12\)\."
$ yes | conda create --name text_summarizer_api python=3.11.9
$ conda activate text_summarizer_api
$ pdm use -f $(which python3)
$ pdm install
Vitualenv
To do so with virtualenv, use the pdm venv command:
$ pdm venv create --name text_summarizer_api --with virtualenv 3.11.9
# To activate the virtual environment
$ eval $(pdm venv activate text_summarizer_api)
$ pdm install
Docker Compose
The development environment is set up using Docker Compose. This setup defines two services:
-
web: sets up the application based on
dev.Dockerfile
. -
wev-db: sets up a PostgreSQL database based on
db.Dockerfile
, which simply Adds a.sql
file to the container at/docker-entrypoint-initdb.d/
. Two databases are created:web_dev
for development andweb_test
for testing; neither is used in production. -
web-redis: sets up a redis server for rate limiting.
name: 'text-summarizer-api'
services:
web:
build:
context: ./project
dockerfile: docker/dev.Dockerfile
container_name: dev-api
command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000
volumes:
- ./project/app:/opt/project/app
- ./project/tests:/opt/project/tests
- ./project/pyproject.toml:/opt/project/pyproject.toml
- ./project/migrations:/opt/project/migrations
ports:
- 8004:8000
environment:
- ENVIRONMENT=dev
- TESTING=0
- DOCS_URL=/docs
- DATABASE_URL=postgres://postgres:postgres@web-db:5432/web_dev
- DATABASE_TEST_URL=postgres://postgres:postgres@web-db:5432/web_test
- REDIS_ENDPOINT=web-redis:6379
- REDIS_PASSWORD=redis
depends_on:
- web-db
- web-redis
web-db:
build:
context: ./project
dockerfile: docker/db.Dockerfile
container_name: db
expose:
- 5432
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
web-redis:
image: redis:bookworm
container_name: redis
expose:
- 6379
command: ["redis-server", "--requirepass", "redis"]
Build and Run the Services
Note: All the commands should be run from the root of the project where compose.yml
is located. In addition, Compose V2 is used, so the docker-compose
command is replaced with docker compose
.
To build the images and run the containers in the background:
Directories such as app/
, tests/
, migrations/
, and the pyproject.toml
file are bind-mounted to their respective counterparts in the web
service container. This setup allows for automatic reloading of the application when changes are made to the code during development.
To stop the containers without removing them:
To stop, remove the containers, and remove named volumes:
Logs
To view the logs of the services:
Interactive Shell
To run an interactive shell in a service:
Database Migrations
The database migrations are managed using aerich, which is a tool specifically designed for Tortoise-ORM.
First Time Setup
Configuration
To set up the initial config file and generate the root migrate location:
The -t
flag specifies the module path to the Tortoise-ORM settings inside the app.db
module. This will add a tool.aerich
section to the pyproject.toml
file.
Initialize Database
To initialize the database:
This will create the tables in the database based on the models defined in app/models/
along with the first migration file in the migrations/
directory.
Migration Workflow
From this point on, since the local migrations/
directory is synced with the migrations/
directory on the container (for both prod
& dev
), each time a change is made to the model, the following steps should be taken:
- In development mode, update the model in
app/models/
and run the following command to generate a new migration:
- Apply the migration in development mode:
-
Run tests and any other necessary checks.
-
Merge the changes to the
main
branch, which will trigger a deployment to the production environment. -
Once the changes are deployed, apply the migration in production via the heroku CLI:
See the aerich's usage documentation for more commands and details.
PSQL
The PostgreSQL database within the docker container can be accessed using psql, a terminal-based front-end to PostgreSQL.
Connect
To connect to a specific PostgreSQL database within the server:
List Tables
To list the tables in the connected database:
Quit
To quit the psql
shell:
Rate Limiting with Redis
Rate limiting is implemented using fastapi-limiter, which requires Redis to store the rate limiting data. Redis runs locally via Docker Compose in development and testing environments, while in production, Redis Cloud is used.
Local Redis Setup
The Redis service is defined in compose.yml
:
services:
web-redis:
image: redis:bookworm
container_name: redis
expose:
- 6379
command: ["redis-server", "--requirepass", "redis"]
Redis is exposed internally for communication between services. The application connects to Redis through the REDIS_ENDPOINT
and REDIS_PASSWORD
environment variables.
CI Redis Setup
In the CI environment, a Redis service is set up in an independent container:
- Create a network that the Redis service and the application service can connect to:
- Start the Redis service (pulling the image from Docker Hub):
- name: Start redis container
id: start-redis-container
run: |
docker run \
--name web-redis \
--network test-network \
--detach \
--expose 6379 \
redis:bookworm \
redis-server --requirepass redis
- When running the development container, connect to the same network:
- name: Run docker container
id: run-docker-container
run: |
docker run \
--name test-container \
--network test-network \
--detach \
-e PORT=8765 \
-e ENVIRONMENT=dev \
-e DATABASE_URL="${DB_URL}" \
-e DATABASE_TEST_URL="${DB_URL}" \
-e REDIS_ENDPOINT=web-redis:6379 \
-e REDIS_PASSWORD=redis \
-p 5003:8765 \
${{ env.IMAGE }}:latest
Production Redis Setup
In production, a free Redis Cloud database is utilized. To set up a Redis Cloud account, refer to this guide. After creating the account, follow the instructions in this guide to link it to Heroku.
The following information from the Redis Cloud console is required:
- Public endpoint
- Password
It is important to keep this information secure, as it will be necessary for the application to connect to the Redis database.
Application Redis Initialization
Redis is initialized in the application using a lifespan context manager in app/db.py
:
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
redis_endpoint = os.getenv("REDIS_ENDPOINT")
redis_encoded_password = quote(os.getenv("REDIS_PASSWORD"), safe="")
redis_url = f"redis://:{redis_encoded_password}@{redis_endpoint}"
redis_connection = redis.from_url(redis_url, encoding="utf8")
await FastAPILimiter.init(redis_connection)
async with RegisterTortoise(
app=app,
db_url=os.environ.get("DATABASE_URL"),
modules={"models": ["app.models.tortoise_model"]},
generate_schemas=False,
add_exception_handlers=True,
):
yield
await FastAPILimiter.close()
This implementation ensures that fastapi-limiter
effectively manages Redis-based rate limiting. The lifespan
context manager is responsible for establishing a connection to Redis at the start of the application and closing it after the application has finished running.
Before deploying to Heroku, ensure that the REDIS_ENDPOINT
(public endpoint) and REDIS_PASSWORD
(password) environment variables are set as config vars in the Heroku application settings.