Skip to content

Share the repeated FiberIO scan/read scaffolding - #859

Merged
d-chambers merged 4 commits into
devfrom
io-dry-cleanup
Aug 11, 2026
Merged

Share the repeated FiberIO scan/read scaffolding#859
d-chambers merged 4 commits into
devfrom
io-dry-cleanup

Conversation

@d-chambers

@d-chambers d-chambers commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

The fiber IO format readers had each grown their own copy of the same scaffolding. This is the first of two DRY passes over dascore/io; it takes the mechanical, behavior-preserving half and leaves the parts that would change how a new FiberIO is written to a follow-up.

Two shared helpers replace the copies:

  • make_scan_payload (already existed privately, now public and used everywhere) takes dims and shape from the coords unless given them. 17 readers hand-rolled the payload dict literal; 10 more already called the helper but still passed dims=coords.dims, shape=coords.shape.
  • dascore.io.utils.build_patches performs the read tail every single-patch reader shares: apply the caller's selections, drop the patch if nothing is left, attach attrs. 15 readers had their own spelling of it.

Also in scope:

  • The four byte-identical _maybe_trim_data helpers (gdr, neubrex ×2, h5simple) collapse to one code path.
  • Evenly sampled coordinates are built with get_coord(..., shape=(n,)) instead of a hand-computed stop, which also removes the .change_length(n) corrections that existed to fix up float rounding.
  • The unused ProdMLPatchAttrs classes in dascore/io/prodml/core.py and dascore/io/dashdf5/core.py are deleted. Both were dead: nothing in the repo referenced either, and the dashdf5 one was a verbatim copy including its "Patch attrs for ProdML" docstring. ProdMLRawPatchAttrs in prodml/utils.py is the copy the reader actually uses.
  • Docstrings that named terra15 in unrelated modules are corrected.

Net −115 lines of library code: the 28 converted reader modules lose 192 lines, against 77 for defining and documenting the two shared helpers. The whole diff is +52 overall, because it also adds 145 lines of tests for the new helpers and 22 of docs/changelog. The consistency win is the bigger one: payload construction and the read tail now each have a single implementation.

Behavior changes worth flagging

These are small but real, and are recorded in the changelog:

  • APSensing and HDAS only checked for an emptied patch inside the trim branch, so an already-empty source yielded a zero-size Patch instead of no patch. They now match every other format.
  • GDR_DAS and Neubrex read() declare time/distance explicitly rather than absorbing them from **kwargs (the removed _maybe_trim_data had already been dropping everything else).
  • A source declaring zero samples now yields an empty coordinate rather than raising a validation error, since the coordinate is built from a sample count instead of stop.

Verification

  • Full suite: 8873 passed, 97 skipped, 2 xfailed. Doctests: 149 passed. pre-commit run --all clean.
  • An A/B of scan / full read / trimmed read / empty-selection read against dev over all 55 registry files (29 formats) produces byte-identical output — patch data sums, coord types/min/max/step/units, attrs classes and values, PatchSummary dumps, and patch counts all match.
  • The get_coord(..., shape=(n,)) conversion was fuzzed over 20k random (start, step, n) triples against the old stop-arithmetic-plus-change_length form: zero mismatches for all n ≥ 1.
  • Local IO benchmarks show no regression (scan 204→194ms, scan_df 268→266ms, get_format 55→50ms); CodSpeed will have the authoritative numbers.

Reviewed by Codex plus three internal passes (behavior equivalence, API/conventions, coverage). Their findings are folded into the second commit — the substantive one was that build_patches originally took selections as **kwargs, which collided with its own parameter names once h5simple forwarded dc.read's injected file_version/_pre_cast through it; it now takes an explicit selection mapping.

Follow-up

PR 2 will cover the higher-risk half: a template-method base class for the ~12 formats whose scan/read reduce to a single _get_attrs_coords_and_data hook, and converting the hand-rolled format fingerprints onto the existing h5_matches_structure / extract_h5_attrs helpers. That one changes what a new FiberIO must implement, so it comes with the docs/contributing/new_format.qmd rewrite.

