Playground

The playground/ directory in the kiln repository is a runnable multi-app FastAPI project that demonstrates kiln’s project mode. It generates a blog API and an inventory API from Jsonnet configs, mounts them both under /v1/, and serves them with uvicorn.

It is the fastest way to see kiln end-to-end without setting up your own project.

Structure

playground/
├── examples/
│   ├── project.jsonnet   # top-level project config
│   ├── blog.jsonnet      # blog app config
│   └── inventory.jsonnet # inventory app config
├── blog/
│   └── models/           # hand-written SQLAlchemy models
├── inventory/
│   └── models.py         # hand-written SQLAlchemy models
├── db/
│   └── base.py           # shared PGCraftBase
├── alembic/              # Alembic migration env
├── _generated/           # written by foundry generate (git-ignored)
├── main.py               # FastAPI entry point
└── justfile              # convenience recipes

_generated/ is the output directory. Everything inside it is overwritten on every foundry generate run and should not be edited by hand.

The blog/ and inventory/ directories contain hand-written code that coexists with the generated _generated/blog/ and _generated/inventory/ packages via Python namespace packages — no __init__.py in blog/ or inventory/, both parent directories on sys.path.

Quick start

From the repo root, install all dependency groups:

uv sync --all-groups

Then from playground/:

just g    # generate from examples/project.jsonnet
just s    # start uvicorn with --reload

Or in one step:

just f    # generate then serve

The server starts at http://localhost:8000. Interactive docs are at http://localhost:8000/docs.

Justfile recipes

All recipes are run from the playground/ directory.

Recipe

Alias

Description

just generate

just g

Run foundry generate against examples/project.jsonnet.

just serve

just s

Start uvicorn with hot-reload.

just fresh

just f

Generate then serve.

just migrate

Apply all pending Alembic migrations.

just migration

Generate a new autogenerated migration revision.

Project config

examples/project.jsonnet is a project-level config — it declares shared auth and databases, then lists each app with its URL prefix:

local auth = import "kiln/auth/jwt.libsonnet";
local db   = import "kiln/db/databases.libsonnet";

{
  auth: auth.jwt({ secret_env: "JWT_SECRET" }),

  databases: [
    db.postgres("primary", { default: true }),
    db.postgres("analytics", { url_env: "ANALYTICS_DATABASE_URL" }),
  ],

  apps: [
    { config: import "blog.jsonnet",      prefix: "/blog"      },
    { config: import "inventory.jsonnet", prefix: "/inventory" },
  ],
}

Each app config (blog.jsonnet, inventory.jsonnet) is a self-contained kiln config with its own module, resources. Kiln merges the project-level auth and databases into each app config before generating, so app configs do not need to redeclare them.

Apps

blog (module: "blog")

Demonstrates plain read_only and full presets alongside a custom CRUD resource. The Author model is read-only; Article has full CRUD with per-operation auth and two actions; Tag uses the full preset with an integer PK and no auth.

inventory (module: "inventory")

Demonstrates the full range of resource patterns:

  • Product — full CRUD with a custom route_prefix, explicit field lists, per-operation auth, and two actions (one with params and auth, one with no params and no auth).

  • StockMovement — create-only (append-only) record with a date field.

  • EventLog — the write_only preset (create/update/delete, no reads) backed by a PGCraftAppendOnly model on the analytics database. Exercises db_key, route_prefix, email/json/datetime field types, require_auth: true (all operations), and a no-param no-auth action.

EventLog and PGCraftAppendOnly

inventory/models.py defines EventLog using __pgcraft__ = {"factory": PGCraftAppendOnly}. This instructs pgcraft to create an SCD Type 2 structure: a root table (for the stable id/created_at identity) and an attributes table (for mutable columns), joined through an event_logs view backed by INSTEAD OF triggers. Each write appends a new attributes row, preserving the full change history.

class EventLog(Base):
    __tablename__ = "event_logs"
    __table_args__ = {"schema": "public"}
    __pgcraft__ = {"factory": PGCraftAppendOnly}

    # Raw Column instances required — pgcraft's plugin pipeline
    # collects columns from the class dict before ORM mapping.
    event_type  = Column(String(80), nullable=False)
    actor_email = Column(String(254), nullable=False)
    payload     = Column(Text, nullable=True)

Kiln exposes it as a write_only resource on the analytics database, so all mutations go to the secondary pool while reads remain absent from the API entirely.