fix(nwis): handle empty peaks response instead of raising KeyError - #344
Conversation
nwis.get_discharge_peaks / get_record(service="peaks") against a site
with no annual-peak data returns a peaks RDB body of comment lines only,
which read_rdb parses to a column-less empty DataFrame. format_response
runs preformat_peaks_response before its own empty-frame check, and that
function's first statement pops "peak_dt", so the empty case raised
KeyError('peak_dt') instead of returning an empty frame.
This is the same empty-result contract fixed for the other services in
issue DOI-USGS#171; peaks was missed because it is preformatted first. Return the
frame unchanged when peak_dt is absent so the empty-frame path in
format_response handles it and callers can check df.empty.
Adds a regression test alongside the existing DOI-USGS#171 coverage.
Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
The empty-peaks test parsed with _read_rdb, which already runs format_response(service=None); the real get_discharge_peaks path uses the raw read_rdb parser followed by format_response(service="peaks"). Switch to read_rdb so the test exercises the actual call path without the redundant format pass. Signed-off-by: thodson-usgs <thodson@usgs.gov> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Conflict was in tests/nwis_test.py, where main added TestGetRecordDispatch directly after the class this branch appends to; both sides kept. read_rdb has since moved to the dataretrieval.rdb leaf, so the test imports it from there rather than through the nwis adapter, and passes _NWIS_RDB_DTYPES to mirror what get_discharge_peaks now does. Retarget the test docstring at the defect that is actually reachable. get_discharge_peaks does not raise KeyError: every empty peaks response the live service returns starts "No sites/data", which _querying turns into NoSitesError before format_response is reached. The real gap is that format_response and preformat_peaks_response are public API and crash on a column-less frame, where every other service yields an empty one (issue DOI-USGS#171). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for this — the fix is right and I've merged current One correction to the premise, because I couldn't reproduce the headline symptom and I'd rather the test docstring not enshrine a repro that doesn't hold.
So the getter path is already guarded. The defect is real anyway, one level down. That asymmetry is exactly what issue #171 established shouldn't happen, and This also sits inside ADR 0005, which permits "compatibility, security, and correctness fixes only" on the deprecated What I changed while resolving:
Verified: the new test fails with One thing I deliberately left alone: there are now two different empty-result behaviors for NWIS — |
Review follow-ups on the empty-peaks guard. The guard fired on any frame missing peak_dt, including a non-empty one from a truncated or altered RDB header. Such a frame is malformed rather than empty, and was being returned silently without its datetime index where it used to raise. Require df.empty too, and pin it with a test. Correct the regression test's docstring, which said the empty responses get_discharge_peaks sees are caught earlier as NoSitesError. That check fires only on a body starting "No sites/data" -- what the live service happens to send today -- so a comment-only RDB does reach the guarded line. As written the docstring read as "this guard is unreachable", inviting its deletion. Widen the test class docstring to cover both arms it now holds, document the pass-through in preformat_peaks_response's public docstring, and add the NEWS entry this behavior change warrants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Ran a review pass over the branch and pushed the follow-ups. No functional bug in the fix itself; these are contract and documentation issues. The guard was broader than intended. I had overstated the Also: widened the test class docstring so it still describes its contents, documented the pass-through in Verified: 1131 tests pass; One thing I deliberately left out: a mocked test driving Separate follow-up, not folded in here: |
|
@thodson-usgs Your narrowing is right. On the conflicts: I resolved them locally and the branch comes out empty against main. #395 already carries the guard and an equivalent malformed-frame test, and merging this as-is would revert the censored-peaks work. It restores Happy to take the |
Main absorbed this PR's empty-peaks guard via DOI-USGS#395 (censored peaks), so the code and NEWS conflicts resolve to upstream wholesale — keeping this branch's version of preformat_peaks_response would have reverted DOI-USGS#395's keep-censored- peaks behavior (it popped peak_dt and dropped NaT rows). The branch's remaining net contribution is the malformed-frame regression test; its duplicate empty-peaks test (now redundant with main's) and the _NWIS_RDB_DTYPES import it needed are dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JAEQqs7XzQHGQQi2KakuXD
|
@arpitjain099 , the bot found #395 while reviewing your PR, but I merged out of order and it clobbered your fix. Sorry about that. I trimmed this PR to a regression test rather than closing it. Thanks for finding the bug! |
Calling
nwis.get_discharge_peaksorget_record(service="peaks")for a site with no annual-peak data raisesKeyError('peak_dt')instead of returning an empty result.When peaks has no data, the RDB body is comment lines only, so
read_rdbparses it to a column-less empty DataFrame.format_responserunspreformat_peaks_responsebefore its own "datetime not in columns" empty-frame check, and that function's first line popspeak_dt, which blows up on the empty frame.This is the same empty-result behavior that #171 fixed for the other services; peaks slipped through because it gets preformatted first. The fix returns the frame unchanged when
peak_dtis absent, so the existing empty-frame path informat_responsetakes over and callers can checkdf.emptyrather than catching an exception.Added a regression test in
TestReadRdbnext to the existing #171 coverage. It fails on main withKeyError('peak_dt')and passes with the change.ruff checkandruff format --checkare clean on both touched files.I work on supply-chain and data-tooling robustness and hit this while looking at the empty-response paths. Happy to adjust if you would rather guard this inside
format_responseinstead.