Skip to content

Commit 6510ff0

Browse files
thodson-usgsclaude
andcommitted
Deprecate the remaining active nwis functions ahead of 2027-05-06 removal
The module-level "use waterdata instead" warning has been firing on import for a while; this PR makes the migration guidance actionable by emitting a per-function DeprecationWarning that names the specific waterdata replacement the user should switch to. Once peaks (DOI-USGS#267) and ratings (DOI-USGS#269) land, every active nwis function has a waterdata replacement, so all nine of them are deprecated here: nwis.get_dv -> waterdata.get_daily() nwis.get_iv -> waterdata.get_continuous() nwis.get_info -> waterdata.get_monitoring_locations() nwis.what_sites -> waterdata.get_monitoring_locations() nwis.get_stats -> waterdata.get_stats_por() / waterdata.get_stats_date_range() nwis.get_discharge_peaks -> waterdata.get_peaks() nwis.get_ratings -> waterdata.get_ratings() nwis.get_record -> the appropriate waterdata.get_*() nwis.query_waterdata -> a high-level waterdata.get_*() helper nwis.query_waterservices -> a high-level waterdata.get_*() helper (get_qwdata, get_discharge_measurements, get_gwlevels, get_pmcodes, and get_water_use are already defunct and raise NameError.) Implementation follows the nadp deprecation template (DOI-USGS#243): a small _REPLACEMENTS dict + a _warn_deprecated(func_name) helper called as the first line of each public function. stacklevel=3 makes the warning point at the caller's code, not the helper's frame. 11 new parametrized tests pin the warning text — that the function name appears, the replacement helper appears, and the removal date appears — plus one end-to-end test that get_iv() actually fires its warning when called. Removal date is set to 2027-05-06, one full year out (vs. the six months used for nadp), since nwis is much more widely used and most users will need migration time. Maintainer can adjust if desired. This depends on DOI-USGS#267 (waterdata.get_peaks) and DOI-USGS#269 (waterdata.get_ratings) being merged: until then the deprecation messages for get_discharge_peaks and get_ratings point at functions users can't yet call. Hold this PR draft until those land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fca3d6c commit 6510ff0

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
**05/06/2026:** Each remaining active function in `dataretrieval.nwis` now emits a per-function `DeprecationWarning` that names its `waterdata` replacement (`get_dv``get_daily`, `get_iv``get_continuous`, `get_info` / `what_sites``get_monitoring_locations`, `get_stats``get_stats_por` / `get_stats_date_range`, `get_discharge_peaks``get_peaks`, `get_ratings``get_ratings`, `get_record` / `query_waterdata` / `query_waterservices` → the appropriate `waterdata.get_*()` helper). The `nwis` module is scheduled for removal on or after **2027-05-06**.
2+
13
**05/06/2026:** Added `waterdata.get_ratings(...)` — wraps the new Water Data STAC catalog (`api.waterdata.usgs.gov/stac/v0/search`) for USGS stage-discharge rating curves. Returns parsed `exsa` / `base` / `corr` rating tables as a dict of DataFrames keyed by feature ID, or just the list of available STAC features when `download_and_parse=False`. Mirrors R's `read_waterdata_ratings`.
24

35
**05/06/2026:** Added `waterdata.get_field_measurements_metadata(...)` — wraps the OGC `field-measurements-metadata` collection. Returns one row per (location, parameter) field-measurement series describing its period of record, units, etc., without the underlying observations. Discrete-measurement analogue to `get_time_series_metadata`. Mirrors R's `read_waterdata_field_meta`.

dataretrieval/nwis.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,35 @@
5353
}
5454

5555

56+
# Per-function deprecation. The module-level warning above tells users that
57+
# `nwis` overall is being phased out; these replacements tell them which
58+
# `waterdata` function to migrate each call to. Scheduled removal: 2027-05-06.
59+
_NWIS_REMOVAL_DATE = "2027-05-06"
60+
_REPLACEMENTS = {
61+
"get_dv": "`waterdata.get_daily()`",
62+
"get_iv": "`waterdata.get_continuous()`",
63+
"get_info": "`waterdata.get_monitoring_locations()`",
64+
"what_sites": "`waterdata.get_monitoring_locations()`",
65+
"get_stats": "`waterdata.get_stats_por()` or `waterdata.get_stats_date_range()`",
66+
"get_discharge_peaks": "`waterdata.get_peaks()`",
67+
"get_ratings": "`waterdata.get_ratings()`",
68+
"get_record": "the appropriate `waterdata.get_*()` for the service you need",
69+
"query_waterdata": "a high-level `waterdata.get_*()` helper",
70+
"query_waterservices": "a high-level `waterdata.get_*()` helper",
71+
}
72+
73+
74+
def _warn_deprecated(func_name: str) -> None:
75+
"""Emit a per-function DeprecationWarning pointing at the waterdata replacement."""
76+
warnings.warn(
77+
f"`nwis.{func_name}` is deprecated and will be removed from "
78+
f"`dataretrieval` on or after {_NWIS_REMOVAL_DATE}; "
79+
f"use {_REPLACEMENTS[func_name]} instead.",
80+
DeprecationWarning,
81+
stacklevel=3,
82+
)
83+
84+
5685
def _parse_json_or_raise(response: requests.Response) -> pd.DataFrame:
5786
"""Parse a JSON NWIS response, raising a helpful error on HTML responses."""
5887
try:
@@ -216,6 +245,7 @@ def get_discharge_peaks(
216245
... )
217246
218247
"""
248+
_warn_deprecated("get_discharge_peaks")
219249
_check_sites_value_types(sites)
220250

221251
kwargs["site_no"] = kwargs.pop("site_no", sites)
@@ -292,6 +322,7 @@ def get_stats(
292322
... )
293323
294324
"""
325+
_warn_deprecated("get_stats")
295326
_check_sites_value_types(sites)
296327

