Modernize the toolchain, drop dead Python versions and close all 16 Dependabot alerts - #5
Merged
Merged
Conversation
Raise the floor to Python 3.10 (the oldest version still receiving upstream support) and migrate the metadata to PEP 621 `[project]`, which Poetry 2.x recommends and which removes the deprecation warnings that `poetry check` now emits for the whole `[tool.poetry]` metadata block. Replace flake8 + isort + yapf with Ruff (lint, import sorting and formatting in one tool, configured in pyproject) and drop five more dev dependencies: - `coveralls`: pulled in requests -> urllib3/idna/certifi, the source of 14 of the 16 open Dependabot alerts. Coverage is now uploaded by the Coveralls JS action instead, which installs no Python packages, so the chain cannot come back on the next urllib3 CVE. - `taskipy`: CI and contributors call ruff/mypy/pytest directly. - `toml`: unused, nothing imports it. - `pytest-sugar`: cosmetic only. - `flake8`/`isort`/`yapf`: superseded by Ruff. The remaining two alerts close on their own: `pytest` moves to 9.1.1 (>= 9.0.3, which fixes CVE-2025-71176) and `zipp` disappears with the Python floor, since it only entered through `importlib-metadata` behind a `python_version < "3.8"` marker. The lock goes from 35 packages to 18, with zero vulnerable packages left. None of them were ever runtime dependencies -- the published package only ever required colorama -- so no user of the library was exposed. `.tool-versions` pins the interpreter and Poetry versions used to produce the lock, matching what CI installs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mechanical pass, no behaviour change except one fix noted below. `ruff format` is configured with `quote-style = "single"` and `line-length = 79` to match the yapf "facebook" style the project used, which keeps this diff at ~70 lines instead of the ~334 that the default double-quote style would have produced. `ruff check --fix` applied the pyupgrade rules now that the floor is 3.10: `List[x]` -> `list[x]`, `Optional[x]` -> `x | None`, and `Callable`/`Iterator` moved from `typing` to `collections.abc`. One non-mechanical change, required to make the lint pass: Ruff's RUF012 flagged `Stopwatch.laps: list[Lap] = []` as a mutable class attribute. It was shared by every instance, and only the `self.laps = []` inside `reset()` kept normal usage from corrupting. Any subclass overriding `__init__` without calling `restart()` -- or `Stopwatch.__new__` without `__init__` -- shared one list across instances and leaked laps between them. All the class-body defaults now initialize in `__init__`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test matrix was not testing what it claimed. `tests.yml` used `steps.setup-python.outputs.python-version` in the venv cache key, but no `actions/setup-python` step had `id: setup-python`, so the expression resolved to an empty string (visible as the doubled `--` in the key). `matrix.python-version` was not in the key either, so all five legs shared one key, four of them restored a `.venv` built by a different interpreter, and `if: cache-hit != 'true'` skipped the `poetry install` that would have corrected it. The 3.7-3.11 badges were green while one version was actually being tested. The steps now carry `id:` and the key includes the Python version. Other changes: - Run on `pull_request`. Tests only ran on push to main, so no pull request was ever tested before merge. - Drop the `paths:` filter. With these workflows as required checks, a path filter leaves the check permanently pending on pull requests that touch no `.py` file. - Add `permissions: contents: read` to both workflows. The repository default is read-write, and neither workflow needs to write. - Add `persist-credentials: false` so the token is not written into `.git/config` where any code in the workspace could read it. - Pin every action to a full commit SHA with the version in a comment. The previous tags were also on deprecated Node16 runtimes. - Replace the `formatting`/`linting` workflows, which were 67 and 75 near-identical lines, with one `quality` workflow. It no longer runs `git-auto-commit-action` on `pull_request`: auto-committing meant running `poetry run task format`, and taskipy reads the command verbatim from the pull request's own `pyproject.toml`, so a pull request could have replaced it with anything. It now fails with a diff instead of pushing a commit, and calls ruff/mypy directly. - Matrix is 3.10-3.14, plus one windows-latest leg because colorama is a dependency specifically for Windows consoles. - Drop the `upload` job, which re-ran the whole suite in 58 duplicated lines just to upload coverage. The Coveralls action now consumes the `coverage.xml` that the test run already produced. - Add `concurrency` and `timeout-minutes`; jobs previously had the 6h default and redundant runs were never cancelled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README claimed Python 3.7+, and both README and CONTRIBUTING documented `poetry run task ...` commands that no longer exist now that taskipy is gone. Also ignore the local agent tooling directories. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The new windows-latest leg failed at `poetry install` with "The term 'poetry' is not recognized": snok/install-poetry does not leave poetry on PATH there. pipx ships with every GitHub-hosted runner and behaves identically on Linux and Windows, so one code path covers both and there is one less third-party action to trust and pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bottom of a 4-PR stack renewing the library. This one is the build and CI
layer; nothing here changes runtime behaviour except one fix the linter
forced (below).
Closes all 16 open Dependabot alerts
Every alert pointed at
poetry.lock, and none of the affected packages wasever a runtime dependency — the published package only ever required
colorama, so no user of the library was exposed. They were all dev-only,reached through one chain:
So the fix is removal, not upgrading: 14 of the 16 close by dropping
coveralls. Upgrading it would have worked today, butcoveralls 4.xstill depends on
requests, so the alerts would return on the nexturllib3 CVE. Coverage is now uploaded by the Coveralls JS action, which
installs no Python packages, and the README badge keeps working. The
remaining two close on their own:
zippdisappears with the Python floor,and
pytestmoves to 9.1.1 (>= 9.0.3, which fixes CVE-2025-71176).The lock goes from 35 packages to 18, with zero vulnerable packages left.
Python 3.10 - 3.14
3.7 was EOL in June 2023 and 3.8/3.9 are gone too, while
pyproject.tomlstill promised
^3.7. The floor is now 3.10, the oldest version stillreceiving upstream support. This is not optional: the whole pytest 9.x
series, which carries the CVE fix, requires >= 3.10 and there is no
backport.
Verified for real rather than just declared in the matrix: the lock
installs and the full suite, mypy and ruff all pass on both 3.10.20 and
3.14.6 locally.
Tooling
flake8+isort+yapf+taskipy->ruff+ direct commands. Ruffdoes lint, import sorting and formatting in one tool with one config block,
which removes three tools, three config sections and the
.flake8file.quote-style = "single"andline-length = 79are set to match the yapf"facebook" style the project used, which keeps the reformat at ~70 lines
instead of the ~334 the default double-quote style would have produced.
Also dropped:
toml(nothing imports it) andpytest-sugar(cosmetic).Dropping taskipy has a security side effect worth naming. CI used to run
poetry run task formaton pull requests, and taskipy reads that commandverbatim from the pull request's own
pyproject.toml— a pull requestcould replace it with anything. CI now calls
ruff/mypy/pytestdirectly.
Metadata moves to PEP 621
[project], which Poetry 2.x recommends;poetry checkwas emitting deprecation warnings for the entire[tool.poetry]metadata block and fordev-dependencies.poetry buildwas verified to still produce correct metadata:
Requires-Python >=3.10,classifiers generated for 3.10-3.14, and
stopwatch/py.typedstill shippedso PEP 561 typing keeps working.
The test matrix was not testing what it claimed
tests.ymlusedsteps.setup-python.outputs.python-versionin the venvcache key, but no
actions/setup-pythonstep hadid: setup-python, so theexpression resolved to an empty string — visible as the doubled
--in thekey.
matrix.python-versionwas not in the key either. All five legs sharedone key, four restored a
.venvbuilt by a different interpreter, andif: cache-hit != 'true'skipped thepoetry installthat would havecorrected it. The 3.7-3.11 badges were green while one version was
actually being tested.
Other CI changes:
pull_request. Tests only ran on push tomain, so no pullrequest was ever tested before merge.
permissions: contents: readon both workflows. The repository default isread-write and neither workflow needs to write.
persist-credentials: false, so the token is not left in.git/configwhere any code in the workspace could read it.
The previous tags were also on deprecated Node16 runtimes. Dependabot
updates the SHA and the comment together.
formatting.yml+linting.yml(67 and 75 near-identical lines) collapseinto one
quality.yml. It no longer runsgit-auto-commit-actiononpull_request; it fails with a diff instead of pushing a commit.windows-latestleg, becausecoloramais a dependency specificallyfor Windows consoles.
uploadjob is gone. It re-ran the entire suite in 58 duplicatedlines just to upload coverage.
concurrencyandtimeout-minutesadded; jobs had the 6h default andredundant runs were never cancelled.
One behaviour fix, forced by the linter
Ruff's RUF012 flagged
Stopwatch.laps: list[Lap] = []— a mutable classattribute shared by every instance. Only the
self.laps = []insidereset()kept normal usage from corrupting. Any subclass overriding__init__without callingrestart(), orStopwatch.__new__without__init__, shared one list across instances and leaked laps between them.All class-body defaults now initialize in
__init__, with a regressiontest in PR #7.
Note
.tool-versionspins the interpreter and Poetry versions used to producethe lock, matching what CI installs. Remove it if you don't use asdf.