Skip to content

support universal paths - #645

Merged
d-chambers merged 7 commits into
devfrom
universal-pathlib
Apr 3, 2026
Merged

support universal paths#645
d-chambers merged 7 commits into
devfrom
universal-pathlib

Conversation

@d-chambers

@d-chambers d-chambers commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Description

  • Add universal-pathlib as a runtime dependency and extend DASCore path handling so str, Path, and UPath inputs work consistently across public IO entry points.

  • Teach dc.read, dc.scan, dc.spool, dc.get_format, FileSpool, and related internals to preserve remote paths, normalize local-vs-remote behavior, and reject directory-only operations when the backend is not a local
    filesystem.

  • Introduce shared remote IO/cache utilities for:

    • normalizing remote resource IDs
    • materializing remote files into a stable local cache when required
    • controlling cache behavior through runtime config
    • supporting metadata-time cache opt-in separately from general read-time cache use
  • Update HDF5-based readers to prefer remote-first access and only fall back to a cached local file when a backend, especially plain HTTP, does not support the random-access behavior needed by h5py/PyTables.

  • Add runtime config support for remote cache settings and related display/progress configuration, plus docs/tutorial updates covering remote patches, remote file IO, and runtime configuration.

  • Expand test coverage for:

    • local UPath inputs
    • remote HTTP reads/scans/get-format behavior
    • in-memory remote filesystems
    • remote directory traversal and timestamp handling
    • remote cache warnings, cache policy, and fallback behavior
  • Include follow-up cleanup and stability fixes:

    • move shared IO test helpers into an explicit helper module
    • scope optional HTTP test dependencies to fixtures
    • standardize warning suppression through suppress_warnings
    • fix a full-suite intermittent hang by reopening the IO resource after format inference before the actual read, instead of reusing the same HDF5/fileobj stack across both phases

edit: updated according to current branch state.

Summary by CodeRabbit

  • New Features

    • First-class UPath (remote URI) support across IO: read/scan/spool/write
    • Remote-file materialization, caching controls, and HDF5 read/write adapters
    • Runtime configuration API (get_config / set_config) for IO, caching, and display
    • New utilities to manage remote cache and ensure local copies; new RemoteCacheError
  • Documentation

    • Tutorials: "Working with Remote Patches" and runtime configuration examples
  • Tests

    • Extensive remote I/O coverage (HTTP, memory, range, caching)
  • Chores

    • Added universal-pathlib dependency

Changelog

  • added: UPath resources work across read, scan, spool, and write, including remote backends such as memory://.
  • changed breaking: DASDAE stores history as a flat string payload; older files stay readable but their original history strings are no longer restored exactly.

@coderabbitai

coderabbitai Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

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 universal-pathlib (UPath) support and comprehensive remote-file handling: new path utilities and runtime config, remote-materialization and caching with HTTP/S3/memory fallbacks, HDF5 reader/writer adapters for remote UPath, widespread IO API/type updates to accept UPath, and extensive test/fixture additions for remote IO.

Changes

