Initialize the TaskFlow Project
Instructions
In this lab, you'll set up the complete foundation for the TaskFlow API. By the end, you'll have a running Docker Compose stack with FastAPI, PostgreSQL 18, and Redis — all connected and verified via a health check endpoint.
Step 1: Create the Project Directory Structure
Create the following directory structure for the TaskFlow project:
mkdir -p taskflow/app/{api/v1,models,schemas,core,tests}
cd taskflow
Create __init__.py files in every Python package directory:
touch app/__init__.py
touch app/api/__init__.py
touch app/api/v1/__init__.py
touch app/models/__init__.py
touch app/schemas/__init__.py
touch app/core/__init__.py
touch app/tests/__init__.py
Step 2: Create the Requirements File
Create requirements.txt with version constraints:
# Web framework
fastapi>=0.136,<1.0
uvicorn[standard]>=0.34,<1.0
# Database
sqlalchemy>=2.0.49,<2.1
psycopg2-binary>=2.9.10,<3.0
alembic>=1.18.4,<2.0
# Validation & Settings
pydantic>=2.13,<3.0
pydantic-settings>=2.8,<3.0
# Authentication
PyJWT>=2.10,<3.0
pwdlib[argon2]>=0.2,<1.0
# Caching
redis>=7.1.1,<9.0
# Testing
pytest>=9.0.2,<10.0
anyio>=4.0,<5.0
httpx>=0.28,<1.0
# Utilities
python-dotenv>=1.0.1,<2.0
python-multipart>=0.0.20,<1.0
Two things about this file are worth more than the file itself.
1. Run the resolver before you trust it. Every line above names a real,
maintained package — and that is not enough. A constraint set can become
unsatisfiable over time without any single line being wrong: one package
raises its minimum supported version of another, and a ceiling that was
correct the day it was written now sits below the floor. pip reports this
as ResolutionImpossible, and nothing else will catch it. Check the file on
its own before building anything on top of it:
pip install --dry-run -r requirements.txt
Put that same command in CI. It is the cheapest guard you will ever add.
2. Ranges are not a lockfile. The constraints above say which versions are
permitted; they do not say which version you actually got. Two people running
pip install a month apart will get different builds from this file, which is
the opposite of reproducible. Ranges belong in requirements.txt; the exact
resolved set belongs in a lockfile you commit — produced by pip freeze,
pip-compile, or uv lock. Deploy from the lock, develop against the range.
Note that the testing dependency here is anyio, not pytest-asyncio. The
tests you write in Phase 4 use the @pytest.mark.anyio marker, which is
provided by AnyIO's own pytest plugin. Installing a second async plugin that
nothing uses is how the ceiling problem above gets created in the first place.
Step 3: Create the Environment Configuration
Create a .env file at the project root:
# Application
APP_NAME=TaskFlow
APP_VERSION=0.1.0
DEBUG=true
# Database
POSTGRES_USER=taskflow
POSTGRES_PASSWORD=taskflow_secret_2026
POSTGRES_DB=taskflow
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
DATABASE_URL=postgresql://taskflow:taskflow_secret_2026@postgres:5432/taskflow
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_URL=redis://redis:6379/0
# JWT (generate your own secret in production!)
JWT_SECRET_KEY=dev-secret-key-change-in-production
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
Step 4: Create the Configuration Module
Create app/core/config.py:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "TaskFlow"
app_version: str = "0.1.0"
debug: bool = False
# Database
database_url: str = "postgresql://taskflow:taskflow_secret_2026@postgres:5432/taskflow"
# Redis
redis_url: str = "redis://redis:6379/0"
# JWT
jwt_secret_key: str = "dev-secret-key-change-in-production"
jwt_algorithm: str = "HS256"
jwt_access_token_expire_minutes: int = 30
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
Step 5: Create the FastAPI Application
Create app/main.py:
from fastapi import FastAPI
from app.core.config import settings
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
docs_url="/docs",
redoc_url="/redoc",
)
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"app": settings.app_name,
"version": settings.app_version,
}
Step 6: Create the Dockerfile
Create a Dockerfile with a multi-stage build:
# ---- Stage 1: Builder ----
FROM python:3.13-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ---- Stage 2: Runtime ----
FROM python:3.13-slim AS runtime
WORKDIR /app
# Copy installed packages from builder
COPY --from=builder /install /usr/local
# Copy application code
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Step 7: Create Docker Compose Configuration
Create docker-compose.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
- .:/app
env_file:
- .env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:18
environment:
POSTGRES_USER: ${POSTGRES_USER:-taskflow}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-taskflow_secret_2026}
POSTGRES_DB: ${POSTGRES_DB:-taskflow}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U taskflow -d taskflow"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:8-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Step 8: Build and Run
Start the entire stack:
docker compose up --build -d
Verify the health check:
curl http://localhost:8000/health
You should see:
{
"status": "healthy",
"app": "TaskFlow",
"version": "0.1.0"
}
Check the auto-generated API docs by visiting http://localhost:8000/docs in your browser.
Step 9: Verify All Services
Check that all containers are running:
docker compose ps
All three services (app, postgres, redis) should show status Up (healthy).
What to Submit
The editor already has 6 file sections pre-filled with TODO comments. Replace each TODO with your actual code. The AI grader will evaluate each file section against the rubric.