Skip to content

Make routing the sole public hydrology API surface (#3528)#3530

Merged
brendancol merged 5 commits into
mainfrom
issue-3528
Jun 26, 2026
Merged

Make routing the sole public hydrology API surface (#3528)#3530
brendancol merged 5 commits into
mainfrom
issue-3528

Conversation

@brendancol

Copy link
Copy Markdown
Contributor

Closes #3528

The unified routing wrappers (flow_direction, flow_accumulation, …) and their
routing='d8'|'dinf'|'mfd' keyword already existed (added in #1020). This PR
finishes the job: the wrappers become the only public top-level surface, and the
suffixed implementations move behind xrspatial.hydro.

Changes

  • Drop the suffixed *_d8/*_dinf/*_mfd hydrology functions from the top-level
    xrspatial namespace. They stay importable from xrspatial.hydro, where the
    wrappers dispatch to them.
  • Give each _RoutingDispatch wrapper a __name__ and a docstring so
    help(xrspatial.flow_direction) explains the routing options.
  • Repoint the one top-level suffixed import in test_dask_task_names.py to
    xrspatial.hydro (the only in-repo caller of a suffixed top-level name).
  • Restructure hydrology.rst to lead with each wrapper and list the routing
    variants 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 longer
works. Callers move to flow_direction(routing='d8') or import from
xrspatial.hydro. The routing kwarg name is unchanged.

Test plan

  • New test_routing_public_api.py: wrappers public, suffixed names hidden at
    top level but importable from xrspatial.hydro, dispatch matches the direct
    implementation for all three routings, default routing is d8, unknown routing
    raises, stream_order forwards ordering.
  • test_dask_task_names.py passes with the repointed import.
  • Spot-ran test_flow_direction_d8, test_flow_direction_dinf,
    test_flow_accumulation_mfd — no collateral breakage.

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 brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 importing flow_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(...) and xrspatial.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 = True and 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 like flow_accum_mfd.

Suggestions (should fix, not blocking)

  • None.

Nits (optional improvements)

  • xrspatial/hydro/__init__.py: the docstring set on each _RoutingDispatch instance is reachable via xrspatial.flow_direction.__doc__, but help(xrspatial.flow_direction) shows the class docstring, not the instance one, because the wrapper is an instance rather than a function. The rst uses manual py:function directives, so Sphinx output is fine. Interactive discoverability through help() 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.hydro counterparts.
  • The routing= dispatch path had no tests before; the new test_routing_public_api.py covers dispatch parity for all three routings, default routing, the unknown-routing error, and the stream_order ordering pass-through.
  • The one in-repo top-level suffixed import (test_dask_task_names.py) was repointed to xrspatial.hydro; the dask key-prefix assertions are unaffected.
  • routing kwarg 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 brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 from 27_Stream_Analysis_Dinf_MFD.ipynb, 11_Hydrology.ipynb, and 32_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 d8 stream_order(..., method=...) calls to ordering=, since method= 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 source as 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.py and test_dask_task_names.py still 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 _RoutingDispatch instances is a larger refactor; xrspatial.flow_direction.__doc__ and the Sphinx py:function directives 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.
@brendancol brendancol merged commit 924eb5c into main Jun 26, 2026
12 checks passed
@brendancol brendancol deleted the issue-3528 branch July 1, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make routing the only public hydrology API surface (hide suffixed variants)

1 participant