From 8e099fdc7cf9771824745c1b420c43025a8cab8a Mon Sep 17 00:00:00 2001 From: evilham Date: Tue, 17 May 2016 09:06:53 +0000 Subject: [PATCH 1/4] Refactored to allow usage as a module. --- Credentials.py | 13 ++++++++++ __init__.py | 2 ++ acadio.py | 69 +++++++++++++++++++++++++++++++++++++------------- 3 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 Credentials.py create mode 100644 __init__.py diff --git a/Credentials.py b/Credentials.py new file mode 100644 index 0000000..ba9ab22 --- /dev/null +++ b/Credentials.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""ApiAuth Container class""" + +class Credentials(): + clientId = None + clientSecret = None + def __init__(self, **kwords): + self.clientId = kwords.get('clientId', None) + self.clientSecret = kwords.get('clientSecret', None) + if None in [self.clientId, self.clientSecret]: + raise ValueError('clientId and clientSecret must be defined') \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..faa18be --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/acadio.py b/acadio.py index b775b8e..3f91bd0 100644 --- a/acadio.py +++ b/acadio.py @@ -1,27 +1,62 @@ -import requests #http://requests.readthedocs.org/en/latest/ -import argparse +#!/usr/bin/env python +# -*- coding: utf-8 -*- -def getToken(consumerKey, consumerSecret): +import requests #http://requests.readthedocs.org/en/latest/ +from Credentials import Credentials + +ApiCredentials = None + +_authEntryPoint = 'https://developer.api.autodesk.com/authentication/v1/authenticate' +_apiEntryPoint = 'https://developer.api.autodesk.com/autocad.io/us-east/v2/' + +class Unauthorized(ValueError): + pass +class NotFound(ValueError): + pass + +def getToken(): """Obtain Apiggee token given a consumer key/secret""" - req = { 'client_id' : consumerKey, 'client_secret': consumerSecret, 'grant_type' : 'client_credentials'} - resp = requests.post('https://developer.api.autodesk.com/authentication/v1/authenticate', req).json(); + req = { + 'client_id' : ApiCredentials.clientId, + 'client_secret': ApiCredentials.clientSecret, + 'grant_type' : 'client_credentials', + } + resp = requests.post(_authEntryPoint, req).json(); + if resp.has_key('errorCode'): + raise Unauthorized( + resp['errorCode'], + resp.get('developerMessage', u'Wrong ApiCredentials'), + resp.get('more info', '') + ) return resp['token_type'] + " " + resp['access_token'] def printReport(args): """Download and print the AutoCAD.IO report for a given workitem""" - token = getToken(args.consumerKey, args.consumerSecret) - target = 'https://developer.api.autodesk.com/autocad.io/us-east/v2/' - resp = requests.get("{0}/WorkItems('{1}')".format(target, args.workItem), headers={'Authorization': token}).json(); + token = getToken() + resp = requests.get("{0}/WorkItems('{1}')".format( + _apiEntryPoint, args.workItem), + headers={'Authorization': token} + ) + if resp.status_code == 404: + raise NotFound(u"WorkItem '{0}' does not exist".format(args.workItem)) + resp = resp.json() resp = requests.get(resp['StatusDetails']['Report']); print(resp.text) #TODO: add support for other operations (submit workitem, query activities etc.) -parser = argparse.ArgumentParser(description='Get error report from AutoCAD.IO for a work item.') -parser.add_argument('--consumerKey', required=True); -parser.add_argument('--consumerSecret', required=True); -parser.add_argument('--workItem', required=True); - -args = parser.parse_args() - -#For now, the only supported operation is printing the report of a workitem. -printReport(args) \ No newline at end of file +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Get error report from AutoCAD.IO for a work item.') + parser.add_argument('--consumerKey', required=True); + parser.add_argument('--consumerSecret', required=True); + parser.add_argument('--workItem', required=True); + + args = parser.parse_args() + + ApiCredentials = Credentials( + clientId = args.consumerKey, + clientSecret = args.consumerSecret + ) + + #For now, the only supported operation is printing the report of a workitem. + printReport(args) \ No newline at end of file From decdb1e89c9eb54cc1508a3af89ec34a432d4a6b Mon Sep 17 00:00:00 2001 From: evilham Date: Tue, 17 May 2016 09:35:38 +0000 Subject: [PATCH 2/4] Reuse tokens when possible --- acadio.py | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/acadio.py b/acadio.py index 3f91bd0..07bcacc 100644 --- a/acadio.py +++ b/acadio.py @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- import requests #http://requests.readthedocs.org/en/latest/ +import datetime + from Credentials import Credentials ApiCredentials = None @@ -9,39 +11,53 @@ _authEntryPoint = 'https://developer.api.autodesk.com/authentication/v1/authenticate' _apiEntryPoint = 'https://developer.api.autodesk.com/autocad.io/us-east/v2/' +_token = {'token': None, 'expires': None} + class Unauthorized(ValueError): pass class NotFound(ValueError): pass def getToken(): - """Obtain Apiggee token given a consumer key/secret""" - req = { - 'client_id' : ApiCredentials.clientId, - 'client_secret': ApiCredentials.clientSecret, - 'grant_type' : 'client_credentials', - } - resp = requests.post(_authEntryPoint, req).json(); - if resp.has_key('errorCode'): - raise Unauthorized( - resp['errorCode'], - resp.get('developerMessage', u'Wrong ApiCredentials'), - resp.get('more info', '') - ) - return resp['token_type'] + " " + resp['access_token'] + """Obtain Apiggee token given a consumer key/secret + reusing a previously gotten one while still valid""" + if _token['token'] is None or \ + _token['expires'] is None or \ + datetime.datetime.utcnow() >= _token['expires']: + req = { + 'client_id' : ApiCredentials.clientId, + 'client_secret': ApiCredentials.clientSecret, + 'grant_type' : 'client_credentials', + } + resp = requests.post(_authEntryPoint, req).json(); + if resp.has_key('errorCode'): + raise Unauthorized( + resp['errorCode'], + resp.get('developerMessage', u'Wrong ApiCredentials'), + resp.get('more info', '') + ) + _token['expires'] = \ + datetime.datetime.utcnow() + datetime.timedelta(seconds = (resp['expires_in'] * 2)/3) + _token['token'] = resp['token_type'] + " " + resp['access_token'] + return _token['token'] + +def _authorizedGet(url): + return requests.get(url, headers = {'Authorization': getToken()}) def printReport(args): """Download and print the AutoCAD.IO report for a given workitem""" token = getToken() - resp = requests.get("{0}/WorkItems('{1}')".format( - _apiEntryPoint, args.workItem), - headers={'Authorization': token} + resp = _authorizedGet("{0}/WorkItems('{1}')".format( + _apiEntryPoint, args.workItem) ) if resp.status_code == 404: raise NotFound(u"WorkItem '{0}' does not exist".format(args.workItem)) resp = resp.json() resp = requests.get(resp['StatusDetails']['Report']); print(resp.text) + +def submitWorkItem(): + pass #TODO: add support for other operations (submit workitem, query activities etc.) if __name__ == '__main__': From c03faf7353d70bc7762904bcffa72b6a3a279494 Mon Sep 17 00:00:00 2001 From: evilham Date: Tue, 17 May 2016 11:06:42 +0000 Subject: [PATCH 3/4] Implemented submitWorkItem and getWorkItem. --- acadio.py | 98 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 18 deletions(-) diff --git a/acadio.py b/acadio.py index 07bcacc..34ad881 100644 --- a/acadio.py +++ b/acadio.py @@ -3,6 +3,7 @@ import requests #http://requests.readthedocs.org/en/latest/ import datetime +import json from Credentials import Credentials @@ -17,6 +18,8 @@ class Unauthorized(ValueError): pass class NotFound(ValueError): pass +class ServerError(ValueError): + pass def getToken(): """Obtain Apiggee token given a consumer key/secret @@ -43,36 +46,95 @@ def getToken(): def _authorizedGet(url): return requests.get(url, headers = {'Authorization': getToken()}) +def _authorizedPost(url, payload): + headers = { + 'Authorization': getToken(), + # POST as application/x-www-form-urlencoded doesn't seem to be working + 'Content-Type': 'application/json' + } + return requests.post(url, data = json.dumps(payload), headers = headers) -def printReport(args): - """Download and print the AutoCAD.IO report for a given workitem""" +def getWorkItem(workItemId): + """Get WorkItem""" token = getToken() resp = _authorizedGet("{0}/WorkItems('{1}')".format( - _apiEntryPoint, args.workItem) + _apiEntryPoint, workItemId) ) if resp.status_code == 404: - raise NotFound(u"WorkItem '{0}' does not exist".format(args.workItem)) + raise NotFound(u"WorkItem '{0}' does not exist".format(workItemId)) resp = resp.json() - resp = requests.get(resp['StatusDetails']['Report']); - print(resp.text) - -def submitWorkItem(): - pass + return resp + +def printReport(workItemId): + """Download and print the AutoCAD.IO report for a given workitem""" + resp = getWorkItem(workItemId) + url = resp['StatusDetails']['Report'] + if url is None: + print("WorkItem('{0}') is: {1}".format(workItemId, resp['Status'])) + else: + resp = requests.get(url) + print(resp.text) + +def submitWorkItem(ActivityId = "PlotToPDF", + InputArguments = None, + OutputArguments = None): + """"Create a WorkItem based on given arguments. + Returns the Id of the newly created WorkItem. + Defaults create the WorkItem described in the API docs: + https://developer.autodesk.com/api/autocadio/#to-create-a-workitem""" + if InputArguments is None: + InputArguments = [{ + "Resource":"https://s3.amazonaws.com/AutoCAD-Core-Engine-Services/TestDwg/makeall.dwg", + "Name":"HostDwg","StorageProvider":"Generic" + }] + if OutputArguments is None: + OutputArguments = [{ + "Name":"Result","StorageProvider":"Generic","HttpVerb":"POST" + }] + req = { + "@odata.type":"#ACES.Models.WorkItem","Arguments":{ + "InputArguments": InputArguments, + "OutputArguments": OutputArguments, + },"ActivityId": ActivityId, "Id":"" + } + resp = _authorizedPost('{0}WorkItems'.format(_apiEntryPoint), req) + if resp.status_code == 401: + raise Unauthorized('Unauthorized to create a WorkItem') + if resp.status_code == 500: + print resp.json() + print resp.json()['error']['innererror']['stacktrace'] + raise ServerError('Remote server error') + resp = resp.json() + return resp['Id'] -#TODO: add support for other operations (submit workitem, query activities etc.) +#TODO: add support for other operations (query activities etc.) if __name__ == '__main__': - import argparse + import argparse, time parser = argparse.ArgumentParser(description='Get error report from AutoCAD.IO for a work item.') - parser.add_argument('--consumerKey', required=True); - parser.add_argument('--consumerSecret', required=True); - parser.add_argument('--workItem', required=True); + parser.add_argument('--clientId', required=True); + parser.add_argument('--clientSecret', required=True); args = parser.parse_args() ApiCredentials = Credentials( - clientId = args.consumerKey, - clientSecret = args.consumerSecret + clientId = args.clientId, + clientSecret = args.clientSecret ) - #For now, the only supported operation is printing the report of a workitem. - printReport(args) \ No newline at end of file + # Create a default WorkItem + workItemId = submitWorkItem() + print("Created WorkItem('{0}')".format(workItemId)) + # Wait until WorkItem is done + while True: + workItem = getWorkItem(workItemId) + if workItem['Status'] not in ['Pending', 'InProgress']: + break + else: + print("WorkItem is {0}".format(workItem['Status'])) + time.sleep(2) + # Print the report of that WorkItem + printReport(workItemId) + workItem = getWorkItem(workItemId) + print('OutputArguments') + print(workItem['Arguments']['OutputArguments']) + \ No newline at end of file From cc986dbc641fae27e1141dbd40beeddc9d174466 Mon Sep 17 00:00:00 2001 From: evilham Date: Fri, 19 Aug 2016 19:53:58 +0000 Subject: [PATCH 4/4] QuotaExceeded Exception. --- acadio.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/acadio.py b/acadio.py index 34ad881..b02ecd1 100644 --- a/acadio.py +++ b/acadio.py @@ -20,6 +20,8 @@ class NotFound(ValueError): pass class ServerError(ValueError): pass +class QuotaExceeded(RuntimeError): + pass def getToken(): """Obtain Apiggee token given a consumer key/secret @@ -104,7 +106,12 @@ def submitWorkItem(ActivityId = "PlotToPDF", print resp.json() print resp.json()['error']['innererror']['stacktrace'] raise ServerError('Remote server error') - resp = resp.json() + if resp.status_code == 429: + raise QuotaExceeded('Quota Exceeded', resp.text) + try: + resp = resp.json() + except: + print(resp.text) return resp['Id'] #TODO: add support for other operations (query activities etc.)