Changelog

  • changed: readers share make_scan_payload and dascore.io.utils.build_patches instead of each carrying its own copy; an already-empty source now yields no patch from APSensing and HDAS, matching every other format.
  • fixed: evenly sampled coordinates are built from a sample count rather than a hand-computed stop, so a source declaring zero samples yields an empty coordinate instead of raising a validation error.
  • removed breaking: the unused ProdMLPatchAttrs classes in dascore.io.prodml.core and dascore.io.dashdf5.core; ProdMLRawPatchAttrs in dascore.io.prodml.utils is the one the reader uses.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • New Features

    • Added standardized scan payload creation across supported formats.
    • Added shared patch-building behavior with consistent selection, trimming, validation, and empty-result handling.
    • Added optional time and distance filtering for additional readers.
    • Coordinate metadata now derives reliably from requested sample shapes.
  • Documentation

    • Updated I/O contribution guidance and the unreleased changelog for the new APIs and behavior.
  • Bug Fixes

    • Corrected several format descriptions and improved consistency in scan and read results.

The format readers had each grown their own copy of the same three
idioms: the scan payload dict literal, the select-then-build-patch tail
of read, and start/step/count coordinate arithmetic.

- Make `make_scan_payload` public and let it take `dims`/`shape` from
  the coords, then use it for the 17 hand-rolled payload dicts.
- Add `dascore.io.utils.build_patches` for the read tail and use it in
  the 15 readers that had their own spelling of it. This also settles a
  drift: ap_sensing and hdas only dropped an empty patch when a trim was
  requested, so an already-empty file yielded a zero-size Patch.
- Drop the four duplicate `_maybe_trim_data` helpers.
- Build evenly sampled coords with `shape=` rather than computing stop
  by hand, which also removes the `change_length` corrections.
- Delete the unused, copy-pasted `ProdMLPatchAttrs` classes from
  dashdf5 and prodml (`ProdMLRawPatchAttrs` in prodml/utils.py is the
  one actually used), and fix docstrings that named terra15 in
  unrelated modules.
- build_patches takes an explicit `selection` mapping with keyword-only
  attrs/attr_cls. Readers forward `**kwargs` into it (h5simple), and
  dc.read always injects file_version/_pre_cast, so name collisions
  turned kwargs dev ignored into a TypeError.
- Validate attrs before the empty-selection early return, so bad
  metadata still raises on a read which selects nothing.
- Index data with an Ellipsis so 0d data also loads.
- Add type hints and examples to both helpers, and direct tests for
  them.
- Drop the dead `dascore.io.build_patches` export; document it as
  `dascore.io.utils.build_patches`, matching get_exact_coord.
- Add the changelog entry and give the read-side helper its own docs
  section.
@d-chambers d-chambers added ready_for_review PR is ready for review benchmark Run the benchmark suite documentation Improvements or additions to documentation labels Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@d-chambers, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b358f5cc-4583-4b91-920a-226cbc00eea8

📥 Commits

Reviewing files that changed from the base of the PR and between e5fa96f and 3c3e688.

📒 Files selected for processing (19)
  • dascore/io/ai4eps/core.py
  • dascore/io/ap_sensing/core.py
  • dascore/io/core.py
  • dascore/io/dashdf5/core.py
  • dascore/io/dasvader/core.py
  • dascore/io/febus/core.py
  • dascore/io/gdr/core.py
  • dascore/io/h5simple/core.py
  • dascore/io/hdas/core.py
  • dascore/io/neubrex/core.py
  • dascore/io/odh4/core.py
  • dascore/io/optodas/core.py
  • dascore/io/segy/core.py
  • dascore/io/silixah5/core.py
  • dascore/io/sintela/core.py
  • dascore/io/sintela/protobuf_utils.py
  • dascore/io/sr4731/utils.py
  • dascore/io/utils.py
  • dascore/io/xml_binary/utils.py
📝 Walkthrough

Walkthrough

Changes

The PR adds public make_scan_payload and build_patches helpers. IO readers now use standardized scan payloads, shared patch construction, explicit coordinate shapes, and centralized filtering and empty-result handling. Tests and format documentation cover the new APIs.

IO standardization

