python provider
A python stage calls a function from a Python file. It runs inside
denver’s own Python process — no subprocess. So you can set a breakpoint in
your function and debug it together with denver.
[my-stage]
provider = "python"
file = "setup/tools.py"
function = "install"
args = { version = "1.2.3" }
# setup/tools.py
def install(ctx, version):
ctx.run(["echo", f"installing {version}"])
ctx.set("TOOL_VERSION", version)
(provider:/description:/disabled:/ci:/depends-on:/scripts:/env:/env-prepend:/env-append: are generic keys every stage has —
see “Generic stage keys” in Configuration.)
Key reference
file(required) — the.pyfile. A relative path is resolved against the env dir, then against imported base envs.function(required) — name of a top-level function infile.args— a mapping. Each entry is passed as a keyword argument.${VAR}values are interpolated first.
denver calls function(ctx, **args). ctx is denver’s Context. Useful parts:
ctx.env— the environment for later stages and the final command. Change it withctx.set(),ctx.prepend_path(),ctx.source().ctx.run([...])— run a command. Honours--dry-run.ctx.resolve_path(...),ctx.env_dir— find files.ctx.fast,ctx.dry_run— the--fast/--dry-runflags.die("message")(fromdenver_providers) — stop with a clean error.
The return value is ignored.
Behaviour
--fastand--dry-run: the function is always called. denver cannot know what your function does, so the function checksctx.fastandctx.dry_runitself and skips its slow or risky parts.Errors: an exception stops denver with the message and the traceback. A failed
ctx.run()is reported like in any other provider.Imports: the file’s directory is added to
sys.path, so the file can import modules next to it. The file is imported once per run; stages using the same file share it.Docker: when a
dockerstage relocates the run, the function runs inside the container, like every other setup stage after it.
Debugging
Start denver with a debugger and set a breakpoint in your function, for example:
python -m pdb -m denver run ...
python -m debugpy --listen 5678 --wait-for-client -m denver run ... # VS Code: attach to port 5678
Or put breakpoint() in the function. This needs denver installed as a
Python package (e.g. uv tool install or a venv), not the single-file binary.
python, custom or an extension provider?
custom— a shell command or a sourced script.python— one function, in Python, easy to debug.Extension provider — a full
Providerclass with its own keys, defaults andwrap().