Skip to content

Commit c00ab77

Browse files
thodson-usgsclaude
andcommitted
Suppress nested deprecation warnings; pin removal date via constant
`get_record(service='iv')` calls `get_iv`, which calls `query_waterservices` — each of which now emits a DeprecationWarning. Without suppression the user sees three near-identical messages for one call. Walk the call stack inside `_warn_deprecated`: if any ancestor frame is also a deprecated nwis function, skip. Only the outermost call surfaces a warning, regardless of how the wrappers are layered. Add a regression test that pins this contract. Loosen `test_warn_message_includes_replacement` to assert against the `_NWIS_REMOVAL_DATE` constant rather than the literal "2027-05-06" so a future date change updates one place, not two. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6510ff0 commit c00ab77

2 files changed

Lines changed: 34 additions & 3 deletions

File tree

dataretrieval/nwis.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
import sys
910
import warnings
1011
from json import JSONDecodeError
1112

@@ -72,7 +73,19 @@
7273

7374

7475
def _warn_deprecated(func_name: str) -> None:
75-
"""Emit a per-function DeprecationWarning pointing at the waterdata replacement."""
76+
"""Emit a per-function DeprecationWarning pointing at the waterdata replacement.
77+
78+
Suppresses the warning when invoked from another deprecated nwis function so
79+
that wrappers like ``get_record`` -> ``get_iv`` -> ``query_waterservices``
80+
surface only the outermost call (otherwise one user call produces three
81+
near-identical messages).
82+
"""
83+
module_globals = globals()
84+
frame = sys._getframe(2) if hasattr(sys, "_getframe") else None
85+
while frame is not None:
86+
if frame.f_globals is module_globals and frame.f_code.co_name in _REPLACEMENTS:
87+
return
88+
frame = frame.f_back
7689
warnings.warn(
7790
f"`nwis.{func_name}` is deprecated and will be removed from "
7891
f"`dataretrieval` on or after {_NWIS_REMOVAL_DATE}; "

tests/nwis_test.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22
import json
3+
import warnings
34
from pathlib import Path
45
from unittest import mock
56

@@ -145,15 +146,15 @@ def test_warn_message_includes_replacement(
145146
self, func_name, replacement_substring, requests_mock
146147
):
147148
"""Each deprecated function emits a warning naming the right replacement."""
148-
from dataretrieval.nwis import _warn_deprecated
149+
from dataretrieval.nwis import _NWIS_REMOVAL_DATE, _warn_deprecated
149150

150151
# Test the helper directly so we don't need to spin up a fake response
151152
# for every function. The integration is checked once below.
152153
with pytest.warns(DeprecationWarning, match=func_name) as record:
153154
_warn_deprecated(func_name)
154155
message = str(record[0].message)
155156
assert replacement_substring in message
156-
assert "2027-05-06" in message
157+
assert _NWIS_REMOVAL_DATE in message
157158

158159
def test_get_iv_fires_deprecation_on_call(self, requests_mock):
159160
"""End-to-end: a real call routes through _warn_deprecated."""
@@ -164,6 +165,23 @@ def test_get_iv_fires_deprecation_on_call(self, requests_mock):
164165
with pytest.warns(DeprecationWarning, match="get_iv.*waterdata.get_continuous"):
165166
get_iv(sites="01491000")
166167

168+
def test_nested_calls_emit_one_warning(self, requests_mock):
169+
"""get_record(service='iv') wraps get_iv -> query_waterservices.
170+
171+
Without re-entrancy suppression the user would see 3 near-identical
172+
deprecation warnings for one call; pin the outermost-only contract.
173+
"""
174+
requests_mock.get(
175+
"https://waterservices.usgs.gov/nwis/iv",
176+
json={"value": {"timeSeries": []}},
177+
)
178+
with warnings.catch_warnings(record=True) as caught:
179+
warnings.simplefilter("always", DeprecationWarning)
180+
get_record(sites="01491000", service="iv")
181+
deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)]
182+
assert len(deprecations) == 1
183+
assert "get_record" in str(deprecations[0].message)
184+
167185

168186
class TestDefunct:
169187
"""Verify that defunct functions raise NameError."""

0 commit comments

Comments
 (0)