Python

The Python rules model applications and pytest suites backed by a locked pyproject.toml project. Applications build into self-contained PEX files; tests create an isolated uv environment and run pytest. Ruff supplies the format and lint products for application targets.

Available goals

GoalToolProduct module
imp gen-lockfilesPex//rules/python/pex_toolchain
imp gen-lockfilesRuff//rules/python/ruff_toolchain
imp gen-lockfilesUv//rules/python/uv_toolchain

Set up the workspace

Import the Python rules in imp.workspace.js; they provide default uv, PEX, and CPython toolchains. Add Ruff when Python targets should participate in formatting and linting:

import "//rules/python";
import "//rules/python/ruff_toolchain";
import "//rules/workflows/fmt";
import "//rules/workflows/lint";
import "//rules/workflows/package";
import "//rules/workflows/test";

Projects are built with uv sync --locked. Keep uv.lock checked in and in sync with pyproject.toml; builds and tests fail rather than resolving or mutating a stale lock file.

Select a dependency resolve

Declare a pythonResolve() for a locked project when applications or tests need optional dependency flavors. A resolve uses uv's checked-in uv.lock as the source of truth; each flavor selects one project extra at sync time.

import { pythonResolve } from "//rules/python";

export const ml = pythonResolve({
    path: "services/ml",
    flavors: {
        default: { extra: "cpu" },
        cpu: { extra: "cpu" },
        cu124: { extra: "cu124" },
    },
});

Then attach it to targets; the resolve supplies their project path (so do not also set src):

export const app = pythonApp({ resolve: ml, entryPoint: "acme.__main__" });
export const tests = pythonTest({ resolve: ml });

Choose a flavor with --axis python=cu124. A workspace can also define a named --profile containing python: "cu124". The resolve's pyproject.toml must declare the matching uv extra and use uv's explicit indexes/sources to bind PyTorch packages to the CPU or CUDA index. Flavors are deliberately extras rather than package-version syntax: uv locks the real PyTorch local version (for example +cu124) and PEX packages the resulting synced venv.

// imp.workspace.js
import { defineProfile } from "imp:core";
defineProfile("cu124", { python: "cu124" });

Declare an application and tests

import { pythonApp } from "//rules/python";
import { pythonTest } from "//rules/python/test";

export const app = pythonApp({
    entryPoint: "acme.__main__",
});

export const tests = pythonTest({
    testArgs: ["-q"],
});

Both targets default to the directory containing BUILD.js; set src to point at another project directory. pythonApp syncs the locked environment and asks PEX to package the project and its installed dependencies. entryPoint becomes the PEX entry point, while extraPexArgs is available for PEX options that do not yet have a dedicated field.

pythonTest is intentionally separate from pythonApp: it does not build a PEX first, and its testArgs are appended to python -m pytest. Test runs are impure so an unchanged previous success is never replayed as the current test result.

Run source files

For direct script execution, declare one shallow source set rather than an application target per file. It uses the Python rule's pinned runtime:

import { pythonSources } from "//rules/python";

// BUILD.js
export const scripts = pythonSources({
    root: "tools",
    sources: ["*.py"],
});

Each direct match is lazily expanded into one python-source target. Run it by file path, including arguments after --:

imp run tools/demo.py -- --verbose

The source set's complete file list is staged and root is added to PYTHONPATH. The run goal keeps the process sandboxed but sets its working directory to the real workspace, so files written by the script persist there. Nested directories need their own pythonSources() declaration; recursive ** patterns are rejected.

Sources do not require a project. They run with the pinned interpreter and the standard library by default. A workspace may declare one optional locked project to supply third-party dependencies:

import { pythonResolve } from "//rules/python";

export const project = pythonResolve({ path: "python" });

Pass that resolve to a source set when it needs third-party dependencies:

export const scripts = pythonSources({
    root: "tools",
    sources: ["*.py"],
    resolve: project,
});

The resolve is synchronized from its checked-in pyproject.toml and uv.lock without becoming the source owner. pythonProject({ default: true }) remains as a compatibility alias for the previous single-default-project source-run API. Import/dependency inference and multiple project resolutions are intentionally future work.

Run goals and find outputs

imp build //services/acme:app
imp test //services/acme:tests
imp fmt --check //services/acme:app
imp lint //services/acme:app
imp package //services/acme:app

build captures the generated .pex as a build artifact without writing it into the source tree. package publishes it below dist/services/acme/app. Formatting and linting currently apply to targets declared with pythonApp(); select the application target when checking the project's Python files.

Configuration

pexToolchain()

pexToolchain(version, opts = {})

Declare a pex toolchain version and optionally set it as the default. matching lockfile entry (warns instead of failing).

ParameterTypeDescription
versionstring
[opts]object
[opts.default=false]boolean
[opts.unverified=false]booleanAllow downloading without a

Returns: object Target handle for this pex toolchain.

ruffToolchain()

ruffToolchain(version, opts = {})

Declare a ruff toolchain version and optionally set it as the default. defaults to the bundled //rules/python/ruff-toolchain.lock. Point this at your own lock (regenerate via imp goal gen-lockfiles) when pinning a non-default version. matching lockfile entry (warns instead of failing).

ParameterTypeDescription
versionstring
[opts]object
[opts.default=false]boolean
[opts.lockfile]stringLockfile address pinning download SHA-256s;
[opts.unverified=false]booleanAllow downloading without a

Returns: object Target handle for this ruff toolchain.

uvToolchain()

uvToolchain(version, opts = {})

Declare a uv toolchain version and optionally set it as the default. matching lockfile entry (warns instead of failing).

ParameterTypeDescription
versionstring
[opts]object
[opts.default=false]boolean
[opts.unverified=false]booleanAllow downloading without a

Returns: object Target handle for this uv toolchain.