dataretrieval.exceptions

Exception taxonomy for dataretrieval.

Every service module (nwis, wqp, nldi, waterdata, nadp, streamstats) raises a subclass of DataRetrievalError when a request fails, so one except dataretrieval.DataRetrievalError catches them all – including connection-level failures (timeouts, DNS, refused connections), which are wrapped as NetworkError with the underlying httpx exception on __cause__.

Most failures are an HTTPError carrying the response .status_code, of which TransientError (429 / 5xx) is the retryable subset. The rest aren’t a plain status: RequestTooLarge (with URLTooLong / Unchunkable), NetworkError (a failed connection, per above), and NoSitesError. error_for_status() maps a status to its type.

This module has no third-party runtime dependencies – httpx is imported only for type checking – so any module can import it without pulling in pandas / httpx and without risking an import cycle.

exception dataretrieval.exceptions.DataRetrievalError[source]

Bases: Exception

Base class for every failed-request error in dataretrieval.

Catch it to handle any USGS or EPA service failure uniformly, and branch on the read-anywhere fields below without needing the concrete subclass:

try:
    df, md = dataretrieval.waterdata.get_daily(...)
except dataretrieval.DataRetrievalError as e:
    if e.retryable:  # 429 / 5xx / connection failure
        time.sleep(e.retry_after or backoff)
        ...  # re-issue the request
    elif e.status_code == 404:  # ``None`` unless an HTTP status error
        ...
    else:
        raise

Connection-level failures (timeouts, DNS) are wrapped as NetworkError, so this single clause covers them too.

retry_after: float | None = None

Seconds the server asked us to wait before retrying (its Retry-After header), or None when it gave no hint. Set by TransientError.

retryable: ClassVar[bool] = False

Whether re-issuing the same request might succeed – True for the transient HTTP statuses (429 / 5xx, TransientError) and for connection failures (NetworkError); False otherwise.

status_code: int | None = None

HTTP status that triggered the error, or None for errors without one (connection failure, too-long URL, no data). Set by HTTPError.

exception dataretrieval.exceptions.HTTPError(message: str, *, status_code: int)[source]

Bases: DataRetrievalError

The service returned an error HTTP status.

The numeric status is on status_code; branch on it, e.g. except HTTPError as e: ... if e.status_code == 404. TransientError (429 / 5xx) is the retryable subset, and is itself an HTTPError. The one exception to “a status is an HTTPError” is a request the service rejects as too long: it surfaces as URLTooLong (a RequestTooLarge), not an HTTPError – so catch DataRetrievalError to be certain of spanning every failure. See error_for_status() for the full mapping.

Parameters:
  • message (str) – Human-readable error message.

  • status_code (int) – The HTTP status the service returned.

exception dataretrieval.exceptions.NetworkError[source]

Bases: DataRetrievalError

The request never completed a round-trip to the service – a DNS failure, refused connection, or timeout – so no HTTP response arrived to classify.

Wraps the underlying httpx transport exception, preserved on __cause__. Worth retrying (retryable is True), but carries no .status_code because no response came back.

retryable: ClassVar[bool] = True

Whether re-issuing the same request might succeed – True for the transient HTTP statuses (429 / 5xx, TransientError) and for connection failures (NetworkError); False otherwise.

exception dataretrieval.exceptions.NoSitesError(url: httpx.URL)[source]

Bases: DataRetrievalError

A request succeeded (HTTP 200) but matched no sites/data.

A no-data result is normally not an error: the modern getters (waterdata, wqp, nldi) return an empty DataFrame. Only the deprecated nwis (waterservices) path still raises this.

exception dataretrieval.exceptions.RateLimited(message: str, *, status_code: int | None = None, retry_after: float | None = None)[source]

Bases: TransientError

A request was rejected with HTTP 429 (too many requests).

_DEFAULT_STATUS: ClassVar[int] = 429

Canonical status a concrete transient stamps when built without an explicit status_code (RateLimited = 429, ServiceUnavailable = 503). TransientError itself is abstract and sets none, so constructing it bare requires status_code.

exception dataretrieval.exceptions.RequestTooLarge[source]

Bases: DataRetrievalError

The request is too large for the service to satisfy.

Base for the two ways that happens; catch it to handle either: URLTooLong (a single request rejected for length) and Unchunkable (a Water Data call the chunker could not split small enough to fit).

exception dataretrieval.exceptions.ServiceUnavailable(message: str, *, status_code: int | None = None, retry_after: float | None = None)[source]

Bases: TransientError

A request was rejected with a server error (HTTP 5xx).

Raised by both the legacy query path and the Water Data path, so a 5xx surfaces as one type whichever subsystem issued the request. .status_code holds the actual 5xx; it falls back to 503 only on a bare hand-construction.

_DEFAULT_STATUS: ClassVar[int] = 503

Canonical status a concrete transient stamps when built without an explicit status_code (RateLimited = 429, ServiceUnavailable = 503). TransientError itself is abstract and sets none, so constructing it bare requires status_code.

exception dataretrieval.exceptions.TransientError(message: str, *, status_code: int | None = None, retry_after: float | None = None)[source]

Bases: HTTPError

A 429 or 5xx the server may serve on a later try – RateLimited for 429, ServiceUnavailable for 5xx.

This only classifies the condition; it does not itself retry. Whether to retry is up to the calling path: a single-shot request raises it for the caller to handle (e.g. wait retry_after seconds, then re-issue), while the Water Data chunker retries and resumes automatically.

Parameters:
  • message (str) – Human-readable error message.

  • status_code (int, optional) – The HTTP status the service returned. Defaults to the leaf’s canonical code (429 / 503) when omitted; error_for_status() always passes the real status.

  • retry_after (float, optional) – Seconds to wait before retrying, parsed from the Retry-After response header; None when the header is absent or unparseable.

_DEFAULT_STATUS: ClassVar[int]

Canonical status a concrete transient stamps when built without an explicit status_code (RateLimited = 429, ServiceUnavailable = 503). TransientError itself is abstract and sets none, so constructing it bare requires status_code.

retryable: ClassVar[bool] = True

Whether re-issuing the same request might succeed – True for the transient HTTP statuses (429 / 5xx, TransientError) and for connection failures (NetworkError); False otherwise.

exception dataretrieval.exceptions.URLTooLong[source]

Bases: RequestTooLarge

A single request URL was too long for the service.

Raised on the legacy query path (which sends one un-chunked request), whether the URL is rejected client-side before sending or by the server (see error_for_status()). Remediation: query fewer sites, or split the call manually.

exception dataretrieval.exceptions.Unchunkable[source]

Bases: RequestTooLarge

No chunking plan fits the URL byte limit.

Raised by the Water Data chunker when even the smallest reducible plan (every list axis at one atom per sub-request, the filter at one clause per sub-request) still exceeds the server’s byte limit – so unlike URLTooLong, automatic splitting has already been tried and exhausted. Shrink the input lists, simplify the filter, or split the call manually.

dataretrieval.exceptions.error_for_status(status: int, message: str, *, retry_after: float | None = None) DataRetrievalError[source]

Return the typed DataRetrievalError for an HTTP error status.

The one status-to-type mapping every request path shares (the legacy query path, waterdata, nadp / streamstats), so a given status becomes the same type everywhere:

message is used verbatim; retry_after is attached only to the transient (TransientError) types. status must be an error status (>= 400) – classifying a success or redirect is a usage error and raises ValueError.