Skip to content

Commit 7980524

Browse files
Merge pull request #1792 from dandi/enh-lad-errors-paths
Enhance path validation error messages
2 parents c58bd37 + ee32925 commit 7980524

2 files changed

Lines changed: 71 additions & 23 deletions

File tree

dandi/move.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from typing import NewType
2525

2626
from . import get_logger
27-
from .consts import DandiInstance
27+
from .consts import DandiInstance, dandiset_metadata_file
2828
from .dandiapi import DandiAPIClient, RemoteAsset, RemoteDandiset
2929
from .dandiarchive import DandisetURL, parse_dandi_url
3030
from .dandiset import Dandiset
@@ -244,7 +244,11 @@ def resolve(self, path: str) -> tuple[AssetPath, bool]:
244244
posixpath.normpath(posixpath.join(self.subpath.as_posix(), path))
245245
)
246246
if p.parts and p.parts[0] == os.pardir:
247-
raise ValueError(f"{path!r} is outside of Dandiset")
247+
raise ValueError(
248+
f"{path!r} is outside of Dandiset. "
249+
"Paths cannot use '..' to navigate above the Dandiset root. "
250+
"All assets must remain within the Dandiset directory structure."
251+
)
248252
return (AssetPath(str(p)), path.endswith("/"))
249253

