Skip to content

add support for sintela protobuf format - #656

Merged
d-chambers merged 13 commits into
devfrom
sintela_proto
Jul 19, 2026
Merged

add support for sintela protobuf format#656
d-chambers merged 13 commits into
devfrom
sintela_proto

Conversation

@d-chambers

@d-chambers d-chambers commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds read and scan support for Sintela's MTLV-wrapped protobuf .pb recordings. It supports format detection without importing protobuf, promotes selected recorder metadata, and decodes supported timeseries, band, and FFT packet families into DASCore patches with validated coordinates.

No linked issue.

Validation

Local validation on the rebased branch:

  • pre-commit run --all-files
  • pytest tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py -q
  • pytest tests/test_io/test_common_io.py tests/test_io/test_io_core.py -q
  • pytest tests --cov dascore --cov-report term-missing --cov-fail-under=100

CI status before the latest review-fix commit was green for lint, full test matrix, min-deps matrix, and Codecov; benchmark/docs jobs were skipped by workflow policy.

Changelog

  • added: a Sintela protobuf reader.
  • removed breaking: dascore.io.sintela_binary (no alias); both Sintela readers now live in dascore.io.sintela. Reading through dc.read/dc.spool/dc.scan is unaffected.

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 support for a new Sintela protobuf recording format, including reading, scanning, and format detection.
    • Expanded supported data type values to include additional spectral and band-energy options.
  • Bug Fixes

    • Improved handling of remote IO and timeout cases for more reliable test behavior.
    • Adjusted remote format coverage so unsupported Sintela protobuf cases are excluded from remote-only checks.

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds SintelaProtobufV1 FiberIO for reading Sintela protobuf MTLV recordings, implemented via a new protobuf_utils.py module. Relocates SintelaBinaryV3 under the consolidated dascore.io.sintela package, updates entry points and optional dependencies, extends VALID_DATA_TYPES, and adds a comprehensive test suite with synthetic protobuf fixtures.

Sintela Protobuf IO

Layer / File(s) Summary
VALID_DATA_TYPES and attrs contracts
dascore/constants.py, dascore/io/sintela/protobuf_utils.py
Adds power_spectral_density and frequency_band_energy to VALID_DATA_TYPES; defines tag constants, dimension-name tuples, and public dataclasses (EnvelopeRecord, ParsedMeta, SintelaProtobufAttrs, TimeseriesMetadata, BandMetadata, FFTMetadata).
Envelope parsing and format detection
dascore/io/sintela/protobuf_utils.py
Implements the MTLV envelope iterator with magic/tag/length/payload validation, get_supported_family_tag for format detection, and optional-protobuf error standardization.
Dynamic protobuf message classes and META parsing
dascore/io/sintela/protobuf_utils.py
Dynamically constructs and caches protobuf message classes via descriptor_pb2/DescriptorPool for CommonHeader, Timeseries, Band, and FFT packets (full and scan variants); builds META message class and parses META payloads into ParsedMeta.
Record parsing, coordinate building, and attrs construction
dascore/io/sintela/protobuf_utils.py
Implements _parse_records to decode packets and extract META; provides coordinate-building utilities for time/distance/frequency; constructs SintelaProtobufAttrs; validates single-family content and extracts CommonFields.
Per-family metadata normalization
dascore/io/sintela/protobuf_utils.py
Normalizes timeseries, band, and FFT packets into validated metadata objects with coords and attrs; checks sample contiguity, band alignment, and FFT bin consistency.
Full decode, scan-only paths, and public entry points
dascore/io/sintela/protobuf_utils.py
Allocates and reshapes numpy arrays per family (including complex FFT); scan-only paths skip sample allocation; defines family dispatch dicts; exposes read_payload and scan_payload.
dascore.io.sintela package and SintelaProtobufV1 FiberIO
dascore/io/sintela/__init__.py, dascore/io/sintela/core.py, dascore/io/sintela_binary/__init__.py, pyproject.toml, dascore/data_registry.txt
Creates the consolidated dascore.io.sintela package re-exporting both IO classes; adds SintelaProtobufV1 FiberIO with get_format/scan/read; clears sintela_binary init; updates entry points and adds protobuf dependency.
Test timeout handling and remote test matrix
tests/test_io/_common_io_test_utils.py, tests/test_io/test_remote_memory.py, tests/test_io/test_remote_common_io.py, tests/test_io/test_remote_http.py
Extends _is_timeout_error to classify pytest.fail.Exception timeouts; adds _skip_if_timeout_else_raise; updates skip_on_timeout to catch BaseException; introduces memory_fetch_copy fixture; excludes Sintela_Protobuf from remote IO test matrix.
Common IO test matrix wiring
tests/test_io/test_common_io.py, tests/test_io/test_sintela/test_binary.py
Updates imports to use dascore.io.sintela; adds SintelaProtobufV1 to COMMON_IO_READ_TESTS with sintela_protobuf_1.pb fixture.
SintelaProtobufV1 test suite
tests/test_io/test_sintela/test_protobuf.py
Adds synthetic envelope fixtures for timeseries/band/FFT; TestSintelaProtobuf covers format detection, scan/read equivalence, dims/coords/attrs, coordinate selection, and extensive error/malformed-input tests; TestSintelaProtobufUtils covers lower-level helper correctness.

