Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2ca1c25
Read missing annexed content with datalad-fuse, cached by annex key
claude Oct 6, 2026
3655287
validate: stream missing annexed content with datalad-fuse
claude Oct 6, 2026
52b675e
Move annex_fingerprint next to AnnexedReadableFile; trim field comments
claude Oct 6, 2026
31b357f
Merge branch 'claude/datalad-fuse-readable' into claude/validate-stre…
claude Oct 6, 2026
22a5459
Note URL order and proxy behavior of datalad-fuse
claude Oct 6, 2026
51fdc5a
Merge branch 'claude/datalad-fuse-readable' into claude/validate-stre…
claude Oct 6, 2026
3361ae3
Document URL order and proxy behavior of stream policy
claude Oct 6, 2026
228026a
Use datalad-fuse's pluggable backends (remfile) when available
claude Oct 6, 2026
1ad6495
Import fscacher lazily in dandi.support.datalad_fuse
claude Oct 6, 2026
c9c4af5
Merge branch 'claude/datalad-fuse-readable' into claude/validate-stre…
claude Oct 6, 2026
c8ee680
Merge branch 'claude/validate-stream-via-datalad-fuse' into claude/da…
claude Oct 6, 2026
40b620a
Merge branch 'master' into claude/datalad-fuse-readable
claude Oct 6, 2026
f9dbdc0
Merge branch 'claude/datalad-fuse-readable' into claude/validate-stre…
claude Oct 6, 2026
5dfd63e
Merge branch 'claude/validate-stream-via-datalad-fuse' into claude/da…
claude Oct 6, 2026
d18843d
TEMP: CI diagnostics for datalad job hang (to be reverted)
claude Oct 7, 2026
7964633
Disable automatic GC while a streamed annexed file is open
claude Oct 7, 2026
de6df64
Revert "TEMP: CI diagnostics for datalad job hang (to be reverted)"
claude Oct 7, 2026
0be1c28
Merge remote-tracking branch 'origin/claude/datalad-fuse-readable' in…
claude Oct 7, 2026
7352f59
Merge branch 'claude/validate-stream-via-datalad-fuse' into claude/da…
claude Oct 7, 2026
5383c68
Read missing annexed content with datalad-fuse, cached by annex key
claude Oct 6, 2026
359e4af
Move annex_fingerprint next to AnnexedReadableFile; trim field comments
claude Oct 6, 2026
3809292
Note URL order and proxy behavior of datalad-fuse
claude Oct 6, 2026
83a1b93
Import fscacher lazily in dandi.support.datalad_fuse
claude Oct 6, 2026
716d0d7
Disable automatic GC while a streamed annexed file is open
claude Oct 7, 2026
c493d6c
validate: stream missing annexed content with datalad-fuse
claude Oct 6, 2026
97a7889
Document URL order and proxy behavior of stream policy
claude Oct 6, 2026
1821e2f
TEMP: CI diagnostics for datalad job hang (to be reverted)
claude Oct 7, 2026
106f661
Revert "TEMP: CI diagnostics for datalad job hang (to be reverted)"
claude Oct 7, 2026
438e5c5
Merge branch 'claude/validate-stream-via-datalad-fuse' into claude/da…
claude Oct 12, 2026
6d23a54
Read streamed annexed content in small cached blocks
claude Oct 12, 2026
baf6aba
Merge remote-tracking branch 'origin/claude/datalad-fuse-readable' in…
claude Oct 12, 2026
2c93792
Merge remote-tracking branch 'origin/claude/validate-stream-via-datal…
claude Oct 12, 2026
611ffb6
Apply the small-reads bound only when fsspec reads the file
claude Oct 12, 2026
2d461dd
Fetch consecutive missing blocks of streamed content in one request
claude Oct 12, 2026
2174306
Merge remote-tracking branch 'origin/claude/datalad-fuse-readable' in…
claude Oct 12, 2026
b2f56f4
Cache NWB Inspector results, like those of PyNWB validation
claude Oct 12, 2026
421f0c6
Add `dandi validate -J/--jobs` to validate NWB files in parallel
claude Oct 12, 2026
79b685c
Merge remote-tracking branch 'origin/claude/validate-stream-via-datal…
claude Oct 12, 2026
a65f6ee
Log the progress of `dandi validate`, one message per file validated
claude Oct 13, 2026
bb56977
Merge remote-tracking branch 'origin/claude/validate-stream-via-datal…
claude Oct 13, 2026
e0aac05
Merge remote-tracking branch 'origin/claude/validate-stream-via-datal…
claude Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ jobs:
- os: ubuntu-latest
python: '3.12'
mode: datalad
- os: ubuntu-latest
python: '3.12'
mode: datalad-dev

steps:
- name: Set up environment
Expand Down Expand Up @@ -130,6 +133,13 @@ jobs:
if: matrix.mode == 'datalad'
run: pip install git-annex ".[datalad]"

# TODO: drop once datalad/datalad-fuse#131 is released
- name: Install git-annex, remfile and development datalad-fuse
if: matrix.mode == 'datalad-dev'
run: |
pip install git-annex remfile ".[datalad]" \
"datalad-fuse @ git+https://github.com/datalad/datalad-fuse@refs/pull/131/head"

- name: Create NFS filesystem
if: matrix.mode == 'nfs'
run: |
Expand Down
25 changes: 20 additions & 5 deletions dandi/support/datalad_fuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
from __future__ import annotations

