From e8ada9a7a2745f5e786cd1ba487b6371d24822bf Mon Sep 17 00:00:00 2001 From: policyengine-bot Date: Mon, 8 Dec 2025 16:21:47 +0000 Subject: [PATCH 1/3] Add Docker Compose structure for local development and CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add docker-compose.yml with services for API, development, testing, and optional MySQL - Add .env.example with documented environment variables - Update Dockerfile.build for better Docker Compose integration - Add docker-* make targets for common operations - Update README.md with Docker Compose quick start guide Closes #896 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.example | 63 +++++++++++++++++++++++ Dockerfile.build | 21 +++++--- Makefile | 25 +++++++++ README.md | 56 ++++++++++++++++++-- docker-compose.yml | 124 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 280 insertions(+), 9 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..4ce8dbba --- /dev/null +++ b/.env.example @@ -0,0 +1,63 @@ +# Docker Compose environment configuration +# Copy this file to .env and fill in your values +# See config/README.md for detailed documentation + +# ============================================================================= +# Server Configuration +# ============================================================================= + +# Port to expose the API (default: 8080) +PORT=8080 + +# Flask debug mode (0=production, 1=development) +FLASK_DEBUG=0 + +# ============================================================================= +# Authentication (Optional) +# ============================================================================= + +# Enable Auth0 authentication (default: false) +AUTH__ENABLED=false + +# Auth0 domain (without https:// or trailing slash) +AUTH0_ADDRESS_NO_DOMAIN= + +# Auth0 API audience +AUTH0_AUDIENCE_NO_DOMAIN= + +# Auth0 test token (for CI testing) +AUTH0_TEST_TOKEN_NO_DOMAIN= + +# ============================================================================= +# Analytics (Optional) +# ============================================================================= + +# Enable user analytics collection (default: false) +ANALYTICS__ENABLED=false + +# For local MySQL (when using docker compose --profile analytics) +MYSQL_ROOT_PASSWORD=rootpass +MYSQL_USER=analytics +MYSQL_PASSWORD=analytics_pass + +# For Google Cloud SQL (production) +# USER_ANALYTICS_DB_CONNECTION_NAME= +# USER_ANALYTICS_DB_USERNAME= +# USER_ANALYTICS_DB_PASSWORD= + +# ============================================================================= +# AI Features (Optional) +# ============================================================================= + +# Enable AI-powered features (default: false) +AI__ENABLED=false + +# Anthropic API key for AI features +ANTHROPIC_API_KEY= + +# ============================================================================= +# Configuration File (Optional) +# ============================================================================= + +# Custom configuration file path (mounted into container) +# CONFIG_FILE=./config/local.yaml diff --git a/Dockerfile.build b/Dockerfile.build index 59e71340..04bc3935 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -1,17 +1,26 @@ +# Development Dockerfile for PolicyEngine Household API +# Use docker-compose.yml for easier development setup FROM python:3.12 -ENV VIRTUAL_ENV=/env -ENV PATH=/env/bin:$PATH # Install system dependencies -RUN apt-get update && apt-get install -y build-essential checkinstall redis-server +RUN apt-get update && apt-get install -y \ + build-essential \ + checkinstall \ + curl \ + && rm -rf /var/lib/apt/lists/* + RUN python3.12 -m pip install --upgrade pip # Copy and install the application WORKDIR /app COPY . /app -RUN pip install -e . +RUN pip install -e .[dev] + +# Set default environment +ENV FLASK_APP=policyengine_household_api.api +ENV FLASK_DEBUG=1 -# Start Redis -RUN service redis-server start +# Expose Flask default port +EXPOSE 5000 CMD ["bash"] \ No newline at end of file diff --git a/Makefile b/Makefile index 31a985ea..e42d85e4 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,31 @@ debug-test: format: black . -l 79 +# Docker Compose commands +docker-build: + docker compose build + +docker-up: + docker compose up + +docker-up-detached: + docker compose up -d + +docker-down: + docker compose down + +docker-dev: + docker compose --profile dev up api-dev + +docker-test: + docker compose --profile test run --rm test + +docker-test-auth: + docker compose --profile test-auth run --rm test-with-auth + +docker-logs: + docker compose logs -f + deploy: python gcp/export.py gcloud config set app/cloud_build_timeout 1800 diff --git a/README.md b/README.md index 20e01c21..0ae60311 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,58 @@ -# PolicyEngine Household API +# PolicyEngine Household API -A version of the PolicyEngine API that runs the `calculate` endpoint over household object. To debug locally, run `make debug`. +A version of the PolicyEngine API that runs the `calculate` endpoint over household object. -## Development rules +## Quick Start + +### Local Development (without Docker) + +```bash +make install +make debug +``` + +### Docker Compose + +```bash +# Copy environment template +cp .env.example .env + +# Start the API +make docker-up + +# Or run in development mode with hot-reload +make docker-dev +``` + +### Running Tests + +```bash +# Without Docker +make test + +# With Docker Compose +make docker-test +``` + +## Docker Compose Services + +| Command | Description | +|---------|-------------| +| `make docker-up` | Start the API service | +| `make docker-up-detached` | Start in background | +| `make docker-dev` | Start with hot-reload | +| `make docker-test` | Run unit tests | +| `make docker-test-auth` | Run auth integration tests | +| `make docker-down` | Stop all services | +| `make docker-logs` | View logs | + +See `docker-compose.yml` for available services and profiles. + +## Configuration + +See [config/README.md](config/README.md) for detailed configuration options. + +## Development Rules 1. Every endpoint should return a JSON object with at least a "status" and "message" field. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..19d5c089 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,124 @@ +# Docker Compose configuration for PolicyEngine Household API +# Use this for local development and CI testing +# +# Usage: +# Development: docker compose up +# Testing: docker compose --profile test up +# With analytics: docker compose --profile analytics up +# +# See config/README.md for configuration options + +services: + # Main API service + api: + build: + context: . + dockerfile: gcp/policyengine_household_api/Dockerfile.production + ports: + - "${PORT:-8080}:8080" + environment: + - PORT=8080 + - FLASK_DEBUG=${FLASK_DEBUG:-0} + # Optional features - disabled by default for local development + - AUTH__ENABLED=${AUTH__ENABLED:-false} + - AUTH0_ADDRESS_NO_DOMAIN=${AUTH0_ADDRESS_NO_DOMAIN:-} + - AUTH0_AUDIENCE_NO_DOMAIN=${AUTH0_AUDIENCE_NO_DOMAIN:-} + - ANALYTICS__ENABLED=${ANALYTICS__ENABLED:-false} + - AI__ENABLED=${AI__ENABLED:-false} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + volumes: + # Mount custom config if provided + - ${CONFIG_FILE:-./config/default.yaml}:/app/config/custom.yaml:ro + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + restart: unless-stopped + + # Development service with hot-reload support + api-dev: + build: + context: . + dockerfile: Dockerfile.build + ports: + - "${PORT:-8080}:5000" + environment: + - FLASK_APP=policyengine_household_api.api + - FLASK_DEBUG=1 + - AUTH__ENABLED=${AUTH__ENABLED:-false} + - AUTH0_ADDRESS_NO_DOMAIN=${AUTH0_ADDRESS_NO_DOMAIN:-} + - AUTH0_AUDIENCE_NO_DOMAIN=${AUTH0_AUDIENCE_NO_DOMAIN:-} + - ANALYTICS__ENABLED=${ANALYTICS__ENABLED:-false} + - AI__ENABLED=${AI__ENABLED:-false} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + volumes: + # Mount source code for hot-reload + - ./policyengine_household_api:/app/policyengine_household_api:ro + - ./config:/app/config:ro + command: ["flask", "run", "--host=0.0.0.0", "--without-threads"] + profiles: + - dev + + # Test runner service + test: + build: + context: . + dockerfile: Dockerfile.build + environment: + - FLASK_DEBUG=1 + - AUTH__ENABLED=${AUTH__ENABLED:-false} + - AUTH0_ADDRESS_NO_DOMAIN=${AUTH0_ADDRESS_NO_DOMAIN:-} + - AUTH0_AUDIENCE_NO_DOMAIN=${AUTH0_AUDIENCE_NO_DOMAIN:-} + - AUTH0_TEST_TOKEN_NO_DOMAIN=${AUTH0_TEST_TOKEN_NO_DOMAIN:-} + - CONFIG_FILE=${CONFIG_FILE:-} + volumes: + - ./tests:/app/tests:ro + - ./policyengine_household_api:/app/policyengine_household_api:ro + - ./config:/app/config:ro + command: ["pytest", "-vv", "--timeout=150", "-rP", "tests/to_refactor", "tests/unit"] + profiles: + - test + + # Test with auth service + test-with-auth: + build: + context: . + dockerfile: Dockerfile.build + environment: + - AUTH__ENABLED=true + - AUTH0_ADDRESS_NO_DOMAIN=${AUTH0_ADDRESS_NO_DOMAIN} + - AUTH0_AUDIENCE_NO_DOMAIN=${AUTH0_AUDIENCE_NO_DOMAIN} + - AUTH0_TEST_TOKEN_NO_DOMAIN=${AUTH0_TEST_TOKEN_NO_DOMAIN} + - CONFIG_FILE=/app/config/test_with_auth.yaml + volumes: + - ./tests:/app/tests:ro + - ./policyengine_household_api:/app/policyengine_household_api:ro + - ./config:/app/config:ro + command: ["pytest", "-vv", "--timeout=150", "-rP", "tests/integration_with_auth"] + profiles: + - test-auth + + # MySQL database for analytics (optional) + mysql: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-rootpass} + - MYSQL_DATABASE=user_analytics + - MYSQL_USER=${MYSQL_USER:-analytics} + - MYSQL_PASSWORD=${MYSQL_PASSWORD:-analytics_pass} + volumes: + - mysql_data:/var/lib/mysql + ports: + - "3306:3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + profiles: + - analytics + +volumes: + mysql_data: From 3c691042d481bf0cc870b6382895ac4de0f5a090 Mon Sep 17 00:00:00 2001 From: policyengine-bot Date: Mon, 8 Dec 2025 16:24:49 +0000 Subject: [PATCH 2/3] Add changelog entry for Docker Compose feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29b..45c12007 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,6 @@ +- bump: minor + changes: + added: + - Docker Compose structure for local development and CI + - .env.example with documented environment variables + - Make targets for common Docker operations (docker-up, docker-dev, docker-test) From 73581da7359a25beaf8ac805cd1951d93b8c863e Mon Sep 17 00:00:00 2001 From: policyengine-bot Date: Mon, 8 Dec 2025 16:41:33 +0000 Subject: [PATCH 3/3] Add missing __init__.py files to test directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the module import conflict between test files with the same name in different directories (e.g., test_auth.py in both tests/unit/decorators/ and tests/integration_with_auth/). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tests/data/__init__.py | 0 tests/fixtures/__init__.py | 0 tests/fixtures/data/__init__.py | 0 tests/fixtures/decorators/__init__.py | 0 tests/fixtures/endpoints/__init__.py | 0 tests/fixtures/utils/__init__.py | 0 tests/integration_with_auth/__init__.py | 0 tests/to_refactor/__init__.py | 0 tests/to_refactor/api/__init__.py | 0 tests/unit/__init__.py | 0 tests/unit/data/__init__.py | 0 tests/unit/decorators/__init__.py | 0 tests/unit/endpoints/__init__.py | 0 tests/unit/utils/__init__.py | 0 14 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/__init__.py create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/data/__init__.py create mode 100644 tests/fixtures/decorators/__init__.py create mode 100644 tests/fixtures/endpoints/__init__.py create mode 100644 tests/fixtures/utils/__init__.py create mode 100644 tests/integration_with_auth/__init__.py create mode 100644 tests/to_refactor/__init__.py create mode 100644 tests/to_refactor/api/__init__.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/data/__init__.py create mode 100644 tests/unit/decorators/__init__.py create mode 100644 tests/unit/endpoints/__init__.py create mode 100644 tests/unit/utils/__init__.py diff --git a/tests/data/__init__.py b/tests/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/data/__init__.py b/tests/fixtures/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/decorators/__init__.py b/tests/fixtures/decorators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/endpoints/__init__.py b/tests/fixtures/endpoints/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/utils/__init__.py b/tests/fixtures/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration_with_auth/__init__.py b/tests/integration_with_auth/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/to_refactor/__init__.py b/tests/to_refactor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/to_refactor/api/__init__.py b/tests/to_refactor/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/data/__init__.py b/tests/unit/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/decorators/__init__.py b/tests/unit/decorators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/endpoints/__init__.py b/tests/unit/endpoints/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/utils/__init__.py b/tests/unit/utils/__init__.py new file mode 100644 index 00000000..e69de29b