2323import google .auth .transport .requests
2424
2525
26+ ASSISTANT_API_VERSION = 'v1alpha2'
27+
28+
2629def failed_request_exception (message , r ):
2730 """Build ClickException from a failed request."""
2831 try :
@@ -37,8 +40,46 @@ def failed_request_exception(message, r):
3740 r .text ))
3841
3942
40- # Prints out a device model in the terminal by parsing dict
43+ def resolve_project_id (client_secrets , credentials ):
44+ """Resolve project ID from client secrets."""
45+ if client_secrets is None :
46+ client_secrets = 'client_secret_%s.json' % credentials .client_id
47+ try :
48+ with open (client_secrets , 'r' ) as f :
49+ secret = json .load (f )
50+ return secret ['installed' ]['project_id' ]
51+ except Exception as e :
52+ raise click .ClickException ('Error loading client secret: %s.\n '
53+ 'Run the device tool '
54+ 'with --client-secrets '
55+ 'or --project option.\n '
56+ 'Or copy the %s file '
57+ 'in the current directory.'
58+ % (e , client_secrets ))
59+
60+
61+ def build_api_url (api_endpoint , api_version , project_id ):
62+ return 'https://%s/%s/projects/%s' % (api_endpoint ,
63+ api_version ,
64+ project_id )
65+
66+
67+ def build_client_from_context (ctx ):
68+ project_id = (ctx .obj ['PROJECT' ]
69+ or resolve_project_id (ctx .obj ['CLIENT_SECRETS' ],
70+ ctx .obj ['CREDENTIALS' ]))
71+ api_url = build_api_url (ctx .obj ['API_ENDPOINT' ],
72+ ctx .obj ['API_VERSION' ],
73+ project_id )
74+ session = (ctx .obj ['SESSION' ] or
75+ google .auth .transport .requests .AuthorizedSession (
76+ ctx .obj ['CREDENTIALS' ]
77+ ))
78+ return session , api_url , project_id
79+
80+
4181def pretty_print_model (devicemodel ):
82+ """Prints out a device model in the terminal by parsing dict."""
4283 PRETTY_PRINT_MODEL = """Device Model Id: %(deviceModelId)s
4384 Project Id: %(projectId)s
4485 Device Type: %(deviceType)s"""
@@ -51,8 +92,8 @@ def pretty_print_model(devicemodel):
5192 logging .info ('' ) # Newline
5293
5394
54- # Prints out a device instance in the terminal by parsing dict
5595def pretty_print_device (device ):
96+ """Prints out a device instance in the terminal by parsing dict."""
5697 logging .info ('Device Instance Id: %s' % device ['id' ])
5798 if 'nickname' in device :
5899 logging .info (' Nickname: %s' % device ['nickname' ])
@@ -67,8 +108,8 @@ def pretty_print_device(device):
67108 'use with the registration tool. If you don\' t use this flag, '
68109 'the tool will use the project listed in the '
69110 '<client_secret_client-id.json> file you specify with the '
70- '--client-secret flag.' )
71- @click .option ('--client-secret ' ,
111+ '--client-secrets flag.' )
112+ @click .option ('--client-secrets ' ,
72113 help = 'Enter the path and filename for the '
73114 '<client_secret_client-id.json> file you downloaded from your '
74115 'developer project. This file is used to infer the Google '
@@ -93,7 +134,7 @@ def pretty_print_device(device):
93134 'API. You can use this flag if the credentials were generated '
94135 'in a location that is different than the default.' )
95136@click .pass_context
96- def cli (ctx , project , client_secret , verbose , api_endpoint , credentials ):
137+ def cli (ctx , project , client_secrets , verbose , api_endpoint , credentials ):
97138 try :
98139 with open (credentials , 'r' ) as f :
99140 c = google .oauth2 .credentials .Credentials (token = None ,
@@ -104,25 +145,12 @@ def cli(ctx, project, client_secret, verbose, api_endpoint, credentials):
104145 raise click .ClickException ('Error loading credentials: %s.\n '
105146 'Run google-oauthlib-tool to initialize '
106147 'new OAuth 2.0 credentials.' % e )
107- if project is None :
108- if client_secret is None :
109- client_secret = 'client_secret_%s.json' % c .client_id
110- try :
111- with open (client_secret , 'r' ) as f :
112- secret = json .load (f )
113- project = secret ['installed' ]['project_id' ]
114- except Exception as e :
115- raise click .ClickException ('Error loading client secret: %s.\n '
116- 'Run the register tool '
117- 'with --client-secret '
118- 'or --project option.\n '
119- 'Or copy the %s file '
120- 'in the current directory.'
121- % (e , client_secret ))
122- ctx .obj ['SESSION' ] = google .auth .transport .requests .AuthorizedSession (c )
123- ctx .obj ['API_URL' ] = ('https://%s/v1alpha2/projects/%s'
124- % (api_endpoint , project ))
125- ctx .obj ['PROJECT_ID' ] = project
148+ ctx .obj ['API_ENDPOINT' ] = api_endpoint
149+ ctx .obj ['API_VERSION' ] = ASSISTANT_API_VERSION
150+ ctx .obj ['SESSION' ] = None
151+ ctx .obj ['PROJECT' ] = project
152+ ctx .obj ['CREDENTIALS' ] = c
153+ ctx .obj ['CLIENT_SECRETS' ] = client_secrets
126154 logging .basicConfig (format = '' ,
127155 level = logging .DEBUG if verbose else logging .INFO )
128156
@@ -179,6 +207,13 @@ def register(ctx, model, type, trait, manufacturer, product_name, description,
179207 hyphen (-), underscore (_), and plus (+). The device nickname can only
180208 contain numbers, letters, and the space ( ) symbol.
181209 """
210+ # cache SESSION and PROJECT so that we don't re-create them between request
211+ ctx .obj ['SESSION' ] = google .auth .transport .requests .AuthorizedSession (
212+ ctx .obj ['CREDENTIALS' ]
213+ )
214+ ctx .obj ['PROJECT' ] = (ctx .obj ['PROJECT' ]
215+ or resolve_project_id (ctx .obj ['CLIENT_SECRETS' ],
216+ ctx .obj ['CREDENTIALS' ]))
182217 ctx .invoke (register_model ,
183218 model = model , type = type , trait = trait ,
184219 manufacturer = manufacturer ,
@@ -223,13 +258,12 @@ def register_model(ctx, model, type, trait,
223258 symbols: period (.), hyphen (-), underscore (_), space ( ) and plus (+).
224259 The first character of a field must be a letter or number.
225260 """
226- session = ctx .obj ['SESSION' ]
227-
228- model_base_url = '/' .join ([ctx .obj ['API_URL' ], 'deviceModels' ])
261+ session , api_url , project_id = build_client_from_context (ctx )
262+ model_base_url = '/' .join ([api_url , 'deviceModels' ])
229263 model_url = '/' .join ([model_base_url , model ])
230264 payload = {
231265 'device_model_id' : model ,
232- 'project_id' : ctx . obj [ 'PROJECT_ID' ] ,
266+ 'project_id' : project_id ,
233267 'device_type' : 'action.devices.types.' + type ,
234268 }
235269 if trait :
@@ -246,11 +280,12 @@ def register_model(ctx, model, type, trait,
246280 if r .status_code == 200 :
247281 click .echo ('Updating existing device model: %s' % model )
248282 r = session .put (model_url , data = json .dumps (payload ))
249- elif r .status_code in (400 , 404 ):
283+ elif r .status_code in (400 , 403 , 404 ):
250284 click .echo ('Creating new device model' )
251285 r = session .post (model_base_url , data = json .dumps (payload ))
252286 else :
253- raise failed_request_exception ('Unknown error occurred' , r )
287+ raise failed_request_exception ('Failed to check existing device model' ,
288+ r )
254289 if r .status_code != 200 :
255290 raise failed_request_exception ('Failed to register model' , r )
256291 click .echo ('Model %s successfully registered' % model )
@@ -284,9 +319,8 @@ def register_device(ctx, device, model, nickname, client_type):
284319 hyphen (-), underscore (_), and plus (+). The device nickname can only
285320 contain numbers, letters, and the space ( ) symbol.
286321 """
287- session = ctx .obj ['SESSION' ]
288-
289- device_base_url = '/' .join ([ctx .obj ['API_URL' ], 'devices' ])
322+ session , api_url , project_id = build_client_from_context (ctx )
323+ device_base_url = '/' .join ([api_url , 'devices' ])
290324 device_url = '/' .join ([device_base_url , device ])
291325 payload = {
292326 'id' : device ,
@@ -303,7 +337,7 @@ def register_device(ctx, device, model, nickname, client_type):
303337 click .echo ('Updating existing device: %s' % device )
304338 session .delete (device_url )
305339 r = session .post (device_base_url , data = json .dumps (payload ))
306- elif r .status_code in (400 , 404 ):
340+ elif r .status_code in (400 , 403 , 404 ):
307341 click .echo ('Creating new device' )
308342 r = session .post (device_base_url , data = json .dumps (payload ))
309343 else :
@@ -325,9 +359,8 @@ def get(ctx, resource, id):
325359 """Gets all of the information (fields) for a given device model or
326360 instance.
327361 """
328- session = ctx .obj ['SESSION' ]
329-
330- url = '/' .join ([ctx .obj ['API_URL' ], resource , id ])
362+ session , api_url , project_id = build_client_from_context (ctx )
363+ url = '/' .join ([api_url , resource , id ])
331364 r = session .get (url )
332365 if r .status_code != 200 :
333366 raise failed_request_exception ('Failed to get resource' , r )
@@ -350,8 +383,8 @@ def get(ctx, resource, id):
350383def delete (ctx , resource , id ):
351384 """Delete given device model or instance.
352385 """
353- session = ctx . obj [ 'SESSION' ]
354- url = '/' .join ([ctx . obj [ 'API_URL' ] , resource , id ])
386+ session , api_url , project_id = build_client_from_context ( ctx )
387+ url = '/' .join ([api_url , resource , id ])
355388 r = session .delete (url )
356389 if r .status_code != 200 :
357390 raise failed_request_exception ('failed to delete resource' , r )
@@ -367,9 +400,8 @@ def list(ctx, resource):
367400 current Google Developer project. To change the current project, use the
368401 devicetool's --project flag.
369402 """
370- session = ctx .obj ['SESSION' ]
371-
372- url = '/' .join ([ctx .obj ['API_URL' ], resource ])
403+ session , api_url , project_id = build_client_from_context (ctx )
404+ url = '/' .join ([api_url , resource ])
373405 r = session .get (url )
374406 if r .status_code != 200 :
375407 raise failed_request_exception ('Failed to list resources' , r )
0 commit comments