Skip to content

✨ Add optional command-line interface + MOAR modernization #1769

Merged
frenck merged 11 commits intomainfrom
frenck-2026-0349
Apr 15, 2026
Merged

✨ Add optional command-line interface + MOAR modernization #1769
frenck merged 11 commits intomainfrom
frenck-2026-0349

Conversation

@frenck
Copy link
Copy Markdown
Owner

@frenck frenck commented Apr 15, 2026

Summary

Adds an optional CLI for quickly inspecting the Twente Milieu waste pickup schedule, installable via pip install "twentemilieu[cli]" and exposed as the twentemilieu console script.

It ships with two commands: upcoming, which lists the next pickups chronologically (with --limit/-n, default 5), and next, 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 as TWENTEMILIEU_* environment variables) and support a --json flag 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 CliRunner and syrupy snapshots, and cleaned up along the way: the duplicated test_internal_eventloop is gone, the four bad-response error tests have been collapsed into one parametrised case, a shared twente fixture removes boilerplate, test_update now asserts against a snapshot, and JSON fixtures under tests/fixtures/ are loaded via helpers in a new tests/conftest.py. --cov has been moved out of addopts so subset runs don't hit the coverage gate, and trailing-whitespace / end-of-file-fixer now skip snapshot files. The README has a short CLI section.

@frenck frenck added the new-feature New features or options. label Apr 15, 2026
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>
Copilot AI review requested due to automatic review settings April 15, 2026 17:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 twentemilieu console script with upcoming and next commands (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.

Comment thread .pre-commit-config.yaml Outdated
Comment thread .pre-commit-config.yaml Outdated
Comment thread pyproject.toml
Comment thread src/twentemilieu/cli/async_typer.py Outdated
Comment thread src/twentemilieu/cli/ruff.toml Outdated
Comment thread src/twentemilieu/cli/__init__.py Outdated
Comment thread tests/test_twentemilieu.py Outdated
frenck and others added 2 commits April 15, 2026 17:51
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>
Copilot AI review requested due to automatic review settings April 15, 2026 17:55
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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pyproject.toml Outdated
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>
@frenck frenck changed the title ✨ Add optional command-line interface ✨ Add optional command-line interface + MOAR modernization Apr 15, 2026
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>
Copilot AI review requested due to automatic review settings April 15, 2026 18:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pyproject.toml
Comment thread .pre-commit-config.yaml Outdated
frenck and others added 2 commits April 15, 2026 18:40
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>
Copilot AI review requested due to automatic review settings April 15, 2026 18:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/twentemilieu/_cli.py
Comment thread .github/workflows/release.yaml Outdated
frenck and others added 2 commits April 15, 2026 18:54
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>
Copilot AI review requested due to automatic review settings April 15, 2026 18:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/twentemilieu/cli/__init__.py
Comment thread src/twentemilieu/cli/ruff.toml
Comment thread src/twentemilieu/cli/async_typer.py
@frenck frenck merged commit 9299a96 into main Apr 15, 2026
32 checks passed
@frenck frenck deleted the frenck-2026-0349 branch April 15, 2026 19:05
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

new-feature New features or options.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants