dataretrieval.configuration
Layered configuration: a dataretrieval.configure(...) block holding one
configuration per adapter, then the API_USGS_* environment variables, then
the adapter’s table and the package-wide keys in
~/.dataretrieval/config.toml, then built-in defaults. A
[<adapter>.<name>] table is a named profile, selected in code with
<Adapter>Configuration.load("<name>"). See the
configuration guide for the settings and
worked examples.
Layered configuration resolution for dataretrieval.
Every tunable setting – the Water Data API key, the fan-out concurrency cap,
the retry count, and the progress line – resolves through one ordered chain so
a caller never has to mutate os.environ to configure a single call.
Sources, highest precedence first:
A configuration passed to
configure()– delivered through aContextVar, so a setting applies to the current thread or asyncio task and cannot leak into another one.The environment variable for that setting (
API_USGS_PAT,API_USGS_CONCURRENT,API_USGS_RETRIES,API_USGS_PROGRESS).The configuration file (TOML):
~/.dataretrieval/config.toml, or the path inDATARETRIEVAL_CONFIG. Top-level keys are the package-wide defaults; a[<adapter>]table is that adapter’s default profile, always in effect; a[<adapter>.<name>]table is a named profile, inert until a caller selects it with<Adapter>Configuration.load("<name>").The built-in default.
Those are the four sources, which is the decomposition this module is built
around – one branch each in _resolve(). ADR 0011 states the same order
as seven rungs by splitting three of them into the scopes inside: source 1 into
a configuration instance and a selected profile, which cannot disagree because
both name one adapter and two configurations for one adapter raise; source 3
into the [<adapter>] table above the top-level keys; and source 4 into an
adapter’s own built-in preference above the package default. That last scope is
invisible here because this module never supplies it – it arrives as the
default a read site like concurrency() passes for its own service.
Precedence applies per setting, not per source: an environment that sets only
API_USGS_PAT leaves a file-provided concurrency fully in effect. Putting
the environment above the file follows common deployment conventions and keeps
the original environment-variable interface authoritative (see ADR 0009) – with
one exception ADR 0011 carves out: a profile named in code is a more
deliberate act than a variable inherited from a shell, and a profile reaches the
chain by being passed to configure(), which is above the environment.
A caller configures by passing configuration objects, at most one per adapter:
with dataretrieval.configure(
Configuration(api_key=vault.read("usgs/pat")),
WaterdataConfiguration.load("bulk"),
NgwmnConfiguration(concurrency=4),
):
...
Settings are scoped per adapter (ADR 0010): a [ngwmn] table in the file,
or an NgwmnConfiguration, applies to NGWMN calls and no others, so one block
can be gentle with one service while leaving the rest alone. Precedence stays
source-major: the chain still walks block, then environment, then file, and an
adapter-scoped value outranks a package-wide one only within the same source.
So a variable exported for one run still beats a stale adapter table. Within the
block source that tie-break applies per block: an adapter configuration outranks
a package-wide value set by the same configure call, while a value set by a
block nested inside it wins over both, so the innermost block still decides.
Which settings an adapter accepts is its own vocabulary – concurrency means
nothing to an adapter that issues one request – so each adapter declares them
on its own BaseConfiguration subclass, defined in the module that
reads them. The API key is not among them: it belongs to the gateway fronting
a host, which Water Data and NGWMN share.
This module is a leaf: it imports only the standard library plus the Python 3.10
tomli backport, so any module can depend on it without an import cycle or
pulling in httpx or pandas. That is also why it holds the adapter names but
never imports an adapter – see ADAPTERS. It centralizes each setting’s
parser while retaining legacy environment behavior and stricter validation for
the new Python/TOML surfaces.
- class dataretrieval.configuration.BaseConfiguration[source]
Bases:
objectA named set of settings for one adapter – a configuration profile.
Subclasses declare the settings their adapter reads as fields, and set
adapterto that adapter’s module name. Every field is optional, so an empty configuration is legal and one can be built up conditionally.Frozen, because a configuration is a value: two with the same settings are interchangeable, and one already handed to
configure()must not change under the block that entered it.Values are checked when the configuration is constructed, so a typo raises where it was written rather than at a later
withstatement or, worse, inside a request.- _provenance() str[source]
How
show_configuration()reports a value this supplied.The profile is named in the file’s own spelling –
[waterdata.bulk]– so the report answers “which profile set this?” rather than only “a block did”, and the answer is greppable in the file that holds it. A configuration written in code has no profile, so it names its adapter alone; the package-wide one narrows to nothing and names neither.
- adapter: ClassVar[str | None] = None
The adapter this configuration targets, by the name of the module a caller imports.
Noneon the package-wideConfiguration, which every adapter reads. AClassVar, not a field: the adapter is a property of the class, which is what stops the caller restating it at every call site and stops the roster being spelled twice.
- classmethod load(profile: str) _C[source]
Read a named profile for this adapter from the configuration file.
[<adapter>.<profile>]. Only the keys that table names are carried, so the profile still inherits the adapter’s default profile and the package-wide keys per setting from the tiers below.Selecting a profile the file does not define raises: a name a caller just typed is a typo worth reporting, not a silent fall-through to settings they did not ask for.
- Parameters:
profile (str) – The name after the adapter, so
[waterdata.bulk]is"bulk".- Returns:
An instance of the class it was called on, remembering the profile it was read from so
show_configuration()can name it.- Return type:
- profile: ClassVar[str | None] = None
The named profile these settings were read from, or
Nonefor a configuration written in code. Provenance rather than a setting: it records where the values came from, which is what letsshow_configuration()name the profile that supplied each value instead of reporting every block alike.A
ClassVarshadowed per instance byload(), so it is neither a field nor part of equality – two configurations carrying the same settings stay interchangeable however each was spelled, which is what “a configuration is a value” means.
- validate() None[source]
Check rules that span more than one setting.
Does nothing by default. Per-setting grammar lives in this module’s parsers and is shared with the file and the environment, so a value means the same thing whichever source wrote it; override this only for a rule no single setting can express.
- values() dict[str, Any][source]
The settings actually supplied, omitting those left unset.
An omitted setting inherits from an outer block or a lower source; an explicit
Nonesuppresses them. Distinguishing the two is the whole job of the_UNSETdefault, so it is done here rather than by every reader.
- class dataretrieval.configuration.Configuration(api_key: str | None = <not set>, concurrency: int | str | None = <not set>, retries: int | None = <not set>, progress: bool | str | None = <not set>, parallel_chunks: int | None = <not set>, stall_timeout: float | int | None = <not set>)[source]
Bases:
BaseConfigurationSettings that apply to every adapter.
The package-wide profile:
adapterstaysNone, so nothing narrows and every adapter reads what this sets unless its own configuration, or a block nested inside, overrides that setting.- Parameters:
api_key (str, optional) – Water Data API key, sent as
X-Api-Keyand only ever toapi.waterdata.usgs.gov. Prefer reading it from a secret store, the environment, or the configuration file over writing a literal into a script. PassNoneto make a call without an ambient key.concurrency (int or str, optional) – Cap on simultaneous sub-requests: a positive integer, or
"unbounded"to disable the cap.retries (int, optional) – Retries attempted after a transient failure;
0disables retrying.progress (bool or str, optional) – Whether to draw the progress line.
Noneleaves the automatic behavior (on for a TTY or Jupyter kernel, off otherwise).parallel_chunks (int, optional) – Default optional fan-out for multi-value queries. It limits extra refinement, but URL-byte safety may already require more sub-requests. Sets the baseline that
dataretrieval.parallel_chunks()overrides per call. Each sub-request spends rate-limit quota, so raise it only for pulls you know are large.stall_timeout (float, optional) – Seconds a call may go without receiving any data before retrying stops and the failure surfaces. Bounds the wall-clock cost of a dead connection, which
retriesdoes not – it counts attempts, not seconds. Progress resets the clock;0disables the bound.
Examples
with dataretrieval.configure(Configuration(api_key=vault.read("usgs"))): df, md = waterdata.get_daily(monitoring_location_id="USGS-05114000")
- dataretrieval.configuration.config_path() Path[source]
Path to the configuration file, honoring
DATARETRIEVAL_CONFIG.Memoized on the raw
DATARETRIEVAL_CONFIGvalue, because this sits on the per-request path viaapi_key()and building the default costs more than thestatit leads to (Path.home()alone dominates the whole resolution). Returning a stable object also lets_load_file()check its cache by identity instead of re-normalizing a freshPath.- Returns:
The explicit path from
DATARETRIEVAL_CONFIGif set, otherwise~/.dataretrieval/config.toml. The file need not exist.- Return type:
pathlib.Path
- dataretrieval.configuration.configure(*configurations: BaseConfiguration) Iterator[None][source]
Apply configuration profiles for the duration of a
withblock.The highest-precedence source. Takes configuration objects positionally, at most one per adapter, and nothing else:
with dataretrieval.configure( Configuration(api_key=secrets["usgs"]), WaterdataConfiguration.load("bulk"), NgwmnConfiguration(concurrency=4), ): df, md = waterdata.get_daily(monitoring_location_id=sites)
The adapter a configuration targets is a property of its class, so the caller never restates it – which is what keeps the adapter roster from being spelled once per call site. Naming two configurations for one adapter raises: they are the one pairing with no defined order between them.
Because the block is delivered through a
ContextVar, a value set here applies to the current thread and to asyncio tasks started inside the block, and cannot leak into another thread, task, or unrelated call the wayos.environdoes – which is what makes it safe for a server or notebook handling several users’ credentials at once.Blocks nest and merge per setting: an inner block that sets only
concurrencykeeps the outer block’sapi_key, and an adapter configuration in an outer block loses to a package-wide value set by a block nested inside it, so the innermost block always decides.- Parameters:
*configurations (BaseConfiguration) – A package-wide
Configurationand/or one configuration per adapter, in any order. Each adapter’s class lives in that adapter’s module –WaterdataConfigurationindataretrieval.waterdata,NgwmnConfigurationindataretrieval.ngwmn, and so on.- Yields:
None
- Raises:
ConfigurationError – If an argument is not a configuration, or two of them target the same adapter. Raised on entry, before any request. A bad value raises earlier still, where the configuration was constructed.
Examples
# credentials from a secret store, no environment mutation with dataretrieval.configure( Configuration(api_key=vault.read("usgs/pat")) ): df, md = waterdata.get_daily(monitoring_location_id="USGS-05114000") # a big overnight pull, from a [waterdata.bulk] table in the file with dataretrieval.configure(WaterdataConfiguration.load("bulk")): df, md = waterdata.get_daily(monitoring_location_id=many_sites)
See also
show_configurationReport the effective configuration and where it came from.
- dataretrieval.configuration.settings_for(adapter: str) frozenset[str] | None[source]
The settings adapter accepts, or
Noneif it has not been imported.Noneis not an error and callers must not treat it as one: a file may name an adapter this process has never loaded, and rejecting that would make a configuration file conditionally valid depending on which optional extras happened to be installed. It means “cannot validate these keys yet”, and the adapter cannot be misreading a setting it has not loaded.
- dataretrieval.configuration.show_configuration(*, stream: TextIO | None = None) None[source]
Print the effective configuration and the source of each setting.
A debugging aid for “why is this using my old key?”. Every value is reported with the source that supplied it, named exactly: which variable, which table of the file, and – when a caller selected one – which profile. The API key is never printed, only whether one is set.
- Parameters:
stream (file-like, optional) – Where to write. Defaults to
sys.stdout.
Examples
The sample below is generated by running this function, not written by hand;
test_show_configuration_sample_output_is_currentre-runs it and fails if the two drift apart.>>> with dataretrieval.configure(WaterdataConfiguration.load("bulk")): ... dataretrieval.show_configuration() config file /home/u/.dataretrieval/config.toml (found) api_key <set> /home/u/.dataretrieval/config.toml concurrency 16 /home/u/.dataretrieval/config.toml retries 8 $API_USGS_RETRIES progress auto built-in default parallel_chunks 1 built-in default stall_timeout 60s built-in default A built-in default is package-wide. An adapter may prefer its own for its own calls; a value from any source above overrides both. adapter overrides waterdata parallel_chunks 8 configure() block [waterdata.bulk] ngwmn concurrency 4 /home/u/.dataretrieval/config.toml [ngwmn] profiles in the file: [waterdata.bulk] A profile applies only where a row above names it; select one in code with <Adapter>Configuration.load("<name>"). not reported: nldi (not imported, so the settings each accepts are unknown here)