Skip to content

Commit 074de39

Browse files
Merge branch 'master' into enh-lad-docstrings-core
2 parents ba3ce41 + 0a09670 commit 074de39

11 files changed

Lines changed: 124 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
# 0.74.3 (Sat Feb 14 2026)
2+
3+
#### 🐛 Bug Fix
4+
5+
- bf: pass aiohttp timeouts to fsspec to fix test hang [#1795](https://github.com/dandi/dandi-cli/pull/1795) ([@yarikoptic](https://github.com/yarikoptic))
6+
- Enhance dandiset metadata error messages [#1790](https://github.com/dandi/dandi-cli/pull/1790) ([@yarikoptic](https://github.com/yarikoptic) [@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
7+
- Fix type annotation in upload sync path prefix calculation [#1794](https://github.com/dandi/dandi-cli/pull/1794) ([@yarikoptic](https://github.com/yarikoptic) [@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
8+
- Fix macOS-15-intel CI failures: h5py and opencv-python regressions [#1783](https://github.com/dandi/dandi-cli/pull/1783) ([@yarikoptic](https://github.com/yarikoptic))
9+
10+
#### 📝 Documentation
11+
12+
- Add module docstrings to validation and NWB utilities [#1789](https://github.com/dandi/dandi-cli/pull/1789) ([@yarikoptic](https://github.com/yarikoptic) [@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
13+
14+
#### Authors: 2
15+
16+
- GitMate for @yarikoptic ([@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
17+
- Yaroslav Halchenko ([@yarikoptic](https://github.com/yarikoptic))
18+
19+
---
20+
121
# 0.74.2 (Fri Jan 30 2026)
222

323
#### 🐛 Bug Fix

dandi/dandiset.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ def __init__(
4242
if not allow_empty and not os.path.lexists(
4343
self.path_obj / dandiset_metadata_file
4444
):
45-
raise ValueError(f"No dandiset at {path}")
45+
raise ValueError(
46+
f"No dandiset at {path}. "
47+
f"The directory does not contain a '{dandiset_metadata_file}' file. "
48+
"Use 'dandi download' to download a dandiset or check the path."
49+
)
4650
self.metadata: dict | None = None
4751
self._metadata_file_obj = self.path_obj / dandiset_metadata_file
4852
self._load_metadata()
@@ -139,11 +143,17 @@ def _get_identifier(metadata: dict) -> str | None:
139143
@property
140144
def identifier(self) -> str:
141145
if self.metadata is None:
142-
raise ValueError("No metadata record found in Dandiset")
146+
raise ValueError(
147+
f"No metadata record found in Dandiset at {self.path}. "
148+
f"The '{dandiset_metadata_file}' file may be empty or corrupted. "
149+
"Use 'dandi download' to re-download the dandiset metadata."
150+
)
143151
id_ = self._get_identifier(self.metadata)
144152
if not id_:
145153
raise ValueError(
146-
f"Found no dandiset.identifier in metadata record: {self.metadata}"
154+
f"Found no dandiset.identifier in metadata record. "
155+
f"The '{dandiset_metadata_file}' file must contain an 'identifier' field. "
156+
f"Metadata: {self.metadata}"
147157
)
148158
return id_
149159

dandi/delete.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
"""Delete assets and dandisets from DANDI Archive.
2+
3+
This module provides functionality for deleting assets and entire dandisets
4+
from DANDI Archive instances. It supports:
5+
- Single and batch asset deletion
6+
- Dandiset deletion with confirmation
7+
- URL-based and path-based deletion
8+
- Skip-missing option for non-existent resources
9+
"""
10+
111
from __future__ import annotations
212

313
from collections.abc import Iterable, Iterator

dandi/download.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
"""Download assets from DANDI Archive.
2+
3+
This module provides functionality for downloading files and Zarr archives
4+
from DANDI Archive instances. It supports:
5+
- Individual file downloads with integrity verification
6+
- Zarr archive downloads with parallel entry handling
7+
- Resume capability for interrupted downloads
8+
- Progress tracking and error recovery
9+
"""
10+
111
from __future__ import annotations
212

313
from collections import Counter, deque

dandi/misctypes.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,27 @@ def open(self) -> IO[bytes]:
345345
# Optional dependency:
346346
import fsspec
347347

348+
from aiohttp import ClientTimeout
349+
348350
# We need to call open() on the return value of fsspec.open() because
349351
# otherwise the filehandle will only be opened when used to enter a
350352
# context manager.
351-
return cast(IO[bytes], fsspec.open(self.url, mode="rb").open())
353+
#
354+
# Pass explicit timeouts to aiohttp to prevent indefinite hangs in
355+
# fsspec's sync() wrapper. Without these, a stalled connection to S3
356+
# (or minio in tests) causes fsspec's background IO thread to block
357+
# forever, which in turn blocks the calling thread in
358+
# threading.Event.wait() — see https://github.com/fsspec/filesystem_spec/issues/1666
359+
return cast(
360+
IO[bytes],
361+
fsspec.open(
362+
self.url,
363+
mode="rb",
364+
client_kwargs={
365+
"timeout": ClientTimeout(total=120, sock_read=60, sock_connect=30)
366+
},
367+
).open(),
368+
)
352369

353370
def get_size(self) -> int:
354371
return self.size

dandi/move.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
"""Move and rename assets in DANDI Archive.
2+
3+
This module provides functionality for moving and renaming assets both
4+
locally and remotely in DANDI Archive instances. Features include:
5+
- Local file reorganization
6+
- Remote asset path changes
7+
- Combined local and remote moves
8+
- Conflict resolution (skip, overwrite, error)
9+
- Validation of move operations
10+
"""
11+
112
from __future__ import annotations
213

314
from abc import ABC, abstractmethod

dandi/organize.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
"""
2-
ATM primarily a sandbox for some functionality for dandi organize
1+
"""Organize and structure NWB files according to DANDI conventions.
2+
3+
This module provides functionality for organizing neuroscience data files
4+
according to DANDI's file organization schema. Features include:
5+
- Automatic path generation from metadata
6+
- BIDS-like subject/session organization
7+
- Metadata-driven file naming
8+
- Validation of organized paths
9+
- Support for videos and generic files
310
"""
411

512
from __future__ import annotations

dandi/pynwb_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
"""Utilities for working with NWB (Neurodata Without Borders) files.
2+
3+
This module provides helper functions for reading, validating, and extracting
4+
metadata from NWB files using PyNWB. Features include:
5+
- NWB file I/O with caching
6+
- Metadata extraction for DANDI schema
7+
- Version compatibility checking
8+
- External link detection
9+
- Validation against NWB standards
10+
"""
11+
112
from __future__ import annotations
213

314
from collections import Counter

dandi/tests/test_metadata.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,7 @@ def test_nwb2asset(simple2_nwb: Path) -> None:
11451145
)
11461146

11471147

1148+
@pytest.mark.timeout(120)
11481149
@pytest.mark.xfail(reason="https://github.com/dandi/dandi-cli/issues/1450")
11491150
def test_nwb2asset_remote_asset(nwb_dandiset: SampleDandiset) -> None:
11501151
pytest.importorskip("fsspec")

dandi/upload.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1+
"""Upload assets to DANDI Archive.
2+
3+
This module handles uploading NWB files and other assets to DANDI Archive
4+
instances. Features include:
5+
- Validation of files before upload
6+
- Progress tracking with resume capability
7+
- Metadata extraction and assignment
8+
- BIDS validation integration
9+
- Concurrent uploads with thread pool
10+
"""
11+
112
from __future__ import annotations
213

314
from collections import defaultdict
415
from collections.abc import Iterator, Sequence
516
from contextlib import ExitStack
617
from enum import Enum
7-
from functools import reduce
818
import io
919
import os.path
1020
from pathlib import Path
@@ -493,7 +503,7 @@ def upload_agg(*ignored: Any) -> str:
493503
for p in paths:
494504
rp = os.path.relpath(p, dandiset.path)
495505
relpaths.append("" if rp == "." else rp)
496-
path_prefix = reduce(os.path.commonprefix, relpaths) # type: ignore[arg-type]
506+
path_prefix = os.path.commonprefix(relpaths)
497507
to_delete = []
498508
for asset in remote_dandiset.get_assets_with_path_prefix(path_prefix):
499509
if any(

0 commit comments

Comments
 (0)