Possibly related PRs

  • DASDAE/dascore#516: Touches the SINTELA_BINARY__V3 fiber IO entry point and VALID_DATA_TYPES, which this PR also updates when relocating SintelaBinaryV3 and extending accepted data type values.

Suggested labels

IO

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding Sintela protobuf format support.
Description check ✅ Passed The description includes the required sections for Description, Validation, and Checklist and is sufficiently complete.
Docstring Coverage ✅ Passed Docstring coverage is 96.59% 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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sintela_proto

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 the IO Work for reading/writing different formats label Apr 8, 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: 44bc09c41d

ℹ️ 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 on lines +68 to +69
from google.protobuf import descriptor_pb2, descriptor_pool, message_factory
from google.protobuf import timestamp_pb2

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 Guard Sintela protobuf tests behind optional dependency

The new Sintela protobuf test helpers import google.protobuf directly, but this dependency is not guaranteed in the project’s test environments (for example, the min-deps workflow installs .[test] and then runs the full tests suite). In environments without protobuf, invoking these helpers raises ModuleNotFoundError and fails the entire test run instead of skipping optional-format coverage, so this change can break CI for unrelated work.

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.

Still open, deliberately. protobuf is listed in the test extra, so the suite always has it and there is no import error; the missing-dependency path itself is covered by test_missing_protobuf_only_affects_scan_and_read, which monkeypatches the import to confirm detection still works while scan/read raise. The tradeoff is that min-deps CI installs [test] and so no longer exercises the genuinely-absent case, which differs from the pymseed convention (extras only, plus pytest.importorskip). Raised with the maintainer; happy to switch to the importorskip pattern if preferred.

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.

Done in 7b9f60a — switched to the pymseed convention: protobuf dropped from the test extra, with a module-level pytest.importorskip("google.protobuf") in the protobuf tests. Verified both directions: with protobuf present 63 tests run; without it the module skips cleanly (no errors), and the common_io matrix already tolerates this via skip_missing(). The concrete payoff is that min-deps is now genuinely protobuf-free: an eager top-level import google.protobuf in the reader previously passed all 71 tests, and now fails at collection.

Comment thread dascore/io/sintela_protobuf/utils.py Outdated
Comment on lines +475 to +476
int(getattr(acquisition, "fiber_id"))
if acquisition is not None and getattr(acquisition, "fiber_id", None) is not 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.

P2 Badge Preserve missing fiber_id instead of coercing to zero

This presence check treats any existing acquisition_stats message as having fiber_id, because unset protobuf scalar fields still read back as 0 rather than None. As a result, files where acquisition_stats is present but fiber_id is unset will be reported with fiber_id=0, silently corrupting metadata in scan/read outputs.

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.

