Skip to content

Commit 874445d

Browse files
authored
Merge pull request #26 from dagger/sdk-module-interface-catchup
Update the Python SDK scope interface
2 parents d8f8eca + 815f4a0 commit 874445d

13 files changed

Lines changed: 117 additions & 213 deletions

File tree

‎.dagger/modules/e2e/fixtures/skip/.dagger-python-sdk-skip-generate‎

Whitespace-only changes.

‎.dagger/modules/e2e/fixtures/skip/app/dagger.json‎

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

‎.dagger/modules/e2e/fixtures/skip/app/pyproject.toml‎

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

‎.dagger/modules/e2e/fixtures/skip/app/src/skip_app/__init__.py‎

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

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

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ type E2e {
88
let lookupModulePath: String! = fixtureRoot + "/lookup/app"
99
let lookupNestedPath: String! = lookupModulePath + "/nested"
1010
let nonPythonModulePath: String! = fixtureRoot + "/lookup/not-python"
11-
let skipModulePath: String! = fixtureRoot + "/skip/app"
1211
let generateModulePath: String! = fixtureRoot + "/generate/app"
1312
let configModulePath: String! = fixtureRoot + "/config/app"
1413
let configuredModulePath: String! = fixtureRoot + "/config/configured"
@@ -70,15 +69,6 @@ type E2e {
7069
null
7170
}
7271

73-
"""
74-
The helper should expose the generate skip marker used by callers.
75-
"""
76-
pub skipGenerateFilenameCheck: Void @check {
77-
assert(pythonSdk.skipGenerateFilename == ".dagger-python-sdk-skip-generate", "skip marker filename changed")
78-
79-
null
80-
}
81-
8272
"""
8373
Mod lookup should find the owning Python SDK module when called from a nested path.
8474
"""
@@ -118,26 +108,26 @@ type E2e {
118108
}
119109

120110
"""
121-
detectScope answers with the directory of the nearest pyproject.toml as a
111+
findClientRoot answers with the directory of the nearest pyproject.toml as a
122112
workspace-root-relative path, with the module rather than its vendored client
123-
library, and with nothing where there is none.
113+
library, and with null where there is none.
124114
"""
125-
pub detectScopeCheck(ws: Workspace!): Void @check {
126-
assert(pythonSdk.detectScope(ws.withWorkdir(lookupNestedPath)) == lookupModulePath, "detectScope should find the module owning a nested path")
127-
assert(pythonSdk.detectScope(ws.withWorkdir(lookupModulePath)) == lookupModulePath, "detectScope should answer with the module root itself")
128-
assert(pythonSdk.detectScope(ws.withWorkdir(runtimeModulePath + "/sdk/src/dagger/client")) == runtimeModulePath, "detectScope should answer with the module owning a vendored sdk/, not the sdk/ itself")
129-
assert(pythonSdk.detectScope(ws.withWorkdir("sdk/src/dagger/client")) == "sdk", "detectScope should not lift a standalone sdk/ project into a parent that is not a Python module")
115+
pub findClientRootCheck(ws: Workspace!): Void @check {
116+
assert(pythonSdk.findClientRoot(ws.withWorkdir(lookupNestedPath)) == lookupModulePath, "findClientRoot should find the module owning a nested path")
117+
assert(pythonSdk.findClientRoot(ws.withWorkdir(lookupModulePath)) == lookupModulePath, "findClientRoot should answer with the module root itself")
118+
assert(pythonSdk.findClientRoot(ws.withWorkdir(runtimeModulePath + "/sdk/src/dagger/client")) == runtimeModulePath, "findClientRoot should answer with the module owning a vendored sdk/, not the sdk/ itself")
119+
assert(pythonSdk.findClientRoot(ws.withWorkdir("sdk/src/dagger/client")) == "sdk", "findClientRoot should not lift a standalone sdk/ project into a parent that is not a Python module")
130120

131121
let nestedSdkWs = ws.withNewFile("/" + runtimeModulePath + "/sdk/dagger-module.toml", "name = \"nested\"\n\n[runtime]\nsource = \"python\"\n")
132-
assert(pythonSdk.detectScope(nestedSdkWs.withWorkdir(runtimeModulePath + "/sdk/src/dagger/client")) == runtimeModulePath + "/sdk", "detectScope should answer with a nested module named sdk, not lift it into its parent")
133-
assert(pythonSdk.detectScope(ws) == "", "detectScope should find no scope at the workspace root")
122+
assert(pythonSdk.findClientRoot(nestedSdkWs.withWorkdir(runtimeModulePath + "/sdk/src/dagger/client")) == runtimeModulePath + "/sdk", "findClientRoot should answer with a nested module named sdk, not lift it into its parent")
123+
assert(pythonSdk.findClientRoot(ws) == null, "findClientRoot should return null when it finds no client root")
134124

135125
null
136126
}
137127

138128
"""
139129
generateScope initializes a module that has no config: the template, a
140-
dagger-module.toml from the engine's builder and no dagger.json, and the
130+
dagger-module.toml from the manifest builder and no dagger.json, and the
141131
generated client library, all under the scope, with the workspace cwd and
142132
existing files untouched. SDK settings select the template and its
143133
pyproject.toml values.
@@ -195,27 +185,28 @@ type E2e {
195185
}
196186

197187
"""
198-
generateScope regenerates a module that already has a config, whether a
199-
dagger-module.toml or a pre-1.0 dagger.json: only the generated client
200-
library is added, and nothing else changes.
188+
generateScope preserves a current manifest. It migrates a pre-1.0
189+
dagger.json to dagger-module.toml. Both cases generate the client library.
201190
"""
202191
pub generateScopeExistingCheck(ws: Workspace!): Void @check {
203-
[
204-
[tomlGenerateModulePath, "toml-generate-app"],
205-
[generateModulePath, "generate-app"],
206-
].each { pair =>
207-
let path = pair[0] ?? ""
208-
let name = pair[1] ?? ""
209-
let changes = pythonSdk
210-
.generateScope(ws.withWorkdir(path), isModule: true, name: name, clients: [])
211-
.withWorkdir(".")
212-
.changes(ws)
213-
214-
assertAdded(changes, path + "/" + generatedMarkerPath)
215-
assert(changes.modifiedPaths.length == 0, name + ": regenerating modified existing files: " + changes.modifiedPaths.join(", "))
216-
assert(contains(changes.addedPaths, path + "/dagger-module.toml") == false, name + ": regenerating must not write a manifest")
217-
null
218-
}
192+
let current = pythonSdk
193+
.generateScope(ws.withWorkdir(tomlGenerateModulePath), isModule: true, name: "toml-generate-app", clients: [])
194+
.withWorkdir(".")
195+
.changes(ws)
196+
assertAdded(current, tomlGenerateModulePath + "/" + generatedMarkerPath)
197+
assertContains(current.after.file(tomlGenerateModulePath + "/dagger-module.toml").contents, "engineVersion = \"v1.0.0-0\"", "regenerating did not preserve engineVersion")
198+
199+
let legacy = pythonSdk
200+
.generateScope(ws.withWorkdir(generateModulePath), isModule: true, name: "generate-app", clients: [])
201+
.withWorkdir(".")
202+
.changes(ws)
203+
assertAdded(legacy, generateModulePath + "/" + generatedMarkerPath)
204+
assertAdded(legacy, generateModulePath + "/dagger-module.toml")
205+
assert(contains(legacy.removedPaths, generateModulePath + "/dagger.json"), "migration did not remove dagger.json")
206+
assertContainsAll(legacy.after.file(generateModulePath + "/dagger-module.toml").contents, [
207+
"name = \"generate-app\"",
208+
"source = \"python\"",
209+
])
219210

220211
null
221212
}
@@ -314,19 +305,6 @@ type E2e {
314305
null
315306
}
316307

317-
"""
318-
A skip marker above a module should make generate return an empty changeset.
319-
"""
320-
pub skipGenerateCheck(ws: Workspace!): Void @check {
321-
let mod = pythonSdk.mod(ws, path: skipModulePath)
322-
let changes = mod.generate
323-
324-
assert(mod.skipGenerate, "skip marker was not detected")
325-
assert(changes.isEmpty, "generate returned changes for a skipped module")
326-
327-
null
328-
}
329-
330308
"""
331309
A module on this repository's runtime builds and runs, driven through a
332310
released CLI. The fixture points `[runtime] source` at `runtime/` by relative

‎.dagger/modules/engine-e2e/dagger-module.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ engineVersion = "v1.0.0-0"
66

77
[[dependencies]]
88
name = "engine-dev"
9-
source = "github.com/dagger/dagger/.dagger/modules/engine-dev@055def6f7794b9d0dfdcc4252f0695ea92257536"
9+
source = "github.com/dagger/dagger/.dagger/modules/engine-dev@8fd9b22b5416f8dc7cb420ba37769adef6e874d2"

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Checks the Python SDK against an engine built from dagger/dagger#13992
44
dagger-module.toml.
55
"""
66
type EngineE2e {
7-
let engineCommit: String! = "055def6f7794b9d0dfdcc4252f0695ea92257536"
7+
let engineCommit: String! = "8fd9b22b5416f8dc7cb420ba37769adef6e874d2"
88
let modulePath: String! = ".dagger/modules/sdk-smoke"
99

1010
let assert(condition: Boolean!, message: String!): Void {
@@ -32,8 +32,10 @@ type EngineE2e {
3232
let listed = client.withExec(["dagger", "sdk", "list"])
3333
assert(listed.stdout.contains("python"), "the development Python SDK was not listed")
3434

35-
let initialized = listed
36-
.withExec(["dagger", "--auto-apply", "module", "init", "python", "--name", "sdk-smoke", "--path", modulePath])
35+
let checked = listed.withExec(["dagger", "-m", ".dagger/modules/e2e", "check"])
36+
37+
let initialized = checked
38+
.withExec(["dagger", "module", "init", "python", "--auto-apply", "--name", "sdk-smoke", "--path", modulePath])
3739
.withExec(["test", "-f", modulePath + "/dagger-module.toml"])
3840
.withExec(["test", "!", "-e", modulePath + "/dagger.json"])
3941
.withExec(["test", "-f", modulePath + "/pyproject.toml"])
@@ -45,6 +47,21 @@ type EngineE2e {
4547
.stdout
4648
assert(release.trimSpace != "", "the generated module did not run")
4749

50+
initialized
51+
.withExec([
52+
"dagger", "module", "init", "python", "--auto-apply",
53+
"--name", "sdk-settings",
54+
"--path", ".dagger/modules/sdk-settings",
55+
"--template", "empty",
56+
"--python-version", "3.13",
57+
"--use-uv=false",
58+
"--base-image", "python:3.13-slim",
59+
])
60+
.withExec(["grep", "-q", "class SdkSettings:", ".dagger/modules/sdk-settings/src/sdk_settings/__init__.py"])
61+
.withExec(["grep", "-q", ">=3.13", ".dagger/modules/sdk-settings/pyproject.toml"])
62+
.withExec(["grep", "-q", "use-uv = false", ".dagger/modules/sdk-settings/pyproject.toml"])
63+
.withExec(["grep", "-q", "python:3.13-slim", ".dagger/modules/sdk-settings/pyproject.toml"])
64+
4865
null
4966
}
5067
}

‎README.md‎

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ SDK-specific module authoring (scaffolding new modules, language build config,
66
codegen) lives in modules like this one. The engine drives the SDK
77
(dagger/dagger#13992): it records a module scope in `dagger.toml`, sets the
88
workspace cwd to it, and asks this module to generate the complete scope
9-
through `detectScope` and `generateScope`. The module writes the manifest and
9+
through `findClientRoot` and `generateScope`. The module writes the manifest and
1010
its own files; the engine owns the workspace bookkeeping.
1111

12-
It uses the engine's native `Workspace` and `ModuleSource` APIs directly.
12+
It uses the engine's native `Workspace` and `ModuleSource` APIs. It uses
13+
`sdkHelpers.moduleManifest` from `dagger/sdk-helpers`.
1314

1415
## What lives here
1516

1617
| Path | What it is |
1718
| --- | --- |
18-
| `python-sdk.dang`, `mod.dang`, `templates/` | authoring: `detectScope`, `generateScope`, `mod` (generate, config), templates |
19+
| `python-sdk.dang`, `mod.dang`, `templates/` | authoring: `findClientRoot`, `generateScope`, `mod` (generate, config), templates |
1920
| `sdk/` | the `dagger-io` client library and code generator |
2021
| `runtime/` | the module runtime the engine calls to run a module |
2122

@@ -26,18 +27,18 @@ result into the module. The runtime never generates: it builds a module from its
2627
`dagger call`, and a module that has not been generated fails with an
2728
actionable error rather than being silently regenerated.
2829

29-
Pre-1.0 `dagger.json` modules are the exception: they keep being generated and
30-
run by the Python SDK baked into the engine, exactly as before.
30+
When a managed pre-1.0 `dagger.json` scope is generated, the SDK writes
31+
`dagger-module.toml` and removes `dagger.json`. An unmanaged legacy module keeps
32+
using the Python SDK that is built into the engine.
3133

3234
## Two runtimes, one name
3335

3436
Python modules reach one of two implementations, and which one is decided by
3537
the module's config format:
3638

37-
- **Legacy** — a `dagger.json` with `"sdk": {"source": "python"}` resolves to
38-
the runtime baked into the engine (`dagger/dagger`'s `sdk/python`), which
39-
still generates bindings at module load. Nothing about those modules changes,
40-
and they need no migration.
39+
- **Legacy** — an unmanaged `dagger.json` with `"sdk": {"source": "python"}`
40+
resolves to the runtime built into the engine (`dagger/dagger`'s
41+
`sdk/python`). It still generates bindings at module load.
4142
- **Modern** — a `dagger-module.toml` can point `[runtime] source` at this
4243
repository's `runtime/`, which is the no-codegen path above. Either a module
4344
ref or a path relative to the module works, for both `dagger generate` and
@@ -167,27 +168,12 @@ again.
167168
Standalone clients, in a scope without a module, are not generated yet; adding
168169
one to a Python scope is refused and the workspace is left unchanged.
169170

170-
## Skipping generation
171-
172-
A `.dagger-python-sdk-skip-generate` file at or above an existing module root
173-
makes `dagger generate` and `mod generate` leave that module as it is. Useful
174-
for fixtures, vendored modules, or anything you don't want regenerated. A new
175-
module is always generated.
176-
177-
```sh
178-
touch some/fixture/.dagger-python-sdk-skip-generate
179-
```
180-
181171
## Test
182172

183173
```sh
184174
dagger check
185175
```
186176

187-
`engine-e-2-e:dev-sdk-check` builds an engine from dagger/dagger#13992 at the
188-
commit pinned in `.dagger/modules/engine-e2e` (the `engine-dev` dependency and
189-
`engineCommit`), installs this checkout as the `python` SDK, initializes a
190-
Python module, and calls it. Bump both to follow the branch. The `e-2-e:*`
191-
checks that call this module need an engine with that change as well; on the
192-
released engine they fail, because the module selects `moduleManifest`, which
193-
that engine does not have.
177+
`engine-e-2-e:dev-sdk-check` builds the pinned dagger/dagger#13992 engine. It
178+
runs the SDK interface checks, initializes Python modules with default and
179+
explicit settings, and calls a generated module.

‎dagger.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,11 @@
99
"!.dagger",
1010
"!future",
1111
"!docs"
12+
],
13+
"dependencies": [
14+
{
15+
"name": "sdk-helpers",
16+
"source": "github.com/dagger/sdk-helpers"
17+
}
1218
]
1319
}

‎dagger.lock‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
[["version","2"]]
2+
["","git-latest",["https://github.com/dagger/sdk-helpers"],"refs/heads/main"]
23
["","git-latest",["https://github.com/dagger/sdk-sdk"],"refs/heads/main"]
34
["","git-sha",["https://github.com/containernetworking/plugins","refs/tags/v1.9.0"],"9b3772e1a7abf93cbb7c6526a28bc0d27b830e02"]
5+
["","git-sha",["https://github.com/dagger/sdk-helpers","refs/heads/main"],"64645f1967d3dba6fce951dd61ae4acd8d9b0861"]
46
["","git-sha",["https://github.com/dagger/sdk-sdk","refs/heads/main"],"334448911a8292fba0d677e5f31926c79ad80ad3"]
57
["","git-sha",["https://github.com/libfuse/sshfs.git","refs/tags/sshfs-3.7.6"],"7a2d988775446ebe7af9b01c99b3b8e86bddb05a"]
68
["","git-sha",["https://github.com/opencontainers/runc","refs/tags/v1.4.2"],"c241c0bb5e60a8e8c1b2e53d4eca8d0068d8d57e"]
79
["","oci-latest",["docker.io/library/busybox"],"1.38.0"]
810
["","oci-sha",["docker.io/library/alpine:3.22"],"sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce"]
911
["","oci-sha",["docker.io/library/busybox:1.38.0"],"sha256:dc2d74b28e4cf8984fa52af1f39bc7c3d9c73760b41a74d629f5d11b1ab28616"]
1012
["","oci-sha",["docker.io/library/golang:1.25-alpine"],"sha256:1ae0735f00daffa3aaf1363a5184c0d2dc55c78e3db4ec70241cdac97bf84b59"]
11-
["","oci-sha",["docker.io/library/golang:1.26-alpine"],"sha256:28d89ee9cc0ff9fec75c82ca201e6bf7fdf9a679d4b7b24dfa04f2bb766bb468"]
13+
["","oci-sha",["docker.io/library/golang:1.26-alpine"],"sha256:ce864e7223ac17b1775e6fd0b4c0db580c2eb50e7953a427916379e4b92a1628"]
1214
["","oci-sha",["docker.io/tonistiigi/xx:1.2.1"],"sha256:8879a398dedf0aadaacfbd332b29ff2f84bc39ae6d4e9c0a1109db27ac5ba012"]
13-
["","oci-sha",["ghcr.io/astral-sh/uv:python3.14-alpine"],"sha256:3f43dc141a3a7e61948cc4c22e0a52666e80ef35ffe6c73e03d60e073b655327"]
15+
["","oci-sha",["ghcr.io/astral-sh/uv:python3.14-alpine"],"sha256:706cd9faf6d3fa7476fc470463bfca625d4b3ea5e46d6dc166ec1fb8ad3976e6"]

0 commit comments

Comments
 (0)