From bca4c366638a0252d1bca52e77563ea8f0e48e4c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 12 Oct 2026 11:36:33 +0100 Subject: [PATCH 1/2] Remove uv version requirements in third-party tests --- .github/renovate.json5 | 6 +++ .github/workflows/third_party.yml | 20 ++++++++++ .pre-commit-config.yaml | 8 ++++ scripts/remove_uv_version_requirement.py | 37 +++++++++++++++++++ scripts/remove_uv_version_requirement.py.lock | 15 ++++++++ 5 files changed, 86 insertions(+) create mode 100644 scripts/remove_uv_version_requirement.py create mode 100644 scripts/remove_uv_version_requirement.py.lock diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 233f26e0..04049434 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -64,6 +64,12 @@ { "matchManagers": ["pep621"], "groupName": "Build dependencies" + }, + { + "description": "Update the astral-sh/uv and uv-pre-commit pins in the same PR to keep CI and pre-commit on the same uv version.", + "matchManagers": ["custom.regex", "pre-commit"], + "matchPackageNames": ["astral-sh/uv", "astral-sh/uv-pre-commit"], + "groupName": "uv" } ], "customManagers": [ diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index 77a874a4..9717e8ec 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -12,6 +12,8 @@ on: pull_request: paths: - ".github/workflows/third_party.yml" + - "scripts/remove_uv_version_requirement.py" + - "scripts/remove_uv_version_requirement.py.lock" workflow_dispatch: permissions: @@ -65,6 +67,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py pydantic - name: Run tests with typing_extensions main branch working-directory: pydantic run: | @@ -101,6 +105,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py typing_inspect - name: Run tests with typing_extensions main branch working-directory: typing_inspect run: | @@ -139,6 +145,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py pycroscope - name: Run tests with typing_extensions main branch working-directory: pycroscope run: | @@ -177,6 +185,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py typeguard - name: Run tests with typing_extensions main branch env: PYTHON_COLORS: 0 # A test fails if tracebacks are colorized @@ -218,6 +228,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py typed-argument-parser - name: Configure git for typed-argument-parser tests # typed-argument parser does this in their CI, # and the tests fail unless we do this @@ -264,6 +276,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py mypy - name: Run tests with typing_extensions main branch working-directory: mypy run: | @@ -303,6 +317,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py cattrs - name: Run tests with typing_extensions main branch working-directory: cattrs run: | @@ -344,6 +360,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py sqlalchemy - name: Run sqlalchemy tests with typing_extensions main branch working-directory: sqlalchemy run: | @@ -379,6 +397,8 @@ jobs: with: path: typing-extensions-latest persist-credentials: false + - name: Remove uv version requirement + run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py litestar - name: Run litestar tests with typing_extensions main branch working-directory: litestar run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 22b01713..e403c4c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,14 @@ repos: rev: c59bba8fb259db0fec2bbb77ad8ba51ea7341b56 # frozen: v0.15.20 hooks: - id: ruff + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0f34924c4110aca2294a4e4204aac7b3e31803fb # frozen: 0.12.7 + hooks: + - id: uv-lock + description: Check only whether scripts/remove_uv_version_requirement.py.lock is up to date. + entry: uv lock --script scripts/remove_uv_version_requirement.py + files: ^(\.pre-commit-config\.yaml|pyproject\.toml|scripts/remove_uv_version_requirement\.py(\.lock)?)$ + args: [--default-index=https://pypi.org/simple, --check, --refresh] - repo: https://github.com/pre-commit/pre-commit-hooks rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: diff --git a/scripts/remove_uv_version_requirement.py b/scripts/remove_uv_version_requirement.py new file mode 100644 index 00000000..5aed7a71 --- /dev/null +++ b/scripts/remove_uv_version_requirement.py @@ -0,0 +1,37 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.10" +# dependencies = ["tomlkit>=0.13.2"] +# /// + +"""Remove uv version requirements from a third-party checkout. + +CI pins uv, which may not satisfy a checkout's uv version requirement. +Preserve the checkout's other uv settings. +""" + +import argparse +from pathlib import Path + +import tomlkit + +parser = argparse.ArgumentParser() +parser.add_argument("repo_dir", type=Path) +repo_dir = parser.parse_args().repo_dir +if not repo_dir.is_dir(): + parser.error(f"{repo_dir} is not a directory") + +for filename in ("pyproject.toml", "uv.toml"): + path = repo_dir / filename + try: + contents = path.read_text(encoding="utf-8") + except FileNotFoundError: + continue + + config = tomlkit.parse(contents) + uv_config = ( + config.get("tool", {}).get("uv", {}) if filename == "pyproject.toml" else config + ) + if "required-version" in uv_config: + del uv_config["required-version"] + path.write_text(tomlkit.dumps(config), encoding="utf-8") diff --git a/scripts/remove_uv_version_requirement.py.lock b/scripts/remove_uv_version_requirement.py.lock new file mode 100644 index 00000000..296bd0ae --- /dev/null +++ b/scripts/remove_uv_version_requirement.py.lock @@ -0,0 +1,15 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[manifest] +requirements = [{ name = "tomlkit", specifier = ">=0.13.2" }] + +[[package]] +name = "tomlkit" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/96/e07752635b98536177fa1f37671c8f3cdde2e724c6bcf6034b2cfb571565/tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97", size = 180129, upload-time = "2026-07-17T01:48:04.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, +] From 8040f33af128449566cc2c504f9362bab27c5225 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 12 Oct 2026 12:03:16 +0100 Subject: [PATCH 2/2] Remove the uv configuration helper's dependency --- .github/renovate.json5 | 6 -- .github/workflows/third_party.yml | 19 +++--- .pre-commit-config.yaml | 8 --- scripts/remove_uv_version_requirement.py | 63 ++++++++++++++----- scripts/remove_uv_version_requirement.py.lock | 15 ----- 5 files changed, 56 insertions(+), 55 deletions(-) delete mode 100644 scripts/remove_uv_version_requirement.py.lock diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 04049434..233f26e0 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -64,12 +64,6 @@ { "matchManagers": ["pep621"], "groupName": "Build dependencies" - }, - { - "description": "Update the astral-sh/uv and uv-pre-commit pins in the same PR to keep CI and pre-commit on the same uv version.", - "matchManagers": ["custom.regex", "pre-commit"], - "matchPackageNames": ["astral-sh/uv", "astral-sh/uv-pre-commit"], - "groupName": "uv" } ], "customManagers": [ diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index 9717e8ec..ccd08101 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -13,7 +13,6 @@ on: paths: - ".github/workflows/third_party.yml" - "scripts/remove_uv_version_requirement.py" - - "scripts/remove_uv_version_requirement.py.lock" workflow_dispatch: permissions: @@ -68,7 +67,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py pydantic + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py pydantic - name: Run tests with typing_extensions main branch working-directory: pydantic run: | @@ -106,7 +105,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py typing_inspect + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py typing_inspect - name: Run tests with typing_extensions main branch working-directory: typing_inspect run: | @@ -146,7 +145,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py pycroscope + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py pycroscope - name: Run tests with typing_extensions main branch working-directory: pycroscope run: | @@ -186,7 +185,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py typeguard + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py typeguard - name: Run tests with typing_extensions main branch env: PYTHON_COLORS: 0 # A test fails if tracebacks are colorized @@ -229,7 +228,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py typed-argument-parser + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py typed-argument-parser - name: Configure git for typed-argument-parser tests # typed-argument parser does this in their CI, # and the tests fail unless we do this @@ -277,7 +276,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py mypy + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py mypy - name: Run tests with typing_extensions main branch working-directory: mypy run: | @@ -318,7 +317,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py cattrs + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py cattrs - name: Run tests with typing_extensions main branch working-directory: cattrs run: | @@ -361,7 +360,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py sqlalchemy + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py sqlalchemy - name: Run sqlalchemy tests with typing_extensions main branch working-directory: sqlalchemy run: | @@ -398,7 +397,7 @@ jobs: path: typing-extensions-latest persist-credentials: false - name: Remove uv version requirement - run: uv run --locked typing-extensions-latest/scripts/remove_uv_version_requirement.py litestar + run: uv run --no-project --python '>=3.11' typing-extensions-latest/scripts/remove_uv_version_requirement.py litestar - name: Run litestar tests with typing_extensions main branch working-directory: litestar run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e403c4c5..22b01713 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,14 +3,6 @@ repos: rev: c59bba8fb259db0fec2bbb77ad8ba51ea7341b56 # frozen: v0.15.20 hooks: - id: ruff - - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0f34924c4110aca2294a4e4204aac7b3e31803fb # frozen: 0.12.7 - hooks: - - id: uv-lock - description: Check only whether scripts/remove_uv_version_requirement.py.lock is up to date. - entry: uv lock --script scripts/remove_uv_version_requirement.py - files: ^(\.pre-commit-config\.yaml|pyproject\.toml|scripts/remove_uv_version_requirement\.py(\.lock)?)$ - args: [--default-index=https://pypi.org/simple, --check, --refresh] - repo: https://github.com/pre-commit/pre-commit-hooks rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: diff --git a/scripts/remove_uv_version_requirement.py b/scripts/remove_uv_version_requirement.py index 5aed7a71..3df84369 100644 --- a/scripts/remove_uv_version_requirement.py +++ b/scripts/remove_uv_version_requirement.py @@ -1,37 +1,68 @@ -#!/usr/bin/env -S uv run --script -# /// script -# requires-python = ">=3.10" -# dependencies = ["tomlkit>=0.13.2"] -# /// - -"""Remove uv version requirements from a third-party checkout. +"""Remove uv's required-version from a third-party checkout. CI pins uv, which may not satisfy a checkout's uv version requirement. -Preserve the checkout's other uv settings. +Preserve the checkout's other uv settings in pyproject.toml and uv.toml. +Fail if a requirement cannot be removed safely as a single-line assignment. + +Using tomlkit would make this simpler and support more TOML layouts, but we +would need to declare the dependency in a PEP 723 script. We would then +want a uv-pre-commit hook to check in CI that the script's lockfile is up to +date, and Renovate configuration to update the hook alongside CI's uv pin +so the hook uses the same uv version as CI. We therefore keep this script +stdlib-only for now, to avoid that spiralling complexity. This can be +reconsidered if we ever need a more general solution in the future. """ import argparse +import tomllib from pathlib import Path -import tomlkit - parser = argparse.ArgumentParser() parser.add_argument("repo_dir", type=Path) repo_dir = parser.parse_args().repo_dir if not repo_dir.is_dir(): parser.error(f"{repo_dir} is not a directory") -for filename in ("pyproject.toml", "uv.toml"): - path = repo_dir / filename +# Collect each updated file's path and contents so neither file is changed if +# a requirement in either file cannot be removed safely. +config_updates: dict[Path, str] = {} + +for config_filename in ("pyproject.toml", "uv.toml"): + config_path = repo_dir / config_filename try: - contents = path.read_text(encoding="utf-8") + raw_config = config_path.read_bytes().decode("utf-8") except FileNotFoundError: continue - config = tomlkit.parse(contents) + parsed_config = tomllib.loads(raw_config) uv_config = ( - config.get("tool", {}).get("uv", {}) if filename == "pyproject.toml" else config + parsed_config.get("tool", {}).get("uv", {}) + if config_filename == "pyproject.toml" + else parsed_config ) if "required-version" in uv_config: del uv_config["required-version"] - path.write_text(tomlkit.dumps(config), encoding="utf-8") + raw_config_lines = raw_config.splitlines(keepends=True) + for index, config_line in enumerate(raw_config_lines): + if "=" not in config_line: + continue + try: + key = tomllib.loads(f"{config_line.partition('=')[0]} = true") + if key != {"required-version": True}: + continue + updated_config = "".join( + raw_config_lines[:index] + raw_config_lines[index + 1 :] + ) + if tomllib.loads(updated_config) == parsed_config: + config_updates[config_path] = updated_config + break + except tomllib.TOMLDecodeError: + continue + else: + raise SystemExit( + f"cannot safely remove required-version from {config_path}; " + "expected a single-line required-version assignment" + ) + +for config_path, updated_raw_config in config_updates.items(): + config_path.write_bytes(updated_raw_config.encode("utf-8")) diff --git a/scripts/remove_uv_version_requirement.py.lock b/scripts/remove_uv_version_requirement.py.lock deleted file mode 100644 index 296bd0ae..00000000 --- a/scripts/remove_uv_version_requirement.py.lock +++ /dev/null @@ -1,15 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.10" - -[manifest] -requirements = [{ name = "tomlkit", specifier = ">=0.13.2" }] - -[[package]] -name = "tomlkit" -version = "0.15.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/96/e07752635b98536177fa1f37671c8f3cdde2e724c6bcf6034b2cfb571565/tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97", size = 180129, upload-time = "2026-07-17T01:48:04.562Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, -]