33import globus_sdk
44from globus_sdk .scopes import TransferScopes
55
6+ # do basic argument parsing
67parser = argparse .ArgumentParser ()
78parser .add_argument ("SRC" )
89parser .add_argument ("DST" )
910args = parser .parse_args ()
1011
12+ # tutorial client ID (we recommend replacing this with your own client)
1113CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
12- auth_client = globus_sdk . NativeAppAuthClient ( CLIENT_ID )
14+ APP_NAME = "proactive-transfer-consent-example"
1315
1416
15- # we will need to do the login flow potentially twice, so define it as a
16- # function
17+ # Try an ls on the source and destination to see if ConsentRequired errors are raised --
18+ # if they are, a fresh login flow will *not* be triggered.
1719#
18- # we default to using the Transfer "all" scope, but it is settable here
19- # look at the ConsentRequired handler below for how this is used
20- def login_and_get_transfer_client (* , scopes = TransferScopes .all ):
21- # note that 'requested_scopes' can be a single scope or a list
22- # this did not matter in previous examples but will be leveraged in
23- # this one
24- auth_client .oauth2_start_flow (requested_scopes = scopes )
25- authorize_url = auth_client .oauth2_get_authorize_url ()
26- print (f"Please go to this URL and login:\n \n { authorize_url } \n " )
27-
28- auth_code = input ("Please enter the code here: " ).strip ()
29- tokens = auth_client .oauth2_exchange_code_for_tokens (auth_code )
30- transfer_tokens = tokens .by_resource_server ["transfer.api.globus.org" ]
31-
32- # return the TransferClient object, as the result of doing a login
33- return globus_sdk .TransferClient (
34- authorizer = globus_sdk .AccessTokenAuthorizer (transfer_tokens ["access_token" ])
35- )
36-
37-
38- # get an initial client to try with, which requires a login flow
39- transfer_client = login_and_get_transfer_client ()
40-
41- # now, try an ls on the source and destination to see if ConsentRequired
42- # errors are raised
43- consent_required_scopes = []
44-
45-
46- def check_for_consent_required (target ):
47- try :
48- transfer_client .operation_ls (target , path = "/" )
49- # catch all errors and discard those other than ConsentRequired
50- # e.g. ignore PermissionDenied errors as not relevant
51- except globus_sdk .TransferAPIError as err :
52- if err .info .consent_required :
53- consent_required_scopes .extend (err .info .consent_required .required_scopes )
54-
55-
56- check_for_consent_required (args .SRC )
57- check_for_consent_required (args .DST )
20+ # This is more sophisticated than handling with `redrive_gares=True` and makes
21+ # sure that the user is only prompted to login *one* extra time, even if both
22+ # collections require additional consent.
23+ def probe_for_consent_required (
24+ transfer_client : globus_sdk .TransferClient , targets : list [str ]
25+ ) -> list [str ]:
26+ consent_required_scopes : list [str ] = []
27+
28+ for target in targets :
29+ try :
30+ transfer_client .operation_ls (target , path = "/" )
31+ # catch all errors and discard those other than ConsentRequired
32+ # e.g. ignore PermissionDenied errors as not relevant
33+ except globus_sdk .TransferAPIError as err :
34+ if err .info .consent_required :
35+ consent_required_scopes .extend (
36+ err .info .consent_required .required_scopes
37+ )
38+
39+ return consent_required_scopes
40+
41+
42+ with globus_sdk .UserApp (APP_NAME , client_id = CLIENT_ID ) as app :
43+ with globus_sdk .TransferClient (app = app ) as transfer_client :
44+ consent_required_scopes = probe_for_consent_required (
45+ transfer_client , [args .SRC , args .DST ]
46+ )
5847
5948# the block above may or may not populate this list
6049# but if it does, handle ConsentRequired with a new login
@@ -63,15 +52,21 @@ def check_for_consent_required(target):
6352 "One of your endpoints requires consent in order to be used.\n "
6453 "You must login a second time to grant consents.\n \n "
6554 )
66- transfer_client = login_and_get_transfer_client (scopes = consent_required_scopes )
67-
68- # from this point onwards, the example is exactly the same as the reactive
69- # case, including the behavior to retry on ConsentRequiredErrors. This is
70- # not obvious, but there are cases in which it is necessary -- for example,
71- # if a user consents at the start, but the process of building task_data is
72- # slow, they could revoke their consent before the submission step
73- #
74- # in the common case, a single submission with no retry would suffice
55+ with globus_sdk .UserApp (
56+ APP_NAME ,
57+ client_id = CLIENT_ID ,
58+ scope_requirements = {
59+ TransferScopes .resource_server : consent_required_scopes
60+ + [TransferScopes .all ]
61+ },
62+ ) as app :
63+ app .login ()
64+
65+
66+ # From this point onwards, the example is exactly the same as the previous scripts.
67+ # We will *not* set `redrive_gares=True`, on the grounds that if you want to use this
68+ # in a context like a job submission system, a prompt for login is not helpful if the
69+ # consent was revoked or insufficient.
7570
7671task_data = globus_sdk .TransferData (
7772 source_endpoint = args .SRC , destination_endpoint = args .DST
@@ -81,23 +76,9 @@ def check_for_consent_required(target):
8176 "/~/example-transfer-script-destination.txt" , # dest
8277)
8378
79+ with globus_sdk .UserApp (APP_NAME , client_id = CLIENT_ID ) as app :
80+ with globus_sdk .TransferClient (app = app ) as transfer_client :
81+ task_doc = transfer_client .submit_transfer (task_data )
8482
85- def do_submit (client ):
86- task_doc = client .submit_transfer (task_data )
87- task_id = task_doc ["task_id" ]
88- print (f"submitted transfer, task_id={ task_id } " )
89-
90-
91- try :
92- do_submit (transfer_client )
93- except globus_sdk .TransferAPIError as err :
94- if not err .info .consent_required :
95- raise
96- print (
97- "Encountered a ConsentRequired error.\n "
98- "You must login a second time to grant consents.\n \n "
99- )
100- transfer_client = login_and_get_transfer_client (
101- scopes = err .info .consent_required .required_scopes
102- )
103- do_submit (transfer_client )
83+ task_id = task_doc ["task_id" ]
84+ print (f"submitted transfer, task_id={ task_id } " )
0 commit comments