Architecture
Purpose and scope
dataretrieval is a Python client library for discovering and retrieving
hydrologic data from several independently operated USGS and partner services.
It is a modular monolith: one installable distribution with public service
facades, service- and protocol-specific adapters, and shared infrastructure.
The architecture favors incremental evolution and stable user-facing functions
over a framework that forces unlike upstream APIs into one shape.
This document records the current structure, the rules contributors should preserve, and known variances that later changes may remove. Rationale for architecturally significant choices is recorded in Architecture Decision Records.
Architecture characteristics
The characteristics below are prioritized in descending order when trade-offs are necessary:
Installability and artifact integrity. A built wheel must contain every supported package and work outside a source checkout.
Public API compatibility. Established imports, function signatures, return shapes, metadata, warnings, and exception types should remain stable; intentional changes follow the project’s deprecation policy.
Correctness and data integrity. Pagination and fan-out must not silently return truncated or duplicated data after a failure.
Resilience. Retry, rate-limit, interruption, and resume behavior must be explicit and bounded. Capabilities may differ where upstream protocols do.
Maintainability. Modules should have cohesive responsibilities and dependencies should point toward stable policies rather than service details.
Bounded performance and resource use. Concurrency, connection pools, retries, and page accumulation require explicit limits.
Observability. Logs, metadata, progress, and typed failures should make remote-service behavior diagnosable without changing results.
Portability. Supported Python and operating-system combinations must work; optional geospatial dependencies must remain isolated.
Context view
The package sits between Python callers and remote hydrologic services:
Python user / notebook / batch process
|
v
dataretrieval public service modules
|
HTTP(S), JSON, CSV, RDB, GeoJSON
|
v
USGS Water Data APIs, NGWMN, NWDC Water Use,
Water Quality Portal, NLDI, StreamStats, legacy NWIS
The remote services own their schemas, paging mechanisms, rate limits, and availability. The library adapts those differences to documented Python contracts but does not hide meaningful service-specific behavior.
Composition and dependency view
Public service facades
dataretrieval.waterdataModern USGS Water Data API facade.
waterdata.apiis a logic-free compatibility facade over collection-family modules:time_series,metadata,measurements,reference,samples, andcql. Focused modules own ratings, nearest-value selection, statistics execution, shared service policy, and type vocabularies. Internal modules import protocol helpers from their canonical OGC modules rather than re-exporting them through Water Data utilities.dataretrieval.ngwmnNGWMN facade. Its only OGC dependency is the public OGC facade, which it configures with an NGWMN-specific base URL, output identifiers, state translation, and
OgcDialect.dataretrieval.wateruseNWDC Water Use facade. Builds CSV requests, follows
Linkheaders, and uses service-neutral transport for bounded fan-out, retry, pagination, response aggregation, and synchronous dispatch. It does not depend on OGC modules.dataretrieval.wqp,dataretrieval.nldi, anddataretrieval.streamstatsService-specific adapters over shared synchronous HTTP and bounded retry policy. Their return types intentionally reflect their upstream data models.
dataretrieval.nwisDeprecated legacy NWIS facade, scheduled for removal on or after 2027-05-06. Modern code must not depend on it.
Interface view
The primary API is a collection of synchronous functions grouped by data
portal. Most tabular download functions return (DataFrame, metadata).
NLDI and StreamStats retain service-specific geospatial or response-object
contracts; consistency alone is not sufficient reason for a breaking change.
Failed requests derive from dataretrieval.DataRetrievalError. Callers can
inspect status_code, retry_after, and retryable without knowing the
concrete subtype. A fanned-out call – an over-large OGC request, or a Water Use
query naming several locations – may raise FanOutInterrupted subclasses
(formerly, and still aliased as, ChunkInterrupted) carrying a resumable call
handle and completed partial state.
Package/module exports and documentation define the public surface. Underscore-prefixed symbols are implementation details even where existing internal adapters currently import them; those imports are known variances, not new extension points.
Service return contracts
The library preserves meaningful upstream differences rather than forcing every service into one return shape:
Water Data, NGWMN, and Water Use tabular getters return
(DataFrame, BaseMetadata). Geometry-bearing Water Data and NGWMN results may use aGeoDataFramein the first position when geopandas is installed.BaseMetadatacarries request URL, elapsed query time, response headers, and comments where the upstream format provides them.WQP getters return
(DataFrame, WQP_Metadata); the service-specific metadata extendsBaseMetadatawith WQP query parameters and site lookup.waterdata.get_ratingsreturns a mapping of feature IDs to parsed ratingDataFrameobjects by default, or the raw STAC feature list when downloads are disabled.NLDI navigation functions return
GeoDataFrameobjects directly, or raw GeoJSON-like dictionaries whenas_json=True; they do not add a metadata tuple.StreamStats functions return raw
httpx.Responseobjects or the service-specificWatersheddomain object, depending on the requested format.Deprecated NWIS functions retain their established DataFrame and legacy metadata contracts through the published deprecation window.
Changing one of these shapes is a public compatibility change and requires the project’s deprecation process; consistency alone is not sufficient reason.
Interaction view
A typical OGC-backed call follows this sequence:
synchronous public getter
-> normalize and validate arguments
-> select OGC dialect and build a chunk plan
-> enter a short-lived anyio blocking portal
-> execute chunks through a shared httpx.AsyncClient
-> paginate each chunk
-> retry bounded transient failures
-> combine and deduplicate pages/chunks
-> shape columns and types
-> return DataFrame and BaseMetadata
A transient failure after some chunks complete raises a resumable
interruption. The retained FanOut (available as ChunkedCall on the OGC
compatibility path) reissues only missing work and applies the same finalization
path when resumed. Cancellation and non-transient programming errors take
precedence over retry/resume wrapping.
Non-OGC services use the same transport policy only where their protocols have matching semantics. Retry, cursor pagination, and fan-out remain explicit adapter choices. Chunk planning remains OGC-specific; resumable fan-out is also used by Water Use because the NWDC accepts only one location per request.
Resource and configuration view
API_USGS_PATOptional USGS API token. It is attached only to requests for
api.waterdata.usgs.gov. Shared synchronous and asynchronous clients re-check every redirected request and strip the token before following a link to any other host, including external rating assets.API_USGS_CONCURRENTFan-out concurrency cap; defaults to 32 for OGC and 4 for Water Use when unset. An explicit value applies to every service,
1is sequential, andunboundedremoves the explicit cap. A semaphore, not pool waiting, is the execution throttle.API_USGS_RETRIESNumber of retries after the first attempt on supported active request paths; defaults to four. Backoff is exponential with full jitter and honors bounded
Retry-Aftervalues. Only failures a later attempt could survive are re-sent: 429 and gateway 5xx, not a 500 rejecting the query itself, and not a transport failure that is settled before the request leaves (unresolvable host, unsupported scheme). Deprecated NWIS compatibility paths do not opt in.API_USGS_STALL_TIMEOUTSeconds a call may go without receiving any data before retrying stops and the failure surfaces; defaults to 60, and
0disables the bound. It complementsAPI_USGS_RETRIES, which caps attempts rather than elapsed time: without this bound, four retries of a request that times out after a minute add up to four silent minutes. Progress restarts the budget — a page received, or a queued chunk acquiring its concurrency slot. Neither a slow but productive download nor the tail of a wide fan-out is cut short, and an attempt already in flight is never interrupted. This bound never withholds the first retry, so one slow attempt cannot disable retry by itself; after that, the budget decides whether to continue. A dead connection therefore costs about two read timeouts rather than five attempts’ worth.API_USGS_PROGRESSControls best-effort progress display. Reporting failures must never change retrieval results.
dataretrieval.transport centralizes HTTP timeout, redirect, and
authentication policy. OGC chunk fan-out and Water Use location
fan-out retain separate explicit concurrency caps because their upstream costs
and request shapes differ.
Known architectural debt
This view records categories and representative locations of debt.
.importlinter is authoritative for exact current dependency allowlists.
ogc/engine.pyretains a compatibility pagination wrapper alongside OGC orchestration. The sync-dispatch wrapper is gone: every retrieval path now enters throughtransport.fanout.FanOut.utils.pycombines shaping with compatibility imports for metadata, ambient configuration, transport, and the query path.waterdata/utils.pycombines endpoint constants, argument normalization, and the OGC engine wrappers.
These are documented so guardrails distinguish accepted current dependencies from new erosion. They should be removed through small, test-protected changes, not a rewrite.
Change process
Architecturally significant changes should:
add or supersede an ADR;
identify affected characteristics and trade-offs;
add or update an executable fitness function, and the matching contract in
.importlinterwhen the change moves a dependency boundary;preserve public contracts or provide a deprecation path; and
update this view when component responsibilities or dependency rules change.