Module Structure
It's essential to understand a few key concepts about Dagger Modules created for use with the Python SDK, for a better fit with your normal development workflow.
Python version
Dagger Modules run in a container that's bootstrapped specifically by the Python SDK, with the necessary environment to run the module's code. It currently runs Python 3.11 by default but, starting with Dagger v0.11.0, the version can be changed.
The desired interpreter version can be pinned by creating a .python-version
file next to your pyproject.toml
:
echo "3.11" > .python-version
This can be useful either to pin the version in case the default one gets an upgrade, or to choose a newer version.
As an alternative, setting requires-python
in pyproject.toml
will also pin the version, but using a lower bound constraint (minimum version). For example, the following will use Python 3.11, even though Python 3.12 is available:
[project]
requires-python = ">=3.11"
Base image
The default base image is python:VERSION-slim (based on the Python version), but it can be overridden in pyproject.toml
since Dagger v0.11:
[dagger.tool]
base-image = "acme/python:3.11"
This can be useful to add a few requirements to the module's execution environment, such as system packages like git
.
Don't deviate from the default base image too much or it may break in a future version of Dagger. Override at own risk.
Automatic SDK install
Each release of Dagger bundles the Python SDK inside the Engine container image. During the Dagger module's code generation phase, that directory is unpacked and installed automatically, including dependencies. That directory is the same one that is exported to the host as ./sdk
when running dagger init --sdk=python
or dagger develop --sdk=python
. Note that it is not the same as the directory obtained when installing from dagger-io.
The runtime container installs the SDK as an editable install, similar to the following:
# executed by the runtime container
uv pip install -e ./sdk
This is why the initial pyproject.toml
file doesn't include any dependencies.
UV installer
uv is used internally to create the virtual environment, install dependencies and create the lock file (starting with Dagger v0.11.0). It's possible to specify a newer version, if needed, in pyproject.toml
:
[tool.dagger]
uv-version = "0.1.30"
If pip
is required instead, uv
can be disabled:
[tool.dagger]
use-uv = false
Note, however, that pinning via requirements.lock
is only supported with uv
.
Modules as Python libraries
Dagger Modules in Python are built to be installed, like libraries. The runtime container installs the module code with:
# executed by the runtime
uv pip install -e .
Due to PEP 517 and pip
supporting pyproject.toml
, this means that all package managers that support the build backend standard are automatically supported.
For example, you can use Hatch, Poetry, or PDM, to manage a module's dependencies and configure how it's built and installed.
Read more about this in the Packaging Python Projects tutorial.
Required main
in site-packages
The module's code isn't executed directly like a script, but it is rather imported under the hardcoded name main
(although this may be configurable in future).
This means that after uv pip install -e .
, there should be a Python module (or package) called main
in the virtual environment's site-packages/
directory inside the runtime container (/opt/venv/lib/python-3.11/site-packages/main
by default).
This is the most important requirement. The other conventions are flexible, as long as the above is verified.
Project name
The default project name in pyproject.toml
is also main
:
[project]
name = "main"
This is because most package managers use the name of the project to find the package to build, under ./src
. So, if you change the name of the project, you also need to make sure your chosen tool can find the package.
Here is an example of using a different project name with Poetry (truncated for brevity):
[tool.poetry]
name = "awesome-module"
packages = [{ include = "main", from = "src" }]
Default project layout
The default template from dagger init
creates a main
package inside a src
directory. This follows a Python convention that requires a project to be installed in order to run its code. This prevents accidental usage of development code since the Python interpreter includes the current working directory as the first item on the import path (more information is available in this blog post on Python packaging).
Tools like build, Hatch, and Poetry already detect this project layout by default, but it's not required and can be overridden as long as main
is installed correctly in site-packages
, or is at least importable with import main
.
Here is an example of moving the main
package next to pyproject.toml
, resulting in the following structure:
.
├── main
│ ├── __init__.py
│ ├── lint.py
│ └── test.py
├── dagger.json
├── pyproject.toml
└── requirements.lock
In this case, it is necessary to explicitly tell Poetry where to find the package, as shown below (truncated for brevity):
[tool.poetry]
name = "awesome-module"
packages = [{ include = "main" }]
Alternative project template
If a different project layout is needed, dagger init
won't override existing pyproject.toml
and .py
files. For example, to create the initial files with Rye:
rye init --name=main acme
cd acme
dagger init --sdk=python --source=.
Rye will create an initial src/main/__init__.py
but Dagger won't recognize any Dagger Functions in it. Either remove it and let Dagger create the default one, or adapt it to your needs before running dagger call
.
Poetry support
Poetry is a very popular workflow tool for Python. It doesn't fully support the PEP 621 yet, but it's coming.
It does support PEP 517 however, which makes it possible to use with Dagger. Same as in alternative project template, the project can be initialized with:
mkdir acme
cd acme
poetry init --name main --python "^3.11"
Remove the "readme"
entry in pyproject.toml
so Dagger doesn't fail with the unknown file. Here's an example of a minimal Poetry configuration:
[tool.poetry]
name = "main"
version = "0.1.0"
description = ""
authors = []
[tool.poetry.dependencies]
python = ">=3.11"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
After dagger init --sdk=python
, the ./sdk
can be added as a development dependency:
poetry add -e --group=dev ./sdk
Which adds the following to pyproject.toml
:
[tool.poetry.group.dev.dependencies]
dagger-io = {path = "sdk", develop = true}
Other workflow tools
Dagger is agnostic to the tool used in development. Similarly to Rye and Poetry, as long as the right standards are supported, Dagger should be able to use it too.