Skip to content
Draft
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
34 changes: 34 additions & 0 deletions docs/collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Collections

Requires the engine changes in [dagger/dagger#14221](https://github.com/dagger/dagger/pull/14221).

A collection has stored keys and a function that returns one item for a key.
The engine supplies `keys`, `get`, `list`, and `subset`. Other exposed
functions appear under `batch`.

```python
import dagger
from dagger import collection, delta, field, function, get, keys, object_type

@object_type
class Item:
name: str = field()

@collection
@object_type
class Items:
paths: list[str] = keys(default=list)
selection: dagger.CollectionDelta | None = delta()

@function
@get
def item(self, key: str) -> Item:
return Item(name=key)
```

The markers are part of the module description. Both the shared entrypoint and
the generated static entrypoint pass them to the engine.

The engine fills the optional delta field before a module call. It compares the
current keys with the original keys. Copies preserve the internal base state.
A new object starts a new base. The internal state is not an exposed field.
14 changes: 11 additions & 3 deletions entrypoint/main.dang
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,25 @@ type Entrypoint implements ModuleEntrypoint {
deprecated: optString(raw, "deprecated"),
)
}
let withFields = raw.field(["fields"]).asArray.{{contents}}.reduce(base) { acc, rawField =>
let collection = if (raw.field(["collection"]).asBoolean) { base.withCollection } else { base }
let withFields = raw.field(["fields"]).asArray.{{contents}}.reduce(collection) { acc, rawField =>
let f = json.withContents(rawField.contents)
acc.withField(
let field = acc.withField(
f.field(["name"]).asString,
typeRef(json.withContents(f.field(["type"]).contents)),
description: optString(f, "description"),
deprecated: optString(f, "deprecated"),
)
let role = optString(f, "collection_role")
if (role == "keys") { field.withCollectionKeys(f.field(["name"]).asString) }
else if (role == "delta") { field.withCollectionDelta(f.field(["name"]).asString) }
else { field }
}
let withFunctions = raw.field(["functions"]).asArray.{{contents}}.reduce(withFields) { acc, rawFn =>
acc.withFunction(functionOf(json.withContents(rawFn.contents)))
let f = json.withContents(rawFn.contents)
let withFunction = acc.withFunction(functionOf(f))
if (f.field(["collection_get"]).asBoolean) { withFunction.withCollectionGet(f.field(["name"]).asString) }
else { withFunction }
}
let ctor = raw.field(["constructor"])
if (ctor.contents == "null") {
Expand Down
103 changes: 103 additions & 0 deletions sdk/src/dagger/client/gen.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions sdk/src/dagger/mod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

agent = _default_mod.agent
check = _default_mod.check
collection = _default_mod.collection
delta = _default_mod.delta
get = _default_mod.get
keys = _default_mod.keys
enum_type = _default_mod.enum_type
function = _default_mod.function
field = _default_mod.field
Expand All @@ -37,11 +41,15 @@ def default_module() -> Module:
"Name",
"agent",
"check",
"collection",
"delta",
"enum_type",
"field",
"function",
"generate",
"get",
"interface",
"keys",
"object_type",
"up",
]
3 changes: 3 additions & 0 deletions sdk/src/dagger/mod/_describe.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class FunctionDescription:
generator: bool = False
service: bool = False
agent: bool = False
collection_get: bool = False
args: tuple[ArgumentDescription, ...] = ()


Expand All @@ -88,6 +89,7 @@ class FieldDescription:
description: str | None = None
deprecated: str | None = None

collection_role: str | None = None

@dataclasses.dataclass(frozen=True, slots=True)
class EnumMemberDescription:
Expand All @@ -108,6 +110,7 @@ class EnumDescription:
class ObjectDescription:
name: str
interface: bool = False
collection: bool = False
description: str | None = None
deprecated: str | None = None
fields: tuple[FieldDescription, ...] = ()
Expand Down
9 changes: 9 additions & 0 deletions sdk/src/dagger/mod/_entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,22 @@ def _object(obj: ObjectDescription) -> list[str]:
deprecated = _opt("deprecated", obj.deprecated)
head = f"typeDef.withObject({_quote(obj.name)}{description}{deprecated})"
lines = [head]
if obj.collection:
lines.append(".withCollection")
lines.extend(
f".withField({_quote(field.name)}, {_type(field.type)}"
f"{_opt('description', field.description)}"
f"{_opt('deprecated', field.deprecated)})"
for field in obj.fields
)
for field in obj.fields:
if field.collection_role == "keys":
lines.append(f".withCollectionKeys({_quote(field.name)})")
elif field.collection_role == "delta":
lines.append(f".withCollectionDelta({_quote(field.name)})")
for func in obj.functions:
if func.collection_get:
lines.append(f".withCollectionGet({_quote(func.name)})")
lines += _wrap("withFunction", _function(func))
if obj.constructor is not None:
lines += _wrap("withConstructor", _function(obj.constructor))
Expand Down
Loading