refactor(errors)!: a lean, idiomatic DataRetrievalError taxonomy#319
Draft
thodson-usgs wants to merge 1 commit into
Draft
refactor(errors)!: a lean, idiomatic DataRetrievalError taxonomy#319thodson-usgs wants to merge 1 commit into
thodson-usgs wants to merge 1 commit into
Conversation
Every request failure raises a subclass of DataRetrievalError, so a caller can handle any of them with a single `except dataretrieval.DataRetrievalError`. The taxonomy stays small -- it adds only what the underlying httpx exceptions can't express: DataRetrievalError(Exception) |- HTTPError # .status_code -- the server returned an error status | '- TransientError # .retry_after -- retryable (429 / 5xx) | |- RateLimited # 429 | '- ServiceUnavailable # 5xx |- RequestTooLarge # the request can't fit | |- URLTooLong # 414 / client-side over-long URL | '- Unchunkable # the Water Data chunker can't split the call '- NoSitesError # a 200 response with no data One factory -- error_for_status(status, message, *, retry_after) -- maps a status to its type, and every request path routes through it (the legacy `query` path, the Water Data chunker, nldi, nadp, streamstats), so a given status surfaces as the same type everywhere. A fatal 4xx is a generic HTTPError carrying .status_code (inspect the code rather than a class per code). The chunker keys retry/resume on TransientError; connection-level failures (timeouts, DNS) surface as httpx exceptions on the single-shot paths. BREAKING CHANGES - Request failures raise typed DataRetrievalError subclasses instead of bare ValueError / RuntimeError / httpx.HTTPStatusError. The exceptions root only at DataRetrievalError(Exception) and no longer also inherit ValueError / RuntimeError -- catch DataRetrievalError (or a subclass), not the builtins. - A fatal 4xx raises HTTPError (read .status_code); there are no per-code types. Also adds a dataretrieval.exceptions API docs page. mypy --strict clean; ruff clean; full suite green (483 passed, 2 skipped); the Water Data chunker's resume tests pass unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3651f23 to
108b1ca
Compare
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.
What
A small, idiomatic exception taxonomy: every request failure raises a subclass of
DataRetrievalError, so oneexcept dataretrieval.DataRetrievalErrorhandles any of them. It adds only what the underlyinghttpxexceptions can't express.A single factory,
dataretrieval.exceptions.error_for_status(status, message, *, retry_after), maps a status to its type, and every request path routes through it — the legacyquerypath (nwis/wqp/nldi), the Water Data chunker, andnadp/streamstats— so a given status surfaces as the same type everywhere.HTTPErrorcarrying.status_code. Inspect the code (except HTTPError as e: ... e.status_code == 404) rather than catching a class per code.RateLimited/ServiceUnavailable(bothTransientError), carrying.retry_after. The Water Data chunker keys its auto-retry/resume onTransientError; the single-shot paths raise the typed error for the caller to handle.Unchunkable(the chunker can't split the call),URLTooLong(a 414 or a client-side over-long URL),NoSitesError(a 200 with no data).httpxexceptions on the single-shot paths.Breaking changes
DataRetrievalErrorsubclasses instead of bareValueError/RuntimeError/httpx.HTTPStatusError. The exceptions root only atDataRetrievalError(Exception)— they no longer also inheritValueError/RuntimeError, soexcept ValueError/except RuntimeErrorno longer catch them. CatchDataRetrievalError(or a subclass).HTTPError(read.status_code); there are no per-code exception types.Verification
mypy --strict clean; ruff clean; full suite green (483 passed, 2 skipped); live spot-checks of the error paths; the Water Data chunker's ~40 resume tests pass unchanged (the resume path is untouched). Adds a
dataretrieval.exceptionsAPI reference page.🤖 Generated with Claude Code