import atexit
from collections.abc import Iterator
from dataclasses import dataclass, field
from datetime import datetime
from functools import cache
import gc
from itertools import chain
import logging
import os
from pathlib import Path
Expand All @@ -51,11 +53,17 @@ def get_adapter() -> Any:
root so that it serves files of any dataset, and closed at exit
"""
# Optional dependency:
from datalad_fuse.fsspec import FsspecAdapter
try:
# datalad-fuse with pluggable backends (datalad/datalad-fuse#131):
# remfile for HDF5-based files (such as NWB) if installed, fsspec
# otherwise, as configured with datalad.fusefs.backends
from datalad_fuse.adapter import RemoteFilesystemAdapter as Adapter
except ImportError:
from datalad_fuse.fsspec import FsspecAdapter as Adapter

# caching=False: do not keep the streamed blocks on disk, in the dataset.
# The root and all paths passed to the adapter must be absolute.
adapter = FsspecAdapter(Path(os.path.abspath(os.sep)), caching=False)
adapter = Adapter(Path(os.path.abspath(os.sep)), caching=False)
atexit.register(adapter.__exit__, None, None, None)
return adapter

Expand Down Expand Up @@ -296,9 +304,12 @@ def get_annexed_readable(path: str | Path) -> AnnexedReadableFile | None:
# Not a broken symbolic link
return None
try:
from datalad_fuse.fsspec import FileState
from datalad_fuse.adapter import FileState
except ImportError:
return None
try:
from datalad_fuse.fsspec import FileState
except ImportError:
return None
if not annex_initialized(filepath.parent):
return None
try:
Expand All @@ -311,7 +322,11 @@ def get_annexed_readable(path: str | Path) -> AnnexedReadableFile | None:
return None
if state is not FileState.NO_CONTENT or key is None or key.size is None:
return None
url = next(dsap.get_urls(str(key)), None)
urls: Iterator[str] = dsap.get_urls(str(key))
if hasattr(dsap, "get_exporttree_urls"):
# Fallback to S3 exports of datalad/datalad-fuse#131
urls = chain(urls, dsap.get_exporttree_urls(relpath, key))
url = next(urls, None)
if url is None:
lgr.debug("%s: git-annex knows no URL for key %s", filepath, key)
return None
Expand Down
37 changes: 35 additions & 2 deletions dandi/support/tests/test_datalad_fuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,15 @@ def test_annexed_readable_reads_little(
r = get_annexed_readable(tmp_path / "ds" / "sub-01" / "sub-01.nwb")
assert r is not None
assert get_neurodata_types.__wrapped__(r) == get_neurodata_types.__wrapped__(local)
# With fsspec's default 5 MiB read-ahead, most of the file was downloaded
assert 0 < server.bytes_served < size / 10
assert server.bytes_served > 0
with r.open() as fp:
remfile_used = type(fp._fp).__name__ == "RemfileWrapper" # type: ignore[attr-defined]
if not remfile_used:
# With fsspec's default 5 MiB read-ahead, most of the file was
# downloaded. (remfile, used for HDF5 files by
# datalad/datalad-fuse#131 when installed, reads ahead by itself, in
# chunks growing up to 100 MiB that datalad-fuse does not configure.)
assert server.bytes_served < size / 10


@pytest.mark.ai_generated
Expand Down Expand Up @@ -214,3 +221,29 @@ def test_annexed_readable_cached_by_key(
assert neurodata_types(get_annexed_readable(nwb)) == expected
assert neurodata_types(nwb) == expected
assert server.ranged_requests == requests


@pytest.mark.ai_generated
def test_annexed_readable_remfile(
streamed_nwb: tuple[Path, RangeHTTPServer], simple2_nwb: Path
) -> None:
"""With datalad/datalad-fuse#131 and remfile, NWB files are read with remfile"""
pytest.importorskip("datalad_fuse.adapter")
pytest.importorskip("remfile")
from datalad_fuse.remfile import RemfileWrapper

nwb, server = streamed_nwb
r = get_annexed_readable(nwb)
assert r is not None
fp = r.open()
try:
# (wrapped to pause garbage collection while it is open)
assert isinstance(fp._fp, RemfileWrapper) # type: ignore[attr-defined]
with h5py.File(fp, "r") as h5:
assert h5.attrs["nwb_version"]
finally:
fp.close()
assert server.ranged_requests > 0
assert get_neurodata_types.__wrapped__(r) == get_neurodata_types.__wrapped__(
simple2_nwb
)
6 changes: 6 additions & 0 deletions docs/source/cmdline/validate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ Notes:

- git-annex must be initialized in the clone, which ``datalad clone`` does
(after ``git clone``, run ``git annex init``).
- With the development version of datalad-fuse from
https://github.com/datalad/datalad-fuse/pull/131 and remfile_ installed
(``pip install remfile``), NWB and other HDF5-based files are read with
remfile, which is optimized for HDF5's access pattern, and content exported
to S3 without a URL registered in git-annex can be found too.
- datalad-fuse does not use the HTTP proxy environment variables
(``HTTPS_PROXY`` etc.), so the URLs must be reachable directly.
- Some nwbinspector checks read data arrays (e.g., timestamps), so the amount
Expand All @@ -145,6 +150,7 @@ Notes:
the file and folder names of the annexed files themselves.

.. _datalad-fuse: https://github.com/datalad/datalad-fuse
.. _remfile: https://github.com/flatironinstitute/remfile


Development Options
Expand Down
Loading