Skip to main content

Module Constructor

The main object's constructor is exposed as the entrypoint function for a module. Its parameters are available as flags in the dagger call command directly.

This is a simple way to accept module-wide configuration, or just to set a few attributes without having to create setter functions for them.

In the Python SDK, the @dagger.object_type decorator wraps @dataclasses.dataclass, which means that an __init__() method is automatically generated, with parameters that match the declared class attributes.

Here is an example with an object that has typed attributes:

from dagger import function, object_type


@object_type
class MyModule:
greeting: str = "Hello"
name: str = "World"

@function
def message(self) -> str:
return f"{self.greeting}, {self.name}!"

And here is an example call for this module:

dagger call --name=Daggerverse message

The result will be:

Hello, Daggerverse!

Constructor arguments are documented through their attribute documentation, using the same annotation used on function arguments:

"""A hello world example, using a constructor."""
from typing import Annotated

from dagger import Doc, function, object_type


@object_type
class MyModule:
"""Functions for greeting the world"""

greeting: Annotated[str, Doc("The greeting to use")] = "Hello"
name: Annotated[str, Doc("Who to greet")] = "World"

@function
def message(self) -> str:
"""Return the greeting message"""
return f"{self.greeting}, {self.name}!"

The flags can be seen documented in the output of dagger call --help:

Flags:
--greeting string The greeting to use (default "Hello")
--name string Who to greet (default "World")

Function Commands:
message Return the greeting message
note

Dagger Modules have only one constructor. Constructors of custom types are not registered; they are constructed by the function that chains them.

Exclude argument in constructor

Same as any data class, attributes can be excluded from the generated __init__() function, using dataclasses.field(init=False):

"""A simple hello world example, using a constructor."""
import dataclasses
from typing import Annotated

from dagger import Doc, function, object_type


@object_type
class MyModule:
"""Functions for greeting the world"""

greeting: str = dataclasses.field(default="Hello", init=False)
name: Annotated[str, Doc("Who to greet")] = "World"

@function
def message(self) -> str:
"""Return the greeting message"""
return f"{self.greeting}, {self.name}!"

In this case, only the name flag was added and is visible in the output:

Flags:
--name string Who to greet (default "World")

Function Commands:
message Return the greeting message

Constructor-only arguments

The opposite is also possible. To define an argument that only exists in the constructor, but not as a class attribute, define it as an init-only variable:

"""An example module controlling constructor parameters."""
import dataclasses

import dagger
from dagger import dag, function, object_type


@object_type
class MyModule:
base: dagger.Container = dataclasses.field(init=False)
variant: dataclasses.InitVar[str] = "alpine"

def __post_init__(self, variant: str):
self.base = dag.container().from_(f"python:{variant}")

@function
def container(self) -> dagger.Container:
return self.base

Complex or mutable defaults

For default values that are more complex, dynamic or just mutable, use a factory function without arguments in dataclasses.field(default_factory=...):

"""An example module using default factory functions."""
import dataclasses

import dagger
from dagger import dag, function, object_type


@object_type
class MyModule:
base: dagger.Container = dataclasses.field(
default_factory=lambda: dag.container().from_("python:alpine"),
)
packages: list[str] = dataclasses.field(default_factory=list)

@function
def container(self) -> dagger.Container:
return self.base.with_exec(["apk", "add", "git", *self.packages])

Asynchronous constructor

If a constructor argument needs an async call to set the default value, it's possible to replace the default constructor function from __init__() to a factory class method which must be named create:

"""An example module using a factory constructor"""
from typing import Annotated

from dagger import Doc, dag, function, object_type


@object_type
class MyModule:
"""Functions for testing a project"""

parallelize: int

@classmethod
async def create(
cls,
parallelize: Annotated[
int | None, Doc("Number of parallel processes to run")
] = None,
):
if parallelize is None:
parallelize = int(
await dag.container().from_("alpine").with_exec(["nproc"]).stdout()
)
return cls(parallelize=parallelize)

@function
def debug(self) -> str:
"""Check the number of parallel processes"""
return f"Number of parallel processes: {self.parallelize}"