Skip to content

FIX: legacy open_*_datatree regressions after #335 - #484

Merged
kmuehlbauer merged 15 commits into
openradar:mainfrom
aladinor:fix/datatree-legacy-regressions
Oct 13, 2026
Merged

kmuehlbauer merged 15 commits into
openradar:mainfrom
aladinor:fix/datatree-legacy-regressions

Conversation

@aladinor

@aladinor aladinor commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Closes #481. Follow-up to #335.

#335 made the deprecated open_*_datatree functions and xd.open_datatree call each backend's open_datatree directly. That skipped xarray's dataset wrapping (chunks, caching, default indexes) and dropped most backend_kwargs. Both now go through xr.open_datatree, with one shared helper (_open_legacy_datatree in common.py) for the 12 deprecated wrappers.

Fixed (each checked against 77534e4, main before #335)

Before #335 #335 This PR
open_odim_datatree(f, chunks={}) (also gamic, iris, rainbow, datamet, cfradial1, cfradial2), xd.open_datatree(..., chunks={}) dask TypeError dask
backend_kwargs=dict(first_dim="time") (8 engines) honoured silently ignored honoured
legacy optional=False (iris, hpl, metek) works TypeError works
range index on iris, rainbow, furuno, datamet sweeps yes no yes
optional_groups=True: station vars in /radar_parameters no yes no

Also:

  • The reindex_angle FutureWarning is emitted once and points at the caller. Before, it fired once per sweep (14 times for one ODIM volume) and pointed inside xradar. The NEXRAD "Dropped incomplete sweeps" warnings and the CfRadial2 warnings also point at the caller.
  • open_uf_datatree has its signature and docstring back.
  • Docstrings no longer list parameters an engine rejects (fix_second_angle on rainbow and datamet, optional on imd), and IRIS drops an unused group parameter.
  • backend_kwargs given to the NEXRAD and UF wrappers may contain decoders such as decode_times again.

Behaviour changes worth knowing

  • CfRadial1 and CfRadial2: the backend option for the netCDF reader is now netcdf_engine. engine collided with xarray's own engine argument, so xr.open_datatree(f, engine="cfradial1", backend_kwargs={"engine": ...}) could not work. The legacy open_cfradial1_datatree(engine=...) and open_cfradial2_datatree(engine=...) still accept engine. ENH: add xarray-native open_datatree with engine= parameter #335 is not released yet, so no released API changes.
  • As in xr.open_datatree, a keyword given both directly and in backend_kwargs takes the backend_kwargs value. Passing both site_coords and site_as_coords raises TypeError, as IMD already did.
  • With chunks={}, root variables are chunked too, because xarray chunks the whole tree.
  • NEXRAD sweeps get a range index like the other engines.
  • CfRadial2 now reads the file in one pass and closes it before returning, so the data are in memory rather than lazy. Going through xr.open_datatree made xarray reopen the closed file for lazy reads, and with several handles on the same file netCDF4/HDF5 segfaulted intermittently, locally and once in CI. A CfRadial2 tree now holds no open file handle.

Tests

New tests in tests/io/test_backend_datatree.py compare legacy output with xd.open_datatree for every deprecated function. They also cover:

  • chunks;
  • backend_kwargs, including decoders and Optional;
  • optional=False, optional_groups and site_as_coords;
  • the range index and xd vs xr equality;
  • which sweep is returned;
  • the reindex_angle warning;
  • the UF signature, docstrings, netcdf_engine and CfRadial2 close().

Mutation checks (reverting each hunk on its own) confirmed a test fails for each fix.

  • pytest -n 4 --dist loadfile tests: 1085 passed, 18 skipped
  • black --check ., ruff check .
  • pytest docs/notebooks: 25 passed. Two failed only because ffmpeg (multiple-sweeps-into-volume-scan) and pyart (Gridding) are not installed locally.
  • Comparison harness: 13 engines × 25 keyword cases against 77534e4. The only differences are the intended ones above, or cases that raised before and now work.

Not in this PR (listed in #481)

#472 touches common.py too; whichever merges second resolves the conflict.

openradar#335 made the deprecated open_*_datatree functions and xd.open_datatree
call the backend's open_datatree directly, which skipped xarray's dataset
wrapping and dropped most backend_kwargs. Both now go through
xr.open_datatree (shared _open_legacy_datatree helper), so:

- chunks, cache and other xarray options work again
- backend_kwargs reach the backend; xarray options passed there are
  lifted to the top level; legacy `Optional` and `site_as_coords` map to
  `optional` and `site_coords` (passing both site spellings raises)
- legacy optional=False no longer raises "multiple values"
- iris, rainbow, furuno and datamet sweeps keep their range index

Also:
- metadata subgroups no longer copy latitude/longitude/altitude
- reindex_angle is resolved once per tree; its FutureWarning, the NEXRAD
  drop warnings and the CfRadial2 warnings point at the caller
- cfradial1/cfradial2 backend option for the netCDF reader is
  netcdf_engine (engine collided with xarray's argument); the legacy
  engine= keyword maps to it
- CfRadial2 keeps its inner file open for the tree's lifetime and
  DataTree.close() closes it (closing and reopening crashed netCDF4)
- open_uf_datatree has its signature and docstring back; docstrings no
  longer list parameters an engine rejects; IRIS drops an unused `group`

Closes openradar#481
Legacy open_*_datatree output must equal xd.open_datatree for the same
options; cover chunks, backend_kwargs (incl. decoders and `Optional`),
optional=False, optional_groups, site_as_coords, the range index,
xd vs xr equality, sweep identity, the reindex_angle warning, the UF
signature, docstrings, netcdf_engine and CfRadial2 close.

Refs openradar#481
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.69%. Comparing base (6c07893) to head (147ba3b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #484      +/-   ##
==========================================
+ Coverage   95.54%   95.69%   +0.14%     
==========================================
  Files          29       29              
  Lines        6783     6759      -24     
==========================================
- Hits         6481     6468      -13     
+ Misses        302      291      -11     
Flag Coverage Δ
notebooktests 0.00% <0.00%> (ø)
unittests 95.69% <100.00%> (+0.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The legacy open_cfradial1_datatree and open_cfradial2_datatree map an inner netCDF `engine` given in backend_kwargs to netcdf_engine; check it reaches the reader.
Going through xr.open_datatree made xarray reopen the CfRadial2 file for
lazy reads after the backend had closed it; with several handles on the
same file netCDF4/HDF5 segfaulted intermittently (locally and once in
CI). The backend now loads the normalized groups inside the `with` block
and closes the file before returning, so a tree holds no open handle.

Refs openradar#481
@aladinor
aladinor requested review from kmuehlbauer and syedhamidali and removed request for syedhamidali October 11, 2026 00:42
@kmuehlbauer

Copy link
Copy Markdown
Collaborator
  • CfRadial2 now reads the file in one pass and closes it before returning, so the data are in memory rather than lazy. Going through xr.open_datatree made xarray reopen the closed file for lazy reads, and with several handles on the same file netCDF4/HDF5 segfaulted intermittently, locally and once in CI. A CfRadial2 tree now holds no open file handle.

@aladinor Just to get this right, when reading CfRadial2 we now materialize everything in memory? Is there no way to have this data lazy? Can this be eventually fixed on xarray side?

There where some changes in xarray recently (pydata/xarray#11692 and pydata/xarray#11629) which might resolve this (not checked).

Another question wrt CfRadial2, what's the difference in using xr.open_datatree(..., engine="netcdf4") compared to our engine="cfradial2"?

Comment thread xradar/io/backends/cfradial2.py Outdated

@syedhamidali syedhamidali left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @aladinor, I tested this independently. The full suite passes locally (1084 passed). Compared with the commit before #335, I ran 10 engines × 10 legacy keyword cases: all four regressions from #481 are fixed, and reindex_angle now warns once at the caller (14 → 1).

Findings:

  1. IMD regression: open_imd_datatree(f, backend_kwargs=dict(Optional=False)) worked before #335 and on main (IMD has no optional variables and ignored it), but raises TypeError here, because the helper maps it to optional= and the engine rejects it. The helper could drop optional for engines that don't take it; a test for it would be good too.
  2. Not in the description: NEXRAD and UF now honour first_dim in backend_kwargs (dims time instead of azimuth), and UF also gets a range index. Both are consistent with the other engines, just worth listing.

On @kmuehlbauer's CfRadial2 question: with a copy of a CfRadial2 file that I delete before reading, engine="cfradial2" on main is lazy but fails with FileNotFoundError (it reopens the closed file), while here the read works and no handle stays open. engine="netcdf4" stays lazy with one open handle. With chunks={} you get dask arrays, but over already-loaded data. Same values as engine="netcdf4", plus platform_is_mobile and wmo__cf_profile in the root and a length-1 frequency variable in each sweep.

Comment thread xradar/io/backends/cfradial2.py Outdated
@kmuehlbauer

Copy link
Copy Markdown
Collaborator

@aladinor CI is finally green. Please feel free to use any of that or remove if you are not satisfied or have another solution in mind. I also did not want to fiddle around with the history.md entry. It's already quite long. But here is what I did in the last commits:

  • Test Suite Refactoring: Wrapped xr.open_datatree invocations inside test_xr_open_datatree_cfradial2 using a explicit context manager (with statement) to enforce deterministic resource cleanup per test execution loop.
  • Backend Refactoring: Removed eager memory loading (.load()) in xradar/io/backends/cfradial2.py. Preserved target file handle reference at tree root via dtree["/"].set_close(tree._close) and routed through datatree_from_dict_with_io_cleanup to enable lazy loading and eliminate repetitive backend disk reopening requests.
  • Parallel Execution Alignment: Synchronized shared session fixture setup across pytest-xdist runners by wrapping file generation hooks with a FileLock mechanism (only cfradial1_file and cfradial2_file).
  • Environment Maintenance: Enforced Python >= 3.12 constraints and dropped legacy pip dependency definitions for upstream xarray builds in favor of conda-forge packages.

@aladinor

aladinor commented Oct 12, 2026 •

Copy link
Copy Markdown
Member Author

Thanks @kmuehlbauer for making CfRadial2 lazy and CI green, and @syedhamidali for the thorough testing! CI changes stay here. Next: single cache layer + close on errors, check the IMD Optional case vs main, update changelog/description, then review.

@aladinor

aladinor commented Oct 12, 2026 •

Copy link
Copy Markdown
Member Author

@kmuehlbauer I tried to move forward with the suggested additions, but CI kept failing on a flaky segfault. It comes from pydata/xarray#11692 (unreleased): on d23bab1, test_backend_datatree.py run serially crashed 8/10 times with xarray 2026.9.0 and 0/10 with xarray main. We strongly suggest waiting for the next xarray release or testing against main until then.

kmuehlbauer pushed a commit that referenced this pull request Oct 12, 2026
* ci: test on Python 3.12-3.14 with numpy 2

main requires the checks "xradar unit tests - linux (3.12, 2)" and
"(3.14, 2)", which the old 3.11/3.13 matrix never produced, so no PR
could merge. Take the matrix and CI environments from #484 by
@kmuehlbauer (8607831, 5fe4348): Python 3.12, 3.13 and 3.14 with
numpy 2, python>=3.12 and xarray from conda, filelock for the tests.

* build: require Python >= 3.12

CI no longer tests 3.11. Set requires-python to >=3.12, list 3.12-3.14
in the classifiers, and pin the dev/ReadTheDocs environment to
python >=3.12 so the docs build cannot resolve to an unsupported
version.

* docs: link issue #487 and PR #488 in the changelog
@kmuehlbauer

Copy link
Copy Markdown
Collaborator

@aladinor Just for testing, can you run this for xarray main? I'll catch up tomorrow.

@aladinor

Copy link
Copy Markdown
Member Author

@kmuehlbauer done: with xarray main, CI is green on 3.12–3.14, no segfault (6d05c94).

@kmuehlbauer
kmuehlbauer enabled auto-merge (squash) October 13, 2026 04:20
@aladinor

Copy link
Copy Markdown
Member Author

@syedhamidali IMD Optional raised the same TypeError before #335, so it is not a #484 regression. @kmuehlbauer cfradial2 gives the same values as netcdf4, plus sweep and FM301 normalization. Follow-ups: #495, #496, #497.

@aladinor
aladinor deleted the fix/datatree-legacy-regressions branch October 13, 2026 12:22
kmuehlbauer pushed a commit that referenced this pull request Oct 13, 2026
aladinor added a commit to aladinor/xradar that referenced this pull request Oct 13, 2026
aladinor added a commit that referenced this pull request Oct 14, 2026
* ci: test against released xarray again, time out hung test jobs

The temporary xarray main install from #484 only moved the flaky HDF5
crash between Python versions; #503 fixed it. The unit, notebook and
nightly jobs time out after 30 minutes instead of GitHub's 6 hours.

* docs: link #495 and #510 in the changelog

* docs: keep one #484 changelog entry, without the xarray main note

* docs: shorten the #510 changelog entry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Regressions in legacy open_*_datatree after #335 (chunks, backend_kwargs, optional, range index)

3 participants