diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..eee3644 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + groups: + dependencies: + patterns: + - "*" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a90455e..dd7f502 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,3 +41,44 @@ jobs: - name: Run pre-push checks run: uv run --no-sync pre-commit run --hook-stage pre-push --all-files + + tests-matrix: + name: tests (py${{ matrix.python }}, ${{ matrix.resolution }}) + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + # Exercise the supported range at both ends: minimum Python with the + # oldest direct dependencies, and maximum Python with the newest. This + # leg is deliberately non-frozen (it re-resolves per matrix entry), so + # it cannot reuse the --frozen pytest-coverage hook. Bump "3.14" when a + # newer interpreter enters the supported range. + include: + - python: "3.12" + resolution: "lowest-direct" + - python: "3.14" + resolution: "highest" + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v8.2.0 + with: + enable-cache: true + + - name: Install Python + run: uv python install ${{ matrix.python }} + + - name: Sync environment (${{ matrix.resolution }}) + run: uv sync --python ${{ matrix.python }} --resolution ${{ matrix.resolution }} + + - name: Run tests with branch coverage + # The 100% threshold comes from [tool.coverage.report] in pyproject.toml, + # the same source the pytest-coverage hook uses -- not a duplicated flag. + run: >- + uv run --no-sync python -m pytest + --cov=fgcz_reference_project + --cov-branch + --cov-report=term-missing diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c063200..b4fe00e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,24 @@ default_install_hook_types: [pre-commit, pre-push] repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.11 + - repo: local hooks: - - id: ruff - args: [--fix] + - id: ruff-check + name: ruff (lint) + entry: uv run --frozen ruff check --force-exclude --fix + language: system + types_or: [python, pyi] + require_serial: true stages: [pre-commit] + - id: ruff-format + name: ruff (format) + entry: uv run --frozen ruff format --force-exclude + language: system + types_or: [python, pyi] + require_serial: true stages: [pre-commit] - - repo: local - hooks: - id: pyright name: pyright (strict) entry: uv run --frozen --group dev pyright diff --git a/CHANGELOG.md b/CHANGELOG.md index 96ae169..0b5add9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,22 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Added + +- CI matrix that runs the tests across the supported Python range and + dependency-resolution bounds (`lowest-direct` on the minimum interpreter, + `highest` on the maximum). +- Dependabot configuration for the `uv` and GitHub Actions ecosystems. +- Documented how to switch the type-check gate from Pyright to basedpyright. + ### Changed - Documented the reference project's intent and complete quality-gate stack. - Enabled blocking Ruff complexity and function-design checks. +- Switched the build backend from hatchling to `uv_build`. +- Run Ruff as a local `uv run` hook so its version lives only in the lockfile; + upgraded Ruff to 0.16.0 and expanded the lint rule set (security, docstrings, + annotations, pytest, pathlib, and more). ## [0.1.0] - 2026-07-23 diff --git a/README.md b/README.md index 9d7cd4f..258ae22 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ structure and quality gates. | Area | Included | |---|---| -| Packaging | `src/` layout, `pyproject.toml`, hatchling, uv lockfile, typed-package marker, console entry point | +| Packaging | `src/` layout, `pyproject.toml`, uv build backend, uv lockfile, typed-package marker, console entry point | | Example | One typed library function and a small `argparse` CLI | | Tests | Unit and CLI error-path tests with 100% line and branch coverage | | Documentation | MkDocs Material site built with `--strict` and deployable to GitHub Pages | diff --git a/docs/development.md b/docs/development.md index b812176..fefd45f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -28,3 +28,28 @@ uv run pre-commit run dependency-audit --hook-stage manual --all-files GitHub Actions runs the same audit weekly. Add project-specific checks as separate hooks, then invoke those hooks from CI so local and remote behavior remain aligned. + +## Type checking + +Strict [Pyright](https://github.com/microsoft/pyright) runs as a blocking +pre-commit hook. To use [basedpyright](https://docs.basedpyright.com/) instead +— a stricter, pure-Python fork that installs through uv without a separate +Node runtime — make three changes: + +- replace `pyright` with `basedpyright` in the `dev` group of `pyproject.toml`; +- change the `pyright` hook `entry` in `.pre-commit-config.yaml` to + `uv run --frozen basedpyright`; +- configure it under `[tool.basedpyright]` (it reads the existing + `[tool.pyright]` keys). + +Pyright is the default because Microsoft maintains it; basedpyright adds +strictness and richer reporting at the cost of a single-maintainer fork. + +## Python and dependency ranges + +The frozen pre-commit and pre-push stages run on a single pinned interpreter for +a fast local loop. CI additionally runs the test suite across the supported +range: the minimum Python with the oldest direct dependencies +(`--resolution lowest-direct`) and the maximum Python with the newest +(`--resolution highest`). This catches both under-specified lower bounds and +breakage on new interpreters. diff --git a/pyproject.toml b/pyproject.toml index 323bf77..e1d2e0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,8 @@ dependencies = [] fgcz-reference = "fgcz_reference_project.cli:main" [build-system] -requires = ["hatchling>=1.28,<2"] -build-backend = "hatchling.build" +requires = ["uv_build>=0.11.7,<0.12"] +build-backend = "uv_build" [dependency-groups] dev = [ @@ -26,14 +26,14 @@ dev = [ "pyright>=1.1.408,<2", "pytest>=9,<10", "pytest-cov>=7,<8", - "ruff==0.15.11", + "ruff==0.16.0", ] docs = [ "mkdocs-material>=9.7,<10", ] -[tool.hatch.build.targets.wheel] -packages = ["src/fgcz_reference_project"] +[tool.uv.build-backend] +module-name = "fgcz_reference_project" [tool.pytest.ini_options] addopts = "-ra --strict-config --strict-markers" @@ -45,20 +45,31 @@ target-version = "py312" [tool.ruff.lint] select = [ - "A", - "B", - "C4", - "C901", - "E", - "F", - "I", - "PLR0911", - "PLR0912", - "PLR0913", - "PLR0915", - "UP", - "W", + "F", # pyflakes + "E", # pycodestyle errors + "W", # pycodestyle warnings + "I", # isort + "N", # pep8-naming + "UP", # pyupgrade + "B", # flake8-bugbear + "A", # flake8-builtins + "C4", # flake8-comprehensions + "RUF", # ruff-specific + "C90", # mccabe complexity + "PL", # pylint (convention/error/warning/refactor) + "SIM", # flake8-simplify + "RET", # flake8-return + "PTH", # flake8-use-pathlib + "PT", # flake8-pytest-style + "TID", # flake8-tidy-imports + "TC", # flake8-type-checking + "ARG", # flake8-unused-arguments + "EM", # flake8-errmsg + "D", # pydocstyle + "ANN", # flake8-annotations + "S", # flake8-bandit (security) ] +ignore = ["ANN401"] [tool.ruff.lint.mccabe] max-complexity = 10 @@ -66,6 +77,13 @@ max-complexity = 10 [tool.ruff.lint.pylint] max-args = 7 +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S101", "D", "ANN", "PLR2004", "ARG"] +"scripts/**" = ["S603", "S607", "D", "PLR2004", "EM"] + [tool.ruff.format] quote-style = "double" diff --git a/src/fgcz_reference_project/__init__.py b/src/fgcz_reference_project/__init__.py index 8b13789..7ab5f09 100644 --- a/src/fgcz_reference_project/__init__.py +++ b/src/fgcz_reference_project/__init__.py @@ -1 +1,2 @@ - +# Intentionally empty package marker; import from concrete modules (see AGENTS.md). +# ruff: noqa: D104 diff --git a/src/fgcz_reference_project/naming.py b/src/fgcz_reference_project/naming.py index 69f9b1e..6b5c775 100644 --- a/src/fgcz_reference_project/naming.py +++ b/src/fgcz_reference_project/naming.py @@ -21,5 +21,6 @@ def project_slug(name: str) -> str: """ slug = _NON_ALPHANUMERIC.sub("-", name.strip().lower()).strip("-") if not slug: - raise ValueError("Project name must contain at least one letter or digit.") + message = "Project name must contain at least one letter or digit." + raise ValueError(message) return slug diff --git a/uv.lock b/uv.lock index ae8fa2d..b7218e5 100644 --- a/uv.lock +++ b/uv.lock @@ -354,7 +354,7 @@ dev = [ { name = "pyright", specifier = ">=1.1.408,<2" }, { name = "pytest", specifier = ">=9,<10" }, { name = "pytest-cov", specifier = ">=7,<8" }, - { name = "ruff", specifier = "==0.15.11" }, + { name = "ruff", specifier = "==0.16.0" }, ] docs = [{ name = "mkdocs-material", specifier = ">=9.7,<10" }] @@ -998,27 +998,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, - { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, - { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, - { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]]