297328
response = query_waterservices(
@@ -322,6 +353,7 @@ def query_waterdata(
322353
request: ``requests.models.Response``
323354
The response object from the API request to the web service
324355
"""
356+
_warn_deprecated("query_waterdata")
325357
major_params = ["site_no", "state_cd"]
326358
bbox_params = [
327359
"nw_longitude_va",
@@ -391,6 +423,7 @@ def query_waterservices(
391423
The response object from the API request to the web service
392424
393425
"""
426+
_warn_deprecated("query_waterservices")
394427
if not any(
395428
key in kwargs for key in ["sites", "stateCd", "bBox", "huc", "countyCd"]
396429
):
@@ -467,6 +500,7 @@ def get_dv(
467500
>>> df, md = dataretrieval.nwis.get_dv(sites="01646500")
468501
469502
"""
503+
_warn_deprecated("get_dv")
470504
_check_sites_value_types(sites)
471505

472506
kwargs["startDT"] = kwargs.pop("startDT", start)
@@ -572,6 +606,7 @@ def get_info(ssl_check: bool = True, **kwargs) -> tuple[pd.DataFrame, BaseMetada
572606
>>> df, md = dataretrieval.nwis.get_info(sites=["05114000", "09423350"])
573607
574608
"""
609+
_warn_deprecated("get_info")
575610
seriesCatalogOutput = kwargs.pop("seriesCatalogOutput", None)
576611
if seriesCatalogOutput in ["True", "TRUE", "true", True]:
577612
warnings.warn(
@@ -650,6 +685,7 @@ def get_iv(
650685
... )
651686
652687
"""
688+
_warn_deprecated("get_iv")
653689
_check_sites_value_types(sites)
654690

655691
kwargs["startDT"] = kwargs.pop("startDT", start)
@@ -719,6 +755,7 @@ def get_ratings(
719755
>>> df, md = dataretrieval.nwis.get_ratings(site="01594440")
720756
721757
"""
758+
_warn_deprecated("get_ratings")
722759
site = kwargs.pop("site_no", site)
723760

724761
payload = {}
@@ -767,6 +804,7 @@ def what_sites(ssl_check: bool = True, **kwargs) -> tuple[pd.DataFrame, BaseMeta
767804
... )
768805
769806
"""
807+
_warn_deprecated("what_sites")
770808

771809
response = query_waterservices(service="site", ssl_check=ssl_check, **kwargs)
772810

@@ -863,6 +901,7 @@ def get_record(
863901
... )
864902
865903
"""
904+
_warn_deprecated("get_record")
866905
_check_sites_value_types(sites)
867906

868907
defunct_replacements = {

tests/nwis_test.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,53 @@ def test_preformat_peaks_response():
118118
# Removed defunct gwlevels tests.
119119

120120

121+
class TestDeprecationWarnings:
122+
"""Verify per-function DeprecationWarning fires with the right replacement.
123+
124+
The module-level "use waterdata instead" warning fires on import; these
125+
tests pin the function-specific replacements so users see actionable
126+
migration guidance the first time they call each NWIS getter.
127+
"""
128+
129+
@pytest.mark.parametrize(
130+
"func_name, replacement_substring",
131+
[
132+
("get_dv", "waterdata.get_daily"),
133+
("get_iv", "waterdata.get_continuous"),
134+
("get_info", "waterdata.get_monitoring_locations"),
135+
("what_sites", "waterdata.get_monitoring_locations"),
136+
("get_stats", "waterdata.get_stats_por"),
137+
("get_discharge_peaks", "waterdata.get_peaks"),
138+
("get_ratings", "waterdata.get_ratings"),
139+
("get_record", "waterdata.get_*"),
140+
("query_waterdata", "waterdata.get_*"),
141+
("query_waterservices", "waterdata.get_*"),
142+
],
143+
)
144+
def test_warn_message_includes_replacement(
145+
self, func_name, replacement_substring, requests_mock
146+
):
147+
"""Each deprecated function emits a warning naming the right replacement."""
148+
from dataretrieval.nwis import _warn_deprecated
149+
150+
# Test the helper directly so we don't need to spin up a fake response
151+
# for every function. The integration is checked once below.
152+
with pytest.warns(DeprecationWarning, match=func_name) as record:
153+
_warn_deprecated(func_name)
154+
message = str(record[0].message)
155+
assert replacement_substring in message
156+
assert "2027-05-06" in message
157+
158+
def test_get_iv_fires_deprecation_on_call(self, requests_mock):
159+
"""End-to-end: a real call routes through _warn_deprecated."""
160+
requests_mock.get(
161+
"https://waterservices.usgs.gov/nwis/iv",
162+
json={"value": {"timeSeries": []}},
163+
)
164+
with pytest.warns(DeprecationWarning, match="get_iv.*waterdata.get_continuous"):
165+
get_iv(sites="01491000")
166+
167+
121168
class TestDefunct:
122169
"""Verify that defunct functions raise NameError."""
123170

0 commit comments

Comments
 (0)