Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/build-contract-shell.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Wraps the Dhall-generated package (from tests/Exhaustive.dhall) in a minimal
# Wraps the Dhall-generated package (from demos/Exhaustive.dhall) in a minimal
# consumer shell, mirroring the hand-written tests/golden/ shell (pyproject.toml
# + py.typed) so basedpyright strict runs against the same layout a real
# consumer would import, per the full_package pattern in tests/conftest.py.
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ jobs:
with:
ref: ${{ inputs.ref || github.sha }}

# The `as Source` import mode on gen/Deps only changes how pgn loads the
# The `as Source` import mode on src/Deps only changes how pgn loads the
# remote packages (unnormalized, to save RAM), not what they evaluate to.
# The pinned action below bundles a dhall fork that predates the mode, so
# strip it here; the evaluation is semantically identical either way. The
Expand All @@ -132,16 +132,16 @@ jobs:
shell: bash
run: |
set -euo pipefail
sed -i -e 's/^[[:space:]]*as Source$//' -e 's/^[[:space:]]*sha256:[0-9a-f]\{64\}$//' gen/Deps/*.dhall
sed -i -e 's/^[[:space:]]*as Source$//' -e 's/^[[:space:]]*sha256:[0-9a-f]\{64\}$//' src/Deps/*.dhall

# A plain, standard-Dhall evaluator cannot run this: gen/Interpreters/Project.dhall's
# A plain, standard-Dhall evaluator cannot run this: src/Interpreters/Project.dhall's
# buildLookup and gen-sdk's own Fixtures.Exhaustive both use Text/equal, a builtin
# from pgn's forked Dhall, absent from the upstream dhall-lang Prelude. This action
# bundles that same fork.
- name: Generate output from Dhall
uses: nikita-volkov/dhall-directory-tree.github-action@60a18dc647d6daea805263ea0fed7bb8011f3bcd # v2
with:
dhall_file: tests/Exhaustive.dhall
dhall_file: demos/Exhaustive.dhall
output_dir: contract-output

- name: Assert the compile did not fail
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
id: resolve
uses: nikita-volkov/dhall-resolve.github-action@7caaf1fdb40ac864bc02e37575f577ee084713a8 # v3
with:
file: gen/Gen.dhall
file: src/package.dhall
minify: true

- name: Prepare changelog for release
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ already says.

## Dhall

`gen/` pins its remote imports by sha256 (`gen/Deps/*.dhall`). Bump those
`src/` pins its remote imports by sha256 (`src/Deps/*.dhall`). Bump those
deliberately, one at a time, and re-run the harness before committing a pin
change.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Upcoming

- Migrated the generator's internal dependencies to `gen-contract` v4.0.1
and `gen-sdk` v2.0.0, adopting `Sdk.Sigs` in place of the local
`Algebras/` module, and restructured the repository layout to match the
pGenie generator architecture: implementation moved from `gen/` to
`src/`, the public entry point renamed from `gen/Gen.dhall` to
`src/package.dhall`, and the fixture driver moved from
`tests/Exhaustive.dhall` to `demos/Exhaustive.dhall`. No change to
generated output or the public Dhall interface (`artifacts.<name>.gen`
URLs pointing at a previously-released `resolved.dhall` are unaffected;
only the next release's URL path changes, from `.../gen/Gen.dhall` — the
unresolved source path some projects may reference directly instead of a
frozen release — to `.../src/package.dhall`).
- The test harness now runs every pgn subprocess in its own process group under
an RSS watchdog: a thread polls `ps -o rss=` every 2 s and, on breach of
`PGN_MAX_RSS_GB` (default 40 GB), kills the whole group and fails the test with
Expand Down
46 changes: 25 additions & 21 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# python.gen DESIGN

Status: current. This describes the generator as shipped, not a plan. The code
is the source of truth: the Dhall generator under `gen/`, the fixture project
is the source of truth: the Dhall generator under `src/`, the fixture project
and golden output under `tests/`. When this doc and the tree disagree, the
tree wins.

Expand Down Expand Up @@ -378,34 +378,37 @@ overwritten on every run. Do not hand-edit it.

## 10. Generator decomposition

`gen/Gen.dhall` is the entry point handed to gen-sdk:
`src/package.dhall` is the entry point handed to gen-sdk:

```dhall
let Sdk = ./Deps/Sdk.dhall

in Sdk ./Config.dhall ./compile.dhall
let Config = ./Config.dhall

let interpret = ./Interpret.dhall

in Sdk.Sigs.generator Config Config/default interpret
```

The Sdk `module` function has signature `\(Config : Type) -> \(compile) ->
{ contractVersion, Config, compile, compileToFileMap }`, and
`compile : Optional Config -> Project -> Lude.Compiled.Type Lude.Files.Type`,
where `Files.Type = List { path : Text, content : Text }`. `compile.dhall`
folds the optional user config into the internal interpreter config and
calls `Interpreters/Project.dhall`, which traverses queries and custom types
and assembles the file list.
`Sdk.Sigs.generator` has signature `\(Config : Type) -> \(defaultConfig : Config) ->
\(interpret : Config -> Contract.Project -> Contract.Output) -> ...`; it curries
`interpret` against `defaultConfig` whenever the user config is absent and hands
the result to gen-contract's `Contract.module`. `Interpret.dhall` folds the
optional user config into the internal interpreter config and calls
`Interpreters/Project.dhall`, which traverses queries and custom types and
assembles the file list (`Contract.Output`).

`gen/` mirrors a typical pgn gen-sdk generator, Python-flavored. The
algebra/interpreter/template split keeps assembly separate from rendering.
`src/` mirrors a typical pgn gen-sdk generator, Python-flavored: `Interpreters/`
assembles data, `Templates/` renders it to Python text. The interpreter/template
algebra signatures themselves live in gen-sdk's `Sdk.Sigs` (`interpreter.dhall`/
`template.dhall`), not a local `Algebras/` dir.

```text
gen/
Gen.dhall # Sdk Config compile (entry handed to gen-sdk)
src/
package.dhall # Sdk.Sigs.generator Config Config/default interpret (entry handed to gen-sdk)
Config.dhall # user config TYPE: { packageName, emitSync, onUnsupported }
compile.dhall # derive interpreter Config from user Config, call Project.run
Deps/ # pinned remote imports (gen-sdk module + Project, lude, Prelude)
Algebras/
Interpreter.dhall # Config + `module Input Output run` ; Run = Config -> Input -> Compiled Output
Template.dhall # `module Params run` ; Run = Params -> Text
Interpret.dhall # derive interpreter Config from user Config, call Project.run
Deps/ # pinned remote imports: gen-sdk, gen-contract, lude, dhall Prelude
Structures/
Surface.dhall # async/sync token table (section 4)
CustomKind.dhall # Lookup : Name -> < Enum | Composite | Absent > + composite fields
Expand All @@ -426,6 +429,7 @@ gen/
Project.dhall # traverse queries+customTypes, assemble all files + facade + header,
# apply the Skip filter (section 11)
Templates/
CoreModule.dhall # shared _core.py: JsonValue, NoRowError/DecodeError, require_array
RuntimeModule.dhall # async + sync _runtime.py bodies
RowsModule.dhall # shared _rows.py (Row dataclasses + decode fns)
StatementModule.dhall # one per-surface statement wrapper
Expand Down Expand Up @@ -629,11 +633,11 @@ SQL rendering are largely driver-agnostic.

CI runs two independent jobs (`.github/workflows/ci.yml`): `harness` (the
pytest suite against a live Postgres) and `contract` (compiles gen-sdk's
`Fixtures.Exhaustive` via `tests/Exhaustive.dhall` and runs basedpyright
`Fixtures.Exhaustive` via `demos/Exhaustive.dhall` and runs basedpyright
strict on the result). The `contract` job needs `nikita-volkov/dhall-directory-tree.github-action`,
a Docker action bundling a forked Dhall evaluator; the local `dhall` CLI most
people have installed is the standard dhall-lang build and does not
understand `Text/equal`, so it cannot run `tests/Exhaustive.dhall` directly.
understand `Text/equal`, so it cannot run `demos/Exhaustive.dhall` directly.
Reproduce the `contract` job locally with [`act`](https://github.com/nektos/act)
(not installed in this environment; `act -j contract` pulls the same pinned
Docker action and runs the job as GitHub would).
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ name:
```yaml
artifacts:
python:
gen: https://raw.githubusercontent.com/slavashvets/python.gen/master/gen/Gen.dhall
gen: https://raw.githubusercontent.com/slavashvets/python.gen/master/src/package.dhall
config:
packageName: my-db-client
emitSync: true
Expand All @@ -61,10 +61,10 @@ database.

| form | example | works? |
| --------------- | --------------------------------------- | ------------------------------------------ |
| plain http(s) | `https://.../python.gen/gen/Gen.dhall` | yes; pgn fetches it and every relative import over HTTP |
| relative path | `../path/to/python.gen/gen/Gen.dhall` | yes, if you keep a local checkout next to your project |
| absolute path | `/abs/path/to/python.gen/gen/Gen.dhall` | yes, but the resulting freeze key is machine-specific |
| `file://` URL | `file:///abs/.../Gen.dhall` | rejected; pgn's project schema does not accept `file://` |
| plain http(s) | `https://.../python.gen/src/package.dhall` | yes; pgn fetches it and every relative import over HTTP |
| relative path | `../path/to/python.gen/src/package.dhall` | yes, if you keep a local checkout next to your project |
| absolute path | `/abs/path/to/python.gen/src/package.dhall` | yes, but the resulting freeze key is machine-specific |
| `file://` URL | `file:///abs/.../package.dhall` | rejected; pgn's project schema does not accept `file://` |

Whichever form you use, the freeze file that caches the resolved generator
(section "Freeze lifecycle" below) keys on the literal `gen:` value and
Expand Down
24 changes: 19 additions & 5 deletions bench/as-source.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Cold-cache benchmark of `pgn generate` on the fixture project, comparing the
# current gen (Deps imported `as Source`) against the same tree with the
# current src (Deps imported `as Source`) against the same tree with the
# pre-as-Source Deps (plain pinned imports). Both variants run the same pgn
# binary by default, so the measurement isolates the import mode itself.
#
Expand All @@ -24,7 +24,7 @@ trap 'rm -rf "$work"' EXIT

prepare() { # $1 = variant root; mirrors the repo layout the fixture expects
rm -rf "$1" && mkdir -p "$1/tests"
cp -R "$root/gen" "$1/gen"
cp -R "$root/src" "$1/src"
cp -R "$root/tests/fixture-project" "$1/tests/fixture-project"
rm -f "$1/tests/fixture-project/freeze1.pgn.yaml"
rm -rf "$1/tests/fixture-project/artifacts"
Expand All @@ -34,9 +34,23 @@ prepare() { # $1 = variant root; mirrors the repo layout the fixture expects
# normalized-expression pins (an `as Source` pin hashes the import's source,
# so the two modes need different sha256 values for the same version).
strip_as_source() { # $1 = variant root
perl -i -ne 'print unless /^\s*as Source$/' "$1"/gen/Deps/*.dhall
perl -i -pe 's/8d43544ecb0e612406af3133bdbca51138c704a77a5a29ef62fe034d0e77a3a6/b9f7bb842345f3864c71e877fda4200306ba5c044a43e6f7713a23bc4769b91a/' "$1/gen/Deps/Sdk.dhall"
perl -i -pe 's/46b527b071eba96a17e76b4bc5774645714dd5b4355974d221e705aa7c126e77/14c43eec97972ae27afe3386ff937d04db66f84273d5551476361db12d2c4b50/' "$1/gen/Deps/Lude.dhall"
perl -i -ne 'print unless /^\s*as Source$/' "$1"/src/Deps/*.dhall

# gen-sdk v2.0.0's src/package.dhall: `mise x -- dhall hash` against the
# live GitHub-hosted package (both `... as Source` and the plain import)
# returns the SAME sha256 (b9def6ab1179bc4aaae7fc6e91977f094f75934cd5755175c294a9e97ca71b15),
# matching the value already committed in src/Deps/Sdk.dhall -- so, unlike
# the old v0.11.0 pin this pair used to target (where the two genuinely
# differed: 8d43544e...->b9f7bb84...), no character swap is needed here
# after the strip above; the committed pin already equals the plain-import
# target. See docs/superpowers/plans/2026-07-11-gen-sdk-v2-migration.md
# Task 6 for how this was verified (dhall's local import cache made the
# lookup instant; a cold, uncached fetch of a *different* URL hung in this
# sandbox, so treat network reachability here as best-effort, not given).

# lude v5.1.0 is unchanged by this migration (see src/Deps/Lude.dhall), so
# its existing as-Source -> plain-import hash swap below is still correct.
perl -i -pe 's/46b527b071eba96a17e76b4bc5774645714dd5b4355974d221e705aa7c126e77/14c43eec97972ae27afe3386ff937d04db66f84273d5551476361db12d2c4b50/' "$1/src/Deps/Lude.dhall"
}

measure() { # $1 = label, $2 = variant root, $3 = pgn binary
Expand Down
26 changes: 20 additions & 6 deletions bench/generate.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Build the fixture client from the working-tree gen, in one of two variants:
# with - gen/Deps as committed (imports `as Source`)
# Build the fixture client from the working-tree src, in one of two variants:
# with - src/Deps as committed (imports `as Source`)
# without - the pre-as-Source Deps (mode stripped, normalized-expression
# pins restored; byte-identical to commit fb78869's Deps)
# Output lands in ./demo-with-as-source or ./demo-without-as-source.
Expand All @@ -15,15 +15,29 @@ url="${PGN_TEST_DATABASE_URL:-postgresql://postgres:postgres@localhost:5432/post
out="$root/demo-$variant-as-source"

rm -rf "$out" && mkdir -p "$out/tests"
cp -R "$root/gen" "$out/gen"
cp -R "$root/src" "$out/src"
cp -R "$root/tests/fixture-project" "$out/tests/fixture-project"
rm -f "$out/tests/fixture-project/freeze1.pgn.yaml"
rm -rf "$out/tests/fixture-project/artifacts"

if [ "$variant" = without ]; then
perl -i -ne 'print unless /^\s*as Source$/' "$out"/gen/Deps/*.dhall
perl -i -pe 's/8d43544ecb0e612406af3133bdbca51138c704a77a5a29ef62fe034d0e77a3a6/b9f7bb842345f3864c71e877fda4200306ba5c044a43e6f7713a23bc4769b91a/' "$out/gen/Deps/Sdk.dhall"
perl -i -pe 's/46b527b071eba96a17e76b4bc5774645714dd5b4355974d221e705aa7c126e77/14c43eec97972ae27afe3386ff937d04db66f84273d5551476361db12d2c4b50/' "$out/gen/Deps/Lude.dhall"
perl -i -ne 'print unless /^\s*as Source$/' "$out"/src/Deps/*.dhall

# gen-sdk v2.0.0's src/package.dhall: `mise x -- dhall hash` against the
# live GitHub-hosted package (both `... as Source` and the plain import)
# returns the SAME sha256 (b9def6ab1179bc4aaae7fc6e91977f094f75934cd5755175c294a9e97ca71b15),
# matching the value already committed in src/Deps/Sdk.dhall -- so, unlike
# the old v0.11.0 pin this pair used to target (where the two genuinely
# differed: 8d43544e...->b9f7bb84...), no character swap is needed here
# after the strip above; the committed pin already equals the plain-import
# target. See docs/superpowers/plans/2026-07-11-gen-sdk-v2-migration.md
# Task 6 for how this was verified (dhall's local import cache made the
# lookup instant; a cold, uncached fetch of a *different* URL hung in this
# sandbox, so treat network reachability here as best-effort, not given).

# lude v5.1.0 is unchanged by this migration (see src/Deps/Lude.dhall), so
# its existing as-Source -> plain-import hash swap below is still correct.
perl -i -pe 's/46b527b071eba96a17e76b4bc5774645714dd5b4355974d221e705aa7c126e77/14c43eec97972ae27afe3386ff937d04db66f84273d5551476361db12d2c4b50/' "$out/src/Deps/Lude.dhall"
fi

cd "$out/tests/fixture-project"
Expand Down
16 changes: 8 additions & 8 deletions tests/Exhaustive.dhall → demos/Exhaustive.dhall
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
-- Applies this generator to gen-sdk's shared cross-backend fixture project
-- (the same "music_catalogue" project java.gen's own tests/Exhaustive.dhall
-- (the same "music_catalogue" project java.gen's own demos/Exhaustive.dhall
-- exercises), so a Python client compiles from it and passes basedpyright
-- strict. Pinned directly at gen-sdk's package.dhall, separately from
-- gen/Deps/Sdk.dhall: that file only imports gen-sdk's `module.dhall` (the
-- generator-construction function), which has no `Fixtures` field.
-- src/Deps/Sdk.dhall: that file only imports gen-sdk's `package.dhall` `as
-- Source` for RAM, and this fixture load doesn't need that mode.
--
-- The fixture project deliberately covers PG types this generator does not
-- support (box, inet, money, ranges, ...), so onUnsupported is set to Skip:
Expand All @@ -13,13 +13,13 @@
-- Intended to be executed with:
--
-- ```bash
-- dhall to-directory-tree --file tests/Exhaustive.dhall --output <dir> --allow-path-separators
-- dhall to-directory-tree --file demos/Exhaustive.dhall --output <dir> --allow-path-separators
-- ```
let Sdk = ../gen/Deps/Sdk.dhall
let Sdk = ../src/Deps/Sdk.dhall

let Gen = ../gen/Gen.dhall
let Gen = ../src/package.dhall

let OnUnsupported = ../gen/Structures/OnUnsupported.dhall
let OnUnsupported = ../src/Structures/OnUnsupported.dhall

let project = Sdk.Fixtures.Exhaustive

Expand All @@ -30,4 +30,4 @@ let config =
, onUnsupported = Some OnUnsupported.Mode.Skip
}

in Gen.compileToFileMap config project
in Sdk.Output.toFileMap (Gen.compile config project)
21 changes: 0 additions & 21 deletions gen/Algebras/Interpreter.dhall

This file was deleted.

5 changes: 0 additions & 5 deletions gen/Algebras/Template.dhall

This file was deleted.

1 change: 0 additions & 1 deletion gen/Algebras/package.dhall

This file was deleted.

3 changes: 0 additions & 3 deletions gen/Deps/Sdk.dhall

This file was deleted.

4 changes: 0 additions & 4 deletions gen/Deps/package.dhall

This file was deleted.

3 changes: 0 additions & 3 deletions gen/Gen.dhall

This file was deleted.

Loading
Loading