//python/uv:lock.bzl

The uv locking rule.

Differences with the legacy compile_pip_requirements rule:

  • This is implemented as a rule that performs locking in a build action.

  • Additionally one can use the runnable target.

  • Uses uv.

  • This does not error out if the output file does not exist yet.

  • Supports transitions out of the box.

Note, this does not provide a test target like compile_pip_requirements does. The uv lock and uv pip compile commands are not hermetic and thus a test based on them would most likely be flaky:

  • It may require auth injected into it, so most likely it requires a local tag added so that the bazel action runs without sandboxing.

  • It requires network access.

Given those points, a test target should be an explicit and properly documented target and not a hidden implicit target. If, you would like to add a test target that always does the locking automatically to ensure that the lock file is up-to-date, add something similar to:

load("@bazel_skylib//rules:native_binary.bzl", "native_test")
load("@rules_python//python/uv:lock.bzl", "lock")

lock(
    name = "requirements",
    srcs = ["pyproject.toml"],
    out = "uv.lock",
)

native_test(
    name = "requirements_test",
    src = "requirements.update",
)

[tool.uv] settings support

When a pyproject.toml file is included in lock.srcs, the --project flag is automatically passed to uv using the directory of the shortest-path pyproject.toml. This causes uv to read [tool.uv] settings such as no-build-isolation, exclude-dependencies, and [tool.uv.workspace] from that file.

If the auto-detection doesn’t select the right project (e.g. in complex workspace layouts), use the project parameter to override it:

lock(
    name = "requirements",
    srcs = [
        "pyproject.toml",
        "requirements.in",
    ],
    out = "requirements_lock.txt",
    project = "subproject",
)
lock(name, srcs, out, args=[], build_constraints=[], constraints=[], env=None, generate_hashes=True, python_version=None, directory=struct(_sentinel=True), project=None, strip_extras=False, **kwargs)

Pin the requirements based on the src files.

This macro creates the following targets:

  • name: the target that creates the lock file (uv.lock or requirements.txt) in a build action. This target will have no-cache and requires-network added to its tags.

  • name.run: a runnable target that can be used to pass extra parameters to the same command that would be run in the name action. This will update the source copy of the lock file. You can customize the args via the command line, but it requires being able to run uv (and possibly python) directly on your host. This target uses the uv cache and inherits its cache settings from the environment, including UV_CACHE_DIR and UV_NO_CACHE. Pass --refresh to refresh cached data or --no-cache to disable caching. Build actions, including those used by name.update, disable the uv cache.

  • name.update: a target that can be run to update the source-tree version of the lock file. The output can be fed to the pip.parse bzlmod extension tag class (via pip.parse.uv_lock or pip.parse.requirements_lock) or the pip_parse WORKSPACE macro. Note, you can use native_test to wrap this target to make a test. You can’t customize the args via command line, but you can use RBE to generate requirements (offload execution and run for different platforms). Note, that for RBE to be usable, one needs to ensure that the nodes running the action have internet connectivity or the indexes are provided in a different way for a fully offline operation.

Note

All of the targets have manual tags as locking results cannot be cached.

Note

A uv toolchain must be registered: under bzlmod via the uv module extension, under WORKSPACE via uv_register_toolchains.

Changed in version 2.5.0: The name.run target uses the uv cache by default.

Changed in version 2.5.0: WORKSPACE builds are supported; previously the targets were marked incompatible unless bzlmod was enabled.

Args:
  • name – (str)

    The prefix of all targets created by this macro.

  • srcs – (list[Label])

    The sources that will be used. Add all of the files that would be passed as srcs to uv pip compile, or the pyproject.toml files when generating uv.lock.

  • out – (str)

    The output file relative to the package. If the file name ends with .lock (e.g. uv.lock), uv lock is used to generate a uv.lock file; otherwise uv pip compile is used to generate a requirements.txt-formatted file.

  • args – (list[str]) (default [])

    The list of args to pass to uv. Note, these are written into the runnable name.run target.

  • build_constraints – (list[Label]) (default [])

    The list of build constraints to use.

  • constraints – (list[Label]) (default [])

    The list of constraints files to use.

  • env – (dict[str, str]) (default None)

    the environment variables to set. Note, this is passed as is and the environment variables are not expanded.

  • generate_hashes – (bool) (default True)

    Generate hashes for all of the requirements. Only meaningful for requirements.txt style output. Defaults to True.

  • python_version – (str | None) (default None)

    the python_version to transition to when locking the requirements. Defaults to the default python version configured by the python module extension or, under WORKSPACE, by the registered Python toolchains.

  • directory – (str | None) (default struct(_sentinel = True))

    The directory into which we should cd when running the command. Defaults to native.package(). Set to None or "" to restore behaviour before 2.4.0. {versionadded} 2.4.0

  • project – (str | None) (default None)

    overrides the --project directory passed to uv. By default the project directory is auto-detected: when lock.srcs contains pyproject.toml files, the one with the shortest directory path is selected. This causes uv to read [tool.uv] settings such as no-build-isolation and exclude-dependencies from that pyproject.toml. If no pyproject.toml is in srcs and no project is given, the Bazel package directory is used as fallback. {versionadded} 2.1.0

  • strip_extras – (bool) (default False)

    whether to strip extras from the output. Currently rules_python requires --no-strip-extras to properly function, but sometimes one may want to not have the extras if you are compiling the requirements file for using it as a constraints file. Defaults to False.

  • kwargs – common kwargs passed to rules.