Cohort / File(s) Summary
Core types & config
dascore/compat.py, dascore/constants.py, dascore/config.py, pyproject.toml
Expose/import UPath; widen path_types to include UPath; add runtime DascoreConfig API and getters/setters; add universal-pathlib runtime dependency and test extras.
Path helpers & validation
dascore/utils/paths.py
New helpers: is_pathlike, coerce_to_upath, get_path_protocol, is_local_path, requires_local_directory (enforces local-dir requirement).
Remote IO core & cache
dascore/utils/remote_io.py, dascore/utils/io.py
New remote-cache/session scoping, deterministic cache paths, ensure_local_file/get_local_handle, FallbackFileObj, get_remote_cache_path/clear_remote_file_cache; IO layer gains UPath-awareness, LocalBinaryReader/LocalPath, and adjusted writer modes.
HDF5 adapters & index manager
dascore/utils/hdf5.py, dascore/io/dasdae/utils.py, dascore/io/h5simple/utils.py
Add LocalPyTablesReader/LocalH5Reader and remote-write wrapper; HDFPatchIndexManager made config-driven; migrate PyTables-style access to h5py-style attrs/datasets and add attr encode/decode helpers.
Spool/indexer/filespool
dascore/core/spool.py, dascore/clients/dirspool.py, dascore/clients/filespool.py, dascore/io/indexer.py
Widen spool API to path_types/UPath, coerce via coerce_to_upath, preserve UPath in FileSpool, enforce local-directory requirement for DirectoryIndexer, and source index_map_path from config.
IO core & format handlers
dascore/io/core.py, dascore/io/*/* (many modules), dascore/io/h5simple/core.py, dascore/io/dasdae/core.py, dascore/io/rsf/core.py, dascore/io/xml_binary/*, dascore/io/wav/core.py
Widen public IO signatures to path_types; normalize/coerce to UPath where appropriate; adapt read/scan/write flows and format handlers for remote/UPath inputs and h5py-backed HDF5 access.
Filesystem iteration & misc IO helpers
dascore/utils/misc.py, dascore/utils/paths.py
Make filesystem iterator UPath-aware with separate local vs remote traversal, tolerant timestamp checks that warn and skip on stat failures, and helpers for hidden/name filters.
Display, formatting, progress, summary
dascore/utils/display.py, dascore/utils/patch.py, dascore/utils/progress.py, dascore/core/summary.py, dascore/constants.py
Replace hard-coded display constants with config-driven values; PatchSummary.path uses path_types and flat_dump emits string; progress/debug driven by config; removed FLOAT_PRECISION and some style entries.
Downloader & registry
dascore/utils/downloader.py
Make pooch fetcher cache-dir configurable at runtime via get_fetcher()/proxy; add exclude_large option to get_registry_df; adapt fetch to runtime cache_dir.
Exceptions
dascore/exceptions.py
Add RemoteCacheError exception type for remote-cache-specific failures.
Tests & test infra
tests/test_io/conftest.py, tests/test_io/_common_io_test_utils.py, tests/test_io/test_remote_*.py, tests/* (many)
Extensive new/updated tests and fixtures for HTTP/memory UPath backends, timeout/skip helpers, remote-cache isolation, and broad remote IO coverage.
Docs & tutorials
docs/*, benchmarks/*, .github/scripts/*, scripts/*
New tutorials/docs for remote patches and runtime config; benchmarks and cache scripts adjusted to exclude large registry files by default; tutorial nav updated.
CI/workflows
.github/actions/load-shared-vars/action.yml, .github/workflows/*.yml
CI action/workflow matrices and outputs updated to emit/consume a shared os-matrix and reduce supported Python matrix for shared-vars action.
Misc small changes
dascore/__init__.py, .gitignore, dascore/io/__init__.py
Remove module-level _debug flag; add .codex to .gitignore; remove deprecated internal HDF imports from io package init.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'support universal paths' is concise and clearly describes the main feature addition. It directly references the primary change across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 88.32% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description comprehensively explains the motivation (UPath support), implementation details (remote cache, HDF5 remote-first access), and scope (tests, documentation, cleanup). The author addresses the template checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 universal-pathlib

@coderabbitai coderabbitai Bot added the IO Work for reading/writing different formats label Mar 28, 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: b7be9612c3

ℹ️ 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 +857 to +859
path = el if isinstance(el, UPath) else Path(el)
if path.exists():
generator = _iter_filesystem(

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 Coerce URL strings before scan directory traversal

When dc.scan(...) receives a remote directory as a plain string (for example "http://.../das" or "memory://..."), this branch wraps it with Path(...), so path.exists() checks the local filesystem and returns false. That skips _iter_filesystem recursion entirely and yields the raw string instead, which means remote directory scans via string inputs silently miss traversal unless callers manually construct a UPath first.

Useful? React with 👍 / 👎.

Comment thread dascore/io/core.py
Comment on lines +516 to +519
if isinstance(obj, str | Path | UPath):
path = obj if isinstance(obj, UPath) else Path(obj)
if path.exists():
out = "directory" if path.is_dir() else "file"

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 Detect input type using remote-aware coercion for strings

This logic treats every str as a local Path, so remote URL strings are almost always classified as "file" because Path("http://...").exists() is false. As a result, directory-only FiberIO handlers are never considered for string-based remote directory inputs, even though remote path support was added elsewhere; callers must pass UPath objects to get the expected behavior.

Useful? React with 👍 / 👎.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_io/test_common_io.py (1)

132-151: ⚠️ Potential issue | 🟡 Minor

Only skip real timeouts in these fetch fixtures.

Lines 139 and 150 now rely on the shared skip_timeout() helper, and that helper currently skips on any urllib.error.URLError. That means a bad registry entry, 404, or other transport failure will quietly skip the session fixture instead of failing the matrix and surfacing the regression.

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

In `@tests/test_io/test_common_io.py` around lines 132 - 151, The fixtures
io_path_tuple and data_file_path currently use skip_timeout() which swallows any
urllib.error.URLError (hiding 404s/bad registry entries); change these fixtures
to only skip on actual network timeouts by replacing the generic skip_timeout()
usage with a context that only catches timeout-related errors (e.g.,
socket.timeout, TimeoutError, or urllib.error.URLError where the underlying
reason is a timeout) around the fetch(fetch_name) and fetch(request.param)
calls; keep the rest of the fixture logic (io_path_tuple, data_file_path, fetch,
SKIP_DATA_FILES, get_registry_df) intact so non-timeout transport errors raise
and fail the test matrix.
🧹 Nitpick comments (5)
pyproject.toml (1)

88-91: Redundant universal-pathlib in test dependencies.

universal-pathlib is already listed in the main dependencies (line 58), so it doesn't need to be repeated in test extras—main dependencies are always available when test extras are installed.

♻️ Proposed fix
 test = [
     "aiohttp",
     "coverage>=7.4,<8",
     "pytest-cov>=4",
     "pre-commit",
     "pytest",
     "pytest-codeblocks",
     "pytest-cov",
     "starlette",
     "twine",
-    "universal-pathlib",
     "uvicorn",
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 88 - 91, Remove the redundant
"universal-pathlib" entry from the test extras list in the pyproject.toml:
update the [project.optional-dependencies] "test" array to delete
"universal-pathlib" since it is already declared in the main [project]
dependencies; ensure only one canonical declaration remains (keep the existing
main dependency and remove the duplicate from the "test" extras).
dascore/io/indexer.py (1)

127-130: Clarify type safety: self.path can be UPath but write_update expects str | Path.

After requires_local_directory validates the path is local, self.path remains a UPath instance if the input was UPath, or a Path if the input was Path. However, write_update at line 348 is called with base_path=self.path, and the signature declares base_path: str | Path (line 295 of dascore/utils/hdf5.py), which does not include UPath.

While the code works in practice because _remove_base_path converts the parameter using str(base), the type annotation mismatch indicates a gap in the API contract. For type safety and clarity, consider normalizing self.path to Path after locality validation:

♻️ Suggested change
         self.path = (
             UPath(path).absolute() if isinstance(path, UPath) else Path(path).absolute()
         )
         requires_local_directory(self.path, label="DirectoryIndexer")
+        # Normalize to Path for downstream compatibility
+        self.path = Path(self.path)
         self.index_path = Path(self._find_index_file(self.path, index_path))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/io/indexer.py` around lines 127 - 130, Normalize self.path to a
stdlib Path after locality validation: ensure the constructor (where self.path
is set and requires_local_directory(self.path, label="DirectoryIndexer") is
called) always assigns a pathlib.Path instance (e.g., Path(str(path)).absolute()
or Path(path).absolute()) instead of leaving a UPath, so that later calls like
write_update(base_path=self.path) match the annotated base_path: str | Path in
dascore/utils/hdf5.py; you can still call requires_local_directory(self.path)
first but then overwrite self.path with a Path conversion to guarantee
type-safety and consistency with _remove_base_path and write_update.
dascore/clients/dirspool.py (1)

47-65: Add UPath to the public signature too.

Line 64 now accepts UPath at runtime, but base_path is still annotated as str | Path | Self | AbstractIndexer. That leaves type checkers and generated docs out of sync with the API you just expanded.

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

In `@dascore/clients/dirspool.py` around lines 47 - 65, Update the __init__
signature so the base_path parameter includes UPath in its type annotation
(e.g., change base_path: str | Path | Self | AbstractIndexer to base_path: str |
Path | UPath | Self | AbstractIndexer), and ensure UPath is imported at the top
of the module; keep the existing runtime isinstance check (Path | str | UPath)
as-is so the behavior matches the public API.
dascore/core/spool.py (1)

749-752: Handle URL strings as remote paths before coercing to Path.

Right now, URL-like strings still get coerced via Path(...), so remote string inputs can’t participate in the new UPath flow. Consider preserving scheme-based strings as UPath too.

Suggested diff
 def _spool_from_str(path, **kwargs):
     """Get a spool from a path."""
-    path = path if isinstance(path, UPath) else Path(path)
+    if isinstance(path, UPath):
+        pass
+    elif isinstance(path, str) and "://" in path:
+        path = UPath(path)
+    else:
+        path = Path(path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/core/spool.py` around lines 749 - 752, In _spool_from_str, avoid
coercing URL-like strings with Path(...) — first detect if path is already a
UPath, otherwise if it's a str and contains a URI scheme (e.g., startswith
something like "<scheme>://" or matches regex r"^[a-zA-Z][a-zA-Z0-9+.-]*://"),
construct a UPath(path) and leave it as such; only call Path(path) for non-URL
strings. Update the initial lines in _spool_from_str to: if isinstance(path,
UPath) -> keep, elif isinstance(path, str) and looks like URL -> path =
UPath(path), else -> path = Path(path), so remote URL strings participate in the
UPath flow.
dascore/io/core.py (1)

466-478: Consider simplifying the path existence check.

The branching logic could be simplified since coerce_path already handles UPath conversion. The current code has a redundant path through is_local_path check.

♻️ Suggested simplification
-            if isinstance(path, UPath):
-                exists = path.exists()
-                suffix = path.suffix
-            else:
-                local_path = (
-                    coerce_path(path) if not is_local_path(path) else Path(path)
-                )
-                exists = local_path.exists()
-                suffix = local_path.suffix
+            coerced = coerce_path(path) if not isinstance(path, UPath) else path
+            exists = coerced.exists()
+            suffix = coerced.suffix
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/io/core.py` around lines 466 - 478, The existence/suffix branching is
redundant; always coerce the incoming path to a local Path using
coerce_path(path) (which already handles UPath and local paths), then use
local_path.exists() and local_path.suffix to set exists and suffix; keep the
FileNotFoundError raise and the ext = suffix[1:] if suffix else None logic but
remove the is_local_path branch and the isinstance(path, UPath) special-case so
only local_path = coerce_path(path) followed by exists/suffix checks remain.
🤖 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/utils/paths.py`:
- Around line 33-36: is_local_path currently treats non-pathlike inputs as local
because get_path_protocol(resource) can return None and None is in the accepted
set; change is_local_path to first verify the input is pathlike (e.g.,
isinstance(resource, (str, os.PathLike))) or explicitly treat a None protocol as
non-local, then call get_path_protocol; update the function is_local_path to
return False for non-pathlike resources or when get_path_protocol(...) is None,
while still recognizing protocols "", "file", and "local" as local.

In `@tests/test_io/conftest.py`:
- Around line 113-119: The readiness probe loop using urlopen(probe_url) should
be hardened: import URLError from urllib.error and call urlopen with a timeout
(e.g., urlopen(probe_url, timeout=5)), catch only URLError and OSError instead
of Exception, and add an else: on the for loop that calls pytest.fail("server
did not start: readiness probe failed") so the fixture fails immediately if all
retries exhaust; update references to urlopen, probe_url, URLError, OSError, and
pytest.fail accordingly.

In `@tests/test_utils/test_paths.py`:
- Around line 10-11: The long single-line import of multiple symbols from
dascore.utils.paths is triggering E501; split the import across lines using
either implicit line continuation with parentheses or separate import statements
so each line is under the line-length limit—target the import that brings in
coerce_path, get_path_protocol, is_local_path, is_pathlike, and
requires_local_directory in tests/test_utils/test_paths.py and wrap those symbol
names onto multiple lines to satisfy the linter.

---

Outside diff comments:
In `@tests/test_io/test_common_io.py`:
- Around line 132-151: The fixtures io_path_tuple and data_file_path currently
use skip_timeout() which swallows any urllib.error.URLError (hiding 404s/bad
registry entries); change these fixtures to only skip on actual network timeouts
by replacing the generic skip_timeout() usage with a context that only catches
timeout-related errors (e.g., socket.timeout, TimeoutError, or
urllib.error.URLError where the underlying reason is a timeout) around the
fetch(fetch_name) and fetch(request.param) calls; keep the rest of the fixture
logic (io_path_tuple, data_file_path, fetch, SKIP_DATA_FILES, get_registry_df)
intact so non-timeout transport errors raise and fail the test matrix.

---

Nitpick comments:
In `@dascore/clients/dirspool.py`:
- Around line 47-65: Update the __init__ signature so the base_path parameter
includes UPath in its type annotation (e.g., change base_path: str | Path | Self
| AbstractIndexer to base_path: str | Path | UPath | Self | AbstractIndexer),
and ensure UPath is imported at the top of the module; keep the existing runtime
isinstance check (Path | str | UPath) as-is so the behavior matches the public
API.

In `@dascore/core/spool.py`:
- Around line 749-752: In _spool_from_str, avoid coercing URL-like strings with
Path(...) — first detect if path is already a UPath, otherwise if it's a str and
contains a URI scheme (e.g., startswith something like "<scheme>://" or matches
regex r"^[a-zA-Z][a-zA-Z0-9+.-]*://"), construct a UPath(path) and leave it as
such; only call Path(path) for non-URL strings. Update the initial lines in
_spool_from_str to: if isinstance(path, UPath) -> keep, elif isinstance(path,
str) and looks like URL -> path = UPath(path), else -> path = Path(path), so
remote URL strings participate in the UPath flow.

In `@dascore/io/core.py`:
- Around line 466-478: The existence/suffix branching is redundant; always
coerce the incoming path to a local Path using coerce_path(path) (which already
handles UPath and local paths), then use local_path.exists() and
local_path.suffix to set exists and suffix; keep the FileNotFoundError raise and
the ext = suffix[1:] if suffix else None logic but remove the is_local_path
branch and the isinstance(path, UPath) special-case so only local_path =
coerce_path(path) followed by exists/suffix checks remain.

In `@dascore/io/indexer.py`:
- Around line 127-130: Normalize self.path to a stdlib Path after locality
validation: ensure the constructor (where self.path is set and
requires_local_directory(self.path, label="DirectoryIndexer") is called) always
assigns a pathlib.Path instance (e.g., Path(str(path)).absolute() or
Path(path).absolute()) instead of leaving a UPath, so that later calls like
write_update(base_path=self.path) match the annotated base_path: str | Path in
dascore/utils/hdf5.py; you can still call requires_local_directory(self.path)
first but then overwrite self.path with a Path conversion to guarantee
type-safety and consistency with _remove_base_path and write_update.

In `@pyproject.toml`:
- Around line 88-91: Remove the redundant "universal-pathlib" entry from the
test extras list in the pyproject.toml: update the
[project.optional-dependencies] "test" array to delete "universal-pathlib" since
it is already declared in the main [project] dependencies; ensure only one
canonical declaration remains (keep the existing main dependency and remove the
duplicate from the "test" extras).
🪄 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: ccf6065f-e0d0-4c61-99a7-0a894968d270

📥 Commits

Reviewing files that changed from the base of the PR and between 7f45632 and b7be961.

📒 Files selected for processing (31)
  • dascore/clients/dirspool.py
  • dascore/clients/filespool.py
  • dascore/compat.py
  • dascore/constants.py
  • dascore/core/spool.py
  • dascore/io/core.py
  • dascore/io/dasdae/core.py
  • dascore/io/h5simple/core.py
  • dascore/io/indexer.py
  • dascore/io/segy/core.py
  • dascore/io/sentek/core.py
  • dascore/io/sintela_binary/core.py
  • dascore/io/tdms/core.py
  • dascore/utils/hdf5.py
  • dascore/utils/io.py
  • dascore/utils/misc.py
  • dascore/utils/paths.py
  • dascore/utils/remote_io.py
  • pyproject.toml
  • tests/test_clients/test_filespool.py
  • tests/test_io/_common_io_test_utils.py
  • tests/test_io/conftest.py
  • tests/test_io/test_common_io.py
  • tests/test_io/test_indexer.py
  • tests/test_io/test_io_core.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_utils/test_io_utils.py
  • tests/test_utils/test_misc.py
  • tests/test_utils/test_paths.py

Comment thread dascore/utils/misc.py Outdated
Comment thread dascore/utils/paths.py
Comment thread tests/test_io/conftest.py Outdated
Comment thread tests/test_utils/test_paths.py Outdated
@coderabbitai coderabbitai Bot added the documentation Improvements or additions to documentation label Mar 31, 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: 7

♻️ Duplicate comments (1)
dascore/utils/misc.py (1)

198-236: ⚠️ Potential issue | 🟠 Major

Warn-once state is still reset during recursion.

warned_no_remote_mtime lives inside _iter_generic(), so nested remote walks can warn once per directory instead of once per traversal. The except Exception path also downgrades real auth/network/backend failures to an unfiltered scan. Please hoist the flag to _iter_filesystem() scope and only degrade on the specific “mtime unsupported” failures.

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

In `@dascore/utils/misc.py` around lines 198 - 236, Move the
warned_no_remote_mtime flag out of _iter_generic into the enclosing
_iter_filesystem scope so a single traversal shares the flag; update
_meets_timestamp to reference that outer warned_no_remote_mtime. Replace the
broad except Exception in _meets_timestamp with a narrow catch for the specific
failure(s) that indicate the backend doesn't support mtime (e.g., AttributeError
and NotImplementedError or the backend-specific exception your path objects
raise) so real auth/network errors still propagate; only on these
mtime-unsupported exceptions set warned_no_remote_mtime once and return True to
degrade to unfiltered iteration.
🧹 Nitpick comments (6)
tests/test_io/test_wav/test_wav.py (1)

56-57: Consider unique memory:// prefixes per test for stronger isolation.

Using fixed in-memory paths can create avoidable coupling across repeated runs/reordered tests.

♻️ Optional test-isolation tweak
+from uuid import uuid4
...
-        path = UPath("memory://dascore/temp.wav")
+        path = UPath(f"memory://dascore/{uuid4().hex}/temp.wav")
...
-        path = UPath("memory://dascore/wav_dir")
+        path = UPath(f"memory://dascore/{uuid4().hex}/wav_dir")

Also applies to: 87-90

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

In `@tests/test_io/test_wav/test_wav.py` around lines 56 - 57, Replace the fixed
in-memory URI with a unique one per test to avoid cross-test coupling: when
constructing the UPath for writing (currently UPath("memory://dascore/temp.wav")
used with dc.write(audio_patch, path, "wav")), generate a unique suffix (e.g.,
via uuid.uuid4() or pytest's tmp_path.name) and build the URI like
f"memory://dascore/{unique_id}.wav" so each test gets its own memory:// path;
apply the same change to the other occurrences around the dc.write calls.
tests/test_io/test_h5simple/test_h5simple.py (1)

54-60: Replace getattr with direct attribute access.

Static analysis correctly flags that getattr(attrs, "dims") with a constant string offers no benefit over attrs.dims.

♻️ Suggested fix
     def test_get_root_attrs_supports_pytables(self, tmp_path):
         """PyTables handles should expose root attrs through the helper."""
         path = tmp_path / "root_attrs.h5"
         with tables.open_file(path, "w") as h5:
             h5.root._v_attrs["dims"] = "distance,time"
             attrs = _get_root_attrs(h5)
-            assert getattr(attrs, "dims") == "distance,time"
+            assert attrs.dims == "distance,time"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_io/test_h5simple/test_h5simple.py` around lines 54 - 60, In
test_get_root_attrs_supports_pytables, replace the redundant getattr call with
direct attribute access: after calling _get_root_attrs(h5) assert the dims using
attrs.dims instead of getattr(attrs, "dims"); update the assertion in
test_get_root_attrs_supports_pytables to use attrs.dims for clarity and to
satisfy static analysis.
dascore/io/indexer.py (1)

123-127: Redundant .absolute() call.

self.path is already absolute after Line 124-125. The second .absolute() on Line 127 is unnecessary.

♻️ Suggested simplification
         self.path = (
             UPath(path).absolute() if isinstance(path, UPath) else Path(path).absolute()
         )
         requires_local_directory(self.path, label="DirectoryIndexer")
-        self.path = Path(self.path).absolute()
+        self.path = Path(self.path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/io/indexer.py` around lines 123 - 127, The second .absolute() call is
redundant because self.path is already made absolute when set from UPath or
Path; update the DirectoryIndexer initialization by removing the extra
reassignment that calls .absolute() (i.e., delete the final "self.path =
Path(self.path).absolute()" line) and ensure requires_local_directory(self.path,
label="DirectoryIndexer") still runs after the initial absolute conversion so
self.path remains a Path-like absolute value; references: self.path, UPath,
Path, requires_local_directory, DirectoryIndexer.
tests/test_io/test_dasdae/test_dasdae.py (1)

799-802: Use a raw string for the regex pattern in pytest.raises.

The | character is a regex metacharacter (alternation). While this likely works as intended, using a raw string makes the regex intent explicit and silences the static analysis warning.

Suggested fix
-            with pytest.raises(TypeError, match="Object dtype|object arrays"):
+            with pytest.raises(TypeError, match=r"Object dtype|object arrays"):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_io/test_dasdae/test_dasdae.py` around lines 799 - 802, Update the
pytest.raises call to use a raw string for the regex pattern: change the pattern
argument in the pytest.raises(...) that wraps the _save_array call (the one
passing "obj", group=group, h5=h5) to a raw string literal (e.g., r"Object
dtype|object arrays") so the alternation '|' is treated as a regex without
triggering static analysis warnings.
dascore/utils/io.py (1)

81-94: Silent exception swallowing in annotation helper warrants consideration.

The static analysis correctly flags try-except-pass with blind Exception. While the # pragma: no cover suggests this is defensive/best-effort code, silently swallowing exceptions can hide unexpected failures. Consider at least a debug-level log for traceability, or narrowing to specific exception types (e.g., AttributeError, TypeError).

♻️ Optional: Add narrow exception types or debug logging
-        try:
-            setattr(handle, attr_name, path_str)
-        except Exception:
-            pass
+        try:
+            setattr(handle, attr_name, path_str)
+        except (AttributeError, TypeError):
+            pass  # Read-only or frozen handle; annotation is best-effort
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/utils/io.py` around lines 81 - 94, The helper _annotate_handle_path
currently swallows all exceptions with bare excepts; change the handlers to
either catch only specific exceptions (e.g., AttributeError, TypeError) when
calling setattr/getattr on handle, and/or emit a debug-level log with the
exception info instead of pass to preserve traceability; update the two
try/except blocks around setattr(handle, attr_name, path_str) and
setattr(handle, "name", path_str) to use narrow exception types and call the
module logger (or logging.getLogger(__name__)) at debug level with the caught
exception and a short context message.
dascore/utils/remote_io.py (1)

20-46: Module-level mutable state may cause issues in concurrent scenarios.

_REMOTE_RESOURCE_CACHE is a module-level dict that's mutated in normalize_remote_id() and cleared in clear_remote_file_cache(). In multi-threaded or multiprocessing scenarios (common for data processing), this could lead to race conditions. Consider if thread-safe alternatives are needed, or document the single-threaded assumption.

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

In `@dascore/utils/remote_io.py` around lines 20 - 46, The module-level dict
_REMOTE_RESOURCE_CACHE is mutated in normalize_remote_id() and cleared in
clear_remote_file_cache(), which is unsafe for concurrent access; make access
thread-safe by introducing a module-level lock (e.g., threading.Lock or RLock)
and acquiring it around any reads/writes to _REMOTE_RESOURCE_CACHE inside
normalize_remote_id() and clear_remote_file_cache(), or alternatively replace
the mutable global with a thread-safe mapping (e.g., collections.defaultdict
with locking or concurrent.futures-safe structure) and update code paths that
reference _REMOTE_RESOURCE_CACHE accordingly to use the locked access pattern;
ensure the lock variable and the cache variable names remain unique and
referenced in normalize_remote_id and clear_remote_file_cache.
🤖 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/dasdae/utils.py`:
- Around line 413-428: _decode_legacy_attr_value currently calls
pickle.loads(raw, encoding="latin1") which allows arbitrary code execution when
deserializing untrusted HDF5 attributes; update this function to avoid insecure
deserialization by either (a) documenting the trust assumption clearly at the
top of the function and gating pickle usage behind an explicit "trusted" flag or
environment check, or (b) replacing pickle.loads with a safer parser flow that
first attempts ast.literal_eval for simple Python literals and only falls back
to pickle for known-trusted inputs; ensure you reference the existing symbols
pickle.loads, _decode_legacy_attr_value, and the fallback path to unbyte so the
change keeps the same fallback behavior for unparseable data.

In `@dascore/io/xml_binary/utils.py`:
- Around line 241-243: The timestamp post-filter using x.stat() can raise on
remote backends; update the code in the timestamp block (where timestamp,
to_float, and paths are used) to perform a guarded stat access instead of
calling x.stat() unguarded—e.g., compute ts = to_float(timestamp) and rebuild
paths with a try/except around x.stat() (catching OSError/Exception) and only
include entries whose stat().st_mtime >= ts, or skip entries where stat is
unavailable; alternatively remove this local filter and rely on the upstream
_iter_filesystem() timestamp filtering so you don't call x.stat() here at all.
- Around line 187-190: The current branch uses path.read_bytes() (with UPath and
not is_local_path(path)) which loads the whole remote file into memory; instead,
materialize or cache the remote file locally and then open it with np.memmap so
slices stay on-disk. Replace the np.frombuffer(path.read_bytes(), ...) approach
by using the project’s local-materialization/cache path routine for UPath (i.e.,
ensure the remote UPath is downloaded to a local file) and pass that local path
into np.memmap(metadata.data_type) so the code continues to use np.memmap for
both local and remote files; keep checks around isinstance(path, UPath) and
is_local_path(path) and only trigger materialization for non-local UPath
instances.

In `@dascore/utils/hdf5.py`:
- Around line 548-596: The __init__ of _RemoteH5Writer can leak the temporary
file if H5pyFile(...) fails; wrap the H5pyFile construction in a try/except or
try/finally so that on any exception you unlink self._temp_path (and set any
state like _closed if needed) before re-raising; specifically protect the call
to H5pyFile(self._temp_path, mode=local_mode) so failures clean up the temp file
created earlier.

In `@dascore/utils/misc.py`:
- Around line 795-803: The current get_buffer_size logic assumes getattr(fid,
"name") points to a real local file and calls Path(path).stat().st_size which
can raise for non-files (e.g., BytesIO that exposes name); wrap the
Path(path).stat().st_size probe in a try/except and if stat() fails (catch
OSError/ValueError/TypeError), fall back to the existing buffer-based approach
using fid.tell()/seek()/tell() to compute file_size; keep the
is_local_path(path) check but ensure exceptions are handled so get_buffer_size
continues to work for wrapped/in-memory streams (refer to variables/functions:
fid, path, is_local_path, get_buffer_size).

In `@tests/test_io/conftest.py`:
- Around line 34-39: The copyfile method currently catches BrokenPipeError and
ConnectionResetError but misses ConnectionAbortedError (seen on Windows as
WinError 10053); update the exception tuple in copyfile to also include
ConnectionAbortedError so the method safely silences Windows connection-abort
scenarios when calling super().copyfile(source, outputfile) in
tests/test_io/conftest.py.

In `@tests/test_utils/test_misc.py`:
- Around line 461-469: The test uses _spool_map(random_spool[:4], ...,
client=DummyClient()) which when os.cpu_count() is 2 will produce two spools of
two patches and therefore four mapped results, so update the assertions in
tests/test_utils/test_misc.py for the _spool_map case: assert that out contains
four items (e.g. assert out == [3, 3, 3, 3] or assert len(out) == 4 and all(v ==
3 for v in out)) and keep/adjust the seen assertion if necessary for DummyClient
logging; locate the checks around the call to _spool_map and replace the old [3,
3, 3] expectation accordingly.

---

Duplicate comments:
In `@dascore/utils/misc.py`:
- Around line 198-236: Move the warned_no_remote_mtime flag out of _iter_generic
into the enclosing _iter_filesystem scope so a single traversal shares the flag;
update _meets_timestamp to reference that outer warned_no_remote_mtime. Replace
the broad except Exception in _meets_timestamp with a narrow catch for the
specific failure(s) that indicate the backend doesn't support mtime (e.g.,
AttributeError and NotImplementedError or the backend-specific exception your
path objects raise) so real auth/network errors still propagate; only on these
mtime-unsupported exceptions set warned_no_remote_mtime once and return True to
degrade to unfiltered iteration.

---

Nitpick comments:
In `@dascore/io/indexer.py`:
- Around line 123-127: The second .absolute() call is redundant because
self.path is already made absolute when set from UPath or Path; update the
DirectoryIndexer initialization by removing the extra reassignment that calls
.absolute() (i.e., delete the final "self.path = Path(self.path).absolute()"
line) and ensure requires_local_directory(self.path, label="DirectoryIndexer")
still runs after the initial absolute conversion so self.path remains a
Path-like absolute value; references: self.path, UPath, Path,
requires_local_directory, DirectoryIndexer.

In `@dascore/utils/io.py`:
- Around line 81-94: The helper _annotate_handle_path currently swallows all
exceptions with bare excepts; change the handlers to either catch only specific
exceptions (e.g., AttributeError, TypeError) when calling setattr/getattr on
handle, and/or emit a debug-level log with the exception info instead of pass to
preserve traceability; update the two try/except blocks around setattr(handle,
attr_name, path_str) and setattr(handle, "name", path_str) to use narrow
exception types and call the module logger (or logging.getLogger(__name__)) at
debug level with the caught exception and a short context message.

In `@dascore/utils/remote_io.py`:
- Around line 20-46: The module-level dict _REMOTE_RESOURCE_CACHE is mutated in
normalize_remote_id() and cleared in clear_remote_file_cache(), which is unsafe
for concurrent access; make access thread-safe by introducing a module-level
lock (e.g., threading.Lock or RLock) and acquiring it around any reads/writes to
_REMOTE_RESOURCE_CACHE inside normalize_remote_id() and
clear_remote_file_cache(), or alternatively replace the mutable global with a
thread-safe mapping (e.g., collections.defaultdict with locking or
concurrent.futures-safe structure) and update code paths that reference
_REMOTE_RESOURCE_CACHE accordingly to use the locked access pattern; ensure the
lock variable and the cache variable names remain unique and referenced in
normalize_remote_id and clear_remote_file_cache.

In `@tests/test_io/test_dasdae/test_dasdae.py`:
- Around line 799-802: Update the pytest.raises call to use a raw string for the
regex pattern: change the pattern argument in the pytest.raises(...) that wraps
the _save_array call (the one passing "obj", group=group, h5=h5) to a raw string
literal (e.g., r"Object dtype|object arrays") so the alternation '|' is treated
as a regex without triggering static analysis warnings.

In `@tests/test_io/test_h5simple/test_h5simple.py`:
- Around line 54-60: In test_get_root_attrs_supports_pytables, replace the
redundant getattr call with direct attribute access: after calling
_get_root_attrs(h5) assert the dims using attrs.dims instead of getattr(attrs,
"dims"); update the assertion in test_get_root_attrs_supports_pytables to use
attrs.dims for clarity and to satisfy static analysis.

In `@tests/test_io/test_wav/test_wav.py`:
- Around line 56-57: Replace the fixed in-memory URI with a unique one per test
to avoid cross-test coupling: when constructing the UPath for writing (currently
UPath("memory://dascore/temp.wav") used with dc.write(audio_patch, path,
"wav")), generate a unique suffix (e.g., via uuid.uuid4() or pytest's
tmp_path.name) and build the URI like f"memory://dascore/{unique_id}.wav" so
each test gets its own memory:// path; apply the same change to the other
occurrences around the dc.write calls.
🪄 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: b3489a7b-28f7-490b-91e4-676338412f2d

📥 Commits

Reviewing files that changed from the base of the PR and between b7be961 and a0fab7e.

📒 Files selected for processing (68)
  • .github/scripts/cache_test_data.py
  • benchmarks/readme.md
  • benchmarks/test_io_benchmarks.py
  • dascore/__init__.py
  • dascore/clients/dirspool.py
  • dascore/clients/filespool.py
  • dascore/compat.py
  • dascore/config.py
  • dascore/constants.py
  • dascore/core/spool.py
  • dascore/core/summary.py
  • dascore/io/__init__.py
  • dascore/io/core.py
  • dascore/io/dasdae/core.py
  • dascore/io/dasdae/utils.py
  • dascore/io/h5simple/core.py
  • dascore/io/h5simple/utils.py
  • dascore/io/indexer.py
  • dascore/io/rsf/core.py
  • dascore/io/segy/core.py
  • dascore/io/sentek/core.py
  • dascore/io/sentek/utils.py
  • dascore/io/sintela_binary/core.py
  • dascore/io/sintela_binary/utils.py
  • dascore/io/tdms/core.py
  • dascore/io/tdms/utils.py
  • dascore/io/wav/core.py
  • dascore/io/xml_binary/core.py
  • dascore/io/xml_binary/utils.py
  • dascore/utils/display.py
  • dascore/utils/downloader.py
  • dascore/utils/hdf5.py
  • dascore/utils/io.py
  • dascore/utils/misc.py
  • dascore/utils/patch.py
  • dascore/utils/paths.py
  • dascore/utils/progress.py
  • dascore/utils/remote_io.py
  • docs/changelog.qmd
  • docs/tutorial/configuration.qmd
  • docs/tutorial/file_io.qmd
  • docs/tutorial/spool.qmd
  • pyproject.toml
  • scripts/_templates/_quarto.yml
  • tests/conftest.py
  • tests/test_clients/test_filespool.py
  • tests/test_io/_common_io_test_utils.py
  • tests/test_io/conftest.py
  • tests/test_io/test_common_io.py
  • tests/test_io/test_dasdae/test_dasdae.py
  • tests/test_io/test_h5simple/test_h5simple.py
  • tests/test_io/test_indexer.py
  • tests/test_io/test_io_core.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_rsf/test_rsf.py
  • tests/test_io/test_tdms/test_tdms_utils.py
  • tests/test_io/test_wav/test_wav.py
  • tests/test_io/test_xml_binary/test_xml_binary.py
  • tests/test_utils/test_config.py
  • tests/test_utils/test_display.py
  • tests/test_utils/test_downloader.py
  • tests/test_utils/test_hdf_utils.py
  • tests/test_utils/test_io_utils.py
  • tests/test_utils/test_misc.py
  • tests/test_utils/test_paths.py
  • tests/test_utils/test_progress.py
💤 Files with no reviewable changes (2)
  • dascore/init.py
  • dascore/io/init.py
✅ Files skipped from review due to trivial changes (9)
  • dascore/compat.py
  • benchmarks/readme.md
  • scripts/_templates/_quarto.yml
  • docs/changelog.qmd
  • docs/tutorial/file_io.qmd
  • tests/test_utils/test_config.py
  • dascore/io/tdms/core.py
  • tests/test_utils/test_paths.py
  • docs/tutorial/configuration.qmd
🚧 Files skipped from review as they are similar to previous changes (13)
  • dascore/constants.py
  • dascore/clients/dirspool.py
  • dascore/io/sintela_binary/core.py
  • dascore/io/h5simple/core.py
  • tests/test_io/test_common_io.py
  • dascore/core/spool.py
  • tests/test_io/test_io_core.py
  • dascore/io/segy/core.py
  • dascore/io/dasdae/core.py
  • dascore/io/sentek/core.py
  • dascore/clients/filespool.py
  • tests/test_io/test_remote_common_io.py
  • tests/test_clients/test_filespool.py

Comment thread dascore/io/dasdae/utils.py
Comment thread dascore/io/xml_binary/utils.py Outdated
Comment thread dascore/io/xml_binary/utils.py
Comment thread dascore/utils/hdf5.py
Comment thread dascore/utils/misc.py
Comment thread tests/test_io/conftest.py
Comment thread tests/test_utils/test_misc.py

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

♻️ Duplicate comments (4)
tests/test_io/conftest.py (1)

34-39: ⚠️ Potential issue | 🟡 Minor

Also swallow ConnectionAbortedError here.

Windows can raise ConnectionAbortedError from super().copyfile(...), so this still leaves the localhost HTTP fixtures prone to noisy disconnect failures.

💡 Proposed fix
     def copyfile(self, source, outputfile):
         """Silence BrokenPipeError/ConnectionResetError from test clients."""
         try:
             return super().copyfile(source, outputfile)
-        except (BrokenPipeError, ConnectionResetError):
+        except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
             return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_io/conftest.py` around lines 34 - 39, The except block in copyfile
(method copyfile) currently catches BrokenPipeError and ConnectionResetError but
misses ConnectionAbortedError (raised on Windows), causing noisy disconnect
failures; update the except to include ConnectionAbortedError in the exception
tuple handled for super().copyfile(source, outputfile) so that BrokenPipeError,
ConnectionResetError, and ConnectionAbortedError are all swallowed and the
method returns None on those disconnects.
dascore/utils/misc.py (1)

832-833: ⚠️ Potential issue | 🟠 Major

Keep the local stat() probe fallible.

Some wrapped or in-memory streams still expose .name for display only. Lines 832-833 will raise before the existing tell()/seek() fallback, which breaks callers like dascore/io/tdms/utils.py:180-189 and dascore/io/sintela_binary/utils.py:111-115 when they pass non-file-backed buffers.

💡 Proposed fix
     path = getattr(fid, "name", None)
     if path is not None and is_local_path(path):
-        file_size = Path(path).stat().st_size
-    else:
-        cur = fid.tell()
-        fid.seek(0, 2)  # end
-        file_size = fid.tell()
-        fid.seek(cur, 0)
+        try:
+            return Path(path).stat().st_size
+        except (OSError, TypeError, ValueError):
+            pass
+    cur = fid.tell()
+    fid.seek(0, 2)  # end
+    file_size = fid.tell()
+    fid.seek(cur, 0)
     return file_size
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/utils/misc.py` around lines 832 - 833, The direct call to
Path(path).stat().st_size when path is not None and is_local_path(path) can
raise for non-file-backed streams; wrap the stat probe in a try/except (catching
OSError/Exception) and only set file_size on success, otherwise leave file_size
unset so the existing tell()/seek() fallback runs; refer to the is_local_path
check, the file_size variable assignment, and the existing tell()/seek()
fallback code to implement the guarded stat call.
dascore/io/dasdae/utils.py (1)

413-423: ⚠️ Potential issue | 🟠 Major

Avoid deserializing file-controlled attrs with pickle.

This fallback still runs pickle.loads on bytes read from the HDF5 file. If untrusted DASDAE inputs can reach this path, that keeps arbitrary code execution in the read flow. Please gate it behind an explicit trusted-source path or replace it with a safer decoder.

dascore/utils/hdf5.py (1)

551-566: ⚠️ Potential issue | 🟡 Minor

Clean up _temp_path if _RemoteH5Writer initialization fails.

Lines 554-566 allocate a temp file and then do remote I/O plus H5pyFile(...) construction without a cleanup guard. Any exception in that window leaves an orphaned temp file behind.

🛠️ Proposed fix
         def __init__(self, resource: UPath, mode: str):  # pragma: no cover
             self._resource = resource
             suffix = resource.suffix or ".h5"
             fd, temp_name = tempfile.mkstemp(suffix=suffix)
             os.close(fd)
             self._temp_path = Path(temp_name)
             self._closed = False
-            if mode != "w" and resource.exists():
-                with resource.open("rb") as src, self._temp_path.open("wb") as dst:
-                    shutil.copyfileobj(src, dst)
-            local_mode = (
-                "a"
-                if self._temp_path.exists() and self._temp_path.stat().st_size
-                else "w"
-            )
-            self._handle = H5pyFile(self._temp_path, mode=local_mode)
+            try:
+                if mode != "w" and resource.exists():
+                    with resource.open("rb") as src, self._temp_path.open("wb") as dst:
+                        shutil.copyfileobj(src, dst)
+                local_mode = (
+                    "a"
+                    if self._temp_path.exists() and self._temp_path.stat().st_size
+                    else "w"
+                )
+                self._handle = H5pyFile(self._temp_path, mode=local_mode)
+            except Exception:
+                self._temp_path.unlink(missing_ok=True)
+                raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/utils/hdf5.py` around lines 551 - 566, The __init__ of
_RemoteH5Writer can leave an orphaned temp file if any exception occurs between
creating self._temp_path and successfully constructing self._handle; wrap the
block that does the remote copy and H5pyFile(self._temp_path, ...) construction
in a try/except (or try/finally) so that on any exception you unlink/delete
self._temp_path (and set any partial state like self._closed) before re-raising
the exception; specifically protect the region that uses resource.open(...),
shutil.copyfileobj(...), and H5pyFile(...) and ensure cleanup of self._temp_path
when initialization fails.
🧹 Nitpick comments (1)
tests/test_io/test_h5simple/test_h5simple.py (1)

78-84: Add explicit h5py-path tests for the other two helper branches.

This class now validates h5py for _get_root_attrs, but _iter_root_arrays and _get_attr_names also have separate h5py branches in dascore/io/h5simple/utils.py (Line 28-Line 44). Adding direct h5py tests for those would close backend parity and guard regressions.

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

In `@tests/test_io/test_h5simple/test_h5simple.py` around lines 78 - 84, Add two
new unit tests mirroring test_get_root_attrs_supports_h5py that explicitly
exercise the h5py-specific branches for _iter_root_arrays and _get_attr_names:
open an h5py.File in write mode, create root datasets/arrays (for
_iter_root_arrays) and several root attributes (for _get_attr_names), call the
corresponding helper (_iter_root_arrays and _get_attr_names) with the h5py.File
object, and assert the returned iterator/list contains the expected dataset
names and attribute names/values; ensure tests use the same tmp_path pattern and
names as the existing test to keep them isolated and deterministic.
🤖 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/core.py`:
- Around line 711-725: The current broad try/except around path.stat() masks
local filesystem errors and treats them as "updated"; change the logic so only
remote backends get the fallback: if is_local_path(resource) call
Path(resource).stat() directly and let exceptions propagate (so
deleted/inaccessible local paths raise), otherwise use
coerce_to_upath(resource).stat() inside a try/except that catches remote/backend
errors and issues the warnings.warn (as currently) and returns True; reference
coerce_to_upath, is_local_path, Path(...).stat().st_mtime and timestamp to
locate the code to adjust.

In `@dascore/io/dasdae/utils.py`:
- Around line 359-367: PatchSummary.shape is being set to an empty tuple instead
of the actual dataset shape; update the code that builds the PatchSummary
(around group.get("data"), dtype, and PatchSummary(...)) to set shape to the
stored data's shape when data exists — e.g., compute shape =
tuple(data_node.shape) if data_node is not None else () and pass that into
PatchSummary(shape=shape) so summaries reflect the real patch dimensions.
- Around line 344-358: When building dims and coord_dims, treat an empty stored
string as an empty tuple instead of splitting to ('',) — change the logic around
out["dims"] and the unbyte(attrs.get(f"_cdims_{name}", "")) parsing so that if
the string is "" you set dims = () (and similarly coord_dims = ()) otherwise
split into a tuple; update the code paths that use dims and coord_dims (the
tuple assigned to dims before calling separate_coord_info and the coord_dims
passed into _get_coord_summary_from_node) so empty stored dims produce no bogus
empty-string dimension.

In `@dascore/utils/downloader.py`:
- Around line 100-107: _fetch_cached currently places kwargs_tuple (including
original values) into the cached function signature, which fails for unhashable
kwarg values; fix by building a hashable representation for the cache key while
still passing the original dict to the fetcher: inside _fetch_cached convert
kwargs_tuple -> kwargs dict for the call to _get_fetcher(cache_dir).fetch(name,
**kwargs) but derive a separate cache-key component from kwargs using a stable,
hashable transformation (e.g., tuple(sorted((k, repr(v)) for k,v in
kwargs.items())) or frozenset of those pairs) so callers' unhashable values are
not required to be hashable; apply the same change to the other cached wrapper
that also accepts kwargs_tuple.

In `@dascore/utils/hdf5.py`:
- Around line 577-592: The context manager currently always calls close() in
__exit__, causing a remote upload even when the with-block raised; change
__exit__ to only perform the remote upload when no exception occurred (i.e., if
exc_type is None) and otherwise avoid calling close() (instead just close the
local HDF5 handle and remove the temp file to abort the upload). Use the
existing symbols: in __exit__ inspect exc_type, exc, tb; on exception call
self._handle.close() and self._temp_path.unlink(missing_ok=True) (do not call
self.close()), and when exc_type is None call self.close(); ensure you still
return False so original exceptions propagate and any upload failures raise
instead of being suppressed.

In `@dascore/utils/misc.py`:
- Around line 251-252: The local-file fallback currently yields the provided
paths unconditionally in the except NotADirectoryError block, skipping the ext,
timestamp and hidden-name filters that are applied for remote inputs; update the
except block in the same function (the generator used by
DirectoryIndexer.update(paths=...)) to pass the single-file input through the
same filtering logic used for the directory/remote branch (i.e. reuse the
existing filter routine or the same checks for ext, timestamp and hidden-name)
and only yield the path if it passes those filters so local files and remote
files are treated consistently.

In `@dascore/utils/remote_io.py`:
- Around line 81-100: The _warn_remote_cache_download function currently
interpolates the raw resource into user-facing messages which can leak secrets
(e.g., presigned URLs); before composing msg, pass resource through the
project's redaction helper (e.g., sanitize_remote_resource or similar) and use
the sanitized value in the f-strings, keeping the same guidance text and
warnings.warn(stacklevel=4). Do the same sanitization for the other
warning/error messages that interpolate a resource (the other remote-cache
warning/error helpers referenced in the review) so all user-facing logs and
exceptions use the redacted identifier instead of the raw resource.
- Around line 111-127: _download_remote_file currently uses a deterministic
tmp_path (local_path.with_suffix(f"{local_path.suffix}.part")) which causes
races when multiple processes/threads download the same resource; change
tmp_path creation to use a unique per-attempt temporary filename (e.g., append a
short random/uuid or use tempfile.NamedTemporaryFile semantics in the same
directory) so each concurrent downloader writes to its own temp file, keep the
existing replace() to atomically move the successful temp into local_path, and
still unlink the specific temp file in the finally block; update references to
tmp_path in the function accordingly.

In `@tests/test_io/test_dasdae/test_dasdae.py`:
- Around line 52-56: The helper written_dascore_v1_random_indexed currently just
copies the file; after copying (new_path) call index(new_path) so the file is
actually indexed before returning it; update the function to import/resolve and
invoke the index() routine used by the dasdae codebase (keep the same new_path
return) so the downstream tests exercise post-index append/scan behavior.

In `@tests/test_io/test_h5simple/test_h5simple.py`:
- Line 60: Replace the getattr usage in the test assertion to use direct
attribute access: locate the assertion that reads like assert getattr(attrs,
"dims") == "distance,time" in test_h5simple.py and change it to use attrs.dims
instead (i.e., assert attrs.dims == "distance,time") to satisfy the Ruff B009
rule and improve clarity.

In `@tests/test_io/test_remote_http.py`:
- Around line 19-29: The autouse fixture suppresses all UserWarning instances,
hiding unrelated regressions; update the suppress_expected_remote_cache_warnings
fixture to only ignore the exact expected warning text(s) instead of all
UserWarning—use warnings.filterwarnings("ignore", message=RE,
category=UserWarning) or your suppress_warnings helper with a message/match
regex that targets the remote-cache/fallback/mtime warning(s) you expect (and
keep the special-case bypass for
"test_http_hdf5_fallback_warns_once_and_reuses_cached_local_copy"); reference
the fixture name suppress_expected_remote_cache_warnings and any uses of
dc.get_format, dc.scan, dc.read when choosing the exact message regex.

---

Duplicate comments:
In `@dascore/utils/hdf5.py`:
- Around line 551-566: The __init__ of _RemoteH5Writer can leave an orphaned
temp file if any exception occurs between creating self._temp_path and
successfully constructing self._handle; wrap the block that does the remote copy
and H5pyFile(self._temp_path, ...) construction in a try/except (or try/finally)
so that on any exception you unlink/delete self._temp_path (and set any partial
state like self._closed) before re-raising the exception; specifically protect
the region that uses resource.open(...), shutil.copyfileobj(...), and
H5pyFile(...) and ensure cleanup of self._temp_path when initialization fails.

In `@dascore/utils/misc.py`:
- Around line 832-833: The direct call to Path(path).stat().st_size when path is
not None and is_local_path(path) can raise for non-file-backed streams; wrap the
stat probe in a try/except (catching OSError/Exception) and only set file_size
on success, otherwise leave file_size unset so the existing tell()/seek()
fallback runs; refer to the is_local_path check, the file_size variable
assignment, and the existing tell()/seek() fallback code to implement the
guarded stat call.

In `@tests/test_io/conftest.py`:
- Around line 34-39: The except block in copyfile (method copyfile) currently
catches BrokenPipeError and ConnectionResetError but misses
ConnectionAbortedError (raised on Windows), causing noisy disconnect failures;
update the except to include ConnectionAbortedError in the exception tuple
handled for super().copyfile(source, outputfile) so that BrokenPipeError,
ConnectionResetError, and ConnectionAbortedError are all swallowed and the
method returns None on those disconnects.

---

Nitpick comments:
In `@tests/test_io/test_h5simple/test_h5simple.py`:
- Around line 78-84: Add two new unit tests mirroring
test_get_root_attrs_supports_h5py that explicitly exercise the h5py-specific
branches for _iter_root_arrays and _get_attr_names: open an h5py.File in write
mode, create root datasets/arrays (for _iter_root_arrays) and several root
attributes (for _get_attr_names), call the corresponding helper
(_iter_root_arrays and _get_attr_names) with the h5py.File object, and assert
the returned iterator/list contains the expected dataset names and attribute
names/values; ensure tests use the same tmp_path pattern and names as the
existing test to keep them isolated and deterministic.
🪄 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: 888420f8-5b13-4d54-ae15-56debf3133c5

📥 Commits

Reviewing files that changed from the base of the PR and between a0fab7e and 7c7d73d.

📒 Files selected for processing (73)
  • .github/scripts/cache_test_data.py
  • .gitignore
  • benchmarks/readme.md
  • benchmarks/test_io_benchmarks.py
  • dascore/__init__.py
  • dascore/clients/dirspool.py
  • dascore/clients/filespool.py
  • dascore/compat.py
  • dascore/config.py
  • dascore/constants.py
  • dascore/core/spool.py
  • dascore/core/summary.py
  • dascore/exceptions.py
  • dascore/io/__init__.py
  • dascore/io/core.py
  • dascore/io/dasdae/core.py
  • dascore/io/dasdae/utils.py
  • dascore/io/h5simple/core.py
  • dascore/io/h5simple/utils.py
  • dascore/io/indexer.py
  • dascore/io/rsf/core.py
  • dascore/io/segy/core.py
  • dascore/io/sentek/core.py
  • dascore/io/sentek/utils.py
  • dascore/io/sintela_binary/core.py
  • dascore/io/sintela_binary/utils.py
  • dascore/io/tdms/core.py
  • dascore/io/tdms/utils.py
  • dascore/io/wav/core.py
  • dascore/io/xml_binary/core.py
  • dascore/io/xml_binary/utils.py
  • dascore/utils/display.py
  • dascore/utils/downloader.py
  • dascore/utils/hdf5.py
  • dascore/utils/io.py
  • dascore/utils/misc.py
  • dascore/utils/patch.py
  • dascore/utils/paths.py
  • dascore/utils/progress.py
  • dascore/utils/remote_io.py
  • docs/changelog.qmd
  • docs/contributing/new_format.qmd
  • docs/tutorial/configuration.qmd
  • docs/tutorial/file_io.qmd
  • docs/tutorial/remote_patches.qmd
  • docs/tutorial/spool.qmd
  • pyproject.toml
  • scripts/_templates/_quarto.yml
  • tests/conftest.py
  • tests/test_clients/test_filespool.py
  • tests/test_io/_common_io_test_utils.py
  • tests/test_io/conftest.py
  • tests/test_io/test_common_io.py
  • tests/test_io/test_dasdae/test_dasdae.py
  • tests/test_io/test_dasvader/test_dasvader.py
  • tests/test_io/test_h5simple/test_h5simple.py
  • tests/test_io/test_indexer.py
  • tests/test_io/test_io_core.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_rsf/test_rsf.py
  • tests/test_io/test_tdms/test_tdms_utils.py
  • tests/test_io/test_wav/test_wav.py
  • tests/test_io/test_xml_binary/test_xml_binary.py
  • tests/test_utils/test_config.py
  • tests/test_utils/test_display.py
  • tests/test_utils/test_downloader.py
  • tests/test_utils/test_hdf_utils.py
  • tests/test_utils/test_io_utils.py
  • tests/test_utils/test_misc.py
  • tests/test_utils/test_paths.py
  • tests/test_utils/test_progress.py
💤 Files with no reviewable changes (2)
  • dascore/init.py
  • dascore/io/init.py
✅ Files skipped from review due to trivial changes (19)
  • .github/scripts/cache_test_data.py
  • dascore/compat.py
  • .gitignore
  • benchmarks/readme.md
  • dascore/exceptions.py
  • scripts/_templates/_quarto.yml
  • dascore/io/tdms/utils.py
  • docs/contributing/new_format.qmd
  • dascore/utils/display.py
  • dascore/constants.py
  • tests/test_utils/test_downloader.py
  • docs/tutorial/configuration.qmd
  • docs/tutorial/file_io.qmd
  • dascore/clients/dirspool.py
  • docs/tutorial/spool.qmd
  • pyproject.toml
  • tests/test_utils/test_paths.py
  • docs/tutorial/remote_patches.qmd
  • tests/test_utils/test_config.py
🚧 Files skipped from review as they are similar to previous changes (23)
  • benchmarks/test_io_benchmarks.py
  • docs/changelog.qmd
  • tests/test_utils/test_progress.py
  • dascore/io/sentek/core.py
  • dascore/io/sintela_binary/utils.py
  • dascore/utils/patch.py
  • tests/test_clients/test_filespool.py
  • dascore/io/sintela_binary/core.py
  • dascore/clients/filespool.py
  • dascore/io/wav/core.py
  • dascore/io/h5simple/utils.py
  • tests/test_utils/test_display.py
  • dascore/io/xml_binary/utils.py
  • dascore/io/h5simple/core.py
  • dascore/core/spool.py
  • tests/test_io/test_wav/test_wav.py
  • dascore/utils/paths.py
  • tests/test_io/test_remote_common_io.py
  • tests/test_io/test_remote_memory.py
  • tests/test_io/test_io_core.py
  • dascore/io/xml_binary/core.py
  • dascore/core/summary.py
  • tests/test_utils/test_io_utils.py

Comment thread dascore/io/core.py
Comment thread dascore/io/dasdae/utils.py Outdated
Comment thread dascore/io/dasdae/utils.py
Comment thread dascore/utils/downloader.py Outdated
Comment thread dascore/utils/hdf5.py Outdated
Comment thread dascore/utils/remote_io.py
Comment thread dascore/utils/remote_io.py
Comment thread tests/test_io/test_dasdae/test_dasdae.py Outdated
Comment thread tests/test_io/test_h5simple/test_h5simple.py Outdated
Comment thread tests/test_io/test_remote_http.py

@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

♻️ Duplicate comments (1)
tests/test_io/conftest.py (1)

34-39: ⚠️ Potential issue | 🟡 Minor

Also catch ConnectionAbortedError here.

Line 38 still misses the Windows disconnect mode previously seen in CI ([WinError 10053]), so this fixture can still fail on client aborts.

Suggested diff
     def copyfile(self, source, outputfile):
         """Silence BrokenPipeError/ConnectionResetError from test clients."""
         try:
             return super().copyfile(source, outputfile)
-        except (BrokenPipeError, ConnectionResetError):
+        except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
             return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_io/conftest.py` around lines 34 - 39, The copyfile method
currently only catches BrokenPipeError and ConnectionResetError; update its
exception handling to also include ConnectionAbortedError so client aborts on
Windows ([WinError 10053]) are silenced. In the copyfile function (method name
copyfile in tests/test_io/conftest.py) add ConnectionAbortedError to the except
tuple so the handler becomes except (BrokenPipeError, ConnectionResetError,
ConnectionAbortedError): and continue returning None as before.
🧹 Nitpick comments (1)
tests/test_io/conftest.py (1)

237-240: Preserve relative paths in these conversion helpers.

Line 238 and Line 252 collapse everything to the basename, so a nested local fixture path like nested/example_dasdae_event_2.h5 gets remapped to /das/example_dasdae_event_2.h5. That’s narrower than the “local fixture path or fetch name” contract in the docstrings.

Either preserve the relative path when the input is already a local fixture path, or narrow the helper contract/docstrings to basename-only inputs.

Also applies to: 251-254

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

In `@tests/test_io/conftest.py` around lines 237 - 240, The helper _convert
currently discards subdirectories by using Path(path_or_name).name; instead,
keep the relative path: construct p = Path(path_or_name), call
ensure_http_fetch_file(p.name) to ensure the file is available, and return
http_das_path / p (so nested paths like "nested/example.h5" map to
http_das_path/nested/example.h5). Apply the same change to the other conversion
helper with the same basename-only pattern so both preserve relative paths while
still passing the basename into ensure_http_fetch_file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_io/conftest.py`:
- Around line 213-228: The readiness probe (the loop using probe_url and
urlopen) must be inside the same try/finally that performs cleanup so the
uvicorn thread is always stopped; move the probe and its failure check into the
try block that surrounds the yield so that any pytest.fail() triggers the
finally which sets server.should_exit and joins thread. Ensure you still start
the thread (thread = threading.Thread(target=_run, daemon=True)) before the try,
then inside try run the readiness loop, call pytest.fail() on exhaustion, yield
UPath(f"http://{host}:{port}/das"), and in finally set server.should_exit = True
and thread.join(timeout=5).
- Around line 190-196: Don’t call Path.resolve() on the requested file (which
removes symlink fallback); instead build the candidate path as file_path =
served_root / rel_path (no .resolve()) and perform the containment check with a
string/commonpath comparison (e.g., os.path.commonpath([str(served_root),
str(file_path)]) != str(served_root)) to prevent path traversal while preserving
symlinks used by _link_or_copy; keep the subsequent exists()/is_file() checks
and return response_cls(404) or file_response_cls(file_path) as before.

---

Duplicate comments:
In `@tests/test_io/conftest.py`:
- Around line 34-39: The copyfile method currently only catches BrokenPipeError
and ConnectionResetError; update its exception handling to also include
ConnectionAbortedError so client aborts on Windows ([WinError 10053]) are
silenced. In the copyfile function (method name copyfile in
tests/test_io/conftest.py) add ConnectionAbortedError to the except tuple so the
handler becomes except (BrokenPipeError, ConnectionResetError,
ConnectionAbortedError): and continue returning None as before.

---

Nitpick comments:
In `@tests/test_io/conftest.py`:
- Around line 237-240: The helper _convert currently discards subdirectories by
using Path(path_or_name).name; instead, keep the relative path: construct p =
Path(path_or_name), call ensure_http_fetch_file(p.name) to ensure the file is
available, and return http_das_path / p (so nested paths like
"nested/example.h5" map to http_das_path/nested/example.h5). Apply the same
change to the other conversion helper with the same basename-only pattern so
both preserve relative paths while still passing the basename into
ensure_http_fetch_file.
🪄 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: 29c34f61-38da-49e7-9129-b49d03568f34

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7d73d and 9045f8a.

📒 Files selected for processing (2)
  • .agents/plans/duck_db_indexer.qmd
  • tests/test_io/conftest.py
✅ Files skipped from review due to trivial changes (1)
  • .agents/plans/duck_db_indexer.qmd

Comment thread tests/test_io/conftest.py
Comment thread tests/test_io/conftest.py Outdated
@github-actions

github-actions Bot commented Apr 1, 2026

Copy link
Copy Markdown

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

@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)
.github/workflows/runtests.yml (1)

96-100: Remove stale commented tmate block.

The commented duplicate debug block is now dead config and can be dropped to reduce workflow noise.

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

In @.github/workflows/runtests.yml around lines 96 - 100, Remove the stale
commented tmate block by deleting the commented lines that declare the "Setup
tmate session" step and the use of "mxschmitt/action-tmate@v3" so the duplicate
debug block is no longer present in the workflow; ensure no other commented
duplicate debug steps remain in runtests.yml.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/load-shared-vars/action.yml:
- Around line 26-30: The shared CI action reduced test coverage by narrowing the
python_test_matrix and test_os_matrix; revert or restore those values so the
action continues to provide full-platform coverage for all consumers—update the
python_test_matrix and test_os_matrix entries in action.yml to include the
original python versions (including 3.11 and 3.12) and all OS entries
(ubuntu-latest, macos-latest, windows-latest), or alternatively move any
experimental matrix narrowing into a workflow-level override so the shared
action remains unchanged.

In @.github/workflows/runtests.yml:
- Around line 73-75: Gate the interactive tmate step ("name: Setup tmate
session" using "mxschmitt/action-tmate@v3") so it only runs under safe
conditions: add a conditional like if: failure() to run on failure-only, set the
action input limit-access-to-actor: true to restrict SSH to the workflow actor,
and (for public repos) move tmate behind a manual trigger by adding
workflow_dispatch with an inputs.debug_enabled flag and change the step
condition to if: ${{ github.event_name == 'workflow_dispatch' &&
inputs.debug_enabled }} so tmate only activates on explicit opt-in.

---

Nitpick comments:
In @.github/workflows/runtests.yml:
- Around line 96-100: Remove the stale commented tmate block by deleting the
commented lines that declare the "Setup tmate session" step and the use of
"mxschmitt/action-tmate@v3" so the duplicate debug block is no longer present in
the workflow; ensure no other commented duplicate debug steps remain in
runtests.yml.
🪄 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: f29dc5ca-346b-49c8-8a3a-949ca6a13268

📥 Commits

Reviewing files that changed from the base of the PR and between 9045f8a and a41d2ad.

📒 Files selected for processing (3)
  • .github/actions/load-shared-vars/action.yml
  • .github/workflows/run_min_dep_tests.yml
  • .github/workflows/runtests.yml

Comment thread .github/actions/load-shared-vars/action.yml Outdated
Comment thread .github/workflows/runtests.yml Outdated
@codecov

codecov Bot commented Apr 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (e656799) to head (91c1bfa).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##               dev      #645    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          134       137     +3     
  Lines        12042     12650   +608     
==========================================
+ Hits         12042     12650   +608     
Flag Coverage Δ
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 Sentry.
📢 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.

@d-chambers d-chambers added the debug Used to enhance logs/start debugging console label Apr 3, 2026
@d-chambers
d-chambers merged commit 2bc087e into dev Apr 3, 2026
25 checks passed
@d-chambers
d-chambers deleted the universal-pathlib branch April 3, 2026 11:56
@d-chambers d-chambers mentioned this pull request Apr 4, 2026
4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

debug Used to enhance logs/start debugging console documentation Improvements or additions to documentation IO Work for reading/writing different formats

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant