Skip to content

Commit

Permalink
Add a TimersClient app scope helper for data_access + example doc (#1074
Browse files Browse the repository at this point in the history
)

* Implement add_app_transfer_data_access_scope

This is a Timers helper for setting up a data_access scope in the
correct nested context.
It has identical validation to the Transfer equivalent, and is tested
for parity.

* Add a new GlobusApp example using TimersClient

This is a new (experimental) example, meant to replace the
"timer_operations" examples with a more refined usage, based on
GlobusApp. It relies on
`TimersClient.add_app_transfer_data_access_scope`.

* Promote the "experimental" timer example doc

This moves the doc into non-experimental examples, and replaces the
older 'timer_operations' example it was written to replace.

* Update Timer Management examples per review

Co-authored-by: Ada <107940310+ada-globus@users.noreply.github.com>

---------

Co-authored-by: Ada <107940310+ada-globus@users.noreply.github.com>
  • Loading branch information
sirosen and ada-globus authored Oct 25, 2024
1 parent 1871538 commit 8f33a74
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 237 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added
~~~~~

- Add ``TimersClient.add_app_transfer_data_access_scope`` for ``TimersClient``
instances which are integrated with ``GlobusApp``. This method registers the
nested scope dependency for a ``data_access`` requirement for a transfer
timer. (:pr:`NUMBER`)
2 changes: 1 addition & 1 deletion docs/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ Each of these pages contains an example of a piece of SDK functionality.
transfer_relative_deadlines
recursive_ls
endpoint_type_enum
timer_operations/index
timer_management/index
guest_collection_creation
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,11 @@
import datetime

import globus_sdk
from globus_sdk.experimental.globus_app import UserApp

# tutorial client ID
# we recommend replacing this with your own client for any production use-cases
CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
NATIVE_CLIENT = globus_sdk.NativeAppAuthClient(CLIENT_ID)


def do_login_flow():
# we will want to request a 'timer' scope for managing timers
scope = globus_sdk.TimersClient.scopes.timer

# run the login flow, finishing with a token exchange
NATIVE_CLIENT.oauth2_start_flow(requested_scopes=scope)
authorize_url = NATIVE_CLIENT.oauth2_get_authorize_url()
print(f"Please go to this URL and login:\n\n{authorize_url}\n")
auth_code = input("Please enter the code here: ").strip()
tokens = NATIVE_CLIENT.oauth2_exchange_code_for_tokens(auth_code)

# pull out the tokens for Globus Timers from the response
return tokens.by_resource_server[globus_sdk.TimersClient.resource_server]


def create_timers_client():
tokens = do_login_flow()
return globus_sdk.TimersClient(
authorizer=globus_sdk.AccessTokenAuthorizer(tokens["access_token"])
)
# Tutorial Client ID - <replace this with your own client>
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
USER_APP = UserApp("manage-timers-example", client_id=NATIVE_CLIENT_ID)


def main():
Expand All @@ -53,7 +31,7 @@ def main():
)
args = parser.parse_args()

client = create_timers_client()
client = globus_sdk.TimersClient(app=USER_APP)

