Skip to content

Commit 0c166c2

Browse files
committed
Prepare Python SDK for CLI 1.0 init contract
Mirror the Go SDK's CLI 1.0 init-contract preparation (dagger/go-sdk#2) for the Python SDK: - add `targetRuntime` returning "python" so modules initialized through this SDK target the built-in Python runtime - replace `init` with `initModule(ws, name, path, template, pythonVersion, useUv, baseImage)` that returns only the Python-owned template files; the engine owns dagger.json / dagger-module.toml and workspace config and merges this changeset with its own bookkeeping - remove the SDK-owned `mod deps` / `mod engine` command surfaces, now owned by the core CLI (`dagger module deps` / `dagger module engine`) - mark the `modules` / `generate-all` discovery path as legacy dagger.json discovery, obsolete for workspace-managed modules - update the e2e checks and README to match The Python build-config surface (`mod config`) stays in this module, as it is genuinely SDK-specific. `initClient` is intentionally out of scope: generating a Python client is a code-generator concern, mirroring the Go SDK which stays module-only. Signed-off-by: Yves Brissaud <yves@dagger.io>
1 parent 06b021e commit 0c166c2

6 files changed

Lines changed: 69 additions & 289 deletions

File tree

‎.dagger/modules/e2e/main.dang‎

Lines changed: 11 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,6 @@ type E2e {
5757
assert(value.contains(want) == false, message)
5858
}
5959

60-
"""
61-
Assert that a dependency edit modified only dagger.json.
62-
"""
63-
let assertOnlyConfigChanged(changes: Changeset!, path: String!): Void {
64-
assert(contains(changes.modifiedPaths, path), "dependency edit did not modify dagger.json")
65-
assert(changes.modifiedPaths.length == 1, "dependency edit modified more than dagger.json")
66-
assert(changes.addedPaths.length == 0, "dependency edit should not add generated files")
67-
assert(changes.removedPaths.length == 0, "dependency edit should not remove generated files")
68-
}
69-
7060
"""
7161
The helper should expose the generate skip marker used by callers.
7262
"""
@@ -90,30 +80,27 @@ type E2e {
9080
}
9181

