Skip to content

Generate a static module entrypoint behind a staticEntrypoint setting - #28

Draft
eunomie wants to merge 8 commits into
dagger:mainfrom
eunomie:python-sdk-static-entrypoint-lead-pythonsdk-080b6d74
Draft

Generate a static module entrypoint behind a staticEntrypoint setting#28
eunomie wants to merge 8 commits into
dagger:mainfrom
eunomie:python-sdk-static-entrypoint-lead-pythonsdk-080b6d74

Conversation

@eunomie

@eunomie eunomie commented Sep 12, 2026

Copy link
Copy Markdown
Member

Static module entrypoint (opt-in)

With the SDK setting staticEntrypoint = true, dagger generate writes a manifest version 2 module whose types the engine loads without running Python.

How it works

  • dagger generate builds the module's container, imports the module inside it (python -m dagger.mod entrypoint) and renders what its decorators registered into sdk/entrypoint/types.dang. The types are the ones the runtime would register, from the same code path (Module.describe() feeds both to_typedef and the renderer).
  • sdk/entrypoint/main.dang implements ModuleEntrypoint: types() returns the literal TypeDef list; call() runs python -m dagger.mod call in the module's container, which dispatches through the existing registry. A per-file content digest of every source file that can change the types guards against a stale entrypoint: a call after an edit is refused with a message to run dagger generate.
  • sdk/entrypoint/build.dang is a copy of the new runtime/build.dang (the container build, split out of runtime/main.dang behind type PythonModuleBuild) with the image pins inlined, so the entrypoint needs nothing but itself.
  • The manifest becomes the five lines version 2 allows. The dynamic runtime stays the default; a module switches either way with dagger module init python --path <module> --static-entrypoint[=false] and dagger generate.

What version 2 cannot carry is refused at dagger generate instead of dropped: module clients, cache= on a function, the legacy template, include, a source other than ., disableDefaultFunctionCaching, another runtime, and the codegen, clients or dependencies tables.

Design and history in future/static-module-entrypoint.md, including why the types come from importing the module rather than a static analyzer (dagger/dagger#11803 and its removal in #13251).

Testing

  • sdk/tests/mod: test_describe.py (parity with the previous to_typedef decisions), test_entrypoint.py (goldens for the rendered Dang, digests in the engine's File.digest(excludeMetadata: true) format, the entrypoint subcommand), test_dispatch.py (the call subcommand).
  • e2e: static-scope-init, static-scope-switch, static-scope-refusals, static-types-load next to the existing checks; all green locally on the CI engine.
  • On a dev engine with the version 2 loader: dagger functions on a generated module takes 1.2 s against 2.9 s on the dynamic path; calls run end to end; an edited or added source file, or a lock file added later, is refused naming the file; a .venv/ or a permission-only change is not.

CI's engine does not load version 2 yet, so the e2e checks prove generation and refusals; loading is covered by the dev-engine run above.

Follow-ups

  • Flip the default once engines with the version 2 loader are released (phase 2 of the rollout in the design doc).
  • Module clients on the static path, when version 2 gains a dependency model.

Generate a manifest v2 entrypoint whose types() is written at
dagger generate time, by importing the module in its own container
and reading the runtime registry, gated by a staticEntrypoint SDK
setting rolled out in two phases with the dynamic path kept.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Module.describe() walks the registry into plain dataclasses, and
_typedefs() and to_typedef() materialise them into API calls. A
static entrypoint renders the same description at generate time.

Signed-off-by: Yves Brissaud <yves@dagger.io>
python -m dagger.mod entrypoint imports the module, describes it and
writes types.dang (a literal TypeDef list) and main.dang (the
ModuleEntrypoint that calls the module's container). main.dang bakes
the content digests of the module's sources, so a stale entrypoint
refuses to run instead of serving wrong types.

Signed-off-by: Yves Brissaud <yves@dagger.io>
python -m dagger.mod call reads the request a ModuleEntrypoint forwards,
runs it through the registry and writes the JSON result to a file, so
the generated entrypoint never depends on stdout.

Signed-off-by: Yves Brissaud <yves@dagger.io>
PythonModuleBuild in runtime/build.dang builds a module's container from
its committed files; the runtime module adds runtime.py on top of it, and
the generated static entrypoint carries a copy of the same build.

Signed-off-by: Yves Brissaud <yves@dagger.io>
…setting

With staticEntrypoint = true, `dagger generate` builds the module's
container, renders its types into sdk/entrypoint/types.dang and writes a
manifest version 2 that points at that entrypoint. The dynamic runtime
stays the default and a module switches back by turning the setting off.

Settings a version 2 manifest cannot carry (module clients, include,
disableDefaultFunctionCaching, another runtime) are refused instead of
silently dropped.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
The static path is in the repository, green in CI, and loads on a dev
engine with the version 2 loader. Flipping the default waits for a
released engine.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant