- Clone the repository
- Create and activate a Python virtual environment
python3 -m venv .venv source .venv/bin/activate - Install dependencies:
pip install -r requirements.txt
- Copy the example environment file and fill in the database credentials:
cp .env.example .env
Start the database and other services with Docker Compose:
docker compose upThen run the FastAPI app in a separate terminal:
python main.pyRun the test suite:
python -m pytest tests/ -vRun a specific test file:
python -m pytest tests/test_users.py -vThe test suite includes:
- test_users.py: User authentication, profile management, and cascade deletion
- test_songs.py: Song CRUD operations and setlist associations
- test_setlists.py: Setlist management, song addition/removal, and access control
All tests use an in-memory SQLite database for isolation and speed (16 tests pass in ~0.5s).
The application uses SQLAlchemy 2.0's cascade delete functionality to automatically clean up related records:
- User → Setlists: When a user is deleted, all their setlists are automatically deleted
- Setlist → SetlistEntries: When a setlist is deleted, all song entries are automatically removed
- Song → SetlistEntries: When a song is deleted, all its entries across setlists are cleaned up
This eliminates the need for manual cascading deletes in route handlers.
Models use Mapped type annotations from SQLAlchemy 2.0 combined with Pydantic for full type safety:
from typing import Optional, TYPE_CHECKING
from sqlalchemy.orm import Mapped
from sqlmodel import SQLModel, Relationship
if TYPE_CHECKING:
from .user import User
class Setlist(SQLModel, table=True):
id: int | None = None
user_id: int | None = None
user: Mapped[Optional["User"]] = Relationship(back_populates="setlists")Key patterns:
- Forward references use
Optional["ClassName"](not"ClassName" | None) to work withMappedtypes - TYPE_CHECKING guards prevent circular imports between related models
- Cascade configuration is defined via
sa_relationship_kwargson relationship fields
The database schema is shown below. The diagram was generated with dbdiagram.io:
