diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33adfa7a60..99876bde0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -109,6 +109,8 @@ END_UNRELEASED_TEMPLATE
* (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 b5d8ec24f7..5c9f0646dc 100644
--- a/docs/pypi/lock.md
+++ b/docs/pypi/lock.md
@@ -68,8 +68,55 @@ compile_pip_requirements(
### 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
` 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 7bcca780a0..7fd50082ea 100644
--- a/python/uv/lock.bzl
+++ b/python/uv/lock.bzl
@@ -45,6 +45,28 @@ native_test(
)
```
+### `[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 6f0b80af89..23f2eed467 100644
--- a/python/uv/private/lock.bzl
+++ b/python/uv/private/lock.bzl
@@ -71,7 +71,8 @@ def _args(ctx):
)
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 @@ def _lock_impl(ctx):
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 @@ modifications and the locking is not done from scratch.
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 @@ def lock(
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 @@ def lock(
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 @@ def lock(
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 6b6902da44..0b72f015b7 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 bcaed95b53..3e067f3e73 100644
--- a/tests/uv/lock/lock_tests.bzl
+++ b/tests/uv/lock/lock_tests.bzl
@@ -91,6 +91,7 @@ def lock_test_suite(name):
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 0000000000..a64fe50d14
--- /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 0000000000..06b96309da
--- /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 0000000000..4ea098b0e4
--- /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 0000000000..0287e66f71
--- /dev/null
+++ b/tests/uv/lock/workspaces/BUILD.bazel
@@ -0,0 +1,25 @@
+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",
+ "//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",
+)
diff --git a/tests/uv/lock/workspaces/packages/BUILD.bazel b/tests/uv/lock/workspaces/packages/BUILD.bazel
new file mode 100644
index 0000000000..5461287d9f
--- /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 0000000000..d89a7b784e
--- /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 0000000000..f26ea85b51
--- /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 0000000000..01e1e2e076
--- /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 0000000000..5ce35d7571
--- /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