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/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:
- Build stage — installs dependencies with
uv sync --frozen, copies source - Runtime stage — slim Python image, non-root user, health check via curl
docker-compose.yml¶
Production compose file:
- migrate service runs
alembic upgrade headthen exits - app service depends on migrate completing successfully
- Both connect to
dokploy-network: external: true - Health check hits
/healthevery 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_URLandREDIS_URLto 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.