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 --------- .. code-block:: text 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. .. list-table:: :header-rows: 1 :widths: 20 10 70 * - 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: .. code-block:: jsonnet 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. .. code-block:: python 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.