Skip to content

Modernize the toolchain, drop dead Python versions and close all 16 Dependabot alerts - #5

Merged
devRMA merged 5 commits into
mainfrom
chore/modern-toolchain
Aug 2, 2026
Merged

Modernize the toolchain, drop dead Python versions and close all 16 Dependabot alerts#5
devRMA merged 5 commits into
mainfrom
chore/modern-toolchain

Conversation

@devRMA

@devRMAdevRMA commented Aug 2, 2026

Copy link
Copy Markdown
Owner

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 was
ever 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:

coveralls -> requests -> urllib3 (8 alerts)
-> idna (2 alerts)
-> certifi (1 alert)
-> requests (3 alerts)
zipp <- importlib-metadata, only via `python_version < "3.8"` markers (1)
pytest (1)

So the fix is removal, not upgrading: 14 of the 16 close by dropping
coveralls
. Upgrading it would have worked today, but coveralls 4.x
still depends on requests, so the alerts would return on the next
urllib3 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: zipp disappears with the Python floor,
and pytest moves 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.toml
still promised ^3.7. The floor is now 3.10, the oldest version still
receiving 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. Ruff
does lint, import sorting and formatting in one tool with one config block,
which removes three tools, three config sections and the .flake8 file.
quote-style = "single" and line-length = 79 are 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) and pytest-sugar (cosmetic).

Dropping taskipy has a security side effect worth naming. CI used to run
poetry run task format on pull requests, and taskipy reads that command
verbatim from the pull request's own pyproject.toml — a pull request
could replace it with anything. CI now calls ruff/mypy/pytest
directly.

Metadata moves to PEP 621 [project], which Poetry 2.x recommends;
poetry check was emitting deprecation warnings for the entire
[tool.poetry] metadata block and for dev-dependencies. poetry build
was verified to still produce correct metadata: Requires-Python >=3.10,
classifiers generated for 3.10-3.14, and stopwatch/py.typed still shipped
so PEP 561 typing keeps working.

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. All five legs shared
one key, four 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.

Other CI changes:

  • Run on pull_request. Tests only ran on push to main, so no pull
    request was ever tested before merge.
  • permissions: contents: read on both workflows. The repository default is
    read-write and neither workflow needs to write.
  • persist-credentials: false, so the token is not left in .git/config
    where any code in the workspace could read it.
  • Every action pinned to a full commit SHA with the version in a comment.
    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) collapse
    into one quality.yml. It no longer runs git-auto-commit-action on
    pull_request; it fails with a diff instead of pushing a commit.
  • One windows-latest leg, because colorama is a dependency specifically
    for Windows consoles.
  • The upload job is gone. It re-ran the entire suite in 58 duplicated
    lines just to upload coverage.
  • concurrency and timeout-minutes added; jobs had the 6h default and
    redundant runs were never cancelled.

One behaviour fix, forced by the linter

Ruff's RUF012 flagged Stopwatch.laps: list[Lap] = [] — a mutable class
attribute shared by every instance. 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 class-body defaults now initialize in __init__, with a regression
test in PR #7.

Note

.tool-versions pins the interpreter and Poetry versions used to produce
the lock, matching what CI installs. Remove it if you don't use asdf.

devRMAand others added 4 commits August 2, 2026 16:35
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>
@vercel

vercelBot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
stopwatch2-omipErrorErrorAug 2, 2026 7:53pm

@devRMAdevRMA changed the title chore/modern toolchainModernize the toolchain, drop dead Python versions and close all 16 Dependabot alertsAug 2, 2026
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>
@devRMAdevRMA self-assigned this Aug 2, 2026
@devRMA
devRMA merged commit 27b53b8 into mainAug 2, 2026
8 of 9 checks passed
@devRMA
devRMA deleted the chore/modern-toolchain branch August 2, 2026 21:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@devRMA