Already addressed in a later commit — _parse_meta guards with HasField("fiber_id") and yields None when the field is absent, so a present-but-empty acquisition_stats no longer coerces to zero. Verified: a META payload with acquisition_stats set but fiber_id unset parses to fiber_id=None, and _build_meta_payload_without_fiber_id covers it.

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dascore/io/sintela_protobuf/utils.py`:
- Around line 93-133: The parser currently buffers all records because
_iter_envelope_records returns a list; change _iter_envelope_records to be a
generator (yield EnvelopeRecord) that reads and yields each EnvelopeRecord as it
is parsed (preserve the same header/size/error handling and EnvelopeRecord
construction), update its return/type hint accordingly, and modify
get_supported_family_tag to iterate over the generator (not collect into a list)
so it can return the first supported tag immediately (ensure it still skips
META_TAG and checks TS_TAGS|BAND_TAGS|FFT_TAGS).
- Around line 454-459: The ParseFromString call in _parse_meta currently runs
inside suppress_warnings() but can raise google.protobuf.message.DecodeError;
catch that exception and re-raise it as InvalidFiberFileError with a descriptive
message. Locate _parse_meta(), wrap msg.ParseFromString(payload) in a try/except
that catches DecodeError (from google.protobuf.message) and raises
InvalidFiberFileError (preserving or including the original exception message).
Apply the same pattern to the other ParseFromString call referenced in this
module so both protobuf parses consistently convert DecodeError into
InvalidFiberFileError.

In `@pyproject.toml`:
- Line 147: The test extras are missing the protobuf runtime so imports like
google.protobuf used by the SINTELA_PROTOBUF__V1 entry point will fail; update
pyproject.toml to add the protobuf package to the .[test]
(project.optional-dependencies test) list so tests install it, e.g. add
"protobuf" to the test extras alongside other test deps, ensuring the
SINTELA_PROTOBUF__V1 entry point can import google.protobuf during test runs.
🪄 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: 9c1c2f0e-2b7f-4f6e-be63-f2bcae0ded19

📥 Commits

Reviewing files that changed from the base of the PR and between da295d3 and 44bc09c.

📒 Files selected for processing (8)
  • dascore/data_registry.txt
  • dascore/io/sintela_protobuf/__init__.py
  • dascore/io/sintela_protobuf/core.py
  • dascore/io/sintela_protobuf/utils.py
  • pyproject.toml
  • tests/test_io/test_common_io.py
  • tests/test_io/test_remote_memory.py
  • tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py

Comment thread dascore/io/sintela_protobuf/utils.py Outdated
Comment thread dascore/io/sintela_protobuf/utils.py Outdated
Comment thread pyproject.toml Outdated
@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (a063147) to head (2042f6e).
⚠️ Report is 2 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##               dev      #656    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          162       163     +1     
  Lines        16544     17023   +479     
==========================================
+ Hits         16544     17023   +479     
Flag Coverage Δ
network 48.96% <28.59%> (-0.65%) ⬇️
unittests 99.98% <100.00%> (+<0.01%) ⬆️

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.

@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: 2

🧹 Nitpick comments (1)
tests/test_io/_common_io_test_utils.py (1)

82-85: Consider documenting the broad BaseException catch.

Catching BaseException is intentionally broad to intercept any timeout-related exception, including those from pytest-timeout. While the logic correctly re-raises non-timeout exceptions, a brief inline comment explaining why BaseException (rather than Exception) is caught would help future maintainers understand this is deliberate.

💡 Suggested comment
         try:
             yield
-        except BaseException as exc:
+        except BaseException as exc:  # Broad catch to handle pytest-timeout's exceptions
             if not _is_timeout_error(exc):
                 raise
             pytest.skip(str(exc))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_io/_common_io_test_utils.py` around lines 82 - 85, Add a brief
