Skip to content

Commit fda9170

Browse files
thodson-usgsclaude
andcommitted
refactor(ogc): adopt combining module directly; drop dead re-export shims
Follow-up cleanup on the module-boundary refactor so the split lands in the import graph, not just the file layout. - Import the recombination helpers straight from ogc.combining at every call site (chunking, engine, wateruse, tests) and delete planning.py's backwards-compat re-export block + __all__. The new module previously had zero direct importers; everything routed through planning. - Drop the dead TYPE_CHECKING re-export of _Fetch/_Finalize/ _passthrough_result from interruptions.py (no importers) and repoint the lone Sphinx xref in shaping.py to ogc.chunking._Finalize, their real home. - Remove ChunkInterrupted.__setstate__: __getstate__ already writes the _pickled_* snapshot keys and the base DataRetrievalError.__setstate__ restores them via __dict__.update, so the override was redundant (the property getattr fallbacks cover the hand-constructed case). - Correct _classify_transient's docstring and _retryable's comment, which claimed a delegation that does not — and deliberately should not — exist (the two use intentionally different type sets). No behavior change; mypy --strict and the affected test suites pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8534615 commit fda9170

9 files changed

Lines changed: 36 additions & 75 deletions

File tree

dataretrieval/ogc/chunking.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,14 @@
8686
from dataretrieval.utils import HTTPX_DEFAULTS, Ambient, _require_positive_int
8787

