Skip to content

fix: update attrs and dims when squeezing patches (#623) - #712

Open
gaoflow wants to merge 1 commit into
DASDAE:masterfrom
gaoflow:fix-squeeze-attrs
Open

fix: update attrs and dims when squeezing patches (#623)#712
gaoflow wants to merge 1 commit into
DASDAE:masterfrom
gaoflow:fix-squeeze-attrs

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #623.squeeze() on a (1,1) patch raises AssertionError: dim mismatch on coords and attrs.

Root cause

Two issues:

  1. squeeze() called self.new(data=data, coords=coords) without passing dims or attrs, so update() fell through to PatchAttrs.update(coords=coords) which uses separate_coord_info with the OLD dims — the old dims leaked through.

  2. separate_coord_info's _get_dims treated empty string ("") and empty tuple (()) as falsy, same as None, so it could not distinguish "dims are empty" from "no dims info found". When the squeezed CoordManager reported empty dims (), the dims update was silently skipped.

Changes

  • dascore/proc/coords.py: squeeze() now passes dims=coords.dims and attrs=self.attrs.new(dims=...) to self.new()
  • dascore/utils/attrs.py: _get_dims() uses "dims" in obj instead of truthiness check, returns None when dims can't be determined (vs () when dims are empty); condition at line 292 uses is not None check
  • dascore/core/coordmanager.py: shape property returns () for truly empty dims (0-D scalar), (0,) for 1-D empty; validate_data uses np.empty(self.shape) for None data to match coord shape
  • dascore/io/dasdae/utils.py: Writer handles 0-D data arrays; reader handles missing data node gracefully

Test plan

  • Full test suite: 6132 passed, 124 skipped
  • New regression test: test_squeeze_single_dim_on_1x1_patch
  • Manually verified: aggregate+squeeze scalar, squeeze specific dim, select+squeeze

Summary by CodeRabbit

  • Bug Fixes
    • Corrected empty/0-D behavior in coordinate manager shape and default data initialization.
    • Improved patch save/load robustness for scalar and edge-case data arrays.
    • Ensured squeeze updates coordinate dims and related metadata consistently.
  • Improvements
    • Made dimension parsing deterministic when reading coordinate information, including explicit dims handling.
  • Tests
    • Updated empty coordinate manager expectations.
    • Added coverage for squeezing a single dimension on a small patch and full squeeze behavior.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

CoordManager now represents dimensionless shapes as (), attrs parsing preserves explicit empty dimensions, and squeeze propagates updated metadata. DASDAE I/O now saves scalar data and supports scalar HDF5 reads with IndexError recovery.

Changes

Empty and 0-D Patch Shape Fixes

Layer / File(s) Summary
CoordManager shape and validation
dascore/core/coordmanager.py, tests/test_core/test_coordmanager.py
Dimensionless managers return (), while zero-length dimensional managers return (0,). Default validation data matches the computed shape, and the empty-manager test expects ().
Attrs dimension normalization
dascore/utils/attrs.py
Explicit dims values are normalized, including empty strings to (), while absent values use coordinate inference. Empty dimension tuples can now update attrs.
Squeezed patch metadata propagation
dascore/proc/coords.py, tests/test_proc/test_proc_coords.py
squeeze passes updated dimensions and attrs to the new patch. Tests cover partial squeezing and full scalar squeezing of a 1x1 patch.
DASDAE scalar data persistence
dascore/io/dasdae/utils.py
Scalar data is written to HDF5, and _read_data_array provides array/scalar loading with IndexError recovery in _read_patch.

Possibly related PRs

Suggested labels: IO

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main squeeze metadata fix and references the linked issue.
Description check ✅ Passed The description covers the problem, root cause, changes, and test plan, with only the checklist left mostly unfilled.
Linked Issues check ✅ Passed The changes address #623 by fixing squeeze metadata, empty-dim handling, and 0-D data shape behavior as expected.
Out of Scope Changes check ✅ Passed The added I/O and test updates support the squeeze fix and do not appear unrelated to the issue scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added bug Something isn't working proc Related to processing module labels Jun 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_proc/test_proc_coords.py (1)

684-698: ⚡ Quick win

Add a direct regression for full (1,1) -> 0-D squeeze.

Line 695 currently tests single-dimension squeeze only. Add a patch.squeeze() assertion path to verify dims == (), attrs.dim_tuple == (), and shape == (), which is the core behavior this fix targets.

Suggested test extension
 def test_squeeze_single_dim_on_1x1_patch(self):
@@
     out = patch.squeeze("distance")
     assert out.dims == ("time",)
     assert out.attrs.dim_tuple == ("time",)
     assert out.shape == (1,)
+
+    out_all = patch.squeeze()
+    assert out_all.dims == ()
+    assert out_all.attrs.dim_tuple == ()
+    assert out_all.shape == ()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_proc/test_proc_coords.py` around lines 684 - 698, The test method
test_squeeze_single_dim_on_1x1_patch currently only validates squeezing a single
dimension. Add additional assertions to verify the full squeeze behavior (0-D
array case) by calling patch.squeeze() without arguments on the original patch
data and asserting that dims equals empty tuple (), attrs.dim_tuple equals empty
tuple (), and shape equals empty tuple (). This provides regression coverage for
the complete dimension elimination case that the fix addresses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dascore/io/dasdae/utils.py`:
- Around line 167-180: The IndexError exception handler at line 178 sets
data=None, which creates an invalid input for the dc.Patch constructor call at
line 180, causing the recovery to still fail. Instead of assigning data=None in
the except IndexError block, fall back to extracting the full data array from
patch_group["data"] similar to the else branch behavior (using
patch_group["data"][:]), which provides a valid recovery path while maintaining
the same contract for creating the Patch object with valid data, coords, dims,
and attrs parameters.

---

Nitpick comments:
In `@tests/test_proc/test_proc_coords.py`:
- Around line 684-698: The test method test_squeeze_single_dim_on_1x1_patch
currently only validates squeezing a single dimension. Add additional assertions
to verify the full squeeze behavior (0-D array case) by calling patch.squeeze()
without arguments on the original patch data and asserting that dims equals
empty tuple (), attrs.dim_tuple equals empty tuple (), and shape equals empty
tuple (). This provides regression coverage for the complete dimension
elimination case that the fix addresses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5113984-fb62-4a19-9909-9e9fe8421300

📥 Commits

Reviewing files that changed from the base of the PR and between 51a310a and 29aa430.

📒 Files selected for processing (6)
  • dascore/core/coordmanager.py
  • dascore/io/dasdae/utils.py
  • dascore/proc/coords.py
  • dascore/utils/attrs.py
  • tests/test_core/test_coordmanager.py
  • tests/test_proc/test_proc_coords.py

Comment thread dascore/io/dasdae/utils.py
@gaoflow
gaoflow force-pushed the fix-squeeze-attrs branch from 29aa430 to 691b8b2 Compare June 19, 2026 20:30
@gaoflow

gaoflow commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit follow-up in the latest push.

  • IndexError recovery in DASDAE reads the stored data node instead of passing None to Patch.
  • Added the full patch.squeeze() (1, 1) -> 0-D regression assertions.
  • Split the existing long sub_kwargs comprehension so ruff check passes on the touched file.

Verified locally:

  • uv run pytest tests/test_proc/test_proc_coords.py::TestSqueeze::test_squeeze_single_dim_on_1x1_patch -q
  • uv run pytest tests/test_proc/test_proc_coords.py -q
  • uv run pytest tests/test_io/test_dasdae/test_dasdae.py -q
  • uv run ruff check dascore/io/dasdae/utils.py tests/test_proc/test_proc_coords.py
  • uv run ruff format --check dascore/io/dasdae/utils.py tests/test_proc/test_proc_coords.py
  • git diff --check

@coderabbitai coderabbitai Bot added the patch related to Patch class label Jun 19, 2026
Squeezing a (1,1) patch raised AssertionError because attrs
dim_tuple was not updated to match squeezed coords. The root
cause was twofold:

1. squeeze() called self.new() without dims/attrs, so update()
   used old attrs which preserved the original dim_tuple
2. separate_coord_info()'s _get_dims treated empty string/empty
   tuple dims as falsy, preventing dims from being updated to
   an empty tuple when all dimensions are squeezed

- Pass dims and updated attrs from squeeze to Patch.new()
- Fix _get_dims to distinguish empty dims ('', ()) from no
  dims info (None), using 'in' check instead of truthiness
- Fix CoordManager.shape to return () for truly empty dims,
  not (0,) (which represents 1-D empty, not 0-D scalar)
- Fix CoordManager.validate_data default for 0-D shape
- Handle 0-D data in DASDAE writer/reader

Fixes DASDAE#623
@gaoflow
gaoflow force-pushed the fix-squeeze-attrs branch from 691b8b2 to e00fe15 Compare July 26, 2026 17:09
@coderabbitai coderabbitai Bot added the IO Work for reading/writing different formats label Jul 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dascore/io/dasdae/utils.py`:
- Around line 196-210: Update the data-loading flow around coords.select and
_read_data_array so IndexError from coordinate selection is never swallowed.
Limit the scalar-read fallback to errors from the initial data read, or
materialize the data and retry coords.select while allowing any second selection
error to propagate; preserve the caller’s filtering arguments and avoid
returning unfiltered data after selection failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ff72838e-abc5-4ee8-8e33-24bf56e4a2bc

📥 Commits

Reviewing files that changed from the base of the PR and between 691b8b2 and e00fe15.

📒 Files selected for processing (6)
  • dascore/core/coordmanager.py
  • dascore/io/dasdae/utils.py
  • dascore/proc/coords.py
  • dascore/utils/attrs.py
  • tests/test_core/test_coordmanager.py
  • tests/test_proc/test_proc_coords.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/test_core/test_coordmanager.py
  • dascore/core/coordmanager.py
  • dascore/proc/coords.py
  • dascore/utils/attrs.py
  • tests/test_proc/test_proc_coords.py

Comment on lines +196 to +210
try:
if kwargs:
# We need to remove any coordinates from kwargs that are multi-dim
# coords.
cmap = coords.dim_map
sub_kwargs = {
i: v
for i, v in kwargs.items()
if (i not in cmap) or (len(cmap[i]) == 1)
}
coords, data = coords.select(array=patch_group["data"], **sub_kwargs)
else:
data = _read_data_array(patch_group["data"])
except IndexError:
data = _read_data_array(patch_group["data"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not swallow IndexError from coordinate selection.

This try block covers both coords.select(...) and HDF5 data loading. If selection raises IndexError, the handler returns the original coordinates with the complete, unfiltered data, silently ignoring the caller’s selector. Restrict the scalar-read retry to the data-read operation, or retry selection with materialized data and let a second selection error propagate.

Proposed fix
-    try:
-        if kwargs:
+    if kwargs:
             cmap = coords.dim_map
             sub_kwargs = {
                 i: v
                 for i, v in kwargs.items()
                 if (i not in cmap) or (len(cmap[i]) == 1)
             }
-            coords, data = coords.select(array=patch_group["data"], **sub_kwargs)
-        else:
-            data = _read_data_array(patch_group["data"])
-    except IndexError:
-        data = _read_data_array(patch_group["data"])
+            try:
+                coords, data = coords.select(
+                    array=patch_group["data"], **sub_kwargs
+                )
+            except IndexError:
+                data = _read_data_array(patch_group["data"])
+                coords, data = coords.select(array=data, **sub_kwargs)
+    else:
+        data = _read_data_array(patch_group["data"])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
if kwargs:
# We need to remove any coordinates from kwargs that are multi-dim
# coords.
cmap = coords.dim_map
sub_kwargs = {
i: v
for i, v in kwargs.items()
if (i not in cmap) or (len(cmap[i]) == 1)
}
coords, data = coords.select(array=patch_group["data"], **sub_kwargs)
else:
data = _read_data_array(patch_group["data"])
except IndexError:
data = _read_data_array(patch_group["data"])
if kwargs:
# We need to remove any coordinates from kwargs that are multi-dim
# coords.
cmap = coords.dim_map
sub_kwargs = {
i: v
for i, v in kwargs.items()
if (i not in cmap) or (len(cmap[i]) == 1)
}
try:
coords, data = coords.select(
array=patch_group["data"], **sub_kwargs
)
except IndexError:
data = _read_data_array(patch_group["data"])
coords, data = coords.select(array=data, **sub_kwargs)
else:
data = _read_data_array(patch_group["data"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/io/dasdae/utils.py` around lines 196 - 210, Update the data-loading
flow around coords.select and _read_data_array so IndexError from coordinate
selection is never swallowed. Limit the scalar-read fallback to errors from the
initial data read, or materialize the data and retry coords.select while
allowing any second selection error to propagate; preserve the caller’s
filtering arguments and avoid returning unfiltered data after selection failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working IO Work for reading/writing different formats patch related to Patch class proc Related to processing module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Squeeze 1,1 patch fails

1 participant