Make routing the sole public hydrology API surface (#3528)#3530
Merged
Conversation
Drop the suffixed *_d8/_dinf/_mfd hydrology functions from the top-level xrspatial namespace; they stay importable from xrspatial.hydro where the routing wrappers dispatch to them. Give each _RoutingDispatch wrapper a __name__ and a docstring describing the routing options. Repoint the one top-level suffixed import in test_dask_task_names.py to xrspatial.hydro. Adds test_routing_public_api.py covering the public surface, the routing dispatch (previously untested), default routing, unknown-routing errors, and stream_order ordering pass-through.
Restructure hydrology.rst so each family leads with the unified wrapper and its routing parameter, listing the d8/dinf/mfd implementations as routing variants. Collapse the README feature matrix from 29 per-variant rows to 13 family rows, noting the routing options.
brendancol
commented
Jun 25, 2026
brendancol
left a comment
Contributor
Author
There was a problem hiding this comment.
PR Review: Make routing the sole public hydrology API surface (#3528)
Blockers (must fix before merge)
-
Three example notebooks still import or call the suffixed hydrology names from the top-level
xrspatial, which this PR removes. They raise ImportError/AttributeError when run:examples/user_guide/27_Stream_Analysis_Dinf_MFD.ipynb(cells importingflow_direction_dinf,flow_direction_mfd,flow_accumulation_mfd,stream_order_dinf,stream_order_mfd,stream_link_dinf,stream_link_mfd)examples/user_guide/11_Hydrology.ipynb(xrspatial.flow_direction_mfd(...)andxrspatial.flow_accumulation_mfd(...))examples/user_guide/32_Flow_Length_MFD.ipynb(from xrspatial import flow_direction_mfd, flow_length_mfd, flow_accumulation_mfd)
These do not fail CI because
nbsphinx_allow_errors = Trueand the notebooks are not in the docs toctree, but the import cell halts the rest of the notebook, so users see broken examples. Switch the calls to the wrapper form, e.g.flow_direction(dem, routing='mfd'). Only touch real API calls and import lines, not variable names likeflow_accum_mfd.
Suggestions (should fix, not blocking)
- None.
Nits (optional improvements)
-
xrspatial/hydro/__init__.py: the docstring set on each_RoutingDispatchinstance is reachable viaxrspatial.flow_direction.__doc__, buthelp(xrspatial.flow_direction)shows the class docstring, not the instance one, because the wrapper is an instance rather than a function. The rst uses manualpy:functiondirectives, so Sphinx output is fine. Interactive discoverability throughhelp()is only partial. Acceptable as-is.
What looks good
- Import removal is surgical: every wrapper import stays, only the suffixed ones go. Verified all 14 wrappers still resolve and are identity-equal to their
xrspatial.hydrocounterparts. - The
routing=dispatch path had no tests before; the newtest_routing_public_api.pycovers dispatch parity for all three routings, default routing, the unknown-routing error, and thestream_orderordering pass-through. - The one in-repo top-level suffixed import (
test_dask_task_names.py) was repointed toxrspatial.hydro; the dask key-prefix assertions are unaffected. routingkwarg name preserved as agreed; no algorithm changes, so backend behavior is untouched.
Checklist
- Algorithm matches reference/paper (no algorithm change)
- All implemented backends produce consistent results (dispatch forwards verbatim)
- NaN handling is correct (unchanged)
- Edge cases are covered by tests (dispatch, default, unknown routing)
- Dask chunk boundaries handled correctly (unchanged)
- No premature materialization or unnecessary copies
- Benchmark exists or is not needed (not needed; no perf change)
- README feature matrix updated
- Docstrings present and accurate
- Example notebooks updated to the new public API (blocker above)
Address review blocker: three user-guide notebooks imported/called the suffixed hydrology names from top-level xrspatial, which this PR removes. Switch them to the wrapper + routing= form (flow_direction(routing='mfd'), flow_accumulation(routing='mfd'), flow_length(routing='mfd', ...), stream_order(routing='dinf', ordering=...), stream_link(routing='mfd', ...)). Also convert the d8 stream_order calls from the fragile method= kwarg to ordering= for consistency, and update two prose cells that named the removed functions.
brendancol
commented
Jun 25, 2026
brendancol
left a comment
Contributor
Author
There was a problem hiding this comment.
Follow-up review (after notebook fixes + main sync)
The one blocker from the previous review is resolved.
Resolved
- Example notebooks updated to the
routing=API. All suffixed top-level references are gone from27_Stream_Analysis_Dinf_MFD.ipynb,11_Hydrology.ipynb, and32_Flow_Length_MFD.ipynb(verified by grep). The MFD/D-inf calls now use the wrapper form (flow_direction(routing='mfd'),flow_accumulation(routing='mfd'),flow_length(routing='mfd', ...),stream_order(routing='dinf', ordering=...),stream_link(routing='mfd', ...)). The two prose cells that named the removed functions were updated. I also converted the d8stream_order(..., method=...)calls toordering=, sincemethod=on the wrapper only works for d8 and is the wrong kwarg to model. Smoke-tested every converted call pattern against a small raster.
Notes
- The edited notebook cells now store
sourceas a single JSON string rather than a list of lines. This is valid nbformat and renders identically; it is an artifact of the editing tool, not a content change. - Merged current
origin/main(templates, geotiff, accessor work); no conflicts, nothing touched hydro.test_routing_public_api.pyandtest_dask_task_names.pystill pass after the merge.
Disposition of original findings
- Blocker (broken example notebooks): fixed.
- Nit (
help()shows the class docstring, not the per-instance one): left as-is by design. Making the wrappers real functions instead of_RoutingDispatchinstances is a larger refactor;xrspatial.flow_direction.__doc__and the Sphinxpy:functiondirectives both carry the routing docs.
CI caught two accessor sites that looked up the suffixed hydrology names on the top-level xrspatial namespace by string, which this PR no longer exports: - accessor._delegated_doc surfaces the documented *_d8 variant docstring on the .xrs hydro methods; fall back to xrspatial.hydro when the name is not top-level so help text is restored. - test_accessor_docstring_matches_source resolves its fill_d8/watershed_d8 source the same way. Fixes the 4 test_accessor.py failures (docstring-match + every-method- documented drift guard) on the full test lane.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3528
The unified routing wrappers (
flow_direction,flow_accumulation, …) and theirrouting='d8'|'dinf'|'mfd'keyword already existed (added in #1020). This PRfinishes the job: the wrappers become the only public top-level surface, and the
suffixed implementations move behind
xrspatial.hydro.Changes
*_d8/*_dinf/*_mfdhydrology functions from the top-levelxrspatialnamespace. They stay importable fromxrspatial.hydro, where thewrappers dispatch to them.
_RoutingDispatchwrapper a__name__and a docstring sohelp(xrspatial.flow_direction)explains the routing options.test_dask_task_names.pytoxrspatial.hydro(the only in-repo caller of a suffixed top-level name).hydrology.rstto lead with each wrapper and list the routingvariants below; collapse the README matrix from 29 rows to 13.
Backend coverage
No algorithm changes. The dispatch forwards verbatim to the existing
implementations, so numpy / cupy / dask+numpy / dask+cupy behavior is unchanged.
Breaking change
from xrspatial import flow_direction_d8(and the other suffixed names) no longerworks. Callers move to
flow_direction(routing='d8')or import fromxrspatial.hydro. Theroutingkwarg name is unchanged.Test plan
test_routing_public_api.py: wrappers public, suffixed names hidden attop level but importable from
xrspatial.hydro, dispatch matches the directimplementation for all three routings, default routing is d8, unknown routing
raises,
stream_orderforwardsordering.test_dask_task_names.pypasses with the repointed import.test_flow_direction_d8,test_flow_direction_dinf,test_flow_accumulation_mfd— no collateral breakage.