8888
from . import progress as _progress
89-
from .interruptions import (
90-
ChunkInterrupted,
91-
)
92-
from .planning import (
93-
ChunkPlan,
89+
from .combining import (
9490
_combine_chunk_frames,
9591
_combine_chunk_responses,
9692
)
93+
from .interruptions import (
94+
ChunkInterrupted,
95+
)
96+
from .planning import ChunkPlan
9797
from .retry import (
9898
_NO_RETRY,
9999
RetryPolicy,

dataretrieval/ogc/engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@
4747
from dataretrieval.ogc import chunking
4848
from dataretrieval.ogc import progress as _progress
4949
from dataretrieval.ogc.chunking import get_active_client
50+
from dataretrieval.ogc.combining import _QUOTA_HEADER, _merge_response, _safe_elapsed
5051
from dataretrieval.ogc.dates import _DATE_RANGE_PARAMS, _format_api_dates
5152
from dataretrieval.ogc.errors import _paginated_failure_message, _raise_for_non_200
52-
from dataretrieval.ogc.planning import _QUOTA_HEADER, _merge_response, _safe_elapsed
5353
from dataretrieval.ogc.shaping import GEOPANDAS, _finalize_ogc, _get_resp_data
5454
from dataretrieval.utils import (
5555
HTTPX_DEFAULTS,

dataretrieval/ogc/interruptions.py

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,6 @@
2222
from dataretrieval.ogc.chunking import ChunkedCall
2323

2424

25-
# These type aliases define the ChunkedCall contract and live in chunking.py;
26-
# re-exported here for backwards compatibility.
27-
if TYPE_CHECKING:
28-
from dataretrieval.ogc.chunking import _Fetch as _Fetch # noqa: F401
29-
from dataretrieval.ogc.chunking import _Finalize as _Finalize # noqa: F401
30-
from dataretrieval.ogc.chunking import ( # noqa: F401
31-
_passthrough_result as _passthrough_result,
32-
)
33-
34-
3525
class ChunkInterrupted(DataRetrievalError):
3626
"""
3727
Base class for mid-stream chunk failures whose completed work is
@@ -134,8 +124,8 @@ def partial_frame(self) -> pd.DataFrame:
134124
"""Live view of work completed so far — delegates to ``call.partial_frame``.
135125
136126
Returns an empty ``DataFrame`` when ``call`` is ``None`` (degraded /
137-
unpickled state), unless a pickled snapshot was restored via
138-
``__setstate__``.
127+
unpickled state), unless a pickled snapshot was restored during
128+
unpickling.
139129
"""
140130
if self.call is not None:
141131
return self.call.partial_frame
@@ -164,24 +154,16 @@ def __getstate__(self) -> dict[str, Any]:
164154
# resume was never possible anyway).
165155
#
166156
# Since ``partial_frame`` and ``partial_response`` are now live
167-
# properties (delegating to ``self.call``), we must snapshot them
168-
# here so the pickled state includes the data that was available
169-
# at serialization time.
157+
# properties (delegating to ``self.call``), snapshot them here into
158+
# private ``_pickled_*`` keys. The base ``__setstate__`` restores
159+
# those keys via ``self.__dict__.update``, and the properties fall
160+
# back to them (through ``getattr``) once ``call`` is ``None`` — so
161+
# no ``__setstate__`` override is needed.
170162
state = {**super().__getstate__(), "call": None}
171163
state["_pickled_partial_frame"] = self.partial_frame.copy()
172164
state["_pickled_partial_response"] = self.partial_response
173165
return state
174166

175-
def __setstate__(self, state: dict[str, Any] | None) -> None:
176-
state = state or {}
177-
# Restore the pickled partial snapshots into private backing attrs
178-
# that the properties will fall back to when call is None.
179-
self._pickled_partial_frame = state.pop(
180-
"_pickled_partial_frame", pd.DataFrame()
181-
)
182-
self._pickled_partial_response = state.pop("_pickled_partial_response", None)
183-
super().__setstate__(state)
184-
185167

186168
class QuotaExhausted(ChunkInterrupted):
187169
"""

dataretrieval/ogc/planning.py

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
:mod:`dataretrieval.ogc.retry` (retry policy), which import the plan and
99
drive it.
1010
11-
Result recombination utilities (:func:`_combine_chunk_frames`,
12-
:func:`_combine_chunk_responses`, etc.) live in the sibling
13-
:mod:`dataretrieval.ogc.combining` module; they are re-exported here for
14-
backwards compatibility.
11+
Result recombination — reassembling the per-chunk frames and responses
12+
back into one result (:func:`_combine_chunk_frames`,
13+
:func:`_combine_chunk_responses`, etc.) — lives in the sibling
14+
:mod:`dataretrieval.ogc.combining` module, which callers import directly.
1515
"""
1616

1717
from __future__ import annotations
@@ -594,30 +594,3 @@ def iter_sub_args(self) -> Iterator[dict[str, Any]]:
594594
for axis, chunk in zip(self.axes, combo, strict=False):
595595
sub_args[axis.arg_key] = axis.render(chunk)
596596
yield sub_args
597-
598-
599-
# ---------------------------------------------------------------------------
600-
# Backwards-compatibility re-exports: these utilities moved to
601-
# ``dataretrieval.ogc.combining`` but existing importers (chunking.py,
602-
# engine.py, wateruse.py, tests) may reference them via ``planning``.
603-
# ---------------------------------------------------------------------------
604-
from dataretrieval.ogc.combining import ( # noqa: E402, F401
605-
_QUOTA_HEADER,
606-
_combine_chunk_frames,
607-
_combine_chunk_responses,
608-
_lowest_remaining,
609-
_merge_response,
610-
_safe_elapsed,
611-
_set_response_url,
612-
)
613-
614-
__all__ = [
615-
"ChunkPlan",
616-
"_QUOTA_HEADER",
617-
"_combine_chunk_frames",
618-
"_combine_chunk_responses",
619-
"_lowest_remaining",
620-
"_merge_response",
621-
"_safe_elapsed",
622-
"_set_response_url",
623-
]

dataretrieval/ogc/retry.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,11 @@ def _classify_transient(
197197
Classify a SINGLE exception as a known transient (resumable) failure.
198198
199199
Does NOT walk the ``__cause__`` chain — inspects only the exception
200-
passed in. This is the shared taxonomy that both
201-
:func:`_classify_chunk_error` (chain-walking) and :func:`_retryable`
202-
(top-level only) delegate to, so a new error type need only be added
203-
in one place.
200+
passed in. Factored out of :func:`_classify_chunk_error` (which walks
201+
the ``__cause__`` chain and calls this on each link) so the transient
202+
taxonomy lives in one place. :func:`_retryable` deliberately does *not*
203+
delegate here — it uses a narrower set (``httpx.InvalidURL`` and a bare
204+
``httpx.HTTPError`` are resumable but not worth an automatic retry).
204205
205206
Parameters
206207
----------
@@ -292,9 +293,12 @@ def _retryable(exc: BaseException) -> tuple[bool, float | None]:
292293
(seconds) when the transient carried one, else ``None``.
293294
"""
294295
# Only initial-page raw transients (TransientError, TransportError) are
295-
# retryable — not wrapped mid-pagination DataRetrievalError. We use
296-
# _classify_transient for the typed status errors and handle bare
297-
# TransportError (which isn't a TransientError) separately.
296+
# retryable — not a wrapped mid-pagination DataRetrievalError. This is a
297+
# deliberately narrower taxonomy than _classify_transient's: RateLimited
298+
# and ServiceUnavailable are caught via their TransientError base, but
299+
# httpx.InvalidURL and bare httpx.HTTPError are intentionally excluded
300+
# (a too-long cursor or non-transport HTTP error won't fix on retry), so
301+
# this stays a separate check rather than delegating to _classify_transient.
298302
if isinstance(exc, TransientError):
299303
return True, exc.retry_after
300304
if isinstance(exc, httpx.TransportError):

dataretrieval/ogc/shaping.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def _finalize_ogc(
373373
as :class:`~dataretrieval.utils.BaseMetadata`.
374374
375375
Injected into the chunker as its ``finalize`` hook (see
376-
:data:`~dataretrieval.ogc.interruptions._Finalize`) so the
376+
:data:`~dataretrieval.ogc.chunking._Finalize`) so the
377377
un-interrupted return *and* a resumed ``ChunkInterrupted.call.resume()``
378378
produce the same post-processed ``(DataFrame, BaseMetadata)`` shape, not
379379
the chunker's raw frame and bare ``httpx.Response``.

dataretrieval/wateruse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353

5454
from dataretrieval.codes.states import to_state
5555
from dataretrieval.exceptions import DataRetrievalError
56+
from dataretrieval.ogc.combining import _combine_chunk_frames, _combine_chunk_responses
5657
from dataretrieval.ogc.engine import _paginate, _run_sync
57-
from dataretrieval.ogc.planning import _combine_chunk_frames, _combine_chunk_responses
5858
from dataretrieval.utils import (
5959
HTTPX_DEFAULTS,
6060
BaseMetadata,

tests/waterdata_chunking_test.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
multi_value_chunked,
4848
parallel_chunks,
4949
)
50+
from dataretrieval.ogc.combining import (
51+
_QUOTA_HEADER,
52+
_combine_chunk_frames,
53+
_combine_chunk_responses,
54+
)
5055
from dataretrieval.ogc.interruptions import (
5156
ChunkInterrupted,
5257
QuotaExhausted,
@@ -56,10 +61,7 @@
5661
_LIST_SEP,
5762
_NEVER_CHUNK,
5863
_OR_SEP,
59-
_QUOTA_HEADER,
6064
ChunkPlan,
61-
_combine_chunk_frames,
62-
_combine_chunk_responses,
6365
_extract_axes,
6466
_request_bytes,
6567
_safe_request_bytes,

tests/wateruse_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def test_fan_out_surfaces_final_rate_limit_header(httpx_mock):
306306
assert md.header["x-ratelimit-remaining"] == "850"
307307

308308

309-
# (response aggregation now reuses ogc.planning._combine_chunk_responses; the
309+
# (response aggregation now reuses ogc.combining._combine_chunk_responses; the
310310
# integration test above pins the rate-limit-header behavior end-to-end.)
311311

312312

0 commit comments

Comments
 (0)