✨ Add optional command-line interface + MOAR modernization #1769
✨ Add optional command-line interface + MOAR modernization #1769
Conversation
Typer caches TERMINAL_WIDTH into typer.rich_utils.MAX_WIDTH at module import time, so setting the env var in a fixture is too late. Locally it fell through to Rich's auto-detection which picked up COLUMNS from the interactive shell, but on CI the fallback path produced an 80-column render instead of 100, breaking the help-output snapshots. Patch the module attribute directly so the rich help renderer always uses a 100-column Console regardless of the surrounding environment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an optional Typer-based CLI (twentemilieu) for inspecting Twente Milieu pickup schedules, plus supporting fixtures/snapshots and test-suite cleanup.
Changes:
- Introduce
twentemilieuconsole script withupcomingandnextcommands (Rich output +--json, env var support, friendly error handling). - Add CLI test suite with Typer
CliRunner+ syrupy snapshots, and refactor existing tests to use shared fixtures. - Update packaging/dev tooling (optional dependency, coverage/pre-commit tweaks) and document CLI usage in the README.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/twentemilieu/cli/__init__.py |
Implements the CLI commands, formatting, and error handlers. |
src/twentemilieu/cli/async_typer.py |
Adds an async-capable Typer wrapper used by the CLI. |
src/twentemilieu/cli/ruff.toml |
CLI-specific Ruff configuration overrides. |
pyproject.toml |
Adds cli extra and registers the twentemilieu console script. |
poetry.lock |
Locks Typer + transitive deps under the cli extra marker. |
README.md |
Documents CLI installation and usage examples. |
tests/cli/test_cli.py |
New CLI tests using mocked TwenteMilieu and snapshot assertions. |
tests/cli/__snapshots__/test_cli.ambr |
Snapshots for CLI output/help text. |
tests/test_twentemilieu.py |
Test refactor: shared twente fixture, parametrized error tests, snapshot for update(). |
tests/__snapshots__/test_twentemilieu.ambr |
Snapshot for test_update. |
tests/conftest.py |
Adds fixture loading helpers and shared pickup/calendar fixtures. |
tests/fixtures/*.json |
Adds JSON fixtures for calendar and pickup schedules. |
.pre-commit-config.yaml |
Adjusts snapshot exclusions and makes pre-commit pytest run coverage explicitly. |
examples/example.py |
Updates the example address used. |
tests/cli/__init__.py |
Marks the CLI tests as a package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The previous fixture-level monkeypatch.setattr of typer.rich_utils.MAX_WIDTH worked locally but still rendered at ~80 cols on CI. Typer caches TERMINAL_WIDTH into MAX_WIDTH at module import time, so the only reliable way to force a deterministic width across environments is to set the env var in conftest.py before pytest imports any test module. Also explicitly reassign typer.rich_utils.MAX_WIDTH after the import as belt-and-suspenders in case typer was already imported by an earlier plugin. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test_help / test_upcoming_help / test_next_help snapshot tests
captured rendered help output from typer's rich formatter, which is
impossible to stabilise across environments: typer caches
TERMINAL_WIDTH into typer.rich_utils.MAX_WIDTH at module import time
and the width fell through to 80 columns on CI regardless of env
variables or monkeypatching.
Replace them with a single test_cli_structure case that introspects
the click Command tree via typer.main.get_command(cli) and snapshots
the {command: [params]} mapping. This asserts exactly what the help
tests were trying to verify (commands and options exist) without
touching any rendered text, so it's environment-independent.
Also drop the now-unused conftest.py TERMINAL_WIDTH + MAX_WIDTH hacks
and the monkeypatch.setattr line from the stable_terminal fixture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous commit accidentally tracked a scheduled_tasks.lock file written by the local Claude Code harness. Remove it from the index and add .claude/ to .gitignore so it doesn't get picked up again. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Several follow-up improvements on top of the CLI work: Tooling & config: - Bump pytest coverage gate from 10% to 95% so real regressions trip CI - Drop dead VS Code settings (python.linting.*, python.formatting.provider) from .devcontainer/devcontainer.json; they were removed from the Python extension in favour of separate Pylint/Ruff extensions - Expand pyproject keywords for PyPI discoverability - Unquote "on": in scorecard.yml and add the yamllint disable comment so it matches the style of every other workflow - Swap the unmaintained aresponses (last release 2021) for aioresponses, and rewrite tests/test_twentemilieu.py against it. test_timeout now uses exception=TimeoutError() instead of an async-sleep handler. Documentation: - Add a "Behavior & error handling" section to the README covering the per-call request_timeout (10 s default, configurable), the explicit lack of retries, asyncio cancellation behaviour, and the TwenteMilieuError exception taxonomy. Source fix: - WasteType._missing_ used to raise KeyError on any unknown integer value, which would crash update() mid-iteration on a newly introduced pickupType. It now returns None for unknown ints so the standard enum machinery raises ValueError, and update() catches that and skips the entry. Added a forward-compatibility entry with pickupType: 99 to the calendar fixture so the skip path is exercised on every run, and an explicit ValueError assertion in test_wastetype_fallback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generate a CycloneDX SBOM for the built wheel via anchore/sbom-action
and create a Sigstore-signed in-toto SBOM attestation covering dist/*
via actions/attest. The SBOM is kept in-workflow only (no release
asset, no workflow artifact) — the attestation is uploaded to GitHub's
attestations API and to Rekor.
Consumers can verify the provenance and SBOM attestations with:
gh attestation verify twentemilieu-<version>-py3-none-any.whl \
--owner frenck
Both new actions are pinned by SHA with # vX.Y.Z comments so Renovate
tracks them automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 22 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Six fixes flagged by Copilot:
- Snapshot exclude regex in .pre-commit-config.yaml matched nested
__snapshots__ dirs but missed the top-level tests/__snapshots__/
used by test_twentemilieu. Widen to ^tests/(?:.*/)?__snapshots__/
so both are protected by the end-of-file and trailing-whitespace
hooks.
- The `twentemilieu` console script was registered unconditionally
and would crash with ModuleNotFoundError when invoked after a plain
`pip install twentemilieu` (no `cli` extra). Add a tiny
twentemilieu._cli module that defers the import of twentemilieu.cli
and raises SystemExit with an install hint if the extras are
missing, and re-point the script entry at it.
- The `cli` extra only listed `typer>=0.15.1`, relying on typer's
transitive dep on rich. Declare `rich>=13.0.0` explicitly so a
future typer resolver can't surprise us at runtime.
- src/twentemilieu/cli/__init__.py docstring said "Asynchronous
Python client for the Twente Milieu API"; this module is the CLI
entry point, not the client. Fix the docstring.
- src/twentemilieu/cli/async_typer.py still had the Peblar copy/paste
docstring mentioning "Peblar EV chargers". Replace with a
project-accurate description.
- src/twentemilieu/cli/ruff.toml comment was grammatically wrong
("This extend") and referred to "examples". Fix both.
The `assert inside pytest.raises` comment was already addressed by
the earlier aioresponses migration and no longer applies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Surfaces the supply-chain security score published by the scorecard workflow next to the existing Build Status / Code Coverage / Dev Containers badges, linking to the public Scorecard viewer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 23 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Two Scorecard-related tweaks: - SECURITY.md now contains explicit URLs — a direct link to GitHub's private advisories endpoint and a mailto: to opensource@frenck.dev. Scorecard's Security-Policy check was scoring 4/10 on "no linked content found" despite the policy being substantive; adding the links should bump it to 10/10. - Document inline in linting.yaml why the npm install step stays at 9/10 on Pinned-Dependencies. Versions are locked via package-lock.json but npm commands can't be hash-pinned the way GitHub Actions can, so the 9/10 score is an accepted trade-off. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two more Copilot findings: - The CLI entry shim's blanket `except ModuleNotFoundError` would turn a genuine internal-module bug (e.g. a renamed or missing private module inside twentemilieu.cli) into a misleading "install the cli extra" message. Narrow the catch to only the optional-dep roots (typer, rich) and re-raise anything else so real errors stay visible. Pylint's type inference is broken on ModuleNotFoundError.name, so the split() call carries an inline no-member disable with an explanatory comment. - The release workflow's wheel-locate step used a bare `ls dist/twentemilieu-*-py3-none-any.whl` which silently takes the first match if the build ever produces more than one wheel, or fails opaquely on zero. Replace with a bash array + explicit count check, erroring out with a GitHub ::error:: annotation if anything other than exactly one pure wheel is present. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 25 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Adds an optional CLI for quickly inspecting the Twente Milieu waste pickup schedule, installable via
pip install "twentemilieu[cli]"and exposed as thetwentemilieuconsole script.It ships with two commands:
upcoming, which lists the next pickups chronologically (with--limit/-n, default 5), andnext, which shows the very next pickup and can be filtered to a specific waste type via--waste-type/-t. Both accept--post-code,--house-number, and--house-letter(also available asTWENTEMILIEU_*environment variables) and support a--jsonflag for machine-readable output. Human-facing output is rendered with Rich tables, and friendly error panels cover address and connection errors.The test suite has been extended with CLI tests using Typer's
CliRunnerand syrupy snapshots, and cleaned up along the way: the duplicatedtest_internal_eventloopis gone, the four bad-response error tests have been collapsed into one parametrised case, a sharedtwentefixture removes boilerplate,test_updatenow asserts against a snapshot, and JSON fixtures undertests/fixtures/are loaded via helpers in a newtests/conftest.py.--covhas been moved out ofaddoptsso subset runs don't hit the coverage gate, andtrailing-whitespace/end-of-file-fixernow skip snapshot files. The README has a short CLI section.