From 77053fdd7c0c9c4fc3a0fe197223e56431a567d7 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 13 Nov 2026 12:59:50 +0000 Subject: [PATCH 1/4] fix: Migrate to tox-uv-bare This removes the need to have `uv` itself packaged into the venv. We now use the devcontainer's default uv version. The devcontainer.json changes are to deal with a migration issue that can occur due to older uv versions being in the venv - if that old version no longer meets the required version in `pyproject.toml` -> `tool.uv` -> `required-version` it means you end up with a venv that cannot run any uv commands as it is below the minimum version. --- .devcontainer/devcontainer.json | 3 ++- pyproject.toml | 9 ++++++- template/pyproject.toml.jinja | 9 ++++++- uv.lock | 42 ++------------------------------- 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c12cce59d..43c3cf688 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -68,5 +68,6 @@ // Mount the parent as /workspaces so we can pip install peers as editable "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", // After the container is created, recreate the venv then make pre-commit first run faster - "postCreateCommand": "uv venv --clear && uv sync && pre-commit install --install-hooks" + // Ensure we use system uv, as there might be a different (old) version of uv in the venv + "postCreateCommand": "/usr/bin/uv venv --clear && /usr/bin/uv sync && pre-commit install --install-hooks" } diff --git a/pyproject.toml b/pyproject.toml index a13dabbb7..62b21a2cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dev = [ "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", - "tox-uv", + "tox-uv-bare", ] [tool.uv] @@ -43,6 +43,13 @@ env_list = ["pre-commit", "tests", "docs"] [tool.tox.env_run_base] # Pass all environment variables to the test environments passenv = ["*"] +allowlist_externals = [ + "pytest", + "pre-commit", + "sphinx-build", + "sphinx-autobuild", + "pyright", +] [tool.tox.env.pre-commit] description = "Run pre-commit on all files" diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja index d6ad19a65..68f3c523a 100644 --- a/template/pyproject.toml.jinja +++ b/template/pyproject.toml.jinja @@ -34,7 +34,7 @@ dev = [ {% if sphinx %}"sphinx-autobuild", "sphinx-copybutton", "sphinx-design", - {% endif %}"tox-uv", + {% endif %}"tox-uv-bare", "types-mock", ] @@ -90,6 +90,13 @@ env_list = ["pre-commit", "type-checking", "tests"{% if sphinx %}, "docs"{% endi [tool.tox.env_run_base] # Pass all environment variables to the test environments passenv = ["*"] +allowlist_externals = [ + "pytest", + "pre-commit", + "sphinx-build", + "sphinx-autobuild", + "pyright", +] [tool.tox.env.pre-commit] description = "Run pre-commit on all files" diff --git a/uv.lock b/uv.lock index eda5cf68a..188014b5d 100644 --- a/uv.lock +++ b/uv.lock @@ -916,7 +916,7 @@ dev = [ { name = "sphinx-autobuild" }, { name = "sphinx-copybutton" }, { name = "sphinx-design" }, - { name = "tox-uv" }, + { name = "tox-uv-bare" }, ] [package.metadata] @@ -932,7 +932,7 @@ dev = [ { name = "sphinx-autobuild" }, { name = "sphinx-copybutton" }, { name = "sphinx-design" }, - { name = "tox-uv" }, + { name = "tox-uv-bare" }, ] [[package]] @@ -1337,18 +1337,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/f0/f32e73acb872b67b97b51d67d7241838136f58127d763afcde7762832655/tox-4.67.13-py3-none-any.whl", hash = "sha256:4d6fa08dbe5e841f2abfee1dd4f5cdf31af30d68a3d78b85a7bf57eced5f3c6c", size = 263353, upload-time = "2026-11-06T15:33:00.306Z" }, ] -[[package]] -name = "tox-uv" -version = "1.37.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tox-uv-bare" }, - { name = "uv" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ba4c9829ef4183d0b1da35db17009b8dc88389f060007118c4d935e8070f/tox_uv-1.37.7-py3-none-any.whl", hash = "sha256:ab2a06e336d12b40a0de3b157ba52032762204c863343dcc57b6f3b77bec337b", size = 7995, upload-time = "2026-11-07T08:14:26.49Z" }, -] - [[package]] name = "tox-uv-bare" version = "1.37.7" @@ -1392,32 +1380,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] -[[package]] -name = "uv" -version = "0.13.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/82/54c00536a763dfb8a6d8d6cf7841cf234a290f4f79ee37189d933f656cb0/uv-0.13.8.tar.gz", hash = "sha256:55c0b405e75dea1d1c53efc9a8e0725f4031be0e219ec2a08b15d78ece9a616a", size = 7577463, upload-time = "2026-11-12T16:49:12.442Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/80/cf6656f96f6ee9c26227e8aa8a81997b9e8a040d4ab9763657dac6dd5442/uv-0.13.8-py3-none-linux_armv6l.whl", hash = "sha256:0ec33661a14e38dea8dfb18167d2a4a5c7fe3d476c62dd8eb6acf5f378727201", size = 18499465, upload-time = "2026-11-12T16:48:15.847Z" }, - { url = "https://files.pythonhosted.org/packages/be/7a/fd8a719c0198f8f8341847fc33b377f25ac79f7c4a56dbce5ceb086f239c/uv-0.13.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b7124ca1956ec2e476a1003ea5715a15f0266afaec37f4dc9c6972ba8183e6d1", size = 17374577, upload-time = "2026-11-12T16:48:19.906Z" }, - { url = "https://files.pythonhosted.org/packages/84/46/deb3a1eb3020575e41d1f9840c07185761d8d4c25cd6f58e813afb017476/uv-0.13.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:62b6943c7fd64fac6854f780bea6e0359e5a5bc3241f83ee357bb38276afac49", size = 14483721, upload-time = "2026-11-12T16:48:22.967Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f0/d989ac5fcebf83f1c8c5d78974f27788e8364945969eb646f5d0db35d924/uv-0.13.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0b3e8b1a4f5e405e31e2ff33de8af4744831e9f7338539671efbd8c3c94e791d", size = 17953056, upload-time = "2026-11-12T16:48:26.234Z" }, - { url = "https://files.pythonhosted.org/packages/88/14/da38bda443e911f1dddfc04588fae72b9790f9b3583a16e8b94c149dc806/uv-0.13.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:9cf0b7b12f13da6ab1c85694ab618d97a2bbd0c904cdea7f0172fb1dd768b9d5", size = 17897031, upload-time = "2026-11-12T16:48:29.323Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d6/2e3a344ead9c5a52f60a0605a3e0a6688f962d51b42e5062638785932a38/uv-0.13.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86f1319f851e645bc295bb921e6be19beae7e69216c75ae809010ca7817393aa", size = 17905040, upload-time = "2026-11-12T16:48:32.638Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3c/12db1b688f523893130d825c0de3986b1db302b6ef1aa61fd41c21aeaf84/uv-0.13.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ace7ec339798aca31be51a75fa7c1544fe84e02fadd17a9fa3324813bbaf237", size = 18813359, upload-time = "2026-11-12T16:48:35.719Z" }, - { url = "https://files.pythonhosted.org/packages/bd/1d/c03fbe2a700484e369556b1a198745f92f44734a47279f3fbce95fbdbbaa/uv-0.13.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6999618363d03a0a085a9491db319e00b461dbda205db06ef1fd55dfcbf6472b", size = 20110957, upload-time = "2026-11-12T16:48:39.285Z" }, - { url = "https://files.pythonhosted.org/packages/73/09/52cdbdc3100cb67a6aa7e0c6b2b1a6a5834b1ac06de684944a9a0f408528/uv-0.13.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13050c64530a1ccfc223af0306a8152d41c0d36ea63bc19161221ca65dfe480a", size = 19815614, upload-time = "2026-11-12T16:48:42.418Z" }, - { url = "https://files.pythonhosted.org/packages/c5/32/52ee34636aea1ab3f8baeeae1c3599461b8cddfb379862cc72ac61b8d91c/uv-0.13.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e18a57b8b352eb7c8feb317b9f6f533c8f0ad9673fd6cf34bbe3cf1a9f6a8984", size = 17389450, upload-time = "2026-11-12T16:48:45.493Z" }, - { url = "https://files.pythonhosted.org/packages/80/ce/f5eba24d8ec7fa566f9d50d1a69dbf9510137c30366b9f16dbeaf9b9dae4/uv-0.13.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5337c30bb893b4f9ce0ceb921ba2da0d13f649da644f40ad3bef84083bef0203", size = 16808714, upload-time = "2026-11-12T16:48:48.321Z" }, - { url = "https://files.pythonhosted.org/packages/37/c6/0bfa359aec27c45dbb8b99eb2e83aa954f919b1f83bb85fc21892dc317b9/uv-0.13.8-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:bfcaada3989c7716ad4b58673cd1413fad4fdc6b2e446f9553db0ef18da20850", size = 18057488, upload-time = "2026-11-12T16:48:51.419Z" }, - { url = "https://files.pythonhosted.org/packages/ab/02/caccaed9a86060eea158f2a16101258e122473c5cb8c4bd9b22b009493c3/uv-0.13.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b26fe533dc6153787e40443b7d9428ed41349127bff36a954ec82c48be01ded9", size = 18250405, upload-time = "2026-11-12T16:48:54.448Z" }, - { url = "https://files.pythonhosted.org/packages/78/cb/42aa2094f03371df6a0585bbd1b6c9c1d10861d71099d75142603d0d37e8/uv-0.13.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:63d807451e63c8d21b1b33c945af16438ac9d8ffddc59b31ce69c692135891f5", size = 18009892, upload-time = "2026-11-12T16:48:57.519Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a3/1ac9cd75985371da0d29c9c12132e6d5b5627b9d0de87c5ad97ab9ad69b0/uv-0.13.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:29451c60f5e8b07403e0a175d5229efc36b0289b34143ff4aaa499a5dd52553a", size = 19139972, upload-time = "2026-11-12T16:49:00.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3f/0219f8104f360c8cbfa8e4d9c5f9ea0b877428eeb927f2e39e273a00e34c/uv-0.13.8-py3-none-win32.whl", hash = "sha256:87614f62b09e3571593ffe01bf2e1dccb8df928a8423324dba80c6d0e33060eb", size = 16255203, upload-time = "2026-11-12T16:49:04.068Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1d/d813afac1a7cbe3eeeca8709b47bdcd5e5189d08364b8d7a9f693082f0f1/uv-0.13.8-py3-none-win_amd64.whl", hash = "sha256:cfbc13020a64cdf2f116d2936824c38edbf41e5c07f1ea291baf02e31ebf3ed0", size = 15624377, upload-time = "2026-11-12T16:49:07.006Z" }, - { url = "https://files.pythonhosted.org/packages/98/fd/9cc888863d2326e6e5c6ae2a66d4b3ff248f9cd6bb50cb8be36a941c3e26/uv-0.13.8-py3-none-win_arm64.whl", hash = "sha256:b4184029b1b7c54409b9cd4372c73a36ef9c2d1d7cf3b0aca59c8b1932f041dc", size = 15685270, upload-time = "2026-11-12T16:49:09.93Z" }, -] - [[package]] name = "uvicorn" version = "0.54.0" From e61b2d9baf37dd808f88b06d131a6311d40aeeeb Mon Sep 17 00:00:00 2001 From: Giles Knap Date: Fri, 13 Nov 2026 15:20:59 +0000 Subject: [PATCH 2/4] fix: Put the generated project's venv on PATH in tests tox-uv-bare no longer prepends the directory of the uv binary to PATH, which used to be /.venv/bin when uv was installed as a package. The nested tox runs therefore picked up pytest, pyright and sphinx-build from the outer venv (or not at all). Activate the venv explicitly. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Uybqz7UjVJfezyDTBqiHGM --- tests/test_example.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_example.py b/tests/test_example.py index ea94347b6..63934a557 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -29,12 +29,16 @@ def copy_project(project_path: Path, **kwargs): def run_pipe(cmd: str, cwd=None, venv="") -> str: + env = dict(os.environ, UV_PROJECT_ENVIRONMENT="", VIRTUAL_ENV=str(venv)) + if venv: + # Activate the venv so tox commands find its tools, not the outer venv's + env["PATH"] = f"{venv}/bin{os.pathsep}{env['PATH']}" sp = subprocess.run( shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, - env=dict(os.environ, UV_PROJECT_ENVIRONMENT="", VIRTUAL_ENV=venv), + env=env, ) output = sp.stdout.decode() assert sp.returncode == 0, output From c2d1d505f2a6618961b4dc45aae7df1164b2214f Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 13 Nov 2026 16:05:46 +0000 Subject: [PATCH 3/4] fix: Ensure all executables are in allowlist --- pyproject.toml | 2 +- template/pyproject.toml.jinja | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 62b21a2cf..8fdd854ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,11 +44,11 @@ env_list = ["pre-commit", "tests", "docs"] # Pass all environment variables to the test environments passenv = ["*"] allowlist_externals = [ + "pyright", "pytest", "pre-commit", "sphinx-build", "sphinx-autobuild", - "pyright", ] [tool.tox.env.pre-commit] diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja index 68f3c523a..ef2da4306 100644 --- a/template/pyproject.toml.jinja +++ b/template/pyproject.toml.jinja @@ -91,11 +91,13 @@ env_list = ["pre-commit", "type-checking", "tests"{% if sphinx %}, "docs"{% endi # Pass all environment variables to the test environments passenv = ["*"] allowlist_externals = [ - "pytest", + {% if type_checker=="mypy" %}"mypy", + {% endif %}{% if type_checker=="pyright" %}"pyright", + {% endif %}"pytest", "pre-commit", - "sphinx-build", + {% if sphinx %}"sphinx-build", "sphinx-autobuild", - "pyright", + {% endif %} ] [tool.tox.env.pre-commit] From dd0ae17b0ba9adc02d9c2d8cbdf5dd5aa51443e4 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Fri, 13 Nov 2026 16:14:58 +0000 Subject: [PATCH 4/4] Remove unnecessary string cast and tweak comment --- tests/test_example.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_example.py b/tests/test_example.py index 63934a557..98b487a00 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -29,9 +29,10 @@ def copy_project(project_path: Path, **kwargs): def run_pipe(cmd: str, cwd=None, venv="") -> str: - env = dict(os.environ, UV_PROJECT_ENVIRONMENT="", VIRTUAL_ENV=str(venv)) + env = dict(os.environ, UV_PROJECT_ENVIRONMENT="", VIRTUAL_ENV=venv) if venv: - # Activate the venv so tox commands find its tools, not the outer venv's + # Activate thevenv. This means the newly created Python project + # is in the environment, along with its associated tools. env["PATH"] = f"{venv}/bin{os.pathsep}{env['PATH']}" sp = subprocess.run( shlex.split(cmd),