Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 61 additions & 28 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,71 +1,104 @@
# Set the default goal to "help" so that running "make" without arguments will display the help message.
.DEFAULT_GOAL := help

# ====================================================================================
# HELP
# ====================================================================================
# This target uses a combination of egrep, sort, and awk to parse the Makefile itself
# and generate a formatted help message. It looks for lines containing '##' and
# uses the text that follows as the help description for the target.
.PHONY: help
help: ## Show this help
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

# ====================================================================================
# DOCKER COMPOSE MANAGEMENT
# ====================================================================================
.PHONY: docker-build
docker-build: ## Build project with compose
docker-build: ## Build project Docker images using compose
docker compose build

.PHONY: docker-up
docker-up: ## Run project with compose
docker compose up --remove-orphans

.PHONY: docker-clean
docker-clean: ## Clean Reset project containers and volumes with compose
docker-clean: ## Clean and reset project containers and volumes
docker compose down -v --remove-orphans | true
docker compose rm -f | true
docker volume rm fastapi_postgres_data | true
docker volume rm panettone_postgres_data | true

# ====================================================================================
# DATABASE MIGRATIONS
# ====================================================================================
.PHONY: docker-apply-db-migrations
docker-apply-db-migrations: ## apply alembic migrations to database/schema
docker compose run --rm app alembic upgrade head
docker-apply-db-migrations: ## Apply alembic migrations to the database schema
docker compose run --rm api1 alembic upgrade head

.PHONY: docker-create-db-migration
docker-create-db-migration: ## Create new alembic database migration aka database revision. Example: make docker-create-db-migration msg="add users table"
docker compose up -d db | true
docker compose run --no-deps app alembic revision --autogenerate -m "$(msg)"
docker-create-db-migration: ## Create a new alembic database migration. Example: make docker-create-db-migration msg="add users table"
docker compose up -d postgres | true
docker compose run --no-deps api1 alembic revision --autogenerate -m "$(msg)"

# ====================================================================================
# TESTING
# ====================================================================================
.PHONY: docker-test
docker-test: ## Run project tests
docker compose -f compose.yml -f test-compose.yml run --rm app pytest tests --durations=0 -vv
docker compose -f compose.yml -f test-compose.yml run --rm api1 pytest tests --durations=0 -vv

.PHONY: docker-test-snapshot
docker-test-snapshot: ## Run project tests with inline snapshot
docker compose -f compose.yml -f test-compose.yml run --rm app pytest tests --inline-snapshot=fix
docker-test-snapshot: ## Run project tests and update snapshots
docker compose -f compose.yml -f test-compose.yml run --rm api1 pytest tests --inline-snapshot=fix

# ====================================================================================
# CODE QUALITY & LINTING
# ====================================================================================
.PHONY: safety
safety: ## Check project and dependencies with safety https://github.com/pyupio/safety
docker compose run --rm app safety check
safety: ## Check for insecure dependencies
docker compose run --rm api1 safety check

.PHONY: py-upgrade
py-upgrade: ## Upgrade project py files with pyupgrade library for python version 3.10
pyupgrade --py313-plus `find app -name "*.py"`
py-upgrade: ## Upgrade Python syntax to a newer version
pyupgrade --py313-plus `find api1 -name "*.py"`

.PHONY: lint
lint: ## Lint project code.
lint: ## Lint and format project code
uv run ruff check --fix .

# ====================================================================================
# DOCKER IMAGE BUILDING
# ====================================================================================
.PHONY: slim-build
slim-build: ## with power of docker-slim build smaller and safer images
docker-slim build --compose-file docker-compose.yml --target-compose-svc app --dep-include-target-compose-svc-deps true --http-probe-exec app fastapi-sqlalchemy-asyncpg_app:latest
slim-build: ## Build smaller and more secure Docker images with docker-slim
docker-slim build --compose-file docker-compose.yml --target-compose-svc api1 --dep-include-target-compose-svc-deps true --http-probe-exec api1 fastapi-sqlalchemy-asyncpg_api1:latest