body = globus_sdk.TransferData(
source_endpoint=args.SOURCE_COLLECTION,
Expand Down
89 changes: 89 additions & 0 deletions docs/examples/timer_management/create_timer_data_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python

import argparse
import datetime

import globus_sdk
from globus_sdk.experimental.globus_app import UserApp

# Tutorial Client ID - <replace this with your own client>
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
USER_APP = UserApp("manage-timers-example", client_id=NATIVE_CLIENT_ID)


def uses_data_access(transfer_client, collection_id):
doc = transfer_client.get_endpoint(collection_id)
if doc["entity_type"] != "GCSv5_mapped_collection":
return False
if doc["high_assurance"]:
return False
return True


def main():
parser = argparse.ArgumentParser()
# the source, destination, and path to a file or dir to sync
parser.add_argument("SOURCE_COLLECTION")
parser.add_argument("DESTINATION_COLLECTION")
parser.add_argument("PATH")
parser.add_argument(
"--interval-seconds",
help="How frequently the timer runs, in seconds (default: 1 hour)",
default=3600,
type=int,
)
parser.add_argument(
"--days",
help="How many days to run the timer (default: 2)",
default=2,
type=int,
)
args = parser.parse_args()

timers_client = globus_sdk.TimersClient(app=USER_APP)
transfer_client = globus_sdk.TransferClient(app=USER_APP)

# check if the source or destination use 'data_access' scopes
# if so, register these requirements with the app
if uses_data_access(transfer_client, args.SOURCE_COLLECTION):
timers_client.add_app_transfer_data_access_scope(args.SOURCE_COLLECTION)
if uses_data_access(transfer_client, args.DESTINATION_COLLECTION):
timers_client.add_app_transfer_data_access_scope(args.DESTINATION_COLLECTION)

# from this point onwards, the example is the same as the basic create_timer.py
# script -- we've handled the nuance of data_access
#
# when the timer submission runs, you *may* be prompted to login again, if
# 'data_access' requirements were detected

body = globus_sdk.TransferData(
source_endpoint=args.SOURCE_COLLECTION,
destination_endpoint=args.DESTINATION_COLLECTION,
)
body.add_item(args.PATH, args.PATH)

# the timer will run until the end date, on whatever interval was requested
schedule = globus_sdk.RecurringTimerSchedule(
interval_seconds=args.interval_seconds,
end={
"condition": "time",
"datetime": datetime.datetime.now() + datetime.timedelta(days=args.days),
},
)

timer = timers_client.create_timer(
timer=globus_sdk.TransferTimer(
name=(
"create-timer-example "
f"[created at {datetime.datetime.now().isoformat()}]"
),
body=body,
schedule=schedule,
)
)
print("Finished submitting timer.")
print(f"timer_id: {timer['timer']['job_id']}")


if __name__ == "__main__":
main()
25 changes: 25 additions & 0 deletions docs/examples/timer_management/delete_timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python

import argparse

import globus_sdk
from globus_sdk.experimental.globus_app import UserApp

# Tutorial Client ID - <replace this with your own client>
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
USER_APP = UserApp("manage-timers-example", client_id=NATIVE_CLIENT_ID)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("TIMER_ID")
args = parser.parse_args()

client = globus_sdk.TimersClient(app=USER_APP)

client.delete_job(args.TIMER_ID)
print("Finished deleting timer.")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
Globus Timers Operations
------------------------
.. _timer_management_examples:

These examples demonstrate how to create, list, and delete Timers with the SDK.
Timer Management
----------------

These examples demonstrate how to create, list, and delete timers with the SDK.

Create a timer
~~~~~~~~~~~~~~

This script creates a new timer, on source and destination collections provided
via the command-line. It syncs an input file or directory between the two.

The script assumes that the path being synced is the same on the source and
destination for simplicity.

.. note::
This example does not handle ``data_access`` scope requirements.
See the later example to handle this.
Expand All @@ -17,11 +22,19 @@ via the command-line. It syncs an input file or directory between the two.
:caption: ``create_timer.py`` [:download:`download <create_timer.py>`]
:language: python

List timers
~~~~~~~~~~~

This script lists your current timers.

.. literalinclude:: list_timers.py
:caption: ``list_timers.py`` [:download:`download <list_timers.py>`]
:language: python

Delete a timer
~~~~~~~~~~~~~~

This script creates a new timer, on source and destination collections provided
via the command-line. It syncs an input file or directory between the two.
This script deletes a timer by ID.

.. literalinclude:: delete_timer.py
:caption: ``delete_timer.py`` [:download:`download <delete_timer.py>`]
Expand All @@ -35,12 +48,12 @@ also handles ``data_access`` scope requirements for the source and destination
collections.

Discovering ``data_access`` requirements requires the use of a
``TransferClient`` to look up the collections. Therefore, this example may put
the user through two login flows.
``TransferClient`` to look up the collections.

As in the simpler example, this script creates a new timer, on source and
destination collections provided via the command-line. It syncs an input
file or directory between the two.
file or directory between the two, and assumes that the path is the same on the
source and destination.

.. literalinclude:: create_timer_data_access.py
:caption: ``create_timer_data_access.py`` [:download:`download <create_timer_data_access.py>`]
Expand Down
24 changes: 24 additions & 0 deletions docs/examples/timer_management/list_timers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python

import globus_sdk
from globus_sdk.experimental.globus_app import UserApp

# Tutorial Client ID - <replace this with your own client>
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
USER_APP = UserApp("manage-timers-example", client_id=NATIVE_CLIENT_ID)


def main():
client = globus_sdk.TimersClient(app=USER_APP)

first = True
for record in client.list_jobs(query_params={"filter_active": True})["jobs"]:
if not first:
print("---")
first = False
print("name:", record["name"])
print("id:", record["job_id"])


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions docs/examples/timer_operations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:orphan:

The documentation which was found on this page has moved to
:ref:`Timer Management Examples <timer_management_examples>`.
Loading

0 comments on commit 8f33a74

Please sign in to comment.