Skip to content

Commit 276f5b0

Browse files
John-Braggrobert-closekennydanielJohn BraggJohn-Bragg
authored
Dev 325 (#116)
* INSIGHTS-12 Initial structure of insight functionality * INSIGHTS-12 Added todo statements * INSIGHTS-12 Moved Insights out of client * INSIGHTS-12 Adjusted insight methods to reside in the client class. Removed the ability to collect insights before sending, now the every time the user invokes the collectInsights method, it will also send. This prevents any State issues with the algorithm. * INSIGHTS-12 Added a todo. Tests fail for unknown reasons at this time * INSIGHTS-12 Fixed method call. Added a todo to get url from config if necessary. * INSIGHTS-12 Fixed method call. * INSIGHTS-12 added json serialization. might not be needed * INSIGHTS-12 commented test temporarily * INSIGHTS-12 comment updates and json .encode change * INSIGHTS-12 comment update * INSIGHTS-12 changed method signatures to match documentation https://insights1.enthalpy.click/developers/clients/python#publishing-algorithmia-insights * INSIGHTS-12 Added system property for queue reader url * INSIGHTS-12 Fixed URL to not be https * INSIGHTS-12 minor version update * INSIGHTS-12 revert change * INSIGHTS-12 removed todo * INSIGHTS-12 uncommented test. May start failing again in the pipeline. * INSIGHTS-12 commented test. * INSIGHTS-12 changed version. Removed unused import. * INSIGHTS-12 changed url to include /v1/ * Allow listing of non-data:// files on cli * Allow catting non-data:// files on cli * Fix tests * adding jwt support to CLI * refactoring bearertoken method * adding test and simplifying getclient method * test fixes * adding test Co-authored-by: robert-close <rclose@algorithmia.io> Co-authored-by: Kenny Daniel <kenny@algorithmia.com> Co-authored-by: Kenny Daniel <3903376+kennydaniel@users.noreply.github.com> Co-authored-by: John Bragg <john.bragg@ruralsource.com> Co-authored-by: John-Bragg <jbragg@algorithmia.io> Co-authored-by: John Bragg <john.bragg@datarobot.com>
1 parent 537945c commit 276f5b0

File tree

5 files changed

+118
-33
lines changed

5 files changed

+118
-33
lines changed

Algorithmia/CLI.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ class CLI:
1212
def __init__(self):
1313
self.client = Algorithmia.client()
1414
# algo auth
15-
16-
def auth(self, apikey, apiaddress, cacert="", profile="default"):
15+
def auth(self, apiaddress, apikey="", cacert="", profile="default", bearer=""):
1716

1817
# store api key in local config file and read from it each time a client needs to be created
1918
key = self.getconfigfile()
@@ -24,20 +23,17 @@ def auth(self, apikey, apiaddress, cacert="", profile="default"):
2423
config['profiles'][profile]['api_key'] = apikey
2524
config['profiles'][profile]['api_server'] = apiaddress
2625
config['profiles'][profile]['ca_cert'] = cacert
26+
config['profiles'][profile]['bearer_token'] = bearer
2727

2828
else:
29-
config['profiles'][profile] = {'api_key': apikey, 'api_server': apiaddress, 'ca_cert': cacert}
29+
config['profiles'][profile] = {'api_key':apikey,'api_server':apiaddress,'ca_cert':cacert,'bearer_token':bearer}
3030
else:
31-
config['profiles'] = {profile: {'api_key': apikey, 'api_server': apiaddress, 'ca_cert': cacert}}
31+
config['profiles'] = {profile:{'api_key':apikey,'api_server':apiaddress,'ca_cert':cacert,'bearer_token':bearer }}
3232

3333
with open(key, "w") as key:
34-
toml.dump(config, key)
35-
client = Algorithmia.client(
36-
api_key=self.getAPIkey(profile),
37-
api_address=self.getAPIaddress(profile),
38-
ca_cert=self.getCert(profile)
39-
)
40-
self.ls(path=None, client=client)
34+
toml.dump(config,key)
35+
36+
self.ls(path = None,client = CLI().getClient(profile))
4137

4238
# algo run <algo> <args..> run the the specified algo
4339
def runalgo(self, options, client):
@@ -366,6 +362,7 @@ def getconfigfile(self):
366362
file.write("api_key = ''\n")
367363
file.write("api_server = ''\n")
368364
file.write("ca_cert = ''\n")
365+
file.write("bearer_token = ''\n")
369366

370367
key = keyPath + keyFile
371368

@@ -383,6 +380,16 @@ def getAPIkey(self, profile):
383380
return config_dict['profiles'][profile]['api_key']
384381
else:
385382
return None
383+
384+
def getBearerToken(self,profile):
385+
key = self.getconfigfile()
386+
config_dict = toml.load(key)
387+
if 'profiles' in config_dict and profile in config_dict['profiles'] and \
388+
config_dict['profiles'][profile]['bearer_token'] != "":
389+
return config_dict['profiles'][profile]['bearer_token']
390+
else:
391+
return None
392+
386393

387394
def getAPIaddress(self, profile):
388395
key = self.getconfigfile()
@@ -401,3 +408,14 @@ def getCert(self, profile):
401408
return config_dict['profiles'][profile]['ca_cert']
402409
else:
403410
return None
411+
412+
def getClient(self,profile):
413+
apiAddress = self.getAPIaddress(profile)
414+
apiKey = self.getAPIkey(profile)
415+
caCert = self.getCert(profile)
416+
bearer = None
417+
418+
if apiKey is None:
419+
bearer = self.getBearerToken(profile)
420+
421+
return Algorithmia.client(api_key=apiKey,api_address=apiAddress,ca_cert=caCert,bearer_token = bearer)

Algorithmia/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ def file(dataUrl):
2323
def dir(dataUrl):
2424
return getDefaultClient().dir(dataUrl)
2525

26-
def client(api_key=None, api_address=None, ca_cert=None):
27-
return Client(api_key, api_address, ca_cert)
26+
def client(api_key=None, api_address=None, ca_cert=None, bearer_token=None):
27+
return Client(api_key, api_address, ca_cert, bearer_token)
2828

2929
def handler(apply_func, load_func=lambda: None):
3030
return Handler(apply_func, load_func)

Algorithmia/__main__.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import six
77
from Algorithmia.CLI import CLI
88
import argparse
9+
import re
910

1011
#bind input to raw input
1112
try:
@@ -145,27 +146,26 @@ def main():
145146
APIkey = input("enter API key: ")
146147
CACert = input('(optional) enter path to custom CA certificate: ')
147148

148-
if len(APIkey) == 28 and APIkey.startswith("sim"):
149-
if APIaddress == "" or not APIaddress.startswith("https://api."):
150-
APIaddress = "https://api.algorithmia.com"
151-
152-
CLI().auth(apikey=APIkey, apiaddress=APIaddress, cacert=CACert, profile=args.profile)
149+
if APIaddress == "" or not APIaddress.startswith("https://api."):
150+
print("invalid API address")
153151
else:
154-
print("invalid api key")
155-
152+
if len(APIkey) == 28 and APIkey.startswith("sim"):
153+
CLI().auth(apikey=APIkey, apiaddress=APIaddress, cacert=CACert, profile=args.profile)
154+
else:
155+
jwt = re.compile(r"^([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_\-\+\/=]*)")
156+
Bearer = input("enter JWT token: ")
157+
if jwt.match(Bearer):
158+
CLI().auth(apikey=APIkey, bearer=Bearer, apiaddress=APIaddress, cacert=CACert, profile=args.profile)
159+
else:
160+
print("invalid authentication")
161+
162+
163+
156164
if args.cmd == 'help':
157165
parser.parse_args(['-h'])
158166

159167
#create a client with the appropreate api address and key
160-
client = Algorithmia.client()
161-
if len(CLI().getAPIaddress(args.profile)) > 1:
162-
client = Algorithmia.client(CLI().getAPIkey(args.profile), CLI().getAPIaddress(args.profile))
163-
elif len(CLI().getAPIaddress(args.profile)) > 1 and len(CLI().getCert(args.profile)) > 1:
164-
client = Algorithmia.client(CLI().getAPIkey(args.profile), CLI().getAPIaddress(args.profile),CLI().getCert(args.profile))
165-
elif len(CLI().getAPIaddress(args.profile)) < 1 and len(CLI().getCert(args.profile)) > 1:
166-
client = Algorithmia.client(CLI().getAPIkey(args.profile), CLI().getAPIaddress(args.profile),CLI().getCert(args.profile))
167-
else:
168-
client = Algorithmia.client(CLI().getAPIkey(args.profile))
168+
client = CLI().getClient(args.profile)
169169

170170
if args.cmd == 'run':
171171

Algorithmia/client.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ class Client(object):
2121
apiKey = None
2222
apiAddress = None
2323
requestSession = None
24+
bearerToken = None
2425

25-
def __init__(self, apiKey=None, apiAddress=None, caCert=None):
26+
27+
def __init__(self, apiKey = None, apiAddress = None, caCert = None, bearerToken=None):
2628
# Override apiKey with environment variable
2729
config = None
2830
self.requestSession = requests.Session()
2931
if apiKey is None and 'ALGORITHMIA_API_KEY' in os.environ:
3032
apiKey = os.environ['ALGORITHMIA_API_KEY']
33+
if apiKey is None:
34+
if bearerToken is None and 'ALGORITHMIA_BEARER_TOKEN' in os.environ:
35+
bearerToken = os.environ['ALGORITHMIA_BEARER_TOKEN']
36+
self.bearerToken = bearerToken
37+
3138
self.apiKey = apiKey
3239
if apiAddress is not None:
3340
self.apiAddress = apiAddress
@@ -217,6 +224,8 @@ def postJsonHelper(self, url, input_object, parse_response_as_json=True, **query
217224
headers = {}
218225
if self.apiKey is not None:
219226
headers['Authorization'] = self.apiKey
227+
else:
228+
headers['Authorization'] = "Bearer "+ self.bearerToken
220229

221230
input_json = None
222231
if input_object is None:
@@ -244,32 +253,42 @@ def getHelper(self, url, **query_parameters):
244253
headers = {}
245254
if self.apiKey is not None:
246255
headers['Authorization'] = self.apiKey
256+
else:
257+
headers['Authorization'] = 'Bearer '+ self.bearerToken
247258
return self.requestSession.get(self.apiAddress + url, headers=headers, params=query_parameters)
248259

249260
def getStreamHelper(self, url, **query_parameters):
250261
headers = {}
251262
if self.apiKey is not None:
252263
headers['Authorization'] = self.apiKey
264+
else:
265+
headers['Authorization'] = 'Bearer '+ self.bearerToken
253266
return self.requestSession.get(self.apiAddress + url, headers=headers, params=query_parameters, stream=True)
254267

255268
def patchHelper(self, url, params):
256269
headers = {'content-type': 'application/json'}
257270
if self.apiKey is not None:
258271
headers['Authorization'] = self.apiKey
272+
else:
273+
headers['Authorization'] = 'Bearer '+ self.bearerToken
259274
return self.requestSession.patch(self.apiAddress + url, headers=headers, data=json.dumps(params))
260275

261276
# Used internally to get http head result
262277
def headHelper(self, url):
263278
headers = {}
264279
if self.apiKey is not None:
265280
headers['Authorization'] = self.apiKey
281+
else:
282+
headers['Authorization'] = 'Bearer '+ self.bearerToken
266283
return self.requestSession.head(self.apiAddress + url, headers=headers)
267284

268285
# Used internally to http put a file
269286
def putHelper(self, url, data):
270287
headers = {}
271288
if self.apiKey is not None:
272289
headers['Authorization'] = self.apiKey
290+
else:
291+
headers['Authorization'] = 'Bearer '+ self.bearerToken
273292
if isJson(data):
274293
headers['Content-Type'] = 'application/json'
275294

@@ -283,6 +302,8 @@ def deleteHelper(self, url):
283302
headers = {}
284303
if self.apiKey is not None:
285304
headers['Authorization'] = self.apiKey
305+
else:
306+
headers['Authorization'] = 'Bearer '+ self.bearerToken
286307
response = self.requestSession.delete(self.apiAddress + url, headers=headers)
287308
if response.reason == "No Content":
288309
return response

Test/CLI_test.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class CLIDummyTest(unittest.TestCase):
1717
@classmethod
1818
def setUpClass(cls):
1919
cls.client = Algorithmia.client(api_address="http://localhost:8080", api_key="simabcd123")
20+
cls.bearerClient = Algorithmia.client(api_address="http://localhost:8080", bearer_token="simabcd123.token.token")
2021

2122
def test_run(self):
2223
name = "util/Echo"
@@ -52,6 +53,40 @@ def test_run(self):
5253
result = CLI().runalgo(args, self.client)
5354
self.assertEqual(result, inputs)
5455

56+
def test_run_token(self):
57+
name = "util/Echo"
58+
inputs = "test"
59+
60+
parser = argparse.ArgumentParser('CLI for interacting with Algorithmia')
61+
62+
subparsers = parser.add_subparsers(help='sub cmd', dest='subparser_name')
63+
parser_run = subparsers.add_parser('run', help='algo run <algo> [input options] <args..> [output options]')
64+
65+
parser_run.add_argument('algo')
66+
parser_run.add_argument('-d', '--data', action='store', help='detect input type', default=None)
67+
parser_run.add_argument('-t', '--text', action='store', help='treat input as text', default=None)
68+
parser_run.add_argument('-j', '--json', action='store', help='treat input as json data', default=None)
69+
parser_run.add_argument('-b', '--binary', action='store', help='treat input as binary data', default=None)
70+
parser_run.add_argument('-D', '--data-file', action='store', help='specify a path to an input file',
71+
default=None)
72+
parser_run.add_argument('-T', '--text-file', action='store', help='specify a path to a text file',
73+
default=None)
74+
parser_run.add_argument('-J', '--json-file', action='store', help='specify a path to a json file',
75+
default=None)
76+
parser_run.add_argument('-B', '--binary-file', action='store', help='specify a path to a binary file',
77+
default=None)
78+
parser_run.add_argument('--timeout', action='store', type=int, default=300,
79+
help='specify a timeout (seconds)')
80+
parser_run.add_argument('--debug', action='store_true',
81+
help='print the stdout from the algo <this only works for the owner>')
82+
parser_run.add_argument('--profile', action='store', type=str, default='default')
83+
parser_run.add_argument('-o', '--output', action='store', default=None, type=str)
84+
85+
args = parser.parse_args(['run', name, '-d', inputs])
86+
87+
result = CLI().runalgo(args, self.bearerClient)
88+
self.assertEqual(result, inputs)
89+
5590

5691
class CLIMainTest(unittest.TestCase):
5792
def setUp(self):
@@ -156,7 +191,7 @@ def test_auth(self):
156191
key = os.getenv('ALGORITHMIA_API_KEY')
157192
address = 'https://api.algorithmia.com'
158193
profile = 'default'
159-
CLI().auth(key, address, profile=profile)
194+
CLI().auth(address, key, profile=profile)
160195
resultK = CLI().getAPIkey(profile)
161196
resultA = CLI().getAPIaddress(profile)
162197
self.assertEqual(resultK, key)
@@ -176,13 +211,24 @@ def test_auth_cert(self):
176211
cacert = localfile
177212
profile = 'test'
178213

179-
CLI().auth(key, address, cacert, profile)
214+
CLI().auth(address, key, cacert=cacert, profile=profile)
180215
resultK = CLI().getAPIkey(profile)
181216
resultA = CLI().getAPIaddress(profile)
182217
resultC = CLI().getCert(profile)
183218
self.assertEqual(resultK, key)
184219
self.assertEqual(resultA, address)
185220
self.assertEqual(resultC, cacert)
221+
222+
def test_auth_token(self):
223+
address = 'https://api.algorithmia.com'
224+
bearer = 'testtokenabcd'
225+
profile = 'test'
226+
227+
CLI().auth(apiaddress=address, bearer=bearer, profile=profile)
228+
resultA = CLI().getAPIaddress(profile)
229+
resultT = CLI().getBearerToken(profile)
230+
self.assertEqual(resultA, address)
231+
self.assertEqual(resultT, bearer)
186232

187233
def test_get_environment(self):
188234
result = CLI().get_environment_by_language("python2", self.client)
@@ -230,7 +276,7 @@ def test_get_template(self):
230276
def test_api_address_auth(self):
231277
api_key = os.getenv('ALGORITHMIA_TEST_API_KEY')
232278
api_address = "https://api.test.algorithmia.com"
233-
CLI().auth(api_key, api_address)
279+
CLI().auth(api_address, api_key)
234280
profile = "default"
235281

236282
client = Algorithmia.client(CLI().getAPIkey(profile), CLI().getAPIaddress(profile), CLI().getCert(profile))

0 commit comments

Comments
 (0)