# ====================================================================================
# DATABASE SEEDING
# ====================================================================================
.PHONY: docker-feed-database
docker-feed-database: ## create database objects and insert data
docker compose exec db psql devdb devdb -f /home/gx/code/shakespeare_work.sql | true
docker compose exec db psql devdb devdb -f /home/gx/code/shakespeare_chapter.sql | true
docker compose exec db psql devdb devdb -f /home/gx/code/shakespeare_wordform.sql | true
docker compose exec db psql devdb devdb -f /home/gx/code/shakespeare_character.sql | true
docker compose exec db psql devdb devdb -f /home/gx/code/shakespeare_paragraph.sql | true
docker compose exec db psql devdb devdb -f /home/gx/code/shakespeare_character_work.sql
docker-feed-database: ## Create database objects and insert seed data
docker compose exec postgres psql devdb devdb -f /home/gx/code/shakespeare_work.sql | true
docker compose exec postgres psql devdb devdb -f /home/gx/code/shakespeare_chapter.sql | true
docker compose exec postgres psql devdb devdb -f /home/gx/code/shakespeare_wordform.sql | true
docker compose exec postgres psql devdb devdb -f /home/gx/code/shakespeare_character.sql | true
docker compose exec postgres psql devdb devdb -f /home/gx/code/shakespeare_paragraph.sql | true
docker compose exec postgres psql devdb devdb -f /home/gx/code/shakespeare_character_work.sql

# ====================================================================================
# MODEL GENERATION
# ====================================================================================
.PHONY: model-generate
model-generate: ## generate sqlalchemy models from database
model-generate: ## Generate SQLAlchemy models from the database schema
poetry run sqlacodegen --generator declarative postgresql://devdb:secret@0.0.0.0/devdb --outfile models.py --schemas shakespeare --options nobidi

# ====================================================================================
# ALTERNATIVE RUNTIMES
# ====================================================================================
.PHONY: docker-up-granian
docker-up-granian: ## Run project with compose and granian
docker-up-granian: ## Run project with compose and the Granian web server
docker compose -f granian-compose.yml up --remove-orphans

.PHONY: docker-up-valkey
docker-up-valkey: ## Run project with compose and valkey
docker-up-valkey: ## Run project with compose and Valkey
docker compose -f valkey-compose.yml up --remove-orphans
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<li><a href="#smtp-setup">Email Configuration</a></li>
<li><a href="#uv-knowledge-and-inspirations">UV knowledge and inspirations</a></li>
<li><a href="#large-language-model">Integration with local LLM</a></li>
<li><a href="#ha-sample-with-nginx-as-load-balancer">High Availability sample with nginx as load balancer</a></li>
</ul>
</li>
<li><a href="#acknowledgments">Acknowledgments</a></li>
Expand Down Expand Up @@ -184,6 +185,14 @@ ollama run llama3.2

<p align="right">(<a href="#readme-top">back to top</a>)</p>

### HA sample with nginx as load balancer
Sample high availability setup with nginx as load balancer and 2 uvicorn instances running on different ports.
```shell
make docker-up-ha
```
<p align="right">(<a href="#readme-top">back to top</a>)</p>


