Skip to content

Make the Python SDK self-contained: bring the runtime and codegen in-repo - #17

Draft
eunomie wants to merge 17 commits into
dagger:mainfrom
eunomie:python-sdk-runtime-consolidation-lead-ea131db2
Draft

Make the Python SDK self-contained: bring the runtime and codegen in-repo#17
eunomie wants to merge 17 commits into
dagger:mainfrom
eunomie:python-sdk-runtime-consolidation-lead-ea131db2

Conversation

@eunomie

@eunomie eunomie commented Aug 18, 2026

Copy link
Copy Markdown
Member

dagger/python-sdk has so far been only the authoring half of the Python SDK. The client library, the code generator and the module runtime all live in dagger/dagger under sdk/python, baked into the engine and resolved by a pinned digest — so the SDK cannot ship a runtime, library or codegen fix without an engine release.

This brings them here.

  • runtime/dagger/dagger's sdk/python/runtime at 501b57e0476dee5881b99a064c3c04173134ecc7, then simplified. It is reached only by modules that build from committed files, so ModuleRuntime becomes that path unconditionally and the template scaffolding it shared with Codegen goes away — seeding a new module's files is initModule's job in this repo.
  • runtime/sdk/ — the dagger-io client library and code generator, vendored, read via dag.CurrentModule().Source() rather than a contextual +defaultPath argument (contextual resolution is redirected to the consuming workspace when one is bound, which for a ref-loaded runtime is the wrong tree).

Legacy modules are untouched. A dagger.json with "sdk": {"source": "python"} still resolves to the engine-baked runtime, with codegen at load, and needs no migration.

This is PR 1 of 2. targetRuntime still returns "python". The engine resolves that short name to exactly one target — the engine-baked runtime (core/sdk/loader.go) — so the modern path is reached by module ref instead. Pointing targetRuntime at github.com/dagger/python-sdk/runtime has to wait until runtime/ is on main, because CI's sdk-sdk checks resolve that ref from the default branch, not from the PR. The follow-up is a one-line flip plus its check.

Verification

Not just unit tests: a dagger-module.toml module whose [runtime] source points at runtime/ is built and called through a released CLI and returns its value, and the same module with a generated file removed fails with an actionable error instead of silently regenerating. Four new checks — e-2-e:runtime-call-check, e-2-e:runtime-requires-generated-files-check, e-2-e:runtime-go-test-check, e-2-e:sdk-test-check (169 vendored-library tests) — plus the legacy regression net (e-2-e:generate-check, e-2-e:generate-all-check, sdk-sdk:module:loads) all green.

Known gaps, recorded rather than papered over

  • The new runtime's codegen path is not exercised end to end here. A local-path [runtime] source resolves on module load but not through dagger generate: polyfill's generate helper builds the module's view from dependencies[].source and include and never reads [runtime] source. That is a fixable polyfill gap, and it disappears in PR 2 where the runtime is referenced by git ref.
  • Keeping the literal name python for both paths needs a dagger/dagger change (three coordinated edits, out of scope here and deliberately not attempted).

Design, alternatives, verified engine behaviour and the full risk list are in future/self-contained-python-sdk.md.

Diff shape