inline comment above the "except BaseException as exc:" block in
tests/test_io/_common_io_test_utils.py explaining that BaseException is
intentionally used (not Exception) to catch timeout-related exceptions raised by
pytest-timeout or similar frameworks, while still re-raising non-timeout errors
via the existing _is_timeout_error(exc) check; reference the existing
_is_timeout_error function in the comment so future maintainers understand the
deliberate design and that non-timeouts are re-raised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dascore/io/sintela_protobuf/utils.py`:
- Around line 606-609: In _get_time_coord_from_samples, validate the sample_rate
before computing step: check that sample_rate is finite (not NaN/Inf), positive,
and non-zero; if it fails, raise InvalidFiberFileError with a clear message;
otherwise compute step = dc.to_timedelta64(1 / sample_rate) and call
get_coord(start=start, stop=start + step * size, step=step) as before. Ensure
you reference the function _get_time_coord_from_samples and helpers
dc.to_timedelta64 and get_coord in the change.
- Around line 557-560: The current presence check for the optional protobuf
INT32 field uses getattr(acquisition, "fiber_id", None) which treats unset
fields as 0; replace that check with acquisition is not None and
acquisition.HasField("fiber_id") and then cast the value (e.g.,
int(acquisition.fiber_id)) to produce the same result or None otherwise. Update
the expression that sets the fiber_id to use acquisition.HasField("fiber_id")
(matching how identification and metadata_recording_time are checked) and ensure
the final value remains int(...) when present or None when absent.

---

Nitpick comments:
In `@tests/test_io/_common_io_test_utils.py`:
- Around line 82-85: Add a brief inline comment above the "except BaseException
as exc:" block in tests/test_io/_common_io_test_utils.py explaining that
BaseException is intentionally used (not Exception) to catch timeout-related
exceptions raised by pytest-timeout or similar frameworks, while still
re-raising non-timeout errors via the existing _is_timeout_error(exc) check;
reference the existing _is_timeout_error function in the comment so future
maintainers understand the deliberate design and that non-timeouts are
re-raised.
🪄 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: 1e73a58b-ec72-402b-8f72-4d55159187fd

📥 Commits

Reviewing files that changed from the base of the PR and between 44bc09c and c6ea016.

📒 Files selected for processing (8)
  • dascore/io/sintela_protobuf/core.py
  • dascore/io/sintela_protobuf/utils.py
  • pyproject.toml
  • tests/test_io/_common_io_test_utils.py
  • tests/test_io/test_remote_common_io.py
  • tests/test_io/test_remote_http.py
  • tests/test_io/test_remote_memory.py
  • tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • pyproject.toml
  • tests/test_io/test_remote_memory.py
  • dascore/io/sintela_protobuf/core.py
  • tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py

Comment thread dascore/io/sintela_protobuf/utils.py Outdated
Comment thread dascore/io/sintela/protobuf_utils.py
@coderabbitai coderabbitai Bot added the patch related to Patch class label Apr 9, 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: 4

🧹 Nitpick comments (1)
tests/test_io/_common_io_test_utils.py (1)

82-87: Optional: extract duplicated timeout-handling block into a helper.

The two except BaseException blocks are identical; a small helper (e.g., _skip_if_timeout_else_raise(exc)) would reduce drift risk.

Also applies to: 101-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_io/_common_io_test_utils.py` around lines 82 - 87, There is
duplicated exception handling logic for timeout errors in two `except
BaseException` blocks around the timeout handling code. Extract this logic into
a helper function named like `_skip_if_timeout_else_raise(exc)` that takes the
caught exception object, checks if it is a timeout error using
`_is_timeout_error`, raises it if not, or calls `pytest.skip` with the exception
string if it is. Replace both original except blocks in the test utils
(including the one at 101-104) to call this new helper, reducing code
duplication and drift risk.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dascore/io/sintela_protobuf/utils.py`:
- Around line 907-919: The code uses bin_res as the step to build the frequency
axis (frequency = get_coord(...)) without validating it's a finite, positive
value; add the same validation you used for sample_rate by checking bin_res
(from _assert_float_equal / variable bin_res) is a finite number and > 0 before
calling get_coord, and raise InvalidFiberFileError with a clear message if the
check fails so malformed FFT headers fail predictably instead of inside
get_coord.
- Around line 613-621: The _get_distance_coord function must validate its
spacing and step inputs (spacing must be finite and > 0; step must be a positive
integer) before calling get_coord; if validation fails, raise
InvalidFiberFileError with a clear message including the offending spacing/step
values so format-specific errors surface (this affects callers building
timeseries/band/FFT coords). Implement checks at the top of _get_distance_coord
for spacing (use math.isfinite and >0) and for step (ensure int-like and >0),
and raise InvalidFiberFileError with context (e.g., "invalid channel_spacing X"
/ "invalid channel_step Y") instead of letting get_coord raise.
- Around line 866-876: The current code uses band_def[0] to set
attrs.extra['data_type'] and 'data_units', which mislabels packets that contain
multiple band semantics; instead inspect all entries in band_def (e.g., iterate
band_def and convert each entry's type with _BAND_DATA_TYPE_MAP) and derive a
consolidated value: if all bands map to the same data_type/data_units keep that
single value, otherwise set a clear "mixed" or aggregated value (e.g., a
tuple/list of types or "mixed" and empty units) before passing into _base_attrs
so attrs.data_type and attrs.data_units accurately reflect mixed-band packets.

In `@tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py`:
- Around line 766-769: The test constructs protobuf payloads
(_build_meta_payload and _build_ts_payloads) before the monkeypatch that
simulates missing protobuf, causing failures during file construction; change
the test_missing_protobuf_only_affects_scan_and_read to use a checked-in .pb
fixture (or copy a prebuilt ts.pb blob into path) instead of calling
_build_meta_payload/_build_ts_payloads before patching, or alternatively move
the calls to _build_meta_payload/_build_ts_payloads to after the monkeypatch is
applied; ensure the test then asserts get_format() still works while
scan()/read() raise under the monkeypatched missing-protobuf environment and
keep references to _write_records, get_format, scan, and read to locate the
changes.

---

Nitpick comments:
In `@tests/test_io/_common_io_test_utils.py`:
- Around line 82-87: There is duplicated exception handling logic for timeout
errors in two `except BaseException` blocks around the timeout handling code.
Extract this logic into a helper function named like
`_skip_if_timeout_else_raise(exc)` that takes the caught exception object,
checks if it is a timeout error using `_is_timeout_error`, raises it if not, or
calls `pytest.skip` with the exception string if it is. Replace both original
except blocks in the test utils (including the one at 101-104) to call this new
helper, reducing code duplication and drift risk.
🪄 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: 0a302fc7-fcc2-4e1d-978a-bf9eb5678b00

📥 Commits

Reviewing files that changed from the base of the PR and between c6ea016 and 0fb6d82.

📒 Files selected for processing (3)
  • dascore/io/sintela_protobuf/utils.py
  • tests/test_io/_common_io_test_utils.py
  • tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py

Comment thread dascore/io/sintela/protobuf_utils.py
Comment thread dascore/io/sintela_protobuf/utils.py Outdated
Comment thread dascore/io/sintela/protobuf_utils.py Outdated
Comment thread tests/test_io/test_sintela_protobuf/test_sintela_protobuf.py Outdated

@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: 3

🤖 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/sintela/core.py`:
- Around line 92-95: The get_format path in SintelaCore is probing the
BinaryReader from the current offset, which can cause false negatives after
prior reads. Update SintelaCore.get_format to rewind or restore the stream
position before calling get_supported_family_tag, matching the behavior of
SintelaBinaryV3.get_format so reused BinaryReader instances are handled
correctly.

In `@dascore/io/sintela/protobuf_utils.py`:
- Around line 769-774: The num_channels value from the header is only being
checked for equality, so non-positive values can still slip through and later
break coord/shape construction. Add an explicit positive validation for
num_channels before any distance coord or output array creation, using the
existing validation flow around _assert_equal and the channel header handling in
protobuf_utils. Keep the check close to where num_channels is derived from
common_headers so invalid headers fail early with a clear error.
- Around line 943-946: The protobuf parsing path currently accepts FFT num_bins
without rejecting zero or negative values, which can later break frequency
coordinate construction and array sizing. Update the validation in the
header-processing logic around _assert_equal("num_bins", ...) to explicitly
require num_bins to be positive before any coord or scan/read shape is built,
and raise InvalidFiberFileError for malformed values. Keep the check close to
the existing num_bins/bin_res validation in the protobuf_utils flow so it is
enforced before array allocation.
🪄 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: c8111e88-1b56-4557-95e3-71192f436926

📥 Commits

Reviewing files that changed from the base of the PR and between 2425f39 and 6a70d7c.

📒 Files selected for processing (11)
  • dascore/constants.py
  • dascore/io/sintela/__init__.py
  • dascore/io/sintela/core.py
  • dascore/io/sintela/protobuf_utils.py
  • dascore/io/sintela/utils.py
  • dascore/io/sintela_binary/__init__.py
  • pyproject.toml
  • tests/test_io/_common_io_test_utils.py
  • tests/test_io/test_common_io.py
  • tests/test_io/test_sintela/test_binary.py
  • tests/test_io/test_sintela/test_protobuf.py
💤 Files with no reviewable changes (1)
  • dascore/io/sintela_binary/init.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_io/test_common_io.py
  • tests/test_io/_common_io_test_utils.py

Comment thread dascore/io/sintela/core.py
Comment thread dascore/io/sintela/protobuf_utils.py Outdated
Comment thread dascore/io/sintela/protobuf_utils.py Outdated
@d-chambers

Copy link
Copy Markdown
Contributor Author

