Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Breaking Changes
----------------

- The ``TimerJob.from_transfer_data`` classmethod, which was deprecated in
globus-sdk version 3, has been removed. Users should use the ``TransferTimer``
class to construct timers which submit transfer tasks. (:pr:`NUMBER`)
8 changes: 5 additions & 3 deletions src/globus_sdk/services/timers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,12 @@ def create_job(
**Examples**

>>> from datetime import datetime, timedelta
>>> transfer_data = TransferData(...)
>>> callback_url = ...
>>> data = ...
>>> timers_client = globus_sdk.TimersClient(...)
>>> job = TimerJob.from_transfer_data(
... transfer_data,
>>> job = TimerJob(
... callback_url,
... data,
... datetime.utcnow(),
... timedelta(days=14),
... name="my-timer-job"
Expand Down
66 changes: 0 additions & 66 deletions src/globus_sdk/services/timers/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
import logging
import typing as t

from globus_sdk._internal.utils import slash_join
from globus_sdk._missing import MISSING, MissingType
from globus_sdk._payload import GlobusPayload
from globus_sdk.config import get_service_url
from globus_sdk.exc import warn_deprecated
from globus_sdk.services.transfer import TransferData

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -266,69 +263,6 @@ def __init__(
if scope is not None:
self["scope"] = scope

@classmethod
def from_transfer_data(
cls,
transfer_data: TransferData | dict[str, t.Any],
start: dt.datetime | str,
interval: dt.timedelta | int | None,
*,
name: str | None = None,
stop_after: dt.datetime | None = None,
stop_after_n: int | None = None,
scope: str | None = None,
environment: str | None = None,
) -> TimerJob:
r"""
Specify data to create a Timers job using the parameters for a transfer. Timers
will use those parameters to run the defined transfer operation, recurring at
the given interval.

:param transfer_data: A :class:`TransferData <globus_sdk.TransferData>` object.
Construct this object exactly as you would normally; Timers will use this to
run the recurring transfer.
:param start: The datetime at which to start the Timers job.
:param interval: The interval at which the Timers job should recur. Interpreted
as seconds if specified as an integer. If ``stop_after_n == 1``, i.e. the
job is set to run only a single time, then interval *must* be None.
:param name: A (not necessarily unique) name to identify this job in Timers
:param stop_after: A date after which the Timers job will stop running
:param stop_after_n: A number of executions after which the Timers job will stop
:param scope: Timers defaults to the Transfer 'all' scope. Use this parameter to
change the scope used by Timers when calling the Transfer Action Provider.
:param environment: For internal use: because this method needs to generate a
URL for the Transfer Action Provider, this argument can control which
environment the Timers job is sent to.
"""
warn_deprecated(
"TimerJob.from_transfer_data(X, ...) is deprecated. "
"Prefer TransferTimer(body=X, ...) instead."
)

transfer_action_url = slash_join(
get_service_url("actions", environment=environment), "transfer/transfer/run"
)
log.debug(
"Creating TimerJob from TransferData, action_url=%s", transfer_action_url
)
for key in ("submission_id", "skip_activation_check"):
if transfer_data.get(key, MISSING) is not MISSING:
raise ValueError(
f"cannot create TimerJob from TransferData which has {key} set"
)
# dict will either convert a `TransferData` object or leave us with a dict here
callback_body = {"body": dict(transfer_data)}
return cls(
transfer_action_url,
callback_body,
start,
interval,
name=name,
stop_after=stop_after,
stop_after_n=stop_after_n,
scope=scope,
)


def _format_date(date: str | dt.datetime | MissingType) -> str | MissingType:
if isinstance(date, dt.datetime):
Expand Down
32 changes: 13 additions & 19 deletions tests/functional/services/timers/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@

import pytest

from globus_sdk import TimerJob, TimersAPIError, TransferData, config, exc
from globus_sdk._internal.utils import slash_join
from globus_sdk import TimerJob, TimersAPIError
from globus_sdk.testing import get_last_request, load_response
from tests.common import GO_EP1_ID, GO_EP2_ID


def test_list_jobs(client):
Expand Down Expand Up @@ -39,17 +37,14 @@ def test_get_job_errors(client):
)
def test_create_job(client, start, interval):
meta = load_response(client.create_job).metadata
transfer_data = TransferData(GO_EP1_ID, GO_EP2_ID)
with pytest.warns(exc.RemovedInV4Warning, match="Prefer TransferTimer"):
timer_job = TimerJob.from_transfer_data(transfer_data, start, interval)
response = client.create_job(timer_job)
assert response.http_status == 201
assert response.data["job_id"] == meta["job_id"]
with pytest.warns(exc.RemovedInV4Warning, match="Prefer TransferTimer"):
timer_job = TimerJob.from_transfer_data(dict(transfer_data), start, interval)
timer_job = TimerJob(
"https://example.bogus/bogus-callback", {"bogus": "bogus_body"}, start, interval
)

response = client.create_job(timer_job)
assert response.http_status == 201
assert response.data["job_id"] == meta["job_id"]

req_body = json.loads(get_last_request().body)
if isinstance(start, datetime.datetime):
assert req_body["start"] == start.isoformat()
Expand All @@ -59,18 +54,17 @@ def test_create_job(client, start, interval):
assert req_body["interval"] == interval.total_seconds()
else:
assert req_body["interval"] == interval
assert req_body["callback_url"] == slash_join(
config.get_service_url("actions"), "/transfer/transfer/run"
)
assert req_body["callback_url"] == "https://example.bogus/bogus-callback"


def test_create_job_validation_error(client):
meta = load_response(client.create_job, case="validation_error").metadata
transfer_data = TransferData(GO_EP1_ID, GO_EP2_ID)
with pytest.warns(exc.RemovedInV4Warning, match="Prefer TransferTimer"):
timer_job = TimerJob.from_transfer_data(
transfer_data, "2022-04-05T06:00:00", 1800
)
timer_job = TimerJob(
"https://example.bogus/bogus-callback",
{"bogus": "bogus_body"},
"2022-04-05T06:00:00",
1800,
)

with pytest.raises(TimersAPIError) as excinfo:
client.create_job(timer_job)
Expand Down
24 changes: 0 additions & 24 deletions tests/unit/helpers/test_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,13 @@
from globus_sdk import (
OnceTimerSchedule,
RecurringTimerSchedule,
TimerJob,
TransferData,
TransferTimer,
exc,
)
from globus_sdk._missing import filter_missing
from tests.common import GO_EP1_ID, GO_EP2_ID


def test_timer_from_transfer_data_ok():
tdata = TransferData(GO_EP1_ID, GO_EP2_ID)
with pytest.warns(exc.RemovedInV4Warning, match="Prefer TransferTimer"):
job = TimerJob.from_transfer_data(tdata, "2022-01-01T00:00:00Z", 600)
assert "callback_body" in job
assert "body" in job["callback_body"]
assert "source_endpoint" in job["callback_body"]["body"]
assert "destination_endpoint" in job["callback_body"]["body"]
assert job["callback_body"]["body"]["source_endpoint"] == GO_EP1_ID
assert job["callback_body"]["body"]["destination_endpoint"] == GO_EP2_ID


@pytest.mark.parametrize(
"badkey, value", (("submission_id", "foo"), ("skip_activation_check", True))
)
def test_timer_from_transfer_data_rejects_forbidden_keys(badkey, value):
tdata = TransferData(GO_EP1_ID, GO_EP2_ID, **{badkey: value})
with pytest.raises(ValueError):
with pytest.warns(exc.RemovedInV4Warning, match="Prefer TransferTimer"):
TimerJob.from_transfer_data(tdata, "2022-01-01T00:00:00Z", 600)


def test_transfer_timer_ok():
tdata = TransferData(GO_EP1_ID, GO_EP2_ID)
timer = TransferTimer(body=tdata, name="foo timer", schedule={"type": "once"})
Expand Down
Loading