Layer / File(s) Summary
Shared helper contracts and validation
dascore/io/core.py, dascore/io/utils.py, tests/test_io/test_io_core.py, docs/contributing/new_format.qmd
Adds public payload and patch-building helpers with derived metadata, selection handling, attribute validation, empty-result behavior, and test coverage.
Scan payload migration
dascore/io/*/core.py, dascore/io/*/utils.py
Migrates format-specific scan methods from inline dictionaries and _make_scan_payload to make_scan_payload.
Reader patch construction
dascore/io/*/utils.py, dascore/io/*/core.py
Migrates reader implementations to build_patches for filtering, patch creation, and empty-result handling.
Explicit coordinate sizing
dascore/io/*/utils*.py
Creates time and distance coordinates with explicit shapes instead of endpoint calculations and length adjustments.

Possibly related PRs

Suggested labels: IO, patch

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 99.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: sharing repeated FiberIO scan and read scaffolding.
Description check ✅ Passed The description explains the changes, behavior impacts, verification results, documentation, tests, and checklist status in sufficient detail.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch io-dry-cleanup

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 IO Work for reading/writing different formats patch related to Patch class labels Aug 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5fa96fd5f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dascore/io/core.py Outdated
Comment on lines +101 to +103
coords,
dims=(),
shape=(),
dims=None,
shape=None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Annotate every parameter in the new public helpers

When plugin authors or static-analysis tools consume these newly documented public helpers, coords, dims, and shape here—and data and attrs in build_patches—are exposed as untyped Any, preventing callers from checking the intended contracts. Add explicit annotations for every parameter before publishing these helpers as public API. .agents/agents.mdL80-L80

Useful? React with 👍 / 👎.

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.

Fixed in 5cd32be — annotated coords/dims/shape on make_scan_payload and data/attrs on build_patches.

@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_io/test_io_core.py (1)

327-426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct tests for the remaining build_patches contract paths.

Add a scalar-data test for the data[...] path. Add a test that invalid attrs still raise when a selection removes all samples. These branches are part of the new shared helper contract.

🤖 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_io/test_io_core.py` around lines 327 - 426, Add direct contract
tests to TestBuildPatches: cover scalar input through the data[...] path in
build_patches, and verify invalid attrs still raise even when the selection
removes every sample. Use the existing patch fixture and build_patches symbols,
preserving the current empty-list behavior only for valid attrs.
🤖 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/ap_sensing/core.py`:
- Line 44: Update the resource parameter descriptions to identify the input as
an already-open reader rather than a filesystem path: use an open HDF5 reader
description in dascore/io/ap_sensing/core.py lines 44-44 and
dascore/io/dashdf5/core.py lines 29-34, and an open binary reader description in
dascore/io/sintela/core.py lines 51-51. Preserve the existing format-author
documentation structure.

---

Nitpick comments:
In `@tests/test_io/test_io_core.py`:
- Around line 327-426: Add direct contract tests to TestBuildPatches: cover
scalar input through the data[...] path in build_patches, and verify invalid
attrs still raise even when the selection removes every sample. Use the existing
patch fixture and build_patches symbols, preserving the current empty-list
behavior only for valid attrs.
🪄 Autofix

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: 85901dad-d41d-4d9d-ad9b-ea765fb7680d

📥 Commits

Reviewing files that changed from the base of the PR and between c97545e and e5fa96f.

📒 Files selected for processing (46)
  • dascore/io/__init__.py
  • dascore/io/ai4eps/core.py
  • dascore/io/ai4eps/utils.py
  • dascore/io/ap_sensing/core.py
  • dascore/io/ap_sensing/utils.py
  • dascore/io/core.py
  • dascore/io/dasdae/utils.py
  • dascore/io/dashdf5/core.py
  • dascore/io/dashdf5/utils.py
  • dascore/io/dasvader/core.py
  • dascore/io/dasvader/utils.py
  • dascore/io/febus/a1utils.py
  • dascore/io/febus/core.py
  • dascore/io/febus/t1utils.py
  • dascore/io/gdr/core.py
  • dascore/io/gdr/utils_das.py
  • dascore/io/h5simple/core.py
  • dascore/io/h5simple/utils.py
  • dascore/io/hdas/core.py
  • dascore/io/hdas/utils.py
  • dascore/io/mseed/utils.py
  • dascore/io/netcdf/core.py
  • dascore/io/neubrex/core.py
  • dascore/io/neubrex/utils_das.py
  • dascore/io/neubrex/utils_rfs.py
  • dascore/io/odh4/core.py
  • dascore/io/odh4/utils.py
  • dascore/io/optodas/core.py
  • dascore/io/optodas/utils.py
  • dascore/io/prodml/core.py
  • dascore/io/prodml/utils.py
  • dascore/io/segy/core.py
  • dascore/io/sentek/core.py
  • dascore/io/silixah5/core.py
  • dascore/io/silixah5/utils.py
  • dascore/io/sintela/core.py
  • dascore/io/sintela/protobuf_utils.py
  • dascore/io/sintela/utils.py
  • dascore/io/sr4731/utils.py
  • dascore/io/tdms/core.py
  • dascore/io/terra15/utils.py
  • dascore/io/utils.py
  • dascore/io/xml_binary/utils.py
  • docs/changelog.qmd
  • docs/contributing/new_format.qmd
  • tests/test_io/test_io_core.py
💤 Files with no reviewable changes (1)
  • dascore/io/neubrex/utils_rfs.py

Comment thread dascore/io/ap_sensing/core.py Outdated
@codspeed-hq

codspeed-hq Bot commented Aug 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 79 untouched benchmarks


Comparing io-dry-cleanup (3c3e688) with dev (c97545e)

Open in CodSpeed

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (c97545e) to head (3c3e688).

Additional details and impacted files
@@            Coverage Diff            @@
##               dev      #859   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          176       176           
  Lines        19372     19275   -97     
=========================================
- Hits         19372     19275   -97     
Flag Coverage Δ
network 48.11% <92.15%> (-0.25%) ⬇️
unittests 100.00% <100.00%> (ø)

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.

- Annotate the remaining parameters of the two public helpers; they are
  the documented entry point for plugin authors, so an untyped `Any`
  hides the contract.
- The get_format docstrings described `resource` as a path, but the
  signature takes an already open reader.
@d-chambers

Copy link
Copy Markdown
Contributor Author

Note on the red network_tests (ubuntu-latest) job: it is the known stall that #784 diagnoses, not a regression from this PR.

The timeout is test_remote_http.py::TestHTTPFormatAndSpool::test_spool_file_path, and the stack hangs in fsspec's event-loop wait (fsspec/asyn.py:91 sync() -> event.wait(1)) reached from dascore/utils/remote_io.py:348 — remote-IO plumbing this PR does not touch. Both of #784's causes line up: its cause 1 converts the very fixture this test uses (http_regression_das_path, still a single-threaded HTTPServer at tests/test_io/conftest.py:301 on dev) to ThreadingHTTPServer for exactly this 30s-timeout symptom, and its cause 2 is the h5py-lock/GC deadlock on the fsspec loop thread that the stack shows.

Consistent with a timing coin flip rather than anything branch-specific: unrelated branches hit the same failure in bursts (inventory-spec failed 4 consecutive runs yesterday), it does not reproduce locally on either this branch or dev (6 full -m network runs, 5-9s each), and the job is continue-on-error — run 31478115709 concluded success overall with it red.

Everything else is green: full matrix on 3 OSes x Python 3.11-3.14, min-deps, free-threaded, wasm, codecov patch+project, and CodSpeed reports 79 untouched benchmarks.

The two helpers' docstrings and the exploded call sites had eaten most
of the duplication this PR removes. Drop the Examples sections (both
helpers are covered by unit tests and 13 call sites), tighten the prose,
and remove the magic trailing comma from the generated calls so the
formatter can keep the short ones on one line.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

✅ Documentation built:
👉 Download
Note: You must be logged in to github and a DASDAE member to access the link.

@d-chambers
d-chambers merged commit 8a4f22e into dev Aug 11, 2026
27 of 28 checks passed
@d-chambers
d-chambers deleted the io-dry-cleanup branch August 11, 2026 11:06
d-chambers added a commit that referenced this pull request Aug 11, 2026
Dev's shared FiberIO scan/read scaffolding (#859) touched most of the
readers this branch had already rewritten for the attr vocabulary, so
several files needed both changes rather than either.

Resolutions worth noting: io/utils.py keeps both sides' helpers, since
each added different functions to the same region; prodml/core.py and
dashdf5/core.py take dev's deletion of the unused ProdMLPatchAttrs, the
real classes living in prodml/utils.py where this branch's changes
already applied; febus/t1utils.py uses dev's make_scan_payload with this
branch's _T1_ATTRS, which never carried the provenance dev's version
strips; sr4731/utils.py gains dev's extras plumbing on top of this
branch's interrogator.* naming; and h5simple keeps dev's structure minus
the two lines putting file_format and file_version back into patch
attrs, which this branch removed and which the vocabulary conformance
test caught immediately.
@d-chambers d-chambers removed the ready_for_review PR is ready for review label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmark Run the benchmark suite documentation Improvements or additions to documentation IO Work for reading/writing different formats patch related to Patch class

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant