Fix the help-text leak and correct the documented error mapping#16
Merged
Conversation
A two-axis review of the codebase -- against the repo's own standards and against its documentation as a spec -- turned up eighteen findings. Behaviour changes are limited to the CLI help text; everything else either makes the documentation match the code or removes machinery nothing used. Fixed: - The `-d/--data` help printed a literal `R|` prefix. The marker belonged to a custom formatter attached only to the top-level parser, never to the subcommands that carried the marker, so it leaked into the output it was meant to control. Formatter and marker are both gone. - The documented exception mapping was wrong twice over. Retries exhausted against a transient status raise `ResponseError` with the real status, not `HTTPConnectionError`: the adapter sets `raise_on_status=False`, so the last response is reported rather than discarded. And a connect timeout is an `HTTPConnectionError`, since `ConnectTimeout` subclasses `ConnectionError`; only read timeouts reach `HTTPClientError`. The docs now say what the code does. Corrected in the API reference and in both guides that repeated it. - `allowed_methods` was assigned and documented but never read -- validation went to the `ALLOWED_METHODS` class constant. It is now the attribute every request checks. Changed: - `__version__` is read from the installed distribution's metadata instead of being a second hand-maintained copy of the version in `pyproject.toml`. It resolves through the existing PEP 562 hook, so `import snaffle` does not pay for the lookup; ADR 0002 records the new deferred import. - The CLI dispatches through `make_request(method, url)` rather than looking the per-verb method up by name with `getattr`, which had erased the typed API. `_emit_response` is annotated `requests.Response` via a `TYPE_CHECKING` import, which keeps the help path clear of the HTTP stack. Removed: - `cli.show_examples` and `main`'s `suppress_output` parameter. Neither is part of the documented public API, and the flag existed only to quiet tests that already redirect stdout. Tests: - Dropped a retry test that mocked `Session.request`. That mock sits above the adapter and cannot observe retries, so the assertion was vacuous -- exactly the trap CONTRIBUTING documents. The real-socket test already covers it. - Split the duplicated large/small-file progress tests so each patches `tqdm` and asserts whether a bar was drawn. The small-file case never checked the suppression it was named for. - Added `test_init.py` and `test_main.py`, the two source modules that had no test module. They cover the Ctrl+C-exits-zero contract of the console script, lazy resolution of `HTTPClient` and `__version__`, and a subprocess guard that a bare `import snaffle` does not pull in `requests`. Co-Authored-By: Claude Opus 5 (1M context) <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 free
to 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.
A two-axis review of the codebase — against the repo's documented standards, and against its own documentation treated as the spec — turned up eighteen findings. This addresses all of them.
Behaviour changes are limited to the CLI help text. Everything else either makes the documentation match the code or removes machinery nothing used.
Fixed
-d/--datahelp printed a literalR|prefix. The marker belonged to a custom formatter attached only to the top-level parser — never to the subcommands that actually carried the marker — so it leaked into the very output it was meant to control. Formatter and marker are both gone.ResponseErrorcarrying the real status, notHTTPConnectionError: the adapter setsraise_on_status=False, so the last response is reported rather than discarded. And a connect timeout is anHTTPConnectionError, sincerequests.exceptions.ConnectTimeoutsubclassesConnectionError; only read timeouts reachHTTPClientError. Corrected in the API reference and in both guides that repeated the claim.allowed_methodsdid nothing. It was assigned in__init__and documented as an instance attribute, but every request validated against theALLOWED_METHODSclass constant instead. It is now the attribute method validation reads.Changed
__version__is read from the installed distribution's metadata instead of being a second hand-maintained copy of the version inpyproject.toml. It resolves through the existing PEP 562 hook, soimport snaffledoes not pay for the lookup; ADR 0002 records the new deferred import.make_request(method, url)rather than looking the per-verb method up by name withgetattr, which had erased the typed API._emit_responseis annotatedrequests.Responsevia aTYPE_CHECKINGimport, keeping the help path clear of the HTTP stack.Removed
cli.show_examplesandmain'ssuppress_outputparameter. Neither is part of the documented public API — that isHTTPClientand the three exceptions — and the flag existed only to quiet tests that already redirect stdout. Filed underRemovedin the changelog rather than as a breaking change for that reason; happy to keep the parameter if you'd rather.Tests
40 → 46.
requests.Session.request. That mock sits above the adapter and cannot observe retries at all, so the assertion was vacuous — it would have passed with 404 inRETRY_STATUSES. This is exactly the trapCONTRIBUTING.mddocuments, andTestRetryAgainstRealServeralready covers the case correctly.tqdmand asserts whether a bar was drawn. Both previously made the identical assertion, and the small-file case never checked the suppression it was named for.tests/test_init.pyandtests/test_main.py, the two source modules that had no test module despite the one-per-source-module convention. They cover theCtrl+C-exits-0contract of the console script, lazy resolution ofHTTPClientand__version__, and a subprocess guard that a bareimport snaffledoes not pull inrequests.Three judgement calls
Three findings were doc-vs-code conflicts. I fixed the docs, not the behaviour:
raise_on_status=Falseis a deliberate, separately documented choice, and aResponseErrorcarrying the real 503 tells the caller more than a generic connection failure. The existing real-socket test already asserted it.[VERBOSE]error linesrequestsexception is what verbose mode is for. Documented rather than removed.Reversing any of these is a behavioural break, so flag it if you'd prefer the other direction.
Verification
All four checks from
CONTRIBUTING.mdpass locally on 3.14:Smoke-tested end to end against a live host:
GETrenders, a 404 exits1with the status in the message,-vprints the underlying exception,python -m snaffleprints help, andsnaffle.__version__resolves to3.0.0from metadata — unchanged from the hard-coded value.🤖 Generated with Claude Code