Skip to content

Commit

Permalink
Added --delete flag to timer create transfer (#1017)
Browse files Browse the repository at this point in the history
* Added --delete flag to timer create transfer

* Update transfer --delete helptext
  • Loading branch information
derek-globus authored Aug 2, 2024
1 parent 0718b38 commit 0237dd0
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 19 deletions.
9 changes: 9 additions & 0 deletions changelog.d/20240801_163618_derek_timer_transfer_delete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

### Enhancements

* Added a `--delete` flag to `globus timer create transfer` to mirror
`globus transfer --delete` functionality.

This option will delete files, directories, and symlinks on the destination endpoint
which don’t exist on the source endpoint or are a different type. Only applies for
recursive directory transfers.
50 changes: 33 additions & 17 deletions src/globus_cli/commands/timer/create/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,6 @@
"""


def resolve_optional_local_time(
start: datetime.datetime | None,
) -> datetime.datetime | globus_sdk.utils.MissingType:
if start is None:
return globus_sdk.MISSING
# set the timezone to local system time if the timezone input is not aware
start_with_tz = start.astimezone() if start.tzinfo is None else start
return start_with_tz


@command("transfer", short_help="Create a recurring transfer timer.")
@click.argument(
"source", metavar="SOURCE_ENDPOINT_ID[:SOURCE_PATH]", type=ENDPOINT_PLUS_OPTPATH
Expand Down Expand Up @@ -108,6 +98,15 @@ def resolve_optional_local_time(
help="Stop running the transfer after this number of runs have happened.",
)
@mutex_option_group("--stop-after-date", "--stop-after-runs")
@click.option(
"--delete",
is_flag=True,
default=False,
help=(
"Delete any files in the destination directory not contained in the source. "
'This results in "directory mirroring." Only valid on recursive transfers.'
),
)
@LoginManager.requires_login("auth", "timer", "transfer")
def transfer_command(
login_manager: LoginManager,
Expand All @@ -122,6 +121,7 @@ def transfer_command(
label: str | None,
stop_after_date: datetime.datetime | None,
stop_after_runs: int | None,
delete: bool,
sync_level: t.Literal["exists", "size", "mtime", "checksum"] | None,
encrypt_data: bool,
verify_checksum: bool,
Expand Down Expand Up @@ -174,13 +174,18 @@ def transfer_command(
source_endpoint, cmd_source_path = source
dest_endpoint, cmd_dest_path = destination

# avoid 'mutex_option_group', emit a custom error message
if recursive is not None and batch:
option_name = "--recursive" if recursive else "--no-recursive"
raise click.UsageError(
f"You cannot use {option_name} in addition to --batch. "
f"Instead, use {option_name} on lines of --batch input which need it."
)
if recursive is not None:
if batch:
# avoid 'mutex_option_group', emit a custom error message
option_name = "--recursive" if recursive else "--no-recursive"
raise click.UsageError(
f"You cannot use {option_name} in addition to --batch. "
f"Instead, use {option_name} on lines of --batch input which need it."
)

if delete and not recursive:
msg = "The --delete option cannot be specified with --no-recursion."
raise click.UsageError(msg)
if (cmd_source_path is None or cmd_dest_path is None) and (not batch):
raise click.UsageError(
"transfer requires either SOURCE_PATH and DEST_PATH or --batch"
Expand Down Expand Up @@ -273,6 +278,7 @@ def transfer_command(
encrypt_data=encrypt_data,
skip_source_errors=skip_source_errors,
fail_on_quota_errors=fail_on_quota_errors,
delete_destination_extra=delete,
# mypy can't understand kwargs expansion very well
**notify, # type: ignore[arg-type]
)
Expand All @@ -296,6 +302,16 @@ def transfer_command(
display(response["timer"], text_mode=display.RECORD, fields=FORMAT_FIELDS)


def resolve_optional_local_time(
start: datetime.datetime | None,
) -> datetime.datetime | globus_sdk.utils.MissingType:
if start is None:
return globus_sdk.MISSING
# set the timezone to local system time if the timezone input is not aware
start_with_tz = start.astimezone() if start.tzinfo is None else start
return start_with_tz


def _derive_needed_scopes(
needs_data_access: list[str],
) -> dict[str, MutableScope]:
Expand Down
4 changes: 2 additions & 2 deletions src/globus_cli/commands/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@
is_flag=True,
default=False,
help=(
"Delete extraneous files in the destination directory. "
"Only applies to recursive directory transfers."
"Delete any files in the destination directory not contained in the source. "
'This results in "directory mirroring." Only valid on recursive transfers.'
),
)
@click.option(
Expand Down
30 changes: 30 additions & 0 deletions tests/functional/timer/test_transfer_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,33 @@ def test_timer_creation_errors_on_data_access_with_client_creds(

req = get_last_request()
assert req.url.startswith("https://timer")


@pytest.mark.parametrize(
"deletion_option,recursion_option,expected_error",
(
("--delete", "--recursive", ""),
("--delete", "", ""),
(
"--delete",
"--no-recursive",
"The --delete option cannot be specified with --no-recursion.",
),
),
)
def test_timer_creation_delete_flag_requires_recursion(
run_line,
client_login,
ep_for_timer,
deletion_option,
recursion_option,
expected_error,
):
base_cmd = f"globus timer create transfer {ep_for_timer}:/foo/ {ep_for_timer}:/bar/"
options_list = ("--interval 60m", deletion_option, recursion_option)
options = " ".join(op for op in options_list if op is not None)

exit_code = 0 if not expected_error else 2
resp = run_line(f"{base_cmd} {options}", assert_exit_code=exit_code)

assert expected_error in resp.stderr

0 comments on commit 0237dd0

Please sign in to comment.