@codex please review

@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: f0d45744d9

ℹ️ 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/sintela/protobuf_utils.py Outdated

PBUF_MAGIC = 0x46554250
META_TAG = "META"
TS_TAGS = frozenset({"TS05", "RF01"})

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 Stop advertising RF01 until its raw frames are decoded

For valid RF01 packets whose samples are carried in the declared raw_frames field, including this tag makes format detection and scanning accept the file, but TimeseriesMetadata.decode() reads only msg.samples. That array is empty for raw-frame packets, so every read fails with Unexpected Sintela protobuf TS sample payload size; either decode raw_frames or exclude RF01 from the supported tag set.

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.

Addressed in 2d1e1c2, though not exactly as suggested. raw_frames was declared in the schema but never read, and there is no RF01 coverage or test data here, so the concern is real. Rather than dropping RF01 from detection, decode() now raises a specific error when a packet has an empty samples field and a populated raw_frames blob, so such files fail with "stores samples in raw_frames, which DASCore cannot yet decode" instead of a misleading payload-size mismatch. Reasoning: RF01 packets that do use samples keep working, and the encoding of raw_frames is not documented here so it cannot be decoded blind. If you would rather RF01 not be advertised at all until a decoder exists, that is a one-line change to TS_TAGS — flagging for the maintainer.

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.

Revisited and went with your original suggestion in 7b9f60aRF01 is removed from TS_TAGS. What tipped it: tracing a raw-frames RF01 file showed get_format detects it and scan succeeds with a plausible shape, because shape and coords are derived from the header fields; only the read fails. A file that can never be read should not index as readable, so leaving the tag undetected surfaces the problem at index time rather than on patch access. The specific raw_frames error is retained for TS05 packets using that encoding, and a test pins that RF01 is not detected so it is not silently re-added.

Comment on lines +469 to +475
msg = messages[packet_name]()
try:
msg.ParseFromString(record.payload)
except decode_error as exc:
out = f"Failed to parse Sintela protobuf {tag} payload: {exc}"
raise InvalidFiberFileError(out) from exc
parsed.append((tag, msg))

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 Discard sample fields during metadata-only scans

On large recordings, the scan-only descriptor does not prevent sample data from being retained: protobuf preserves omitted wire fields as unknown fields when ParseFromString runs, and each resulting message is then appended to parsed. Consequently dc.scan() retains roughly the entire recording in memory despite omitting the sample declarations, which can make routine metadata scans of DAS-sized files exhaust memory; the scan path needs to discard unknown fields after parsing or extract only headers without retaining payload fields.

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 2d1e1c2. Confirmed the diagnosis empirically on the registry file: scan-mode messages retained 1,200,418 bytes of a 1,232,801-byte recording, so the metadata-only path was holding essentially the whole file. Now calls DiscardUnknownFields() after parsing in scan mode, which drops that to 9,203 bytes (99.2% reduction). Regression test asserts the invariant directly — scan-retained size is unchanged when 5,000 samples per packet are added — and it fails without the fix.

Comment thread pyproject.toml
SEGY__V2_1 = "dascore.io.segy.core:SegyV2_1"
SILIXA_H5__V1 = "dascore.io.silixah5:SilixaH5V1"
SINTELA_BINARY__V3 = "dascore.io.sintela_binary.core:SintelaBinaryV3"
SINTELA_BINARY__V3 = "dascore.io.sintela.core:SintelaBinaryV3"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the existing sintela_binary import path

Moving the entry point while deleting dascore.io.sintela_binary breaks existing imports such as from dascore.io.sintela_binary import SintelaBinaryV3, even though the binary reader itself remains supported. Retain a forwarding compatibility package at the old path so upgrading DASCore does not cause an immediate ModuleNotFoundError for callers that imported the previously exposed reader.

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.

Good catch — confirmed dascore/io/sintela_binary/ is present in the released v0.1.18, so this would have broken from dascore.io.sintela_binary import SintelaBinaryV3 for existing users. Restored the old path as a forwarding module in 2d1e1c2, with a test pinning that it resolves to the same class. The entry point stays on the new dascore.io.sintela location, and only one registration exists for each format.

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.

