fix(uv): respect uv.tool settings in pyproject.toml (#3811)
With this we auto-detect the location of the project file based on
where the first pyproject.toml file is located. Whilst this may work
for majority of the cases there could be a case where the user wants
to leverage the workspaces, we iterate through all pyproject.toml
files and choose the one with the shortest directory path. If this
does not work, we you can just override it manually.
Along the way, give some advice to agents for handling flakey CI and
pushing changes to PRs.
Fixes #3807
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/AGENTS.md b/AGENTS.md
index 65c5baf..4e1e88a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -31,6 +31,11 @@
When adding `{versionadded}` or `{versionchanged}` sections, add them add the
end of the documentation text.
+### PR Updates
+
+Once a PR is created, create new commits and merges. Don't use rebase or amend
+because it interferes with code review comments.
+
### PR descriptions
Follow the advice in `CONTRIBUTING.md` for PR descriptions. PR descriptions
@@ -150,6 +155,8 @@
* `gazelle/` is for the `@rules_python_gazelle_plugin` module.
* All other code is part of `@rules_python`.
+`tests/support/` contains utility code and helpers for testing.
+
`python/config_settings/BUILD.bazel` contains build flags that are part of the
public API. DO NOT add, remove, or modify these build flags unless specifically
@@ -213,3 +220,18 @@
When inspecting CI failures, if the failure is due to a network error
downloading a repository, check if that rule set is mirrored on
mirror.bazel.build. If so, add it to the downloader config.
+
+### CI Flakiness and Monitoring
+
+CI is known to have flakey network issues. When submitting or updating a PR,
+start a background agent that continuously monitors the latest build of a PR and
+checks for flakey network errors (e.g., 504 gateway errors).
+
+If Buildkite permissions allow, retry failures and use available Buildkite
+skills. If permissions do not allow, modify `.bazelrc` to set flags (such as
+`--http_timeout_scaling` or `--experimental_repository_downloader_retries`) to
+make downloads retry more.
+
+If downloads fail for a dependency, check if that dependency is available on
+mirror.bazel.build and, if so, add it to downloader_config.cfg if it isn't
+already there.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33adfa7..99876bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -109,6 +109,8 @@
* (uv) fix the execution of the `uv pip compile` in the sandbox. Work
towards better supporting `uv` out of the box on our platforms.
([#1975](https://github.com/bazel-contrib/rules_python/issues/1975))
+* (uv) automatically pass the `--project` parameter based on the source files.
+ ([#3087](https://github.com/bazel-contrib/rules_python/issues/3087))
{#v0-0-0-added}
### Added
diff --git a/docs/pypi/lock.md b/docs/pypi/lock.md
index b5d8ec2..5c9f064 100644
--- a/docs/pypi/lock.md
+++ b/docs/pypi/lock.md
@@ -68,8 +68,55 @@
### uv pip compile (bzlmod only)
-We also have experimental setup for the `uv pip compile` way of generating lock files.
+We also have an experimental setup for the `uv pip compile` way of generating lock files.
This is well tested with the public PyPI index, but you may hit some rough edges with private
mirrors.
-For more documentation see {obj}`lock` documentation.
+#### Example usage
+
+```starlark
+load("@rules_python//python/uv:lock.bzl", "lock")
+
+lock(
+ name = "requirements",
+ srcs = ["pyproject.toml", "requirements.in"],
+ out = "requirements_lock.txt",
+)
+```
+
+#### `[tool.uv]` settings from pyproject.toml
+
+When a `pyproject.toml` file is among the {attr}`lock.srcs`, the
+{obj}`lock` rule auto-detects the project directory and passes
+`--project <dir>` to `uv pip compile`. This causes `uv` to read
+`[tool.uv]` settings from that `pyproject.toml`, such as
+`no-build-isolation`, `exclude-dependencies`, and workspace members.
+
+If multiple `pyproject.toml` files are in {attr}`lock.srcs`, the one
+with the shortest directory path is selected (this heuristic works for
+typical uv workspace layouts where the root configuration is at the
+shortest path).
+
+If the auto-detection picks the wrong project directory, use the
+`project` parameter to override:
+
+```starlark
+lock(
+ name = "requirements",
+ srcs = ["pyproject.toml", "requirements.in"],
+ out = "requirements_lock.txt",
+ project = "subproject",
+)
+```
+
+:::{warning}
+**Known limitations of auto-detection**
+
+1. **Workspace heuristic** — the shortest-path selection may incorrectly assume the upper-most
+ workspace `pyproject.toml` is the correct one. For monorepos with multiple independent
+ sub-projects, you must set `project` explicitly for each {obj}`lock` target.
+1. **No test target** — unlike {obj}`compile_pip_requirements`, no test target is auto-created; see
+ the {obj}`lock` docs for how to add one manually using `diff_test` from `bazel_skylib`.
+:::
+
+For more documentation see {obj}`lock`.
diff --git a/python/uv/lock.bzl b/python/uv/lock.bzl
index 7bcca78..7fd5008 100644
--- a/python/uv/lock.bzl
+++ b/python/uv/lock.bzl
@@ -45,6 +45,28 @@
)
```
+### `[tool.uv]` settings support
+
+When a `pyproject.toml` file is included in {attr}`lock.srcs`, the
+`--project` flag is automatically passed to `uv pip compile` 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:
+
+```starlark
+lock(
+ name = "requirements",
+ srcs = [
+ "pyproject.toml",
+ "requirements.in",
+ ],
+ project = "subproject",
+)
+```
+
EXPERIMENTAL: This is experimental and may be changed without notice.
"""
diff --git a/python/uv/private/lock.bzl b/python/uv/private/lock.bzl
index 6f0b80a..23f2eed 100644
--- a/python/uv/private/lock.bzl
+++ b/python/uv/private/lock.bzl
@@ -71,7 +71,8 @@
)
def _lock_impl(ctx):
- srcs = ctx.files.srcs
+ srcs = [] + ctx.files.srcs
+
fname = "{}.out".format(ctx.label.name)
python_version = ctx.attr.python_version
if python_version:
@@ -99,6 +100,27 @@
args.add("--generate-hashes")
if not ctx.attr.strip_extras:
args.add("--no-strip-extras")
+
+ project = None
+ if ctx.attr.project:
+ project = ctx.attr.project
+ else:
+ # Autodetect the project based on the `pyproject.toml` location - it will be the first src that
+ # we see that is named "pyproject.toml"
+ for src in srcs:
+ if src.basename == "pyproject.toml":
+ if project == None:
+ project = src.dirname
+ elif len(project) > len(src.dirname):
+ # select the shortest match
+ project = src.dirname
+
+ if project == None:
+ project = pkg
+
+ if project:
+ args.add_all([project], before_each = "--project")
+
args.add_all(ctx.files.build_constraints, before_each = "--build-constraints")
args.add_all(ctx.files.constraints, before_each = "--constraints")
args.add_all(ctx.attr.args)
@@ -275,6 +297,19 @@
doc = "Public, see the docs in the macro.",
mandatory = True,
),
+ "project": attr.string(
+ doc = """\
+Overrides the `--project` directory passed to `uv pip compile`.
+If not set, the project directory is auto-detected: when
+`pyproject.toml` files are in {obj}`lock.srcs`, the one with the
+shortest directory path is selected. This makes `uv` read
+`[tool.uv]` settings (e.g. `no-build-isolation`,
+`exclude-dependencies`) from that `pyproject.toml`.
+
+:::{versionadded} VERSION_NEXT_FEATURE
+:::
+""",
+ ),
"python_version": attr.string(
doc = "Public, see the docs in the macro.",
),
@@ -438,6 +473,7 @@
env = None,
generate_hashes = True,
python_version = None,
+ project = None,
strip_extras = False,
**kwargs):
"""Pin the requirements based on the src files.
@@ -484,6 +520,16 @@
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`.
+ project: {type}`str | None` overrides the `--project` directory
+ passed to `uv pip compile`. By default the project directory
+ is auto-detected: when {obj}`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}VERSION_NEXT_FEATURE
python_version: {type}`str | None` the python_version to transition to
when locking the requirements. Defaults to the default python version
configured by the {obj}`python` module extension.
@@ -509,6 +555,7 @@
env = env,
existing_output = maybe_out,
generate_hashes = generate_hashes,
+ project = project,
is_windows = select({
"@platforms//os:windows": True,
"//conditions:default": False,
diff --git a/tests/uv/lock/BUILD.bazel b/tests/uv/lock/BUILD.bazel
index 6b6902d..0b72f01 100644
--- a/tests/uv/lock/BUILD.bazel
+++ b/tests/uv/lock/BUILD.bazel
@@ -1,5 +1,10 @@
load(":lock_tests.bzl", "lock_test_suite")
+exports_files(
+ glob(["testdata/*"]),
+ visibility = ["//tests:__subpackages__"],
+)
+
lock_test_suite(
name = "lock_tests",
)
diff --git a/tests/uv/lock/lock_tests.bzl b/tests/uv/lock/lock_tests.bzl
index bcaed95..3e067f3 100644
--- a/tests/uv/lock/lock_tests.bzl
+++ b/tests/uv/lock/lock_tests.bzl
@@ -91,6 +91,7 @@
name = name,
tests = [
":requirements_test",
+ "//tests/uv/lock/pyproject_toml:requirements_test",
":requirements_run_tests",
],
)
diff --git a/tests/uv/lock/pyproject_toml/BUILD.bazel b/tests/uv/lock/pyproject_toml/BUILD.bazel
new file mode 100644
index 0000000..a64fe50
--- /dev/null
+++ b/tests/uv/lock/pyproject_toml/BUILD.bazel
@@ -0,0 +1,30 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("//python/uv:lock.bzl", "lock")
+
+# This test verifies that the `lock` rule automatically passes `--project` to `uv pip compile` based
+# on the package directory, so that `[tool.uv]` settings from a `pyproject.toml` in the same
+# directory are applied. It will exclude a particular dependency from the lock-file, so it will be
+# easy to see if we have any issues.
+lock(
+ name = "requirements",
+ srcs = ["pyproject.toml"],
+ out = "requirements.txt",
+ build_constraints = [
+ "//tests/uv/lock:testdata/build_constraints.txt",
+ "//tests/uv/lock:testdata/build_constraints2.txt",
+ ],
+ constraints = [
+ "//tests/uv/lock:testdata/constraints.txt",
+ "//tests/uv/lock:testdata/constraints2.txt",
+ ],
+ # It seems that the CI remote executors for the RBE do not have network
+ # connectivity due to current CI setup.
+ tags = ["no-remote-exec"],
+)
+
+diff_test(
+ name = "requirements_test",
+ timeout = "short",
+ file1 = ":requirements",
+ file2 = "requirements.txt",
+)
diff --git a/tests/uv/lock/pyproject_toml/pyproject.toml b/tests/uv/lock/pyproject_toml/pyproject.toml
new file mode 100644
index 0000000..06b9630
--- /dev/null
+++ b/tests/uv/lock/pyproject_toml/pyproject.toml
@@ -0,0 +1,8 @@
+[project]
+name = "test"
+version = "0.0.0"
+dependencies = ["requests"]
+
+[tool.uv]
+no-build-isolation = true
+exclude-dependencies = ["charset-normalizer"]
diff --git a/tests/uv/lock/pyproject_toml/requirements.txt b/tests/uv/lock/pyproject_toml/requirements.txt
new file mode 100644
index 0000000..4ea098b
--- /dev/null
+++ b/tests/uv/lock/pyproject_toml/requirements.txt
@@ -0,0 +1,18 @@
+# This file was autogenerated by uv via the following command:
+# bazel run //tests/uv/lock/pyproject_toml:requirements.update
+certifi==2025.1.31 \
+ --hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \
+ --hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe
+ # via requests
+idna==3.10 \
+ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
+ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
+ # via requests
+requests==2.32.3 \
+ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
+ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
+ # via test (tests/uv/lock/pyproject_toml/pyproject.toml)
+urllib3==2.3.0 \
+ --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \
+ --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d
+ # via requests
diff --git a/tests/uv/lock/workspaces/BUILD.bazel b/tests/uv/lock/workspaces/BUILD.bazel
new file mode 100644
index 0000000..aa1fabe
--- /dev/null
+++ b/tests/uv/lock/workspaces/BUILD.bazel
@@ -0,0 +1,27 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("//python/uv:lock.bzl", "lock")
+load("//tests/support:support.bzl", "NOT_WINDOWS")
+
+# This test verifies that the `lock` rule automatically passes `--project` to `uv pip compile` based
+# on the package directory, so that `[tool.uv]` settings from a `pyproject.toml` in the same
+# directory are applied. It will exclude a particular dependency from the lock-file, so it will be
+# easy to see if we have any issues.
+lock(
+ name = "requirements",
+ srcs = [
+ "pyproject.toml",
+ "//tests/uv/lock/workspaces/packages",
+ ],
+ out = "requirements.txt",
+ # It seems that the CI remote executors for the RBE do not have network
+ # connectivity due to current CI setup.
+ tags = ["no-remote-exec"],
+)
+
+diff_test(
+ name = "requirements_test",
+ timeout = "short",
+ file1 = ":requirements",
+ file2 = "requirements.txt",
+ target_compatible_with = NOT_WINDOWS,
+)
diff --git a/tests/uv/lock/workspaces/packages/BUILD.bazel b/tests/uv/lock/workspaces/packages/BUILD.bazel
new file mode 100644
index 0000000..5461287
--- /dev/null
+++ b/tests/uv/lock/workspaces/packages/BUILD.bazel
@@ -0,0 +1,5 @@
+filegroup(
+ name = "packages",
+ srcs = ["//tests/uv/lock/workspaces/packages/foo:pyproject.toml"],
+ visibility = ["//tests/uv/lock/workspaces:__pkg__"],
+)
diff --git a/tests/uv/lock/workspaces/packages/foo/BUILD.bazel b/tests/uv/lock/workspaces/packages/foo/BUILD.bazel
new file mode 100644
index 0000000..d89a7b7
--- /dev/null
+++ b/tests/uv/lock/workspaces/packages/foo/BUILD.bazel
@@ -0,0 +1 @@
+exports_files(["pyproject.toml"])
diff --git a/tests/uv/lock/workspaces/packages/foo/pyproject.toml b/tests/uv/lock/workspaces/packages/foo/pyproject.toml
new file mode 100644
index 0000000..f26ea85
--- /dev/null
+++ b/tests/uv/lock/workspaces/packages/foo/pyproject.toml
@@ -0,0 +1,7 @@
+[project]
+name = "foo"
+version = "0.0.0"
+dependencies = ["black"]
+
+[tool.uv]
+no-build-isolation = true
diff --git a/tests/uv/lock/workspaces/pyproject.toml b/tests/uv/lock/workspaces/pyproject.toml
new file mode 100644
index 0000000..01e1e2e
--- /dev/null
+++ b/tests/uv/lock/workspaces/pyproject.toml
@@ -0,0 +1,10 @@
+[project]
+name = "test"
+version = "0.0.0"
+dependencies = ["requests"]
+
+[tool.uv]
+no-build-isolation = true
+
+[tool.uv.workspace]
+members = ["packages/*"]
diff --git a/tests/uv/lock/workspaces/requirements.txt b/tests/uv/lock/workspaces/requirements.txt
new file mode 100644
index 0000000..5ce35d7
--- /dev/null
+++ b/tests/uv/lock/workspaces/requirements.txt
@@ -0,0 +1,242 @@
+# This file was autogenerated by uv via the following command:
+# bazel run //tests/uv/lock/workspaces:requirements.update
+black==26.5.1 \
+ --hash=sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7 \
+ --hash=sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418 \
+ --hash=sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe \
+ --hash=sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0 \
+ --hash=sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4 \
+ --hash=sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3 \
+ --hash=sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3 \
+ --hash=sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3 \
+ --hash=sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217 \
+ --hash=sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8 \
+ --hash=sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2 \
+ --hash=sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59 \
+ --hash=sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50 \
+ --hash=sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22 \
+ --hash=sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52 \
+ --hash=sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef \
+ --hash=sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90 \
+ --hash=sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c \
+ --hash=sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893 \
+ --hash=sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d \
+ --hash=sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264 \
+ --hash=sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73 \
+ --hash=sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a \
+ --hash=sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168 \
+ --hash=sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18 \
+ --hash=sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294 \
+ --hash=sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae
+ # via foo (tests/uv/lock/workspaces/packages/foo/pyproject.toml)
+certifi==2025.1.31 \
+ --hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \
+ --hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe
+ # via requests
+charset-normalizer==3.4.7 \
+ --hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \
+ --hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \
+ --hash=sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67 \
+ --hash=sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4 \
+ --hash=sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0 \
+ --hash=sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c \
+ --hash=sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5 \
+ --hash=sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444 \
+ --hash=sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153 \
+ --hash=sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9 \
+ --hash=sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01 \
+ --hash=sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217 \
+ --hash=sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b \
+ --hash=sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c \
+ --hash=sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a \
+ --hash=sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83 \
+ --hash=sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5 \
+ --hash=sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7 \
+ --hash=sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb \
+ --hash=sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c \
+ --hash=sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1 \
+ --hash=sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42 \
+ --hash=sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab \
+ --hash=sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df \
+ --hash=sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e \
+ --hash=sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207 \
+ --hash=sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18 \
+ --hash=sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734 \
+ --hash=sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38 \
+ --hash=sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110 \
+ --hash=sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18 \
+ --hash=sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44 \
+ --hash=sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d \
+ --hash=sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48 \
+ --hash=sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e \
+ --hash=sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5 \
+ --hash=sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d \
+ --hash=sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53 \
+ --hash=sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790 \
+ --hash=sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c \
+ --hash=sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b \
+ --hash=sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116 \
+ --hash=sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d \
+ --hash=sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10 \
+ --hash=sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6 \
+ --hash=sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2 \
+ --hash=sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776 \
+ --hash=sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a \
+ --hash=sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265 \
+ --hash=sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008 \
+ --hash=sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943 \
+ --hash=sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374 \
+ --hash=sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246 \
+ --hash=sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e \
+ --hash=sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5 \
+ --hash=sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616 \
+ --hash=sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15 \
+ --hash=sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41 \
+ --hash=sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960 \
+ --hash=sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752 \
+ --hash=sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e \
+ --hash=sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72 \
+ --hash=sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7 \
+ --hash=sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8 \
+ --hash=sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b \
+ --hash=sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4 \
+ --hash=sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545 \
+ --hash=sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706 \
+ --hash=sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366 \
+ --hash=sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb \
+ --hash=sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a \
+ --hash=sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e \
+ --hash=sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00 \
+ --hash=sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f \
+ --hash=sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a \
+ --hash=sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1 \
+ --hash=sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66 \
+ --hash=sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356 \
+ --hash=sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319 \
+ --hash=sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4 \
+ --hash=sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad \
+ --hash=sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d \
+ --hash=sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5 \
+ --hash=sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7 \
+ --hash=sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0 \
+ --hash=sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686 \
+ --hash=sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34 \
+ --hash=sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49 \
+ --hash=sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c \
+ --hash=sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1 \
+ --hash=sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e \
+ --hash=sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60 \
+ --hash=sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0 \
+ --hash=sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274 \
+ --hash=sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d \
+ --hash=sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0 \
+ --hash=sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae \
+ --hash=sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f \
+ --hash=sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d \
+ --hash=sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe \
+ --hash=sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3 \
+ --hash=sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393 \
+ --hash=sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1 \
+ --hash=sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af \
+ --hash=sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44 \
+ --hash=sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00 \
+ --hash=sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c \
+ --hash=sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3 \
+ --hash=sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7 \
+ --hash=sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd \
+ --hash=sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e \
+ --hash=sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b \
+ --hash=sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8 \
+ --hash=sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259 \
+ --hash=sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859 \
+ --hash=sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46 \
+ --hash=sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30 \
+ --hash=sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b \
+ --hash=sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46 \
+ --hash=sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24 \
+ --hash=sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a \
+ --hash=sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24 \
+ --hash=sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc \
+ --hash=sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215 \
+ --hash=sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063 \
+ --hash=sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832 \
+ --hash=sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6 \
+ --hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \
+ --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464
+ # via requests
+click==8.4.1 \
+ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \
+ --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96
+ # via black
+idna==3.10 \
+ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
+ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
+ # via requests
+mypy-extensions==1.1.0 \
+ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \
+ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558
+ # via black
+packaging==26.2 \
+ --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
+ --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
+ # via black
+pathspec==1.1.1 \
+ --hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \
+ --hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189
+ # via black
+platformdirs==4.10.0 \
+ --hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \
+ --hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a
+ # via black
+pytokens==0.4.1 \
+ --hash=sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1 \
+ --hash=sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009 \
+ --hash=sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083 \
+ --hash=sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1 \
+ --hash=sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de \
+ --hash=sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2 \
+ --hash=sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a \
+ --hash=sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1 \
+ --hash=sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5 \
+ --hash=sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a \
+ --hash=sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3 \
+ --hash=sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db \
+ --hash=sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68 \
+ --hash=sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037 \
+ --hash=sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321 \
+ --hash=sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc \
+ --hash=sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7 \
+ --hash=sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f \
+ --hash=sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918 \
+ --hash=sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9 \
+ --hash=sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c \
+ --hash=sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1 \
+ --hash=sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1 \
+ --hash=sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3 \
+ --hash=sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b \
+ --hash=sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb \
+ --hash=sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1 \
+ --hash=sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a \
+ --hash=sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4 \
+ --hash=sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa \
+ --hash=sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78 \
+ --hash=sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe \
+ --hash=sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9 \
+ --hash=sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d \
+ --hash=sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975 \
+ --hash=sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440 \
+ --hash=sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16 \
+ --hash=sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc \
+ --hash=sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d \
+ --hash=sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6 \
+ --hash=sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6 \
+ --hash=sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324
+ # via black
+requests==2.32.3 \
+ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
+ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
+ # via test (tests/uv/lock/workspaces/pyproject.toml)
+urllib3==2.3.0 \
+ --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \
+ --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d
+ # via requests