250254
def calculate_moves(
@@ -483,11 +487,17 @@ def get_path(self, path: str, is_src: bool = True) -> File | Folder:
483487
rpath, needs_dir = self.resolve(path)
484488
p = self.dandiset_path / rpath
485489
if not os.path.lexists(p):
486-
raise NotFoundError(f"No asset at local path {path!r}")
490+
raise NotFoundError(
491+
f"No asset at local path {path!r}. "
492+
"Verify the path is correct and the file exists locally."
493+
)
487494
if p.is_dir():
488495
if is_src:
489496
if p == self.dandiset_path / self.subpath:
490-
raise ValueError("Cannot move current working directory")
497+
raise ValueError(
498+
"Cannot move current working directory. "
499+
"Change to a different directory before moving this location."
500+
)
491501
files = [
492502
df.filepath.relative_to(p).as_posix()
493503
for df in find_dandi_files(
@@ -499,7 +509,10 @@ def get_path(self, path: str, is_src: bool = True) -> File | Folder:
499509
files = []
500510
return Folder(rpath, files)
501511
elif needs_dir:
502-
raise ValueError(f"Local path {path!r} is a file")
512+
raise ValueError(
513+
f"Local path {path!r} is a file but a directory was expected. "
514+
"Use a path ending with '/' for directories."
515+
)
503516
else:
504517
return File(rpath)
505518

@@ -623,7 +636,10 @@ def get_path(self, path: str, is_src: bool = True) -> File | Folder:
623636
file_found = False
624637
if rpath == self.subpath.as_posix():
625638
if is_src:
626-
raise ValueError("Cannot move current working directory")
639+
raise ValueError(
640+
"Cannot move current working directory. "
641+
"Change to a different directory before moving this location."
642+
)
627643
else:
628644
return Folder(rpath, [])
629645
for p in self.assets.keys():
@@ -640,7 +656,10 @@ def get_path(self, path: str, is_src: bool = True) -> File | Folder:
640656
if relcontents:
641657
return Folder(rpath, relcontents)
642658
if needs_dir and file_found:
643-
raise ValueError(f"Remote path {path!r} is a file")
659+
raise ValueError(
660+
f"Remote path {path!r} is a file but a directory was expected. "
661+
"Use a path ending with '/' for directories."
662+
)
644663
elif (
645664
not needs_dir
646665
and not is_src
@@ -652,7 +671,11 @@ def get_path(self, path: str, is_src: bool = True) -> File | Folder:
652671
# remote directory.
653672
return Folder(rpath, [])
654673
else:
655-
raise NotFoundError(f"No asset at remote path {path!r}")
674+
raise NotFoundError(
675+
f"No asset at remote path {path!r}. "
676+
"Verify the path is correct and the asset exists on the server. "
677+
"Use 'dandi ls' to list available assets."
678+
)
656679

657680
def is_dir(self, path: AssetPath) -> bool:
658681
"""Returns true if the given path points to a directory"""
@@ -902,7 +925,11 @@ def find_dandiset_and_subpath(path: Path) -> tuple[Dandiset, Path]:
902925
path = path.absolute()
903926
ds = Dandiset.find(path)
904927
if ds is None:
905-
raise ValueError(f"{path}: not a Dandiset")
928+
raise ValueError(
929+
f"{path}: not a Dandiset. "
930+
f"The directory does not contain a '{dandiset_metadata_file}' file. "
931+
"Use 'dandi download' to download a dandiset first."
932+
)
906933
return (ds, path.relative_to(ds.path))
907934

908935

dandi/tests/test_move.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,15 @@ def test_move_nonexistent_src(
374374
work_on=work_on,
375375
dandi_instance=moving_dandiset.api.instance_id,
376376
)
377-
assert (
378-
str(excinfo.value)
379-
== f"No asset at {'remote' if work_on == 'remote' else 'local'} path 'subdir1/avocado.txt'"
377+
asset_type = "remote" if work_on == MoveWorkOn.REMOTE else "local"
378+
assert str(excinfo.value) == (
379+
f"No asset at {asset_type} path 'subdir1/avocado.txt'. "
380+
+ (
381+
"Verify the path is correct and the asset exists on the server. "
382+
"Use 'dandi ls' to list available assets."
383+
if work_on == MoveWorkOn.REMOTE
384+
else "Verify the path is correct and the file exists locally."
385+
)
380386
)
381387
check_assets(moving_dandiset, starting_assets, work_on, {})
382388

@@ -400,9 +406,10 @@ def test_move_file_slash_src(
400406
work_on=work_on,
401407
dandi_instance=moving_dandiset.api.instance_id,
402408
)
403-
assert (
404-
str(excinfo.value)
405-
== f"{'Remote' if work_on == 'remote' else 'Local'} path 'subdir1/apple.txt/' is a file"
409+
path_type = "Remote" if work_on == MoveWorkOn.REMOTE else "Local"
410+
assert str(excinfo.value) == (
411+
f"{path_type} path 'subdir1/apple.txt/' is a file but a directory "
412+
"was expected. Use a path ending with '/' for directories."
406413
)
407414
check_assets(moving_dandiset, starting_assets, work_on, {})
408415

@@ -425,9 +432,10 @@ def test_move_file_slash_dest(
425432
work_on=work_on,
426433
dandi_instance=moving_dandiset.api.instance_id,
427434
)
428-
assert (
429-
str(excinfo.value)
430-
== f"{'Remote' if work_on == 'remote' else 'Local'} path 'subdir1/apple.txt/' is a file"
435+
path_type = "Remote" if work_on == MoveWorkOn.REMOTE else "Local"
436+
assert str(excinfo.value) == (
437+
f"{path_type} path 'subdir1/apple.txt/' is a file but a directory "
438+
"was expected. Use a path ending with '/' for directories."
431439
)
432440
check_assets(moving_dandiset, starting_assets, work_on, {})
433441

@@ -593,9 +601,14 @@ def test_move_from_subdir_abspaths(
593601
work_on=work_on,
594602
dandi_instance=moving_dandiset.api.instance_id,
595603
)
596-
assert (
597-
str(excinfo.value)
598-
== f"No asset at {'remote' if work_on == 'remote' else 'local'} path 'file.txt'"
604+
assert str(excinfo.value) == (
605+
f"No asset at {'remote' if work_on == MoveWorkOn.REMOTE else 'local'} path 'file.txt'. "
606+
+ (
607+
"Verify the path is correct and the asset exists on the server. "
608+
"Use 'dandi ls' to list available assets."
609+
if work_on == MoveWorkOn.REMOTE
610+
else "Verify the path is correct and the file exists locally."
611+
)
599612
)
600613
check_assets(moving_dandiset, starting_assets, work_on, {})
601614

@@ -619,7 +632,11 @@ def test_move_from_subdir_as_dot(
619632
dandi_instance=moving_dandiset.api.instance_id,
620633
devel_debug=True,
621634
)
622-
assert str(excinfo.value) == "Cannot move current working directory"
635+
assert (
636+
str(excinfo.value)
637+
== "Cannot move current working directory. "
638+
"Change to a different directory before moving this location."
639+
)
623640
check_assets(moving_dandiset, starting_assets, work_on, {})
624641

625642

@@ -769,7 +786,11 @@ def test_move_not_dandiset(
769786
monkeypatch.chdir(tmp_path)
770787
with pytest.raises(ValueError) as excinfo:
771788
move("file.txt", "subdir2/banana.txt", dest="subdir1", work_on=work_on)
772-
assert str(excinfo.value) == f"{tmp_path.absolute()}: not a Dandiset"
789+
assert str(excinfo.value) == (
790+
f"{tmp_path.absolute()}: not a Dandiset. "
791+
"The directory does not contain a 'dandiset.yaml' file. "
792+
"Use 'dandi download' to download a dandiset first."
793+
)
773794

774795

775796
def test_move_local_delete_empty_dirs(

0 commit comments

Comments
 (0)