Skip to content

Commit 0237dd0

Browse files
authored
Added --delete flag to timer create transfer (#1017)
* Added --delete flag to timer create transfer * Update transfer --delete helptext
1 parent 0718b38 commit 0237dd0

File tree

4 files changed

+74
-19
lines changed

4 files changed

+74
-19
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
### Enhancements
3+
4+
* Added a `--delete` flag to `globus timer create transfer` to mirror
5+
`globus transfer --delete` functionality.
6+
7+
This option will delete files, directories, and symlinks on the destination endpoint
8+
which don’t exist on the source endpoint or are a different type. Only applies for
9+
recursive directory transfers.

src/globus_cli/commands/timer/create/transfer.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,6 @@
5555
"""
5656

5757

58-
def resolve_optional_local_time(
59-
start: datetime.datetime | None,
60-
) -> datetime.datetime | globus_sdk.utils.MissingType:
61-
if start is None:
62-
return globus_sdk.MISSING
63-
# set the timezone to local system time if the timezone input is not aware
64-
start_with_tz = start.astimezone() if start.tzinfo is None else start
65-
return start_with_tz
66-
67-
6858
@command("transfer", short_help="Create a recurring transfer timer.")
6959
@click.argument(
7060
"source", metavar="SOURCE_ENDPOINT_ID[:SOURCE_PATH]", type=ENDPOINT_PLUS_OPTPATH
@@ -108,6 +98,15 @@ def resolve_optional_local_time(
10898
help="Stop running the transfer after this number of runs have happened.",
10999
)
110100
@mutex_option_group("--stop-after-date", "--stop-after-runs")
101+
@click.option(
102+
"--delete",
103+
is_flag=True,
104+
default=False,
105+
help=(
106+
"Delete any files in the destination directory not contained in the source. "
107+
'This results in "directory mirroring." Only valid on recursive transfers.'
108+
),
109+
)
111110
@LoginManager.requires_login("auth", "timer", "transfer")
112111
def transfer_command(
113112
login_manager: LoginManager,
@@ -122,6 +121,7 @@ def transfer_command(
122121
label: str | None,
123122
stop_after_date: datetime.datetime | None,
124123
stop_after_runs: int | None,
124+
delete: bool,
125125
sync_level: t.Literal["exists", "size", "mtime", "checksum"] | None,
126126
encrypt_data: bool,
127127
verify_checksum: bool,
@@ -174,13 +174,18 @@ def transfer_command(
174174
source_endpoint, cmd_source_path = source
175175
dest_endpoint, cmd_dest_path = destination
176176

177-
# avoid 'mutex_option_group', emit a custom error message
178-
if recursive is not None and batch:
179-
option_name = "--recursive" if recursive else "--no-recursive"
180-
raise click.UsageError(
181-
f"You cannot use {option_name} in addition to --batch. "
182-
f"Instead, use {option_name} on lines of --batch input which need it."
183-
)
177+
if recursive is not None:
178+
if batch:
179+
# avoid 'mutex_option_group', emit a custom error message
180+
option_name = "--recursive" if recursive else "--no-recursive"
181+
raise click.UsageError(
182+
f"You cannot use {option_name} in addition to --batch. "
183+
f"Instead, use {option_name} on lines of --batch input which need it."
184+
)
185+
186+
if delete and not recursive:
187+
msg = "The --delete option cannot be specified with --no-recursion."
188+
raise click.UsageError(msg)
184189
if (cmd_source_path is None or cmd_dest_path is None) and (not batch):
185190
raise click.UsageError(
186191
"transfer requires either SOURCE_PATH and DEST_PATH or --batch"
@@ -273,6 +278,7 @@ def transfer_command(
273278
encrypt_data=encrypt_data,
274279
skip_source_errors=skip_source_errors,
275280
fail_on_quota_errors=fail_on_quota_errors,
281+
delete_destination_extra=delete,
276282
# mypy can't understand kwargs expansion very well
277283
**notify, # type: ignore[arg-type]
278284
)
@@ -296,6 +302,16 @@ def transfer_command(
296302
display(response["timer"], text_mode=display.RECORD, fields=FORMAT_FIELDS)
297303

298304

305+
def resolve_optional_local_time(
306+
start: datetime.datetime | None,
307+
) -> datetime.datetime | globus_sdk.utils.MissingType:
308+
if start is None:
309+
return globus_sdk.MISSING
310+
# set the timezone to local system time if the timezone input is not aware
311+
start_with_tz = start.astimezone() if start.tzinfo is None else start
312+
return start_with_tz
313+
314+
299315
def _derive_needed_scopes(
300316
needs_data_access: list[str],
301317
) -> dict[str, MutableScope]:

src/globus_cli/commands/transfer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@
141141
is_flag=True,
142142
default=False,
143143
help=(
144-
"Delete extraneous files in the destination directory. "
145-
"Only applies to recursive directory transfers."
144+
"Delete any files in the destination directory not contained in the source. "
145+
'This results in "directory mirroring." Only valid on recursive transfers.'
146146
),
147147
)
148148
@click.option(

tests/functional/timer/test_transfer_create.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,3 +498,33 @@ def test_timer_creation_errors_on_data_access_with_client_creds(
498498

499499
req = get_last_request()
500500
assert req.url.startswith("https://timer")
501+
502+
503+
@pytest.mark.parametrize(
504+
"deletion_option,recursion_option,expected_error",
505+
(
506+
("--delete", "--recursive", ""),
507+
("--delete", "", ""),
508+
(
509+
"--delete",
510+
"--no-recursive",
511+
"The --delete option cannot be specified with --no-recursion.",
512+
),
513+
),
514+
)
515+
def test_timer_creation_delete_flag_requires_recursion(
516+
run_line,
517+
client_login,
518+
ep_for_timer,
519+
deletion_option,
520+
recursion_option,
521+
expected_error,
522+
):
523+
base_cmd = f"globus timer create transfer {ep_for_timer}:/foo/ {ep_for_timer}:/bar/"
524+
options_list = ("--interval 60m", deletion_option, recursion_option)
525+
options = " ".join(op for op in options_list if op is not None)
526+
527+
exit_code = 0 if not expected_error else 2
528+
resp = run_line(f"{base_cmd} {options}", assert_exit_code=exit_code)
529+
530+
assert expected_error in resp.stderr

0 commit comments

Comments
 (0)