Large but mostly vendored/generated — runtime/sdk/**, runtime/internal/**, runtime/dagger.gen.go, and the fixture's committed sdk/**. The hand-written surface is ~700 lines across runtime/main.go, runtime/python.go, runtime/python_test.go, python-sdk.dang, dagger.json, .dagger/modules/e2e/main.dang and the fixture config.

@eunomie
eunomie force-pushed the python-sdk-runtime-consolidation-lead-ea131db2 branch 2 times, most recently from e826c1c to 78d67fa Compare August 21, 2026 08:02
eunomie added 15 commits August 21, 2026 11:44
Record the architecture for moving the Python client library, code generator
and module runtime into this repository, with the legacy dagger.json path left
on dagger/dagger's in-tree sdk/python.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Copy dagger/dagger's sdk/python/runtime at commit
501b57e0476dee5881b99a064c3c04173134ecc7. This is the module runtime the
engine bakes in and serves for the "python" runtime name today; bringing it
here is what lets this SDK own and release it.

Copied verbatim apart from the module rename: the upstream module is called
python-sdk, which is this repository's root module name. The simplification to
a committed-files-only runtime is a separate patch so its diff stands alone.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Copy dagger/dagger's sdk/python client library (src/dagger) and code generator
(codegen) at commit 501b57e0476dee5881b99a064c3c04173134ecc7, plus the two test
suites that cover what this repository now owns: generator output and module
registration.

The library lives inside the runtime module and is read from
currentModule().source() rather than a contextual +defaultPath argument.
Contextual arguments resolve against a Workspace bound into the context when
there is one, which for a runtime loaded by ref is the consuming workspace, not
this module. The +ignore allowlist the contextual argument carried is applied
explicitly instead, so vendoring into user modules keeps the same contents.

Signed-off-by: Yves Brissaud <yves@dagger.io>
This runtime is reached only through a dagger-module.toml module's [runtime]
source, and the engine never runs codegen for those: it decides from the config
filename alone and omits the introspection JSON when the SDK declares the
argument optional. So the branch can go away rather than be carried.

ModuleRuntime is now what the trusted path used to be, and the template
scaffolding it shared with Codegen goes with it: seeding a new module's files
is initModule's job in this repository, not the runtime's. The entrypoint
script moves out of template/, which it was never part of.

introspectionJson stays declared and optional because that optionality is the
signal the engine reads to skip runtime codegen at all.

TrustedSource stays: it still separates building a module from generating its
code, which is the one path allowed to re-vendor the SDK and re-resolve the
lock file.

Signed-off-by: Yves Brissaud <yves@dagger.io>
The root module declared no include, so every initModule, generate and config
call would now carry the vendored client library and the runtime's generated Go
bindings — tens of thousands of lines that the authoring module never reads,
and whose every edit would invalidate its cache.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Four checks. Two drive a real module through the runtime with a released CLI
via the sdk-sdk harness: a dagger-module.toml module whose [runtime] source
points at runtime/ builds and returns its value, and the same module with a
generated file removed fails with an actionable error instead of quietly
regenerating it. Two more run the runtime's own Go tests and the vendored
library's code-generator and module-registration suites, which are otherwise
carried without ever being executed.

The fixture references the runtime by relative path, which resolves when the
CLI loads the module but not through dagger generate: polyfill's generate
helper builds the module's view from its dependencies and include list and
never reads [runtime] source, so the runtime is filtered out. The fixture is
therefore not registered as a managed module, and its vendored sdk/ was
generated once and committed. Modules created by this SDK will reference the
runtime by git ref, which has no such limit.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
The runtime, the vendored client library and the code generator are in the
repository and green in CI. What remains is pointing targetRuntime at them,
which needed this to land first.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Code generation belongs to `dagger generate`, and under CLI 1.0 that means the
SDK module's @generate hook — not the runtime a module names. This runtime only
ever built from committed files; keeping a code generator inside it left the SDK
with two, and made which one ran depend on a module's runtime source rather than
on which SDK manages it.

So codegen here becomes a no-op. The engine reads its presence as the SDK's
code-generator capability, so dropping it entirely would route generation
elsewhere. Everything that existed to serve it goes with it: vendoring the
client library, the composable Common/WithSDK/WithUpdates chain, the SDK source
directory and its dist/ probing, and TrustedSource, which now only ever has one
value.

The client library moves out of the runtime to the repository root, where the
SDK module that now generates with it can read it.

Signed-off-by: Yves Brissaud <yves@dagger.io>
generateAll is this SDK's @generate hook, but it handed each module back to the
engine, which dispatches codegen to whatever the module's runtime source names.
So `dagger generate` on a Python module ran the engine's builtin generator, and
the code generator vendored into this repository was never reached.

Modern (dagger-module.toml) modules are now generated here: take the module's
dependency schema, run this repository's code generator against it, and vendor
the result. Pre-1.0 dagger.json modules keep going through the engine, so they
stay on the builtin generator, exactly as before.

The schema comes from ModuleSource rather than Module: it is the dependency
schema, so it does not need to load the module being generated — which cannot
work before that module's bindings exist.

Signed-off-by: Yves Brissaud <yves@dagger.io>
The tell that generation is this SDK's and not the engine's is what lands in the
module: this SDK vendors only the client library, while the builtin passes its
whole sdk/python tree and drops a copy of its runtime in too.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
The runtime was still a pre-1.0 dagger.json module, which meant the engine
regenerated its bindings on every load — the very codegen-at-load this SDK
exists to avoid, applied to itself. As a dagger-module.toml module it builds
from committed bindings like any other 1.0 module.

That only works if something regenerates them, so the workspace now installs
github.com/dagger/go-sdk and lets it claim runtime/: `dagger generate` keeps
the committed bindings current.

Signed-off-by: Yves Brissaud <yves@dagger.io>
The codegen function was a no-op kept only so the engine would still see a
code-generator capability on this SDK. Nothing needs it: generation is the SDK
module's @generate hook, and a module reaching this runtime is already
generated. So it goes, along with the comments that still described the runtime
as generating.

introspectionJson stays on moduleRuntime, declared and never read. Its
optionality is what tells the engine it may skip runtime codegen and omit the
argument; removing it would tell the engine the opposite. It is the opt-out,
not a code path.

Signed-off-by: Yves Brissaud <yves@dagger.io>
Two follow-ups worth the record: module identity now comes from the workspace
rather than a module's config, and this branch sits on the polyfill removal —
which fixed `dagger module init` writing the vendored library to a doubled
path, and required vendoring to layer onto sdk/ rather than replace it.

Also pins go-sdk, which the workspace now installs to regenerate the runtime.

Signed-off-by: Yves Brissaud <yves@dagger.io>
@eunomie
eunomie force-pushed the python-sdk-runtime-consolidation-lead-ea131db2 branch from 78d67fa to e5d312c Compare August 21, 2026 10:01
Picks up the new monorepo checks, which cover a workspace config living in a
git subdirectory: this SDK installs, scaffolds and loads modules under one.

Signed-off-by: Yves Brissaud <yves@dagger.io>
The copy came with an extension surface: a dozen chainable functions and every
piece of state exposed as an object field, so a custom SDK could depend on this
module and hook into the middle of it. Nothing here needs that, and carrying it
means every internal step is public API that cannot change.

So the module now exposes exactly one function, moduleRuntime, and no fields.
extension.go is gone; the three helpers it held that the build path actually
uses — uv on/off and the two index URLs — moved in as plain functions. The
steps are unexported, and the state fields are +private: still marshalled
between the constructor and the call, which unexporting them would silently
break, but absent from the schema.

Also gone: the EnableCustomConfig toggle, which only the deleted extension
function could turn off.

1432 lines of Go down to 1012.

Signed-off-by: Yves Brissaud <yves@dagger.io>
@eunomie
eunomie force-pushed the python-sdk-runtime-consolidation-lead-ea131db2 branch from 06d110a to 9779540 Compare August 21, 2026 13:45
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