Python quickstart

A Zuke build in Python is a class whose fields are targets, with dependencies the type checker sees and a typed facade for every tool. The Zuke engine runs it — scheduling, every command, caching, locks, resume, CI generation and the MCP server — exactly as it runs a TypeScript build. This page gets you from nothing to a green build.

Install

You need the zuke command and uv. Install the CLI one of three ways; each is the same program.

deno install -A -g -n zuke jsr:@zuke/cli   # any OS, with Deno
brew install zuke-build/tap/zuke           # macOS, Linux
choco install zuke                         # Windows

The Python package is zuke on PyPI. It needs Python 3.12 or newer and has no runtime dependencies. A scaffolded build installs it by itself, so there is nothing to pip install yet.

Scaffold a Python build

In your project:

zuke setup --sdk python   # build.py, zuke.json, the launchers
zuke                      # run the default target

zuke setup --sdk python writes a starter build.py, a zuke.json, the ./zuke and zuke.ps1 launchers and a .gitignore, and no deno.json. The starter carries inline script metadata, so uv run build.py installs the zuke package with no project file of its own:

# /// script
# requires-python = ">=3.12"
# dependencies = ["zuke"]
# ///
"""Your project's build. Run a target with ./zuke <target>."""

from zuke import Build, run, target


class App(Build):
    """The targets of this project."""

    hello = (
        target()
        .description("A sample target, replace me with real work")
        .executes(lambda ctx: ctx.log("Hello from Zuke!"))
    )

    # Convention: default runs when no target is named.
    default = target().description("Default target").depends_on(hello)


run(App)

zuke.json names the command that starts your build and the exact engine release that runs it:

{
  "name": "App",
  "sdk": { "command": ["uv", "run", "build.py"] },
  "engine": "1.2.1"
}

The other setup flags work as they do for TypeScript — --mcp registers the build's MCP server for your agent, and --engine <version> pins a release without asking the network. See the CLI reference.

Your first build

Replace the sample target with real work:

from zuke import Build, ci, parameter, run, target
from zuke.tools.docker import DockerTasks


class App(Build):
    tag = parameter("image tag").default("dev")
    pipeline = ci().path(".github/workflows/ci.yml")

    lint = target().description("ruff").executes(lambda ctx: ctx.sh("ruff", "check", "."))
    test = target().depends_on(lint).executes(lambda ctx: ctx.sh("pytest", "-q"))
    image = (
        target()
        .depends_on(test)
        .executes(
            lambda ctx: DockerTasks.build(
                ctx, lambda s: s.tag(f"app:{tag.value}").file("Dockerfile")
            )
        )
    )


run(App)
  • depends_on(lint) is a reference to the sibling field, not a string: pyright sees it, a rename moves every use, and a typo fails before anything runs. A target may only depend on fields declared above it.
  • ctx.sh(...) runs a command through the engine — argv only, no shell — and raises CommandError on a non-zero exit.
  • DockerTasks.build(ctx, lambda s: ...) is the typed Docker wrapper, configured with a settings lambda exactly like DockerTasks.build((s) => ...) in TypeScript.
  • tag.value is the parameter's resolved value, set by the engine before any body runs.
  • run(App) makes the file a build; it does nothing when the file is imported.

Running it

With the CLI installed, drive the build from anywhere inside the project, exactly as a TypeScript one:

zuke image                 # lint, test, then image — once each, in order
zuke image --tag 1.4.0     # parameters are flags (or TAG in the environment)
zuke --list                # every target, its description and dependencies
zuke graph --output=html   # the dependency graph, interactive
zuke generate-ci           # write the workflow the ci() field declares
zuke mcp                   # serve the build to an agent as typed tools

zuke --help lists the build's live targets and parameters, and zuke --version names the zuke.json and the engine release it runs.

Into an existing project

A project with its own pyproject.toml can add the package as a dev dependency instead:

uv add --dev zuke   # or: pip install zuke

Then delete the # /// script block from build.py: uv runs a script that carries that block in an environment of its own, and one without it in your project's environment, beside your own tools. The scaffolded sdk.command, ["uv", "run", "build.py"], stays as it is.

On CI

A CI runner has no zuke command, so it runs the committed launchers. They download a pinned, checksum-verified Deno on first use and run the engine; uv provides the Python side.

./zuke ci       # Linux and macOS: bootstraps a pinned Deno, then runs the engine
./zuke.ps1 ci   # Windows

Better still, let the build write its own pipeline: the ci() field above becomes .github/workflows/ci.yml with zuke generate-ci, and zuke generate-ci --check fails when the committed file drifts. See CI files.

The engine pin

zuke.json's engine is an exact release, never a range, so the engine cannot change between two runs. Move it deliberately:

zuke upgrade --engine          # move zuke.json to the latest @zuke/engine
zuke upgrade --engine 1.2.1    # or to an exact release

What differs from TypeScript

The model, the CLI and every engine feature are the same. The spelling is Python's:

TypeScriptPython
class CI extends Buildclass CI(Build)
.dependsOn(this.lint).depends_on(lint)
.executes(async () => ...).executes(lambda ctx: ...) or an async def body(ctx)
await $`pytest -q`await ctx.sh("pytest", "-q")
import { DockerTasks } from "@zuke/docker"from zuke.tools.docker import DockerTasks
DockerTasks.build((s) => s.tag("a"))DockerTasks.build(ctx, lambda s: s.tag("a"))
cicd({ provider: "github" })ci().provider("github")
await run(CI)run(CI)

zuke add, remove, cache and relock manage a project's own Deno code, so they refuse a Python build: its tool wrappers come with the SDK, as zuke.tools.

Next steps

  • Authoring in Python — targets and their options, parameters, groups, services, CI files and hooks.
  • Bodies, context & tools — what a body gets in ctx, durable state, and the typed tool facades.
  • Python API reference — every class, function and facade, generated from the package's docstrings.
  • Engine & workers — how a Python build runs on the engine.
  • Core concepts — the build, target and graph model, shared by every SDK; its samples are TypeScript.