· 6 years ago · Jun 05, 2019, 05:26 PM
1#/usr/bin/python
2import urllib
3import httplib2
4import requests
5import json
6import sys
7import threading
8import signal
9
10# Modified by Jarrett for using authentication keys to bypass login as well as use in Python3 since urllib is implemented differently
11
12class trackvia:
13 """Class that wraps the TrackVia API. This client handles the initial Oauth authentication as well as refreshing the token before expiration."""
14
15 def __init__(self, url, username, password, apikey, auth_key=None):
16 """This is a constructor for the TrackVia client."""
17
18 #Register a method to handle signals and close our threads cleanly. Only if part of main thread
19 if __name__ == "__main__":
20 signal.signal(signal.SIGINT, self.__signal_handler)
21
22 self.base_url=url
23 self.apikey=apikey
24
25 #Login information
26 values = { 'grant_type' : 'password',
27 'client_id' : 'TrackViaAPI',
28 'username' : username,
29 'password' : password}
30
31 if values['username'] is not None and values['password'] is not None:
32 #Actually do the login and parse the token from the response.
33 resp,body=self.__json_request("{0}/oauth/token".format(self.base_url),post=values)
34
35 if resp["status"] is 200:
36 self.token=body['value']
37 self.refreshToken=body['refreshToken']
38 self.expiresIn=body['expires_in']
39
40 #Get the account ID that we are dealing with here
41 get_values= {'access_token' : self.token}
42 resp,body=self.__json_request("{0}/users".format(self.base_url), type="GET", get=get_values)
43 self.account_id=body['accounts'][0]['id']
44
45 else:
46 # print("Key auth failed api_file_l34")
47 if auth_key is not None:
48 self.token=auth_key
49 self.refreshToken=None
50 else:
51 raise Exception('Login to Trackvia failed and no Authentication Token was provided')
52
53 if hasattr(self, "expiresIn") and self.refreshToken is not None:
54 #Schedule a oauth_token refresh 15s before it expires
55 self.thread=threading.Timer(self.expiresIn-15, self.__refresh_token)
56 self.thread.daemon = True
57 self.thread.start()
58
59 # This is a helper class to make requests to the trackvia API
60 def __json_request(self,url,type="POST",post=None,get=None,content_type="urlencode"):
61 """This method is used internally for sending requests to the API."""
62 if post == None:
63 post_data=None
64 else:
65 try:
66 post_data = urllib.parse.urlencode(post)
67 except:
68 print("Failed to encode body (" + sys.exc_info()[0] + ")")
69
70 #Convert get params to be used in the URL
71 if get == None:
72 get_data=""
73 else:
74 get_list=[]
75 for element in get.keys():
76 get_list.append(urllib.parse.quote_plus(element)+"="+urllib.parse.quote_plus(str(get[element])))
77 get_data="?"+'&'.join(get_list)
78
79 if content_type=="json":
80 headers={'Content-Type': 'application/json; charset=UTF-8'}
81 else:
82 headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
83
84 h = httplib2.Http()
85
86 response, content = h.request(url+get_data,type,headers=headers,body=json.dumps(post))
87
88 try:
89 body=json.loads(content)
90 except ValueError as e:
91 body=content
92
93 return response,body
94
95 # It is much cleaner to upload multipart files using the Requests framework.
96 def __post_file(self, url, data, file):
97 r = requests.post(url,params=data,files=file)
98 return r.status_code, r.content
99
100 def __refresh_token(self):
101 """This method is used internally to refresh the oauth token."""
102 #Login information
103 values = { 'grant_type' : 'refresh_token',
104 'client_id' : 'TrackViaAPI',
105 'refresh_token' : self.refreshToken['value']}
106
107 #Actually do the login and parse the token from the response.
108 resp,body=self.__json_request("{0}/oauth/token".format(self.base_url),type="POST",post=values)
109 self.token=body['value']
110 self.refreshToken=body['refreshToken']
111 self.expiresIn=body['expires_in']
112
113 self.thread=threading.Timer(self.expiresIn-15, self.__refresh_token)
114 self.thread.daemon = True
115 self.thread.start()
116
117 def get_all_apps(self):
118 """Returns all apps for the logged in account."""
119 get_values={"access_token" : self.token,
120 "user_key" : self.apikey}
121 resp,body=self.__json_request("{0}/openapi/apps".format(self.base_url),type='GET', get=get_values)
122 return resp,body
123
124 def get_all_views(self):
125 """Returns all views for the logged in account."""
126 get_values={"access_token" : self.token,
127 "user_key" : self.apikey}
128 resp,body=self.__json_request("{0}/openapi/views".format(self.base_url),type='GET', get=get_values)
129 return resp,body
130
131 def get_view(self,viewId):
132 """Returns a specific view by the viewId."""
133 get_values={"access_token" : self.token,
134 "user_key" : self.apikey,
135 "viewId" : viewId }
136 resp,body=self.__json_request("{0}/openapi/views/{1}".format(self.base_url,viewId),type='GET', get=get_values)
137 return resp,body
138
139 def find_records(self,viewId,query,start=0,max=50):
140 """Search for records in a view."""
141 get_values={"access_token" : self.token,
142 "user_key" : self.apikey,
143 "viewId" : viewId,
144 "q" : query,
145 "start": start,
146 "max": max }
147 resp,body=self.__json_request("{0}/openapi/views/{1}/find".format(self.base_url, viewId),type='GET', get=get_values)
148 return resp,body
149
150 def get_all_records(self,viewId,start=0,max=50):
151 """Returns all records within a view, from start to max."""
152 get_values={"access_token" : self.token,
153 "user_key" : self.apikey,
154 "viewId" : viewId,
155 "q" : "",
156 "start": start,
157 "max": max }
158 resp,body=self.__json_request("{0}/openapi/views/{1}/find".format(self.base_url, viewId),type='GET', get=get_values)
159 return resp,body
160
161 def get_record(self,viewId,recordId):
162 """Returns a specific record from a view."""
163 get_values={"access_token" : self.token,
164 "user_key" : self.apikey,
165 "viewId" : viewId,
166 "recordId" : recordId }
167 resp,body=self.__json_request("{0}/openapi/views/{1}/records/{2}".format(self.base_url, viewId, recordId),type='GET', get=get_values)
168
169 return resp,body
170
171 def delete_record(self,viewId,recordId):
172 """Deletes a record in a view."""
173 get_values={"access_token" : self.token,
174 "user_key" : self.apikey,
175 "viewId" : viewId,
176 "recordId" : recordId }
177 resp,body=self.__json_request("{0}/openapi/views/{1}/records/{2}".format(self.base_url, viewId, recordId),type='DELETE', get=get_values)
178 return resp,body
179
180 def delete_file(self, viewId, recordId, fieldName):
181 """Deletes a file from a field in a view."""
182 get_values={"access_token" : self.token,
183 "user_key" : self.apikey,
184 "viewId" : viewId,
185 "recordId" : recordId }
186 resp,body=self.__json_request("{0}/openapi/views/{1}/records/{2}/files/{3}".format(self.base_url, viewId, recordId, fieldName),type='DELETE', get=get_values)
187 return resp,body
188
189 def get_file(self, viewId, recordId, fieldName):
190 """Returns a file from a field in a view."""
191 get_values={"access_token" : self.token,
192 "user_key" : self.apikey,
193 "viewId" : viewId,
194 "recordId" : recordId }
195 resp,body=self.__json_request("{0}/openapi/views/{1}/records/{2}/files/{3}".format(self.base_url, viewId, recordId, fieldName),type='GET', get=get_values)
196 return resp,body
197
198 def attach_file(self, viewId, recordId, fieldName, file):
199 """Attaches a file to a record"""
200 get_values={"access_token" : self.token,
201 "user_key" : self.apikey,
202 "viewId" : viewId,
203 "recordId" : recordId }
204 file={'file':open(file,'r')}
205 resp,body=self.__post_file("{0}/openapi/views/{1}/records/{2}/files/{3}".format(self.base_url, viewId, recordId, fieldName),get_values,file)
206 return resp,body
207
208 def create_record(self, viewId, data):
209 """Creates a record in a view"""
210 post_data={"data":data}
211 get_values={"access_token" : self.token,
212 "user_key" : self.apikey,
213 "viewId" : viewId }
214 resp,body=self.__json_request("{0}/openapi/views/{1}/records".format(self.base_url, viewId),type='POST', post=post_data, get=get_values, content_type="json")
215
216 return resp,body
217
218 def update_record(self, viewId, recordId, data):
219 """Updates a record in a view"""
220 post_data={"data":data}
221 get_values={"access_token" : self.token,
222 "user_key" : self.apikey,
223 "viewId" : viewId }
224 resp,body=self.__json_request("{0}/openapi/views/{1}/records/{2}".format(self.base_url, viewId, recordId),type='PUT', post=post_data, get=get_values, content_type="json")
225
226 return resp,body
227
228 def delete_record(self, viewId, recordId):
229 """Deletes a record from a view"""
230 get_values={"access_token" : self.token,
231 "user_key" : self.apikey,
232 "viewId" : viewId,
233 "recordId" : recordId }
234 resp,body=self.__json_request("{0}/openapi/views/{1}/records/{2}".format(self.base_url, viewId, recordId),type="DELETE",get=get_values)
235 return resp,body
236
237
238 def delete_all_records(self, viewId):
239 """Deletes all records from a view"""
240 get_values={"access_token" : self.token,
241 "user_key" : self.apikey,
242 "viewId" : viewId}
243 resp,body=self.__json_request("{0}/openapi/views/{1}/records/all".format(self.base_url, viewId),type="DELETE",get=get_values)
244 return resp,body
245
246
247 def get_users(self,start=0,max=50):
248 """Gets users in the account"""
249 get_values={"access_token" : self.token,
250 "user_key" : self.apikey,
251 "start" : start,
252 "max" : max }
253 resp,body=self.__json_request("{0}/openapi/users".format(self.base_url),type="GET",get=get_values)
254 return resp,body
255
256 def create_user(self,email,firstName,lastName,timeZone):
257 """Creates a user"""
258 get_values={"access_token" : self.token,
259 "user_key" : self.apikey,
260 "email" : email,
261 "firstName" : firstName,
262 "lastName" : lastName,
263 "timeZone" : timeZone }
264 resp,body=self.__json_request("{0}/openapi/users".format(self.base_url),type="POST",get=get_values)
265 return resp,body
266
267 def __signal_handler(self, signal, frame):
268 """Internal method for handling signals."""
269 print("Caught Signal: {0}".format(signal) )
270 self.stop()
271 sys.exit(0)
272
273 def stop(self):
274 """Cleanly stop the client and kill the oauth token refresh thread."""
275 print("Stopping client")
276 self.thread.cancel()