9282
"""
93-
Init should seed template files without running codegen. Generated SDK files
94-
appear later via mod.generate.
83+
initModule should seed SDK-owned template files without running codegen and
84+
without writing engine-owned files (dagger.json / dagger-module.toml).
85+
Generated SDK files appear later via mod.generate.
9586
"""
9687
pub initCheck(ws: Workspace!): Void @check {
9788
let defaultPath = outputRoot + "/init-default"
98-
let ignoredPath = outputRoot + "/init-ignore-generated"
9989
let legacyPath = outputRoot + "/init-legacy"
10090

101-
let defaultChanges = pythonSdk.init(ws, name: "init-default", path: defaultPath)
102-
let ignoredChanges = pythonSdk.init(ws, name: "init-ignore-generated", path: ignoredPath, ignoreGenerated: true)
103-
let legacyChanges = pythonSdk.init(ws, name: "init-legacy", path: legacyPath, template: "legacy")
91+
let defaultChanges = pythonSdk.initModule(ws, name: "init-default", path: defaultPath)
92+
let legacyChanges = pythonSdk.initModule(ws, name: "init-legacy", path: legacyPath, template: "legacy")
10493

105-
assertAdded(defaultChanges, defaultPath + "/dagger.json")
10694
assertAdded(defaultChanges, defaultPath + "/pyproject.toml")
95+
assert(contains(defaultChanges.addedPaths, defaultPath + "/dagger.json") == false, "initModule should not write the engine-owned dagger.json")
10796
assert(contains(defaultChanges.addedPaths, defaultPath + "/" + generatedMarkerPath) == false, "init should not produce SDK-generated files")
10897
assert(defaultChanges.modifiedPaths.length == 0, "init unexpectedly modified existing files")
10998
assert(defaultChanges.removedPaths.length == 0, "init unexpectedly removed files")
110-
assertContains(defaultChanges.layer.file(defaultPath + "/dagger.json").contents, "\"automaticGitignore\": false", "default init should check in generated files")
11199
assertContains(defaultChanges.layer.file(defaultPath + "/src/init_default/__init__.py").contents, "class InitDefault:", "minimal template did not render the module type")
112100
assertContains(defaultChanges.layer.file(defaultPath + "/src/init_default/__init__.py").contents, "source: dagger.Directory", "minimal template did not declare a Directory source field")
113101
assertContains(defaultChanges.layer.file(defaultPath + "/src/init_default/__init__.py").contents, "def __init__(self, ws: dagger.Workspace)", "minimal template did not declare a Workspace constructor")
114102
assertContains(defaultChanges.layer.file(defaultPath + "/src/init_default/__init__.py").contents, "self.source = ws.directory(\"/\")", "minimal template constructor did not assign ws.directory to source")
115103

116-
assertContains(ignoredChanges.layer.file(ignoredPath + "/dagger.json").contents, "\"automaticGitignore\": true", "ignoreGenerated init should configure automatic .gitignore output")
117104
assertContains(legacyChanges.layer.file(legacyPath + "/src/init_legacy/main.py").contents, "class InitLegacy:", "legacy template did not render the module type")
118105

119106
null
@@ -161,66 +148,10 @@ type E2e {
161148
}
162149

163150
"""
164-
Engine version helpers should read dagger.json and write normalized versions.
165-
"""
166-
pub engineVersionCheck(ws: Workspace!): Void @check {
167-
let engine = pythonSdk.mod(ws, path: lookupModulePath).engine
168-
let fixed = engine.require(version: "0.21.0")
169-
let latest = engine.requireLatest
170-
171-
assert(engine.required == "0.20.8", "engine.required should trim a leading v")
172-
assertContains(fixed.layer.file(lookupModulePath + "/dagger.json").contents, "\"engineVersion\": \"v0.21.0\"", "engine.require should write a normalized version")
173-
assertContains(latest.layer.file(lookupModulePath + "/dagger.json").contents, "\"engineVersion\": \"latest\"", "engine.requireLatest should write latest")
174-
175-
null
176-
}
177-
178-
"""
179-
Dependency listing should preserve how users identify dependencies: string
180-
entries return their source, named object entries return their name.
181-
"""
182-
pub dependencyListCheck(ws: Workspace!): Void @check {
183-
let deps = pythonSdk.mod(ws, path: depsModulePath).deps.list
184-
185-
assert(contains(deps, "../dep-string"), "string dependency source was not listed")
186-
assert(contains(deps, "dep-named"), "named dependency was not listed")
187-
assert(contains(deps, "../dep-source-only"), "source-only dependency was not listed by source")
188-
189-
null
190-
}
191-
192-
"""
193-
Dependency edits should update only dagger.json. Users run generate
194-
separately when they want generated SDK files refreshed.
151+
The SDK should report the builtin runtime that modules it authors target.
195152
"""
196-
pub dependencyEditCheck(ws: Workspace!): Void @check {
197-
let deps = pythonSdk.mod(ws, path: depsModulePath).deps
198-
199-
let added = deps.add(source: "../dep-new")
200-
assertOnlyConfigChanged(added, depsModulePath + "/dagger.json")
201-
let addedConfig = added.after.file(depsModulePath + "/dagger.json").contents
202-
assertContains(addedConfig, "\"../dep-new\"", "adding an unnamed dependency did not keep its source")
203-
assertNotContains(addedConfig, "\"name\": \"dep-new\"", "adding an unnamed dependency should not load the module to discover its name")
204-
205-
let named = deps.add(source: "../dep-new", name: "dep-alias")
206-
assertOnlyConfigChanged(named, depsModulePath + "/dagger.json")
207-
let namedConfig = named.after.file(depsModulePath + "/dagger.json").contents
208-
assertContains(namedConfig, "\"name\": \"dep-alias\"", "adding a named dependency did not keep the requested name")
209-
assertContains(namedConfig, "\"source\": \"../dep-new\"", "adding a named dependency did not keep its relative source")
210-
211-
let removed = deps.remove(name: "dep-named")
212-
assertOnlyConfigChanged(removed, depsModulePath + "/dagger.json")
213-
let removedConfig = removed.after.file(depsModulePath + "/dagger.json").contents
214-
assertNotContains(removedConfig, "\"name\": \"dep-named\"", "removing a named dependency left it in dagger.json")
215-
216-
let sourceRemoved = deps.remove(name: "../dep-source-only")
217-
assertOnlyConfigChanged(sourceRemoved, depsModulePath + "/dagger.json")
218-
let sourceRemovedConfig = sourceRemoved.after.file(depsModulePath + "/dagger.json").contents
219-
assertNotContains(sourceRemovedConfig, "\"source\": \"../dep-source-only\"", "removing by source left the dependency in dagger.json")
220-
assertContains(sourceRemovedConfig, "\"name\": \"dep-named\"", "removing by source removed a different dependency")
221-
222-
let updated = deps.update
223-
assert(updated.isEmpty, "updating only local dependencies should not edit dagger.json")
153+
pub targetRuntimeCheck: Void @check {
154+
assert(pythonSdk.targetRuntime == "python", "targetRuntime should be the python builtin runtime")
224155

225156
null
226157
}
@@ -276,7 +207,7 @@ type E2e {
276207
let configuredPath = outputRoot + "/init-configured"
277208
let defaultPath = outputRoot + "/init-config-default"
278209

279-
let configured = pythonSdk.init(ws, name: "init-configured", path: configuredPath, pythonVersion: "3.13", useUv: false, baseImage: "python:3.13-slim")
210+
let configured = pythonSdk.initModule(ws, name: "init-configured", path: configuredPath, pythonVersion: "3.13", useUv: false, baseImage: "python:3.13-slim")
280211
let pyproj = configuredPath + "/pyproject.toml"
281212
assertAdded(configured, pyproj)
282213
assert(configured.modifiedPaths.length == 0, "configured init should not modify existing files")
@@ -286,7 +217,7 @@ type E2e {
286217
assertContains(configured.layer.file(pyproj).contents, "python:3.13-slim", "init --base-image not written")
287218
assertContains(configured.layer.file(pyproj).contents, "dagger-io", "init config dropped template data")
288219

289-
let default = pythonSdk.init(ws, name: "init-config-default", path: defaultPath)
220+
let default = pythonSdk.initModule(ws, name: "init-config-default", path: defaultPath)
290221
let defaultPyproj = defaultPath + "/pyproject.toml"
291222
assertContains(default.layer.file(defaultPyproj).contents, ">=3.14", "default init should keep the template python version")
292223
assertNotContains(default.layer.file(defaultPyproj).contents, "use-uv", "default init should not write use-uv")

‎README.md‎

Lines changed: 37 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
A Dagger module for managing Dagger modules that use the Python SDK.
44

5-
The Dagger CLI ships without built-in module-management commands like
6-
`init` or `develop`. Those operations live in SDK-specific modules like this
7-
one, called through `dagger call`.
5+
SDK-specific module authoring (scaffolding new modules, language build config,
6+
codegen) lives in modules like this one. Under the CLI 1.0 init contract the
7+
engine drives the SDK: this module exposes `initModule` and `targetRuntime`,
8+
and the engine merges the SDK-owned files with its own workspace bookkeeping.
9+
Shared, language-agnostic operations — editing a module's dependencies or its
10+
required engine version — are owned by the core CLI (`dagger module deps`,
11+
`dagger module engine`) and are no longer part of this module's surface.
812

913
Backed by [`github.com/dagger/sdk-sdk/polyfill`](https://github.com/dagger/sdk-sdk/tree/main/polyfill).
1014

@@ -23,42 +27,39 @@ before writing anything to your workspace.
2327

2428
## Create a new module
2529

26-
Create a Python SDK module under the nearest `.dagger/modules/<name>/`:
30+
With a CLI that supports the 1.0 init contract, the engine dispatches to this
31+
SDK's `initModule`:
2732

2833
```sh
29-
dagger call python-sdk init --name my-module
34+
dagger module init python my-module
3035
```
3136

32-
Pick a different location:
37+
`initModule` only seeds the SDK-owned template files; the engine writes the
38+
module config and workspace entries. Run `generate` afterwards to produce the
39+
generated SDK bindings.
3340

34-
```sh
35-
dagger call python-sdk init --name my-module --path some/dir/my-module
36-
```
37-
38-
Pick a starter template (`minimal` is the default; `legacy` gives you a
39-
container-echo example):
41+
The SDK-specific args below become typed flags on `dagger module init python`:
4042

4143
```sh
42-
dagger call python-sdk init --name my-module --template legacy
44+
dagger module init python my-module --template legacy
45+
dagger module init python my-module \
46+
--python-version 3.13 \
47+
--use-uv=false \
48+
--base-image python:3.13-slim
4349
```
4450

45-
`init` only seeds template files. Run `mod ... generate` to produce the
46-
generated SDK.
47-
48-
### Configure a module at creation
51+
`--template` picks a starter template (`minimal` is the default; `legacy` gives
52+
you a container-echo example). The three `pyproject.toml` flags are optional; by
53+
default the template's Python version is used, uv is enabled, and no base image
54+
override is written.
4955

50-
`init` accepts configuration flags written into the module's `pyproject.toml`:
56+
You can also call the function directly for testing. `path` is required (the
57+
engine supplies it in the dispatched path):
5158

5259
```sh
53-
dagger call python-sdk init --name my-module \
54-
--python-version 3.13 \
55-
--use-uv=false \
56-
--base-image python:3.13-slim
60+
dagger call python-sdk init-module --name my-module --path .dagger/modules/my-module
5761
```
5862

59-
All three are optional. By default the template's Python version is used, uv is
60-
enabled, and no base image override is written.
61-
6263
## Configure an existing module
6364

6465
Read the current configuration. Settings that are not explicitly written to
@@ -101,55 +102,14 @@ For every Python SDK module in the workspace (skipping any with a
101102
dagger call python-sdk generate-all
102103
```
103104

104-
## Manage dependencies
105+
## Manage dependencies and the engine version
105106

106-
List:
107+
Editing a module's dependencies or its required engine version is identical
108+
across SDKs, so the core CLI owns it:
107109

108110
```sh
109-
dagger call python-sdk mod --path my-module deps list
110-
```
111-
112-
Add (run `mod ... generate` after to refresh generated SDK files):
113-
114-
```sh
115-
dagger call python-sdk mod --path my-module \
116-
deps add --source github.com/some/module
117-
```
118-
119-
Add with a custom local name:
120-
121-
```sh
122-
dagger call python-sdk mod --path my-module \
123-
deps add --source github.com/some/module --name alias
124-
```
125-
126-
Remove by name or source:
127-
128-
```sh
129-
dagger call python-sdk mod --path my-module deps remove --name alias
130-
```
131-
132-
Update one remote dependency, or all of them:
133-
134-
```sh
135-
dagger call python-sdk mod --path my-module deps update
136-
dagger call python-sdk mod --path my-module deps update --name some-dep
137-
```
138-
139-
## Manage the required engine version
140-
141-
```sh
142-
# Read the version pinned in dagger.json
143-
dagger call python-sdk mod --path my-module engine required
144-
145-
# Pin to a specific version
146-
dagger call python-sdk mod --path my-module engine require --version 0.20.8
147-
148-
# Pin to the engine version you're currently running
149-
dagger call python-sdk mod --path my-module engine require-current
150-
151-
# Pin to "latest"
152-
dagger call python-sdk mod --path my-module engine require-latest
111+
dagger module deps add github.com/some/module
112+
dagger module engine require-latest
153113
```
154114

155115
## Discover modules in a workspace
@@ -159,6 +119,12 @@ dagger call python-sdk mod --path my-module engine require-latest
159119
dagger call python-sdk modules path
160120
```
161121

122+
> [!NOTE]
123+
> `modules` and `generate-all` discover modules by scanning legacy
124+
> `dagger.json` files for `sdk.source == "python"`. This is obsolete for
125+
> workspace-managed modules, where the engine owns the
126+
> `modules.<sdk>.as-sdk.modules` source of truth.
127+
162128
See [`python-sdk.dang`](./python-sdk.dang) for the full type surface.
163129

164130
## Skipping generation

‎mod-deps.dang‎

Lines changed: 0 additions & 47 deletions
This file was deleted.

‎mod-engine.dang‎

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)