Skip to content

Generated files

Every file generated by bluefox init is documented here.

main.py

The application entry point. Uses the bluefox-core app factory with bluefox-auth and bluefox-components:

from bluefox_auth import BluefoxAuth
from bluefox_components import setup_components
from bluefox_core import BluefoxSettings, create_bluefox_app


def create_app() -> object:
    settings = BluefoxSettings()
    app = create_bluefox_app(settings, welcome=True)
    BluefoxAuth(app, settings)
    setup_components(app)
    return app


app = create_app()

welcome=True enables the interactive welcome page at / with system status badges, a message playground, and an auth panel. BluefoxAuth(app, settings) adds JWT authentication, cookie sessions, and user management endpoints (/auth/register, /auth/login, /auth/logout, /auth/me). setup_components(app) registers Jinja2 template macros and static assets from the component library. The create_app factory is also used by bluefox-test to spin up the app during testing.

items/ (example module)

A starter module demonstrating the auto-discovery convention:

items/
  __init__.py
  api.py          # Router auto-mounted at /items
  models.py       # Item model auto-discovered

items/api.py:

from fastapi import APIRouter, Depends
from bluefox_auth import BluefoxUser, current_active_user

router = APIRouter()


@router.get("/")
async def list_items():
    return {"items": []}


@router.get("/me")
async def my_items(user: BluefoxUser = Depends(current_active_user)):
    return {"items": [], "owner": user.email}

items/models.py:

from sqlalchemy import Column, Integer, String
from bluefox_core import BluefoxBase


class Item(BluefoxBase):
    __tablename__ = "items"

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

bluefox-core auto-discovers */api.py files and mounts their router at /{dirname}. Models in */models.py are auto-imported so they register in BluefoxBase.metadata. No manual imports needed.

The GET /items/me endpoint demonstrates route protection with current_active_user from bluefox-auth. It returns 401 for unauthenticated requests and the current user's email when authenticated.

models.py

Root-level models file. Auto-discovered by bluefox-core. Add models here or in module directories (e.g., items/models.py).

.env and .env.example

.env has dev defaults filled in (DEBUG=true, LOG_LEVEL=DEBUG, local Postgres URL). .env.example is the template for production with placeholder values.

Both contain all BluefoxSettings fields:

APP_NAME=myapp
ENVIRONMENT=development
DEBUG=true
LOG_LEVEL=DEBUG
SECRET_KEY=dev-secret-key-change-in-production
DATABASE_URL=postgresql://myuser:mypass@localhost:5432/myapp
REDIS_URL=redis://localhost:6379/0

Makefile

Target Description
make dev Start Postgres + Redis + run migrations + app with --reload
make dev-down Stop all dev containers
make run Run the app directly (no Docker)
make migrate Apply pending migrations
make migrate-make name=... Generate a new migration
make test Run the test suite

Dockerfile

Multi-stage build optimized for uv:

  1. Build stage — installs dependencies with uv sync --frozen, copies source
  2. Runtime stage — slim Python image, non-root user, health check via curl

docker-compose.yml

Production compose file:

  • migrate service runs alembic upgrade head then exits
  • app service depends on migrate completing successfully
  • Both connect to dokploy-network: external: true
  • Health check hits /health every 30 seconds

docker-compose.dev.yml

Dev overrides that layer on top of the production compose:

  • Adds a local Postgres 17 container with health checks
  • Adds a local Redis 7 container for auth token storage
  • Sets DATABASE_URL and REDIS_URL to point at the local services
  • Mounts source files and module directories for hot reload
  • Overrides the network to a local bridge (no external dokploy-network needed)

alembic.ini

Standard Alembic config with script_location = migrations. The sqlalchemy.url is intentionally not set here — it comes from DATABASE_URL via migrations/env.py.

migrations/env.py

One-line configuration using bluefox-core:

from alembic import context
from bluefox_core.migrations import configure_alembic

configure_alembic(context)

configure_alembic() reads DATABASE_URL, imports your models, and sets up both online and offline migration modes.

migrations/script.py.mako

The Alembic template used to generate each new migration file. Alembic reads this when you run bfx db migrate. It uses Mako ${} expressions to fill in the revision ID, message, and upgrade/downgrade stubs. This is the standard Alembic template — you shouldn't need to modify it.

tests/conftest.py

Wires up all bluefox-test fixtures:

from bluefox_test import bluefox_test_setup
from bluefox_core import BluefoxBase

globals().update(bluefox_test_setup(base=BluefoxBase, app_factory="main:create_app"))

This gives you client, db_session, and factory fixtures. Models are auto-discovered by bluefox-core — no manual imports needed.

tests/test_health.py

A smoke test that verifies the /health endpoint:

import pytest


@pytest.mark.asyncio
async def test_health(client):
    response = await client.get("/health")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] == "ok"

tests/test_items.py

Tests for the items module, including auth-protected endpoints:

import pytest


@pytest.mark.asyncio
async def test_list_items__public(client):
    response = await client.get("/items/")
    assert response.status_code == 200
    assert response.json() == {"items": []}


@pytest.mark.asyncio
async def test_my_items__requires_auth(client):
    response = await client.get("/items/me")
    assert response.status_code == 401


@pytest.mark.asyncio
async def test_my_items__authenticated(client):
    # Register a user
    register = await client.post("/auth/register", json={
        "email": "test@example.com",
        "password": "testpassword123",
    })
    assert register.status_code == 200

    # Log in to get a token
    login = await client.post("/auth/login", json={
        "email": "test@example.com",
        "password": "testpassword123",
    })
    assert login.status_code == 200
    token = login.json()["access_token"]

    # Access the protected endpoint
    response = await client.get(
        "/items/me",
        headers={"Authorization": f"Bearer {token}"},
    )
    assert response.status_code == 200
    data = response.json()
    assert data["owner"] == "test@example.com"
    assert data["items"] == []

Three patterns are demonstrated: testing a public endpoint, verifying auth is required (401), and the full register/login/access flow with Bearer tokens. Tests run inside SAVEPOINTs — no cleanup needed between runs.