### UV knowledge and inspirations
- https://docs.astral.sh/uv/
- https://hynek.me/articles/docker-uv/
Expand Down Expand Up @@ -217,6 +226,7 @@ I've included a few of my favorites to kick things off!
<details>
<summary>2025 (3 changes)</summary>
<ul>
<li>[SEP 2 2025] add sample high availability with nginx as load balancer</li>
<li>[AUG 23 2025] intro exception handlers</li>
<li>[JUL some sunny day 2025] add rotoger</li>
<li>[MAY 3, 2025] add large language model integration :robot:</li>
Expand Down
3 changes: 3 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ async def lifespan(app: FastAPI):
"Postgres pool created", idle_size=app.postgres_pool.get_idle_size()
)
yield
except Exception as e:
await logger.aerror("Error during app startup", error=repr(e))
raise
finally:
await app.redis.close()
await app.postgres_pool.close()
Expand Down
25 changes: 11 additions & 14 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
services:
app:
container_name: fsap_app
network_mode: host
api1:
container_name: panettone_api1
build: .
environment:
- PYTHONPATH=/panettone
Expand All @@ -12,7 +11,6 @@ services:
uvicorn app.main:app
--host 0.0.0.0 --port 8080
--lifespan=on --use-colors --loop uvloop --http httptools
--reload --log-level debug
"
volumes:
- ./app:/panettone/app
Expand All @@ -22,17 +20,16 @@ services:
ports:
- "8080:8080"
depends_on:
- db
- inmemory
- postgres
- redis

db:
container_name: fsap_db
network_mode: host
postgres:
container_name: panettone_postgres
build:
context: ./db
dockerfile: Dockerfile
volumes:
- fastapi_postgres_data:/var/lib/postgresql/data
- panettone_postgres_data:/var/lib/postgresql/data
env_file:
- .env
ports:
Expand All @@ -50,15 +47,15 @@ services:
timeout: 5s
retries: 5

inmemory:
redis:
image: redis:latest
network_mode: host
container_name: fsap_inmemory
container_name: panettone_redis
ports:
- "6379:6379"
env_file:
- .env
entrypoint: redis-server --appendonly yes


volumes:
fastapi_postgres_data: {}
panettone_postgres_data: {}
30 changes: 15 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
[project]
name = "fastapi-sqlalchemy-asyncpg"
version = "0.20.0"
version = "0.21.0"
description = "A modern FastAPI application with SQLAlchemy 2.0 and AsyncPG for high-performance async database operations. Features include JWT authentication with Redis token storage, password hashing, connection pooling, data processing with Polars, Rich logging, task scheduling with APScheduler, and Shakespeare datasets integration."
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi[all]>=0.116.1",
"fastapi[all]>=0.116.2",
"pydantic[email]>=2.12.0a1",
"pydantic-settings>=2.10.1",
"sqlalchemy>=2.0.43",
"uvicorn[standard]>=0.35.0",
"uvicorn[standard]>=0.36.0",
"asyncpg>=0.30.0",
"alembic>=1.16.4",
"alembic>=1.16.5",
"httpx>=0.28.1",
"pytest>=8.4.1",
"pytest-cov>=6.2.1",
"pytest>=8.4.2",
"pytest-cov>=7.0.0",
"uvloop>=0.21.0",
"httptools>=0.6.4",
"rich>=14.1.0",
"pyjwt>=2.10.1",
"redis>=6.4.0",
"bcrypt>=4.3.0",
"polars>=1.32.3",
"polars>=1.33.1",
"python-multipart>=0.0.20",
"fastexcel>=0.14.0",
"inline-snapshot>=0.27.2",
"dirty-equals>=0.9.0",
"fastexcel>=0.15.1",
"inline-snapshot>=0.29.0",
"dirty-equals>=0.10.0",
"polyfactory>=2.22.2",
"granian>=2.5.0",
"granian>=2.5.4",
"apscheduler[redis,sqlalchemy]>=4.0.0a6",
"rotoger",
]

[tool.uv]
dev-dependencies = [
"ruff>=0.12.10",
"ruff>=0.13.1",
"devtools[pygments]>=0.12.2",
"pyupgrade>=3.20.0",
"ipython>=9.4.0",
"sqlacodegen<=3.1.0",
"ipython>=9.5.0",
"sqlacodegen<=3.1.1",
"tryceratops>=2.4.1",
"locust>=2.39.0"
"locust>=2.40.5"

]

Expand Down
4 changes: 2 additions & 2 deletions test-compose.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
services:
app:
api1:
environment:
- POSTGRES_DB=testdb

db:
postgres:
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_DB=testdb
Loading
Loading