Skip to content

Commit cb5f1b9

Browse files
Merge pull request #1415 from datajoint/fix/1410-filepath-directory-support
feat: support directory references in <filepath@store> (fixes #1410)
2 parents 08cc2d9 + 345309e commit cb5f1b9

File tree

4 files changed

+34
-13
lines changed

4 files changed

+34
-13
lines changed

src/datajoint/builtin_codecs/filepath.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,22 @@ def encode(self, value: Any, *, key: dict | None = None, store_name: str | None
145145
if not backend.exists(path):
146146
raise FileNotFoundError(f"File not found in store '{store_name or 'default'}': {path}")
147147

148-
# Get file info
149-
try:
150-
size = backend.size(path)
151-
except Exception:
152-
size = None
148+
# Detect whether the path is a directory or a file
149+
is_dir = backend.isdir(path)
150+
151+
# Get file size (not applicable for directories)
152+
size = None
153+
if not is_dir:
154+
try:
155+
size = backend.size(path)
156+
except (FileNotFoundError, OSError):
157+
pass
153158

154159
return {
155160
"path": path,
156161
"store": store_name,
157162
"size": size,
158-
"is_dir": False,
163+
"is_dir": is_dir,
159164
"timestamp": datetime.now(timezone.utc).isoformat(),
160165
}
161166

src/datajoint/objectref.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ def _verify_folder(self) -> bool:
379379
manifest_path = f"{self.path}.manifest.json"
380380

381381
if not self._backend.exists(manifest_path):
382-
raise IntegrityError(f"Manifest file missing: {manifest_path}")
382+
# Directory was stored without a manifest — treat as unverified but valid
383+
return True
383384

384385
# Read manifest
385386
manifest_data = self._backend.get_buffer(manifest_path)

src/datajoint/storage.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ def get_buffer(self, remote_path: str | PurePosixPath) -> bytes:
555555

556556
def exists(self, remote_path: str | PurePosixPath) -> bool:
557557
"""
558-
Check if a file exists in storage.
558+
Check if a path (file or directory) exists in storage.
559559
560560
Parameters
561561
----------
@@ -565,15 +565,28 @@ def exists(self, remote_path: str | PurePosixPath) -> bool:
565565
Returns
566566
-------
567567
bool
568-
True if file exists.
568+
True if the path exists.
569569
"""
570570
full_path = self._full_path(remote_path)
571571
logger.debug(f"exists: {self.protocol}:{full_path}")
572+
return self.fs.exists(full_path)
572573

573-
if self.protocol == "file":
574-
return Path(full_path).is_file()
575-
else:
576-
return self.fs.exists(full_path)
574+
def isdir(self, remote_path: str | PurePosixPath) -> bool:
575+
"""
576+
Check if a path refers to a directory in storage.
577+
578+
Parameters
579+
----------
580+
remote_path : str or PurePosixPath
581+
Path in storage.
582+
583+
Returns
584+
-------
585+
bool
586+
True if the path is a directory.
587+
"""
588+
full_path = self._full_path(remote_path)
589+
return self.fs.isdir(full_path)
577590

578591
def remove(self, remote_path: str | PurePosixPath) -> None:
579592
"""

tests/unit/test_codecs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ def test_filepath_allows_user_paths(self):
548548
with patch("datajoint.hash_registry.get_store_backend") as mock_get_backend:
549549
mock_backend = MagicMock()
550550
mock_backend.exists.return_value = True
551+
mock_backend.isdir.return_value = False
551552
mock_backend.size.return_value = 1024
552553
mock_get_backend.return_value = mock_backend
553554

@@ -636,6 +637,7 @@ def test_filepath_enforces_filepath_prefix(self):
636637
with patch("datajoint.hash_registry.get_store_backend") as mock_get_backend:
637638
mock_backend = MagicMock()
638639
mock_backend.exists.return_value = True
640+
mock_backend.isdir.return_value = False
639641
mock_backend.size.return_value = 3072
640642
mock_get_backend.return_value = mock_backend
641643

0 commit comments

Comments
 (0)