Move CLI dependencies to optional cli extra - #275
Conversation
cli extra
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #275 +/- ##
==========================================
+ Coverage 94.62% 94.86% +0.23%
==========================================
Files 49 50 +1
Lines 4203 4321 +118
==========================================
+ Hits 3977 4099 +122
+ Misses 226 222 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
- Keep the Click implementation in cli.py and guard the click/rich imports with an ImportError that names the cli extra, matching xarr.py. Remove _cli_impl.py and the proxy wrapper. - Add _spp_entry.main() as the console-script target. It imports the CLI lazily and exits 1 with an install hint when the cli extra is missing. - Reject an out-of-range or negative --packet index as a usage error (exit 2) instead of printing a message and exiting 0. - Mirror click/rich into meta.yaml test.requires and include the cli extra in check_metadata.py so the metadata consistency hook passes. - Verify both the bare and [cli] wheel installs in the build workflow. - Link #274 in the changelog and note the cli extra in the repo instructions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7ba62db to
22bf4da
Compare
medley56
left a comment
There was a problem hiding this comment.
Review of the Copilot agent's head (7ba62db), rebased onto main, with fixes in 22bf4da. Tally: 2 critical, 3 warnings, 3 suggestions, 1 nitpick group. Everything below is addressed in 22bf4da.
🛑 CRITICAL — check_metadata.py failed on this branch
meta.yaml run: kept click/rich while pyproject core dropped them, and test.requires lacked the click/rich the pyproject test extra gained. The pre-commit hook runs this script on every commit. Conda has no extras, so the recipe keeps click/rich as run deps the way it already does for xarray; verify_run_dependencies now unions the cli extra, and test.requires mirrors the test extra.
🛑 CRITICAL — Build workflow's spp --version fails on a bare wheel install
_build.yml installs dist/*.whl without the extra and then runs spp --version, which now exits 1. The Build job was skipped on the PR head so CI did not show it. The step now asserts the bare install exits non-zero with the install hint, then installs [cli] and runs spp --version. Reproduced locally against a built wheel in a fresh venv.
WARNING — wrapper design did more than the goal needed
_cli_impl.py, _SppCommandProxy, module __getattr__ and __dir__ existed to keep the entry-point string space_packet_parser.cli:spp, which is not public API, and the proxy was not a faithful click.Group (isinstance false, extra kwargs dropped). Replaced with the pattern already in xarr.py: cli.py is the real Click module with a guarded import block raising an ImportError that names the extra, and _spp_entry.main() is the console-script target that prints that error and exits 1. import space_packet_parser.cli without the extra now raises, which is what #274 asked for.
WARNING — cli.py failed ruff format --check. Moot after the rewrite.
WARNING — rebase conflicts. docs/source/users.md was deleted on main (#273); the two install snippets moved to getting_started.md and user_guide/xtce_validation.md. The changelog entry moved under [Unreleased] above 6.2.0.
💡 SUGGESTION — changelog entry lacked the issue link, and the --packet bound change had no entry. Both added.
💡 SUGGESTION — out-of-range --packet printed a message and exited 0, and the new test locked that in. Now click.BadParameter (exit 2); negative indices are rejected too.
💡 SUGGESTION — tests: sys.modules.pop(...) was never restored, one test name did not match its assertion, and four tests targeted the wrapper's main(). Rewritten against _spp_entry.main() with monkeypatch.delitem. The non-standalone test was dropped; it only exercised Click's own standalone_mode passthrough.
✏️ NITPICK — repo instructions listed Click/Rich as core dependencies; now marked as the cli extra. The shebang on the private module and its autoapi page go away with the rewrite. click/rich stay duplicated in the test extra, matching xarray/numpy.
Verified and unchanged: click/rich are out of core deps and in the cli extra, spp is still declared, nothing else in the package imports click/rich, no lockfile is tracked, the docs build does not need click, README wording is accurate. Full suite: 491 passed at 22bf4da.
🤖 AI-assisted comment, reviewed and approved by @medley56 before posting.
There was a problem hiding this comment.
🟡 Changes recommended
The new build-workflow assertion for “no cli extra installed” is not isolated from other job-installed tooling and may be flaky or ineffective unless run in an isolated environment (or with click/rich explicitly absent).
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR implements issue #274 by moving CLI-only dependencies (click, rich) behind an optional cli extra while keeping the spp console script always installed, and ensuring a bare install fails with a clear install hint rather than a traceback.
Changes:
- Packaging: move
click/richto[project.optional-dependencies].cli, keep them intest, and pointsppat a new lazy entrypoint module (space_packet_parser._spp_entry:main). - CLI behavior: guard
click/richimports inspace_packet_parser.cliwith a friendly ImportError, and tightenparse --packet Nbounds to fail as a usage error (exit 2). - Project hygiene: add integration tests for the new entrypoint/failure mode, update docs/README/CHANGELOG, and update metadata verification for conda’s lack of extras.
File summaries
| File | Description |
|---|---|
| tests/integration/test_cli.py | Adds tests for the new spp entrypoint behavior and --packet bounds handling, plus simulates missing cli deps. |
| space_packet_parser/cli.py | Guards click/rich imports with an install-hint ImportError; makes --packet out-of-range a Click usage error. |
| space_packet_parser/_spp_entry.py | New lazy console-script entrypoint that prints the install hint and exits non-zero when cli extra is missing. |
| scripts/check_metadata.py | Updates metadata checks to union cli + xarray extras into conda run requirements. |
| README.md | Documents installing the optional CLI via space_packet_parser[cli]. |
| pyproject.toml | Moves click/rich into cli extra; updates spp script entrypoint; keeps deps in test extra. |
| meta.yaml | Updates conda spp entrypoint; ensures test requirements include click/rich. |
| docs/source/user_guide/xtce_validation.md | Adds CLI extra install instructions before using spp. |
| docs/source/getting_started.md | Adds CLI extra install instructions. |
| CHANGELOG.md | Adds breaking-change notes and documents the --packet exit-code behavior change. |
| .github/workflows/_build.yml | Adds a wheel-install verification that expects spp --version to fail without the cli extra, then succeed with it. |
| .github/instructions/space_packet_parser.instructions.md | Updates repo guidance to mark Click/Rich as optional via the cli extra. |
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # Without the cli extra, spp must exit non-zero with an install hint rather than a traceback | ||
| if spp --version; then | ||
| echo "spp --version succeeded without the cli extra" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
This speaks to a couple of possibilities:
- We are over-testing and could just verify the behavior and leave this particular behavior un-covered.
- We need to isolate the test from the environment better (more careful mocking)
The thing I don't want to do is create a complex, environment level harnessing approach just to test the handling of a missing dependency.
There was a problem hiding this comment.
Fixed in e1c1f44. The "Verify installation of built wheel" step now installs the wheel into a fresh python -m venv and runs spp from there, so the check depends only on the wheel.
One correction to the framing: the check was deterministic, not flaky, and it failed safe. twine does pull in rich, but not click, and import click is the first line of the guarded block, so the bare install failed as intended. A transitive click would have turned the step red rather than letting it pass silently. The venv removes that dependence on import order and on twine's dependency tree.
🤖 AI-assisted comment, reviewed and approved by @medley56 before posting.
| try: | ||
| from space_packet_parser.cli import spp | ||
| except ImportError as exc: | ||
| print(exc, file=sys.stderr) | ||
| raise SystemExit(1) from exc | ||
| spp() |
There was a problem hiding this comment.
Yeah, this seems pretty reasonable I think.
There was a problem hiding this comment.
Fixed in e1480a7. cli.py now raises MissingExtraError, a new ImportError subclass in exceptions.py, from the guarded click/rich import, and _spp_entry.main() catches only that class. Any other import failure inside the CLI propagates with its normal traceback. test_spp_entry_point_propagates_unrelated_import_error covers the negative case by blocking an unrelated internal import.
🤖 AI-assisted comment, reviewed and approved by @medley56 before posting.
Add MissingExtraError (an ImportError subclass) and raise it from the guarded click/rich import in cli.py. The spp console-script entry point now catches only that class, so any other import failure inside the CLI propagates with its normal traceback instead of being reduced to a one-line message and exit 1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The build job already has rich installed via twine, so checking that a bare install of the wheel fails to import the cli deps depended on click happening to be absent from the job environment. Install the wheel into a dedicated venv so the check depends only on the wheel under test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sage error Definitions whose root SequenceContainer is not named CCSDSPacket (for example the CTIM test definition, whose root is CCSDSTelemetryPacket) could not be parsed from the CLI at all, and describe-xtce raised a bare KeyError on them. Both commands now share a --root-container option and a loader that fails with exit code 2, naming the missing container and listing the containers that have no BaseContainer as the possible roots. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Checklist
Summary
clickandrichwere installed for API-only users because the CLI imported them as baseline dependencies. This change moves them behind acliextra and makessppexit with an install hint when the extra is missing.clickandrichfrom base dependencies to[project.optional-dependencies].cli. They stay in thetestextra so CLI tests run in CI.sppconsole script (pyproject.toml and meta.yaml) atspace_packet_parser._spp_entry:main. The conda recipe keeps click/rich as run deps because conda has no extras, andcheck_metadata.pyaccounts for thecliextra.space_packet_parser.cliis unchanged except that the click/rich imports are guarded and raiseMissingExtraError, a newImportErrorsubclass inexceptions.py, naming the extra. Same pattern asxarr.py._spp_entry.main()imports the CLI lazily. It catches onlyMissingExtraError, prints the hint to stderr and exits 1. Any other import failure propagates with its normal traceback. PEP 621 cannot condition a console script on an extra, so the script is always installed.[cli]and runsspp --version. The venv matters because the job environment already hasrichviatwine.parse --packet Nrejects an out-of-range or negative index as a usage error (exit 2). PreviouslyNequal to the packet count raisedIndexError, and other out-of-range values printed a message and exited 0.parsegains--root-container, matchingdescribe-xtce, so definitions whose root is not namedCCSDSPacketcan be parsed from the CLI.describe-xtceandparsefail as a usage error (exit 2) when the named root container is not in the document, listing the containers with noBaseContaineras possible roots. Previously both raised a bareKeyError. The CTIM test definition (rootCCSDSTelemetryPacket) exercises this.getting_started.md,user_guide/xtce_validation.md, README) call outpip install "space_packet_parser[cli]"._BREAKING_) and Fixed.