Reversed this after maintainer input — the shim is removed in 2042f6e and the break is taken deliberately, documented under "Unreleased API Changes" in the changelog. Rationale: both Sintela readers now live in dascore.io.sintela, and reading via dc.read/dc.scan/dc.spool is unaffected, so only direct module imports change. Worth noting the merged module is safe for the binary reader when protobuf is absent — protobuf_utils imports protobuf lazily, so SintelaBinaryV3 still imports, detects, scans and reads without it; the min-deps job (now genuinely protobuf-free) covers that with 14 passing Sintela binary cases in the common-IO matrix.

Comment thread dascore/io/sintela/protobuf_utils.py Outdated
num_samples_per_packet[:-1],
strict=False,
):
if current + count != nxt:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle uint32 rollover when validating sample counts

When a recording crosses the sample_count rollover, the next protobuf uint32 counter wraps to zero, but this comparison uses unbounded Python integer addition and rejects the otherwise contiguous packet as a gap. At common DAS sample rates the 32-bit boundary can be reached during a multi-day acquisition, or sooner when files inherit a recorder-wide counter, so continuity should compare modulo 2**32.

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 2d1e1c2. Continuity now compares (current + count) % 2**32 so a uint32 wrap mid-acquisition is not read as a gap. Test builds a packet ending exactly on the boundary followed by one wrapping to zero, and fails without the modulo.

Comment thread dascore/io/sintela/protobuf_utils.py Outdated
extra=dict(
gauge_length=gauge_length,
channel_stride=channel_step,
**_FFT_ATTR_DEFAULTS,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Label complex FFT coefficients according to their representation

When has_complex_data is true, the reader constructs complex Fourier coefficients, but these defaults still label the patch as power_spectral_density. A PSD is real-valued power per frequency, so downstream consumers will receive materially incorrect metadata for every complex FFT recording; select an amplitude/complex-spectrum type (or leave the type unset) based on the FFT header flags instead of applying the power-data default unconditionally.

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 2d1e1c2. Complex packets are now labeled fourier_transform (added to VALID_DATA_TYPES on dev) rather than power_spectral_density, selected from the has_complex_data header flag; real packets keep the PSD label. Test covers both branches.

The index reserves any {name}_{min,max,step} shaped attr as a coordinate
envelope column, so channel_step was dropped from the index with a warning
on every scan. Rename the patch attr (the protobuf wire field keeps its
vendor name) so the value stays queryable.
Dropping timeout(0) lets the module's 30s pytest-timeout bound the parts of
the test outside the narrow skip_on_timeout guard; the nested-alarm handling
added in #750 already restores the outer alarm, so the inner 15s skip still
works. Also explain why Sintela protobuf is excluded from the localhost-HTTP
matrix.
Attrs and coords are independent as of #757, so the channel_stride stopgap
is no longer needed; the attr keeps the vendor's channel_step name and a
test pins that it survives indexing.

Review fixes:
- Discard unknown fields after scan-mode parsing. Omitting the sample
  declarations stopped them being decoded but protobuf still retained their
  raw bytes, so a metadata-only scan held ~99% of the recording in memory.
- Label complex FFT packets fourier_transform rather than
  power_spectral_density; complex coefficients are not real power.
- Compare sample_count modulo 2**32 so a uint32 rollover mid-acquisition is
  not misread as a gap.
- Raise a specific error for timeseries packets carrying samples in
  raw_frames instead of a confusing payload-size mismatch.
- Restore dascore.io.sintela_binary as a forwarding module; it shipped in
  v0.1.18 and the move would have broken existing imports.
RF01 packets carry samples in the packed raw_frames blob, which has no
decoder here. Because shape and coords come from the headers, advertising
the tag let such a file scan cleanly and fail only once a patch was read;
leaving it undetected surfaces the problem at index time instead.

Drop protobuf from the test extra and skip the protobuf tests when it is
absent, matching the pymseed convention. This keeps the min-deps job
genuinely protobuf-free, which is what catches an accidental eager import
of google.protobuf in the reader; with protobuf installed everywhere, such
a regression passed the whole suite.
Take the import break rather than carry a forwarding shim; both Sintela
readers live in dascore.io.sintela. Reading via dc.read/scan/spool is
unaffected, so only direct module imports change. Documented under
Unreleased API Changes.
@d-chambers
d-chambers merged commit 47ce757 into dev Jul 19, 2026
27 checks passed
@d-chambers
d-chambers deleted the sintela_proto branch July 19, 2026 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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