Skip to main content

Async Python client for the USACE NWD Dataquery 2.0 hydrologic timeseries endpoint.

Project description

nwd-dataquery

PyPI Python versions License: MIT CI Ruff

Async Python client for the USACE Northwestern Division Dataquery 2.0 hydrologic timeseries endpoint.

The underlying endpoint (https://www.nwd-wc.usace.army.mil/dd/common/web_service/webexec/getjson) is undocumented. This package was reverse-engineered from the Dataquery 2.0 UI and targets NWD-only data (e.g. Lake Washington Ship Canal, Howard Hanson, Mud Mountain). For districts that are migrated to the modern CWMS Data API, use cwms-python instead.

Install

pip install nwd-dataquery            # core: pyarrow output + CLI
pip install nwd-dataquery[polars]    # adds polars frame support
pip install nwd-dataquery[pandas]    # adds pandas frame support

Python ≥3.12.

A note on SSL

The USACE host serves only the leaf certificate, omitting the DigiCert intermediate. Python's stdlib ssl doesn't perform AIA chasing, so default verification fails (unable to get local issuer certificate) on any platform whose TLS stack doesn't fetch missing intermediates on its own. The client transparently fetches the intermediate via the leaf's AIA extension (using aia-chaser) the first time a session is opened to an HTTPS endpoint, and reuses the resulting SSLContext thereafter. No global SSL stack mutation occurs on import.

Alternate endpoint

A public mirror reportedly exists at public.crohms.org (Columbia River Operational Hydromet Management System, a multi-agency partnership) using the same URL paths. Point to it via --endpoint / AsyncDataQueryClient(endpoint=...) if the primary host is unreachable. Unverified.

Quick start (Python)

import asyncio
from datetime import datetime, timezone
from nwd_dataquery import AsyncDataQueryClient

async def main():
    async with AsyncDataQueryClient() as client:
        # Default: last 7 days, pyarrow.Table out
        table = await client.fetch("LWSC.Elev-Lake.Ave.1Hour.0.NWSRADIO-RAW")
        print(table.to_pandas().head())

        # Decade backfill in one request
        backfill = await client.fetch(
            "LWSC.Elev-Lake.Ave.1Hour.0.NWSRADIO-RAW",
            start=datetime(2016, 1, 1, tzinfo=timezone.utc),
            end=datetime(2026, 1, 1, tzinfo=timezone.utc),
        )

        # Metadata only
        meta = await client.describe("LWSC.Elev-Lake.Ave.1Hour.0.NWSRADIO-RAW")

asyncio.run(main())

Switch frame types:

tbl = await client.fetch(tsid)                  # pyarrow.Table (default)
df  = await client.fetch(tsid, backend="polars")  # requires nwd-dataquery[polars]
df  = await client.fetch(tsid, backend="pandas")  # requires nwd-dataquery[pandas]

Quick start (CLI)

nwd-dq fetch LWSC.Elev-Lake.Ave.1Hour.0.NWSRADIO-RAW --lookback 30d
nwd-dq fetch LWSC.Flow-In.Ave.~1Day.1Day.CENWS-COMPUTED-RAW \
    --start 2016-01-01 --format parquet --out flows.pq
nwd-dq describe LWSC.Elev-Lake.Ave.1Hour.0.NWSRADIO-RAW | jq

Exit codes: 0 success, 1 transport error, 2 server/data-query error, 3 empty result with --strict.

Output schema

fetch() returns a long-format frame with columns:

column type meaning
timestamp timestamp[us, tz=UTC] observation time
value float64 measurement
quality int64 server quality flag (may be null)
tsid string CWMS timeseries id
location string location code (LWSC, …)
parameter string parameter name (Elev-Lake, Flow-In, …)
units string server-reported units (FT, CFS, …)

TSID anatomy

The 6-part CWMS identifier: LOC.PARAMETER.TYPE.INTERVAL.DURATION.VERSION.

  • TYPEInst (instantaneous) or Ave (interval-averaged).
  • INTERVAL0, 15Minutes, 1Hour, ~1Day, 1Day. A leading ~ marks irregular cadence.
  • DURATION0 for point observations, or an interval for aggregations.
  • VERSIONSOURCE-QUALITY. Sources observed: NWSRADIO, IRIDIUM, GOES, USGS, USBR, CENWS-COMPUTED, CENWP-COMPUTED, CENWW-COMPUTED, CBT, RFC-NOS, NOAA, MIXED-COMPUTED. Quality is RAW or REV. The special version Best is an alias for whichever source/quality is canonical for that series — prefer it for downstream consumption and keep the raw-version tsids for provenance.

Known tsids

tsid location description period of record
LWSC.Elev-Lake.Ave.1Hour.0.NWSRADIO-RAW LWSC Pool elevation — hourly average, NWS radio DCP, raw 2001–present
LWSC.Elev-Lake.Inst.1Hour.0.NWSRADIO-REV LWSC Pool elevation — hourly instantaneous, NWS radio DCP, reviewed
LWSC.Elev-Lake.Ave.1Hour.1Hour.IRIDIUM-REV LWSC Pool elevation — hourly average, Iridium satellite, reviewed
LWSC.Flow-In.Ave.~1Day.1Day.CENWS-COMPUTED-RAW LWSC Daily average inflow — computed by Seattle District

See Discovering tsids for how to grow this list.

Discovering tsids

This package covers only the getjson endpoint — there is no catalog or search API from its point of view. Practical paths:

1. The Dataquery 2.0 UI. Open https://www.nwd-wc.usace.army.mil/dd/common/dataquery/, navigate to a station, and watch the Network tab in DevTools. XHR requests to webexec/getjson include the tsid in the query= parameter — copy it out.

2. Grammar-based expansion from a seed. Given one tsid you already know, enumerate plausible variants by swapping parts — AveInst, different INTERVALs, different VERSIONs like NWSRADIO-RAWIRIDIUM-REVBest — and probe each with fetch(). An empty payload (triggers UnknownTsidWarning) means the variant doesn't exist or has no data; a non-empty payload gives you a new confirmed tsid. See TSID anatomy for the vocabulary.

3. Track your own list. The endpoint has no "list all" verb. Most users accumulate a curated list of tsids as they explore.

Gotchas

  • Empty payload is ambiguous. The server returns {} for "unknown tsid," "no data in the requested window," and for seasonal tsids that aren't currently deployed (e.g. temporary summer gauges). The client always emits UnknownTsidWarning; you can't distinguish the cases without out-of-band knowledge.
  • Everything comes back as text/plain. Both successful responses and server-side errors use Content-Type: text/plain; charset=UTF-8 and always respond HTTP 200. The client parses the body, checks for a top-level "error" key, and raises DataQueryError if present.
  • Wildcards don't work. ["LWSC.*"] returns {}. Query the UI to discover tsids.
  • Seasonal stations move in and out of the catalog. Some station codes only appear in the Dataquery UI while the underlying gauge is physically deployed. Treat your tsid list as a moving target, not a fixed registry.

Development

uv sync --all-extras --group dev
prek install
uv run pytest              # unit + integration (cassettes)
uv run pytest -m live      # live smoke (requires network)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nwd_dataquery-0.2.0.tar.gz (86.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nwd_dataquery-0.2.0-py3-none-any.whl (12.6 kB view details)

Uploaded Python 3

File details

Details for the file nwd_dataquery-0.2.0.tar.gz.

File metadata

  • Download URL: nwd_dataquery-0.2.0.tar.gz
  • Upload date:
  • Size: 86.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nwd_dataquery-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d6083057bb7519f156cd544cc36c1770478e1b3eb2c9dec291e2c1b2a4f9a0a0
MD5 a8eb3e2bf7677f80f779db6449f5e321
BLAKE2b-256 58ca33c6173023ba6025cba1fa492a4ad974a7fdd3c87e2b1a6a308c5db0a732

See more details on using hashes here.

Provenance

The following attestation bundles were made for nwd_dataquery-0.2.0.tar.gz:

Publisher: release.yml on briandconnelly/nwd-dataquery

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nwd_dataquery-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: nwd_dataquery-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 12.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nwd_dataquery-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7336857ffcf6a97db91950edd99409b438e4475978d914f8dd7c9f51e1228943
MD5 2b205a67d2c32b848338725289c46d0f
BLAKE2b-256 c24b603217417a3f1ce8fb001f95d5b0ed7ac6b9a3adebb7f369d5ce1f38c783

See more details on using hashes here.

Provenance

The following attestation bundles were made for nwd_dataquery-0.2.0-py3-none-any.whl:

Publisher: release.yml on briandconnelly/nwd-dataquery

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page