Skip to content

Development

Prerequisites

  • Python >= 3.12
  • PostgreSQL 14+
  • uv package manager
  • task (go-task)

Environment Variables

Variable Description Default
CELINE_ENV Deployment posture; only dev accepts the development defaults below (ENVIRONMENT is read when it is unset). Read from the process environment, not .env unset = hardened
DATABASE_URL PostgreSQL async connection string postgresql+asyncpg://postgres:securepassword123@host.docker.internal:15432/celine_rec_registry
DATABASE_ECHO Log SQL statements false
BASE_URL Public base URL of the service http://api.celine.localhost/rec-registry
DEFAULT_PAGE_SIZE Default pagination page size 50
MAX_PAGE_SIZE Maximum pagination page size 500
AUTH_ENABLED Enable JWT authentication true
AUTH_HEADER_NAME HTTP header for auth token authorization
CELINE_OIDC_BASE_URL OIDC issuer (from celine-sdk) local Keycloak (dev only)
CELINE_OIDC_JWKS_URI JWKS every JWT is verified against local Keycloak (dev only)
CELINE_OIDC_AUDIENCE Expected token audience svc-rec-registry
CELINE_OIDC_CLIENT_ID / CELINE_OIDC_CLIENT_SECRET The service's own client, if any —
POLICIES_ENABLED Enable OPA policy evaluation true
CELINE_POLICIES_DIR Directory containing .rego files ./policies
CELINE_POLICIES_DATA_DIR Optional directory for policy data JSON —
POLICIES_PACKAGE OPA package to evaluate celine.rec_registry.access
CELINE_POLICIES_CACHE_ENABLED Enable in-memory decision cache true
CELINE_POLICIES_CACHE_TTL Cache TTL in seconds 300

Development defaults and CELINE_ENV

The defaults above are for the local stack, and the service refuses them unless CELINE_ENV=dev (REQ-0088). Unset is hardened, as is any value other than dev (staging, prod, a typo). Hardened, startup fails with InsecureConfiguration listing every one of these at once; in dev the same list is a single warning:

  • DATABASE_URL carrying a local-stack password (securepassword123, postgres);
  • AUTH_ENABLED=false or POLICIES_ENABLED=false — with policies off, any validly signed token is a full administrator of /admin;
  • CELINE_OIDC_BASE_URL or CELINE_OIDC_JWKS_URI not set (the SDK's local Keycloak);
  • CELINE_OIDC_CLIENT_SECRET empty or equal to CELINE_OIDC_CLIENT_ID, when a client id is set.

task run and task debug export CELINE_ENV=dev unless it is already set, so the local runner needs nothing. CELINE_ENV=staging task run is the prod-like mode of the same entry point: it starts only with real values. The CLI applies the same rule to client credentials: a --client-secret equal to --client-id (the local celine-cli/celine-cli) is refused unless CELINE_ENV=dev (REQ-0089).

The check lives in celine.sdk.posture, first released in celine-sdk 2.0.0.

Local Setup

uv sync

# Run migrations
task db:migrate

# Start development server (port 8004)
task run

Taskfile Commands

Command Description
task run Start dev server on port 8004
task debug Start with debugger (port 48004)
task db:migrate Run alembic upgrade head
task db:revision Create new Alembic migration
task import:community -- --file <path> Import a community YAML bundle
task import:community:example Import the example community
task release Run semantic-release

Alembic Migrations

# Apply pending migrations
task db:migrate

# Create a new migration
task db:revision -- -m "add delivery points"

# Or directly:
uv run alembic upgrade head
uv run alembic revision --autogenerate -m "description"
uv run alembic downgrade -1

Testing

uv run pytest -q

Tests marked integration need a real PostgreSQL and skip when none is reachable, so a green run means different things depending on whether one was up — with no database, the write, lookup and self-service tests do not run:

uv run pytest -q -m integration    # only those; TEST_DATABASE_URL overrides the connection

Each run creates and drops throwaway schemas named for that run (rec_registry_test_<random>, and rec_registry_migrations_<random> for the migration test), so it needs no CREATE DATABASE rights and two runs can share one database. A run killed mid-way leaves its schemas behind, to be dropped by hand.

A test declares which requirement it covers with a @verifies REQ-#### tag in its docstring; see the requirements and, for the working procedure, the companion's testing playbook.

Project Layout

src/celine/rec_registry/
  main.py                    # FastAPI app factory (create_app)
  db/
    models.py                # SQLAlchemy ORM: Community, Member, Asset
    session.py               # Async session management
  schemas/
    models.py                # Pydantic response schemas
    bundle.py                # Bundle import schemas (v0.7)
  api/
    user.py                  # Self-service user endpoints
    meta.py                  # Health, version
    admin/
      communities.py         # Community/member/asset browsing (reads)
      writes.py              # Runtime member/asset/area writes
      lookup.py              # Cross-community lookups
      management.py          # Import/export operations
  services/
    members.py               # Row building, shared by the importer and the write API
    sensors.py               # One active holder per sensor id (REQ-0069)
    importer.py              # Bundle import logic
    exporter.py              # YAML export logic
  core/
    settings.py              # Pydantic settings
    middleware.py             # Auth/policy middleware
    yaml_io.py               # YAML parsing utilities
    area_boundary.py         # One area, one primary substation; area keys (REQ-0067)
    errors.py                # Refusal codes, {detail, code} (REQ-0073)
    member_values.py         # Role and status sets (REQ-0066)
    sensor_id.py             # Sensor id trimming, shared with SQL
    versions.py              # Schema and API versions, one copy
  cli/
    main.py                  # CLI entry point (celine-rec-registry)
    config.py                # CLI configuration
policies/                    # OPA .rego policy files
alembic/
  versions/                  # Migration scripts
tests/
  conftest.py                # Fixtures, including the live-database ones
docs/
  specifications/            # What the service must do; REQ-#### identifiers
  decisions/                 # Why a technical choice was made