Skip to content

Commit 0a89353

Browse files
committed
Add --yes/-y flag to dandi download for headless sync usage
When using `dandi download --sync`, the CLI prompts for confirmation before deleting local assets not found on the server. This fails in headless environments (CI/CD, cron jobs) where no interactive terminal is available. The new `--yes` / `-y` flag auto-confirms such prompts. Closes #1833 https://claude.ai/code/session_01297X1VGdztFArkpVFB1zEk
1 parent 0af950f commit 0a89353

File tree

3 files changed

+53
-19
lines changed

3 files changed

+53
-19
lines changed

dandi/cli/cmd_download.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@
118118
@click.option(
119119
"--sync", is_flag=True, help="Delete local assets that do not exist on the server"
120120
)
121+
@click.option(
122+
"-y",
123+
"--yes",
124+
is_flag=True,
125+
help="Automatically confirm yes to any prompts (e.g., deletion with --sync)",
126+
)
121127
@instance_option(
122128
default=None,
123129
help=(
@@ -151,6 +157,7 @@ def download(
151157
format: DownloadFormat,
152158
download_types: set[str],
153159
sync: bool,
160+
yes: bool,
154161
dandi_instance: str,
155162
path_type: PathType,
156163
preserve_tree: bool,
@@ -191,6 +198,7 @@ def download(
191198
get_assets="assets" in download_types or preserve_tree,
192199
preserve_tree=preserve_tree,
193200
sync=sync,
201+
yes=yes,
194202
path_type=path_type,
195203
# develop_debug=develop_debug
196204
)

dandi/download.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def download(
108108
get_assets: bool = True,
109109
preserve_tree: bool = False,
110110
sync: bool = False,
111+
yes: bool = False,
111112
path_type: PathType = PathType.EXACT,
112113
) -> None:
113114
# TODO: unduplicate with upload. For now stole from that one
@@ -204,25 +205,31 @@ def p4e(out):
204205
if sync:
205206
to_delete = [p for dl in downloaders for p in dl.delete_for_sync()]
206207
if to_delete:
207-
while True:
208-
opt = abbrev_prompt(
209-
f"Delete {pluralize(len(to_delete), 'local asset')}?",
210-
"yes",
211-
"no",
212-
"list",
213-
)
214-
if opt == "list":
215-
for p in to_delete:
216-
print(p)
217-
elif opt == "yes":
218-
for p in to_delete:
219-
if p.is_dir():
220-
rmtree(p)
221-
else:
222-
p.unlink()
223-
break
224-
else:
225-
break
208+
do_delete = False
209+
if yes:
210+
do_delete = True
211+
else:
212+
while True:
213+
opt = abbrev_prompt(
214+
f"Delete {pluralize(len(to_delete), 'local asset')}?",
215+
"yes",
216+
"no",
217+
"list",
218+
)
219+
if opt == "list":
220+
for p in to_delete:
221+
print(p)
222+
elif opt == "yes":
223+
do_delete = True
224+
break
225+
else:
226+
break
227+
if do_delete:
228+
for p in to_delete:
229+
if p.is_dir():
230+
rmtree(p)
231+
else:
232+
p.unlink()
226233
if errors:
227234
error_msg = f"Encountered {pluralize(len(errors), 'error')} while downloading."
228235
# Also log the first error for easier debugging

dandi/tests/test_download.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,25 @@ def test_download_sync(
319319
assert (dspath / "file.txt").exists()
320320

321321

322+
@pytest.mark.ai_generated
323+
def test_download_sync_yes(
324+
mocker: MockerFixture, text_dandiset: SampleDandiset, tmp_path: Path
325+
) -> None:
326+
text_dandiset.dandiset.get_asset_by_path("file.txt").delete()
327+
dspath = tmp_path / text_dandiset.dandiset_id
328+
os.rename(text_dandiset.dspath, dspath)
329+
confirm_mock = mocker.patch("dandi.download.abbrev_prompt")
330+
download(
331+
f"dandi://{text_dandiset.api.instance_id}/{text_dandiset.dandiset_id}",
332+
tmp_path,
333+
existing=DownloadExisting.OVERWRITE,
334+
sync=True,
335+
yes=True,
336+
)
337+
confirm_mock.assert_not_called()
338+
assert not (dspath / "file.txt").exists()
339+
340+
322341
def test_download_sync_folder(
323342
mocker: MockerFixture, text_dandiset: SampleDandiset
324343
) -> None:

0 commit comments

Comments
 (0)