From 0fc1bcd093d8df2ef51f64742fb22dcf6d4256f6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 28 Sep 2026 03:22:17 +0000
Subject: [PATCH 1/3] build(deps): bump idna from 3.19 to 3.20 in
/tools/publish (#4170)
Bumps [idna](https://github.com/kjd/idna) from 3.19 to 3.20.
Release notes
Sourced from idna's
releases.
v3.20
- Update to Unicode 18.0.0.
- Better enforcement of the domain length limit in the incremental
codec.
- Add support for Python 3.15.
Changelog
Sourced from idna's
changelog.
3.20 (2026-09-17)
- Update to Unicode 18.0.0.
- Better enforcement of the domain length limit in the incremental
codec.
- Add support for Python 3.15.
Commits
d55e65e
Release 3.20
0c0824a
Pre-release 3.20rc0
bd7c316
Note Python 3.15 support in the 3.20 changelog
b6cce85
Merge pull request #276 from
kjd/unicode-18
9a4bc59
Update to Unicode 18.0.0
dfab5a0
Merge branch 'python-3.15'
417c354
Read the latest Unicode version from the DerivedAge.txt header instead
of the...
cd17392
Merge pull request #274 from
kjd/fix-decode-length-check
c5796d7
Skip the decode round-trip check for domains past encode's length
limit
d6ee690
Update to Python 3.15 release candidate in CI and add trove
classifier
- Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
tools/publish/requirements_darwin.txt | 6 +++---
tools/publish/requirements_linux.txt | 6 +++---
tools/publish/requirements_universal.txt | 6 +++---
tools/publish/requirements_windows.txt | 6 +++---
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/tools/publish/requirements_darwin.txt b/tools/publish/requirements_darwin.txt
index eb97048d1b..9368554310 100644
--- a/tools/publish/requirements_darwin.txt
+++ b/tools/publish/requirements_darwin.txt
@@ -192,9 +192,9 @@ id==1.6.1 \
--hash=sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069 \
--hash=sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca
# via twine
-idna==3.19 \
- --hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
- --hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
+idna==3.20 \
+ --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
+ --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
# via requests
importlib-metadata==9.0.1 \
--hash=sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99 \
diff --git a/tools/publish/requirements_linux.txt b/tools/publish/requirements_linux.txt
index a9caddb0c0..bb67382b12 100644
--- a/tools/publish/requirements_linux.txt
+++ b/tools/publish/requirements_linux.txt
@@ -342,9 +342,9 @@ id==1.6.1 \
--hash=sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069 \
--hash=sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca
# via twine
-idna==3.19 \
- --hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
- --hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
+idna==3.20 \
+ --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
+ --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
# via requests
importlib-metadata==9.0.1 \
--hash=sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99 \
diff --git a/tools/publish/requirements_universal.txt b/tools/publish/requirements_universal.txt
index a0a28091ac..7113fad355 100644
--- a/tools/publish/requirements_universal.txt
+++ b/tools/publish/requirements_universal.txt
@@ -342,9 +342,9 @@ id==1.6.1 \
--hash=sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069 \
--hash=sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca
# via twine
-idna==3.19 \
- --hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
- --hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
+idna==3.20 \
+ --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
+ --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
# via requests
importlib-metadata==9.0.1 ; python_full_version < '3.12' and platform_machine != 'ppc64le' and platform_machine != 's390x' \
--hash=sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99 \
diff --git a/tools/publish/requirements_windows.txt b/tools/publish/requirements_windows.txt
index 95de3ce933..cc9a4d9910 100644
--- a/tools/publish/requirements_windows.txt
+++ b/tools/publish/requirements_windows.txt
@@ -192,9 +192,9 @@ id==1.6.1 \
--hash=sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069 \
--hash=sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca
# via twine
-idna==3.19 \
- --hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
- --hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
+idna==3.20 \
+ --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
+ --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
# via requests
importlib-metadata==9.0.1 \
--hash=sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99 \
From fa8a0e8e118639449c74ef400c1696e836138b51 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 28 Sep 2026 03:22:22 +0000
Subject: [PATCH 2/3] build(deps): bump urllib3 from 2.7.0 to 2.8.0 in
/tools/publish (#4169)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.7.0 to 2.8.0.
Release notes
Sourced from urllib3's
releases.
2.8.0
🚀 urllib3 is fundraising for HTTP/2 support
urllib3
is raising ~$40,000 USD to release HTTP/2 support and ensure
long-term sustainable maintenance of the project. If your company or
organization uses Python and would benefit from HTTP/2 support in
Requests, pip, cloud SDKs, and thousands of other projects please consider contributing
financially to ensure HTTP/2 support is developed sustainably and
maintained for the long-haul.
Thank you for your support.
Security
Fixed the following security issues:
- The TLS configuration for HTTPS proxies could be ignored or
overridden. (High severity, GHSA-8988-9cw3-xx77)
HTTPResponse.stream() and read_chunked()
could buffer a chunk-size line of unbounded length in memory. (High
severity, GHSA-vxq7-64xx-v4gw)
- Chunked Deflate streaming could enter an infinite loop. (Medium
severity, GHSA-gh4c-6fx4-qh6g)
[!IMPORTANT]
urllib3 2.8.0 fixes HTTPS proxy TLS configuration being ignored or
overridden by destination settings. Configurations relying on that
behavior may require changes.
Configure proxy CA certificates and client certificates in
proxy_ssl_context, and proxy identity checks with
proxy_assert_hostname or
proxy_assert_fingerprint. Destination client certificates
and identity overrides no longer apply to HTTPS forwarding proxy
connections.
[!NOTE]
CVE IDs had not yet been assigned to these advisories at the time of
release due to a backlog at GitHub's CNA.
Deprecations & Removals
- Deprecated using an empty collection as the
Retry
option allowed_methods to retry any verb. (#5044)
Features
- Added
Url.auth_decoded and
Url.auth_decoded_joined convenience properties to the
result of parse_url(). (#4945)
- Added
basic_auth_encoding and
proxy_basic_auth_encoding parameters to
urllib3.util.make_headers(). (#5092)
Bugfixes
-
Fixed response header handling to replace obsolete folded header
lines (obs-fold) with spaces in accordance with RFC 9112,
preventing raw CRLF sequences from appearing in header values such as
Set-Cookie. (#1362)
-
Fixed usage of proxy_ssl_context with
ProxyManager when
use_forwarding_for_https=True. Passing
ssl_context instead of proxy_ssl_context for
HTTPS proxies in this configuration now emits a
FutureWarning and will raise an error in v3.0. (#2577)
-
Changed behavior of the default ConnectionPool.pool
initialization. LifoQueue is now resolved from the
queue module after the ConnectionPool is
instantiated instead of using the default cached QueueCls
class property. This is done because sometimes the
queue.LifoQueue is monkey-patched late in the program, such
as by gevent. (#3289)
-
Raised UnrewindableBodyError instead of
ValueError when retrying a request whose body had
tell() but not seek(). (#3779)
-
Decoded percent-encoded SOCKS proxy credentials before authenticating
with the proxy server. (#3785)
-
Fixed HTTPResponse.drain_conn() to discard unread
response data in 64 KiB chunks (same as the default amt
when doing HTTPResponse.stream(...)). (#5019)
-
Fixed is_ipaddress() to detect non-standard IPv4 forms
accepted by socket.connect, such as hex
(0x7f000001), octal (0177.0.0.1), and decimal
integers (2130706433), ensuring SSL certificate
verification uses the correct mode for these addresses. (#5029)
-
Fixed HTTPConnectionPool.urlopen raising a misleading
FullPoolError instead of ValueError when
called with an invalid timeout argument on a pool created
with block=True. (#5059)
-
Fixed port-zero handling to preserve explicit :0 values
instead of substituting the default ports 80 or 443 in URL parsing, pool
selection, proxy configuration, connection_from_url(), and
HTTP/2 request authority. (#5071,
#5101)
-
Fixed a bug where PoolManager passed the
assert_hostname and assert_fingerprint
parameters to HTTP connection pools. (#5077)
-
Fixed HTTPConnectionPool.urlopen() and HTTP proxy
forwarding to strip URL fragments from absolute request targets before
sending requests. (#5079)
-
Added safeguards to the proxy tunneling code to prevent potential
security issues when handling invalid characters in the proxy host and
HTTP headers. This change affects users of Python 3.10, Python 3.11, and
Python 3.12 when the standard library does not contain the fix; those on
newer Python versions should upgrade to 3.13.14+ or 3.14.5+ to get the
same security fixes. (#5091)
-
Fixed HTTPSConnection.connect() overriding
ProxyConfig.ssl_context's certificate policy and proxy
identity checks with the target connection's TLS settings when
forwarding through an HTTPS proxy.
HTTPSConnection no longer applies target SNI,
assertions, or client credentials to forwarding proxy handshakes and
continues to use its ssl_context as a fallback when an
HTTPS proxy forwards an HTTP target. (#5093)
-
Fixed URL parsing to more strictly enforce RFC 3986 host syntax,
rejecting invalid host input such as raw spaces and control characters,
malformed percent-encodings, and percent-encoded control characters in
HTTP(S) hosts and IPv6 zone identifiers, including proxy CONNECT tunnel
targets. Host normalization now also follows RFC 3986 normalization
rules for percent-encoded octets by decoding percent-encoded unreserved
characters and uppercasing the hexadecimal digits of retained
percent-encoded octets. (#5095)
... (truncated)
Changelog
Sourced from urllib3's
changelog.
2.8.0 (2026-09-15)
Security
Fixed the following security issues:
- The TLS configuration for HTTPS proxies could be ignored or
overridden.
(High severity,
GHSA-8988-9cw3-xx77
<https://github.com/urllib3/urllib3/security/advisories/GHSA-8988-9cw3-xx77>__)
HTTPResponse.stream() and read_chunked()
could buffer a chunk-size
line of unbounded length in memory. (High severity,
GHSA-vxq7-64xx-v4gw
<https://github.com/urllib3/urllib3/security/advisories/GHSA-vxq7-64xx-v4gw>__)
- Chunked Deflate streaming could enter an infinite loop. (Medium
severity,
GHSA-gh4c-6fx4-qh6g
<https://github.com/urllib3/urllib3/security/advisories/GHSA-gh4c-6fx4-qh6g>__)
.. caution::
urllib3 2.8.0 fixes HTTPS proxy TLS configuration being
ignored or
overridden by destination settings. Configurations relying on that
behavior may require changes.
Configure proxy CA certificates and client certificates in
proxy_ssl_context, and proxy identity checks with
proxy_assert_hostname or
proxy_assert_fingerprint.
Destination client certificates and identity overrides no longer
apply to HTTPS forwarding proxy connections.
Deprecations & Removals
- Deprecated using an empty collection as the
Retry
option
allowed_methods to retry any verb.
([#5044](https://github.com/urllib3/urllib3/issues/5044)
<https://github.com/urllib3/urllib3/issues/5044>__)
Features
- Added
Url.auth_decoded and
Url.auth_decoded_joined convenience
properties to the result of parse_url().
([#4945](https://github.com/urllib3/urllib3/issues/4945)
<https://github.com/urllib3/urllib3/issues/4945>__)
- Added
basic_auth_encoding and
proxy_basic_auth_encoding parameters to
urllib3.util.make_headers().
([#5092](https://github.com/urllib3/urllib3/issues/5092)
<https://github.com/urllib3/urllib3/issues/5092>__)
Bugfixes
... (truncated)
Commits
b1d30ab
Release 2.8.0
9016d7e
Skip test_read_chunked_with_trailing_data_does_not_hang for
brotlicffi (#5258)
9101f58
Fix nox -s docs warning (#5256)
cd770b0
Merge commit from fork
ea2ad7b
Merge commit from fork
0716e31
Fix loading unencrypted client keys with a password in pyOpenSSL (#5255)
43c68c8
Test pickling of InvalidChunkLength (#5247)
308b279
Share security policy between GitHub and Read the Docs (#5253)
53fa073
Add policy on duplicate pull requests (#5252)
5f2a6a8
Assert on the ALPN extension in test_tunnel_sets_http_11_alpn (#5232)
- Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
tools/publish/requirements_darwin.txt | 6 +++---
tools/publish/requirements_linux.txt | 6 +++---
tools/publish/requirements_universal.txt | 6 +++---
tools/publish/requirements_windows.txt | 6 +++---
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/tools/publish/requirements_darwin.txt b/tools/publish/requirements_darwin.txt
index 9368554310..8504cd82de 100644
--- a/tools/publish/requirements_darwin.txt
+++ b/tools/publish/requirements_darwin.txt
@@ -295,9 +295,9 @@ twine==7.0.0 \
--hash=sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177 \
--hash=sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7
# via rules-python-publish-deps (pyproject.toml)
-urllib3==2.7.0 \
- --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
- --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
+urllib3==2.8.0 \
+ --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
+ --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
# via
# id
# requests
diff --git a/tools/publish/requirements_linux.txt b/tools/publish/requirements_linux.txt
index bb67382b12..7ea7999787 100644
--- a/tools/publish/requirements_linux.txt
+++ b/tools/publish/requirements_linux.txt
@@ -459,9 +459,9 @@ twine==7.0.0 \
--hash=sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177 \
--hash=sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7
# via rules-python-publish-deps (pyproject.toml)
-urllib3==2.7.0 \
- --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
- --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
+urllib3==2.8.0 \
+ --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
+ --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
# via
# id
# requests
diff --git a/tools/publish/requirements_universal.txt b/tools/publish/requirements_universal.txt
index 7113fad355..d71645e10f 100644
--- a/tools/publish/requirements_universal.txt
+++ b/tools/publish/requirements_universal.txt
@@ -463,9 +463,9 @@ twine==7.0.0 \
--hash=sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177 \
--hash=sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7
# via rules-python-publish-deps (pyproject.toml)
-urllib3==2.7.0 \
- --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
- --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
+urllib3==2.8.0 \
+ --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
+ --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
# via
# id
# requests
diff --git a/tools/publish/requirements_windows.txt b/tools/publish/requirements_windows.txt
index cc9a4d9910..a3009479d8 100644
--- a/tools/publish/requirements_windows.txt
+++ b/tools/publish/requirements_windows.txt
@@ -299,9 +299,9 @@ twine==7.0.0 \
--hash=sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177 \
--hash=sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7
# via rules-python-publish-deps (pyproject.toml)
-urllib3==2.7.0 \
- --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
- --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
+urllib3==2.8.0 \
+ --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
+ --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
# via
# id
# requests
From 6efa7c319433c30eecdbfc090952d2a8369f03f4 Mon Sep 17 00:00:00 2001
From: Kristian Hartikainen
Date: Mon, 28 Sep 2026 03:32:22 +0000
Subject: [PATCH 3/3] fix(uv): Reuse the `uv` cache in runnable lock targets
(#4180)
This PR lets `.run` targets reuse the caller's `uv` cache by moving
`--no-cache` from the shared arguments to the build-action arguments.
Repeated runs can reuse registry metadata, Git checkouts, and build
metadata. Build actions retain cache isolation, and callers can still
pass `--no-cache`.
The lock tests use a sandbox-writable `UV_CACHE_DIR` and verify that
`.run` creates it.
Closes #4179.
---------
Co-authored-by: Richard Levasseur
---
news/4179.fixed.md | 5 ++
python/uv/private/lock.bzl | 14 ++++-
tests/integration/uv_lock/BUILD.bazel | 7 +++
tests/integration/uv_lock_test.py | 49 +++++++++++++++
tests/uv/lock/BUILD.bazel | 3 +
tests/uv/lock/cache_tests.bzl | 90 +++++++++++++++++++++++++++
tests/uv/lock/lock_run_test.py | 3 +-
7 files changed, 168 insertions(+), 3 deletions(-)
create mode 100644 news/4179.fixed.md
create mode 100644 tests/uv/lock/cache_tests.bzl
diff --git a/news/4179.fixed.md b/news/4179.fixed.md
new file mode 100644
index 0000000000..3225cfb421
--- /dev/null
+++ b/news/4179.fixed.md
@@ -0,0 +1,5 @@
+(uv) The {obj}`lock` macro's `.run` target uses the `uv` cache and honors
+`UV_CACHE_DIR` and `UV_NO_CACHE`. Pass `--refresh` to refresh cached data or
+`--no-cache` to disable caching. Lock build actions, including those used by
+`.update`, disable the `uv` cache.
+([#4179](https://github.com/bazel-contrib/rules_python/issues/4179))
diff --git a/python/uv/private/lock.bzl b/python/uv/private/lock.bzl
index 33c301d49c..3d39788d9d 100644
--- a/python/uv/private/lock.bzl
+++ b/python/uv/private/lock.bzl
@@ -143,7 +143,9 @@ def _common_lock(ctx, locker):
srcs, output_filename, mnemonic, progress_message = locker(args, output)
args.add("--no-python-downloads")
- args.add("--no-cache")
+
+ # Build actions must not depend on the host cache but `.run` may reuse it.
+ args.add_run_shell("--no-cache")
project = ctx.attr.project
if not project:
@@ -607,7 +609,11 @@ def lock(
to the same command that would be run in the `name` action. This will
update the source copy of the requirements 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.
+ 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 requirements lock file. The output can be fed to the
{obj}`pip.parse` bzlmod extension tag class. Note, you can use
@@ -622,6 +628,10 @@ def lock(
All of the targets have `manual` tags as locking results cannot be cached.
:::
+ :::{versionchanged} VERSION_NEXT_PATCH
+ The `name.run` target uses the `uv` cache by default.
+ :::
+
Args:
name: {type}`str` The prefix of all targets created by this macro.
srcs: {type}`list[Label]` The sources that will be used. Add all of the
diff --git a/tests/integration/uv_lock/BUILD.bazel b/tests/integration/uv_lock/BUILD.bazel
index 01c90d356b..27ee012709 100644
--- a/tests/integration/uv_lock/BUILD.bazel
+++ b/tests/integration/uv_lock/BUILD.bazel
@@ -24,3 +24,10 @@ diff_test(
file1 = ":requirements",
file2 = ":requirements.txt",
)
+
+lock(
+ name = "project_lock",
+ srcs = ["pyproject.toml"],
+ out = "uv.lock",
+ tags = ["no-remote-exec"],
+)
diff --git a/tests/integration/uv_lock_test.py b/tests/integration/uv_lock_test.py
index 8efd3cb84f..8ac95ca9f1 100644
--- a/tests/integration/uv_lock_test.py
+++ b/tests/integration/uv_lock_test.py
@@ -185,6 +185,55 @@ def _assert_lock_file(self, result):
self.assertIn("my-local-pkg", contents)
self.assertIn("--hash=sha256:", contents)
+ def _run_cached_lock(self, target, *args, check=True):
+ return self.run_bazel(
+ "run",
+ target,
+ "--",
+ "--default-index=" + self.server_url + "/simple/",
+ *args,
+ check=check,
+ )
+
+ def test_run_reuses_cache_offline(self):
+ for target, filename in (
+ ("//:requirements.run", "requirements.txt"),
+ (
+ "//:project_lock.run",
+ "bazel-bin/project_lock.run.runfiles/_main/uv.lock",
+ ),
+ ):
+ with self.subTest(target=target):
+ self.bazel_env["UV_CACHE_DIR"] = str(
+ self.test_tmp_dir / (Path(filename).name + ".cache")
+ )
+ output = self.repo_root / filename
+
+ output.unlink(missing_ok=True)
+ cold = self._run_cached_lock(target, "--offline", check=False)
+ self.assertNotEqual(cold.exit_code, 0, cold.describe())
+
+ self._run_cached_lock(target)
+ expected = output.read_text()
+ self.assertIn("my-local-pkg", expected)
+
+ output.unlink()
+ self._run_cached_lock(target, "--offline")
+ self.assertEqual(expected, output.read_text())
+
+ output.unlink()
+ bypass = self._run_cached_lock(
+ target, "--offline", "--no-cache", check=False
+ )
+ self.assertNotEqual(bypass.exit_code, 0, bypass.describe())
+
+ self.bazel_env["UV_NO_CACHE"] = "true"
+ try:
+ bypass_env = self._run_cached_lock(target, "--offline", check=False)
+ self.assertNotEqual(bypass_env.exit_code, 0, bypass_env.describe())
+ finally:
+ del self.bazel_env["UV_NO_CACHE"]
+
def test_lock_update_with_custom_index(self):
self._assert_server_requires_auth()
self._assert_simple_api_sha256()
diff --git a/tests/uv/lock/BUILD.bazel b/tests/uv/lock/BUILD.bazel
index 60d680bde8..156b9dbe6f 100644
--- a/tests/uv/lock/BUILD.bazel
+++ b/tests/uv/lock/BUILD.bazel
@@ -1,3 +1,4 @@
+load(":cache_tests.bzl", "cache_test_suite")
load(":lock_tests.bzl", "lock_test_suite")
load(":uv_lock_to_requirements_tests.bzl", "uv_lock_to_requirements_test_suite")
@@ -13,3 +14,5 @@ lock_test_suite(
uv_lock_to_requirements_test_suite(
name = "uv_lock_to_requirements_tests",
)
+
+cache_test_suite(name = "cache_tests")
diff --git a/tests/uv/lock/cache_tests.bzl b/tests/uv/lock/cache_tests.bzl
new file mode 100644
index 0000000000..a1e528d5dc
--- /dev/null
+++ b/tests/uv/lock/cache_tests.bzl
@@ -0,0 +1,90 @@
+"""Cache policy tests for build actions and runnable lock targets."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/uv:lock.bzl", "lock")
+load("//tests/support/platforms:platforms.bzl", "platform_targets")
+
+def _cache_tests(name, is_windows, extension):
+ if is_windows:
+ platform = platform_targets.WINDOWS_X86_64
+ else:
+ platform = platform_targets.LINUX_X86_64
+ config_settings = {
+ "//command_line_option:platforms": [platform],
+ }
+ for no_cache in [False, True]:
+ subject = name + ("_no_cache" if no_cache else "_default")
+ lock(
+ name = subject,
+ srcs = ["testdata/pyproject.toml"],
+ args = ["--no-cache"] if no_cache else [],
+ out = subject + extension,
+ )
+ analysis_test(
+ name = subject + "_build_test",
+ impl = _test_windows_build_impl if is_windows else _test_build_impl,
+ target = subject,
+ config_settings = config_settings,
+ )
+ analysis_test(
+ name = subject + "_run_test",
+ impl = _test_no_cache_run_impl if no_cache else _test_run_impl,
+ target = subject + ".run",
+ config_settings = config_settings,
+ )
+ native.test_suite(
+ name = name,
+ tests = [
+ name + mode + kind
+ for mode in ["_default", "_no_cache"]
+ for kind in ["_build_test", "_run_test"]
+ ],
+ )
+
+def _test_build_impl(env, target):
+ output = target[DefaultInfo].files.to_list()[0]
+ env.expect.that_target(target).action_generating(
+ output.short_path,
+ ).argv().contains("--no-cache")
+
+def _test_windows_build_impl(env, target):
+ env.expect.that_target(target).action_generating(
+ "{package}/{name}_lock.bat",
+ ).content().contains("--no-cache")
+
+def _run_script(env, target):
+ executable = target[DefaultInfo].files_to_run.executable
+ return env.expect.that_target(target).action_generating(
+ executable.short_path,
+ ).content()
+
+def _test_run_impl(env, target):
+ _run_script(env, target).split("--no-cache").has_size(1)
+
+def _test_no_cache_run_impl(env, target):
+ _run_script(env, target).contains("--no-cache")
+
+def _test_requirements_cache(name):
+ _cache_tests(name, is_windows = False, extension = ".txt")
+
+def _test_uv_lock_cache(name):
+ _cache_tests(name, is_windows = False, extension = ".lock")
+
+def _test_windows_requirements_cache(name):
+ _cache_tests(name, is_windows = True, extension = ".txt")
+
+def _test_windows_uv_lock_cache(name):
+ _cache_tests(name, is_windows = True, extension = ".lock")
+
+def cache_test_suite(name):
+ """Check cache defaults and explicit `--no-cache` for both lock formats."""
+ test_suite(
+ name = name,
+ tests = [
+ _test_requirements_cache,
+ _test_uv_lock_cache,
+ _test_windows_requirements_cache,
+ _test_windows_uv_lock_cache,
+ ],
+ )
diff --git a/tests/uv/lock/lock_run_test.py b/tests/uv/lock/lock_run_test.py
index 139d8bb2c8..d2d18e69f7 100644
--- a/tests/uv/lock/lock_run_test.py
+++ b/tests/uv/lock/lock_run_test.py
@@ -51,6 +51,7 @@ class LockTests(unittest.TestCase):
def _subprocess_env(self, workspace_dir: Path) -> dict[str, str]:
env = {
"BUILD_WORKSPACE_DIRECTORY": str(workspace_dir),
+ "UV_CACHE_DIR": str(workspace_dir / "uv-cache"),
}
# Inherit specific env vars needed for finding runfiles on Windows
for key in (
@@ -249,7 +250,7 @@ def test_requirements_run_script_has_expected_args(self):
self.assertIn("--generate-hashes", content)
self.assertIn("--no-strip-extras", content)
self.assertIn("--no-python-downloads", content)
- self.assertIn("--no-cache", content)
+ self.assertNotIn("--no-cache", content)
self.assertIn("--no-progress", content)
self.assertIn("--quiet", content)
self.assertIn("--output-file", content)