Skip to content

Commit 5e2a8a8

Browse files
authored
added get and set secrets endpoints for python client (#129)
* added get and set secrets endpoints for python client * bumped version support to 1.4x of adk * adjusted description field to always be present * added better test coverage, removed get test (verified works on marketplace)
1 parent 73348a3 commit 5e2a8a8

File tree

7 files changed

+182
-28
lines changed

7 files changed

+182
-28
lines changed

Algorithmia/algorithm.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,47 @@ def set_options(self, timeout=300, stdout=False, output=OutputType.default, **qu
3838
self.query_parameters.update(query_parameters)
3939
return self
4040

41+
def get_algorithm_id(self):
42+
url = '/v1/algorithms/' + self.username + '/' + self.algoname
43+
print(url)
44+
api_response = self.client.getJsonHelper(url)
45+
if 'id' in api_response:
46+
return api_response['id']
47+
else:
48+
raise Exception("field 'id' not found in response: ", api_response)
49+
50+
51+
def get_secrets(self):
52+
algorithm_id = self.get_algorithm_id()
53+
url = "/v1/algorithms/" + algorithm_id + "/secrets"
54+
api_response = self.client.getJsonHelper(url)
55+
return api_response
56+
57+
58+
def set_secret(self, short_name, secret_key, secret_value, description=None):
59+
algorithm_id = self.get_algorithm_id()
60+
url = "/v1/algorithms/" + algorithm_id + "/secrets"
61+
secret_providers = self.client.get_secret_providers()
62+
provider_id = secret_providers[0]['id']
63+
64+
create_parameters = {
65+
"owner_type": "algorithm",
66+
"owner_id": algorithm_id,
67+
"short_name": short_name,
68+
"provider_id": provider_id,
69+
"secret_key": secret_key,
70+
"secret_value": secret_value,
71+
}
72+
if description:
73+
create_parameters['description'] = description
74+
else:
75+
create_parameters['description'] = " "
76+
77+
print(create_parameters)
78+
api_response = self.client.postJsonHelper(url, create_parameters, parse_response_as_json=True)
79+
return api_response
80+
81+
4182
# Create a new algorithm
4283
def create(self, details, settings, version_info=None, source=None, scmsCredentials=None):
4384
url = "/v1/algorithms/" + self.username

Algorithmia/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ def get_supported_languages(self):
177177
response = self.getHelper(url)
178178
return response.json()
179179

180+
def get_secret_providers(self):
181+
url = "/v1/secret-provider"
182+
api_response = self.getJsonHelper(url)
183+
return api_response
184+
180185
def get_organization_errors(self, org_name):
181186
"""Gets the errors for the organization.
182187

Test/api/app.py

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async def process_hello_world(request: Request, username, algoname, githash):
6060

6161
### Algorithm Routes
6262
@regular_app.get('/v1/algorithms/{username}/{algoname}')
63-
async def process_get_algo(request: Request, username, algoname):
63+
async def process_get_algo(username, algoname):
6464
if algoname == "echo" and username == 'quality':
6565
return {"id": "21df7a38-eab8-4ac8-954c-41a285535e69", "name": "echo",
6666
"details": {"summary": "", "label": "echo", "tagline": ""},
@@ -444,3 +444,99 @@ async def get_environments_by_lang(language):
444444
}
445445
]
446446
}
447+
448+
449+
@regular_app.get("/v1/secret-provider")
450+
async def get_service_providers():
451+
return [
452+
{
453+
"id": "dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
454+
"name": "algorithmia_internal_secret_provider",
455+
"description": "Internal Secret Provider",
456+
"moduleName": "module",
457+
"factoryClassName": "com.algorithmia.plugin.sqlsecretprovider.InternalSecretProviderFactory",
458+
"interfaceVersion": "1.0",
459+
"isEnabled": True,
460+
"isDefault": True,
461+
"created": "2021-03-11T20:42:23Z",
462+
"modified": "2021-03-11T20:42:23Z"
463+
}
464+
]
465+
466+
467+
@regular_app.get("/v1/algorithms/{algorithm_id}/secrets")
468+
async def get_secrets_for_algorithm(algorithm_id):
469+
return {
470+
"secrets": [
471+
{
472+
"id": "45e97c47-3ae6-46be-87ee-8ab23746706b",
473+
"short_name": "MLOPS_SERVICE_URL",
474+
"description": "",
475+
"secret_key": "MLOPS_SERVICE_URL",
476+
"owner_type": "algorithm",
477+
"owner_id": "fa2cd80b-d22a-4548-b16a-45dbad2d3499",
478+
"provider_id": "dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
479+
"created": "2022-07-22T14:36:01Z",
480+
"modified": "2022-07-22T14:36:01Z"
481+
},
482+
{
483+
"id": "50dca60e-317f-4582-8854-5b83b4d182d0",
484+
"short_name": "deploy_id",
485+
"description": "",
486+
"secret_key": "DEPLOYMENT_ID",
487+
"owner_type": "algorithm",
488+
"owner_id": "fa2cd80b-d22a-4548-b16a-45dbad2d3499",
489+
"provider_id": "dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
490+
"created": "2022-07-21T19:04:31Z",
491+
"modified": "2022-07-21T19:04:31Z"
492+
},
493+
{
494+
"id": "5a75cdc8-ecc8-4715-8c4b-8038991f1608",
495+
"short_name": "model_path",
496+
"description": "",
497+
"secret_key": "MODEL_PATH",
498+
"owner_type": "algorithm",
499+
"owner_id": "fa2cd80b-d22a-4548-b16a-45dbad2d3499",
500+
"provider_id": "dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
501+
"created": "2022-07-21T19:04:31Z",
502+
"modified": "2022-07-21T19:04:31Z"
503+
},
504+
{
505+
"id": "80e51ed3-f6db-419d-9349-f59f4bbfdcbb",
506+
"short_name": "model_id",
507+
"description": "",
508+
"secret_key": "MODEL_ID",
509+
"owner_type": "algorithm",
510+
"owner_id": "fa2cd80b-d22a-4548-b16a-45dbad2d3499",
511+
"provider_id": "dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
512+
"created": "2022-07-21T19:04:30Z",
513+
"modified": "2022-07-21T19:04:30Z"
514+
},
515+
{
516+
"id": "8773c654-ea2f-4ac5-9ade-55dfc47fec9d",
517+
"short_name": "datarobot_api_token",
518+
"description": "",
519+
"secret_key": "DATAROBOT_MLOPS_API_TOKEN",
520+
"owner_type": "algorithm",
521+
"owner_id": "fa2cd80b-d22a-4548-b16a-45dbad2d3499",
522+
"provider_id": "dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
523+
"created": "2022-07-21T19:04:31Z",
524+
"modified": "2022-07-21T19:04:31Z"
525+
}
526+
]
527+
}
528+
529+
530+
@regular_app.post("/v1/algorithms/{algorithm_id}/secrets")
531+
async def set_algorithm_secret(algorithm_id):
532+
return {
533+
"id":"959af771-7cd8-4981-91c4-70def15bbcdc",
534+
"short_name":"tst",
535+
"description":"",
536+
"secret_key":"test",
537+
"owner_type":"algorithm",
538+
"owner_id":"fa2cd80b-d22a-4548-b16a-45dbad2d3499",
539+
"provider_id":"dee00b6c-05c4-4de7-98d8-e4a3816ed75f",
540+
"created":"2022-07-22T18:28:42Z",
541+
"modified":"2022-07-22T18:28:42Z"
542+
}

Test/regular/algo_test.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ def setUpClass(cls):
2020

2121
def test_call_customCert(self):
2222
result = self.client.algo('quality/echo').pipe(bytearray('foo', 'utf-8'))
23-
self.assertEquals('binary', result.metadata.content_type)
24-
self.assertEquals(bytearray('foo', 'utf-8'), result.result)
23+
self.assertEqual('binary', result.metadata.content_type)
24+
self.assertEqual(bytearray('foo', 'utf-8'), result.result)
25+
26+
2527

2628
def test_normal_call(self):
2729
result = self.client.algo('quality/echo').pipe("foo")
28-
self.assertEquals("text", result.metadata.content_type)
29-
self.assertEquals("foo", result.result)
30+
self.assertEqual("text", result.metadata.content_type)
31+
self.assertEqual("foo", result.result)
3032

3133
def test_async_call(self):
3234
result = self.client.algo('quality/echo').set_options(output=OutputType.void).pipe("foo")
@@ -35,40 +37,40 @@ def test_async_call(self):
3537

3638
def test_raw_call(self):
3739
result = self.client.algo('quality/echo').set_options(output=OutputType.raw).pipe("foo")
38-
self.assertEquals("foo", result)
40+
self.assertEqual("foo", result)
3941

4042
def test_dict_call(self):
4143
result = self.client.algo('quality/echo').pipe({"foo": "bar"})
42-
self.assertEquals("json", result.metadata.content_type)
43-
self.assertEquals({"foo": "bar"}, result.result)
44+
self.assertEqual("json", result.metadata.content_type)
45+
self.assertEqual({"foo": "bar"}, result.result)
4446

4547
def test_algo_exists(self):
4648
result = self.client.algo('quality/echo').exists()
47-
self.assertEquals(True, result)
49+
self.assertEqual(True, result)
4850

4951
def test_algo_no_exists(self):
5052
result = self.client.algo('quality/not_echo').exists()
51-
self.assertEquals(False, result)
53+
self.assertEqual(False, result)
5254

5355
#TODO: add more coverage examples to check kwargs
5456
def test_get_versions(self):
5557
result = self.client.algo('quality/echo').versions()
5658
self.assertTrue('results' in result)
5759
self.assertTrue('version_info' in result['results'][0])
5860
self.assertTrue('semantic_version' in result['results'][0]['version_info'])
59-
self.assertEquals('0.1.0', result['results'][0]['version_info']['semantic_version'])
61+
self.assertEqual('0.1.0', result['results'][0]['version_info']['semantic_version'])
6062

6163
def test_text_unicode(self):
6264
telephone = u"\u260E"
6365
# Unicode input to pipe()
6466
result1 = self.client.algo('quality/echo').pipe(telephone)
65-
self.assertEquals('text', result1.metadata.content_type)
66-
self.assertEquals(telephone, result1.result)
67+
self.assertEqual('text', result1.metadata.content_type)
68+
self.assertEqual(telephone, result1.result)
6769

6870
# Unicode return in .result
6971
result2 = self.client.algo('quality/echo').pipe(result1.result)
70-
self.assertEquals('text', result2.metadata.content_type)
71-
self.assertEquals(telephone, result2.result)
72+
self.assertEqual('text', result2.metadata.content_type)
73+
self.assertEqual(telephone, result2.result)
7274

7375
def test_algo_info(self):
7476
result = self.client.algo('quality/echo').info()
@@ -175,6 +177,16 @@ def test_algorithm_programmatic_create_process(self):
175177

176178
self.assertEqual(response['version_info']['semantic_version'], "0.1.0", "information is incorrect")
177179

180+
181+
def test_set_secret(self):
182+
short_name = "tst"
183+
secret_key = "test_key"
184+
secret_value = "test_value"
185+
description = "loreum epsum"
186+
response = self.client.algo("quality/echo").set_secret(short_name, secret_key, secret_value, description)
187+
self.assertEqual(response['id'], "959af771-7cd8-4981-91c4-70def15bbcdc", "invalid ID for created secret")
188+
189+
178190
else:
179191
class AlgoTest(unittest.TestCase):
180192
def setUp(self):
@@ -184,17 +196,17 @@ def test_call_customCert(self):
184196
open("./test.pem", 'w')
185197
c = Algorithmia.client(ca_cert="./test.pem")
186198
result = c.algo('quality/echo').pipe(bytearray('foo', 'utf-8'))
187-
self.assertEquals('binary', result.metadata.content_type)
188-
self.assertEquals(bytearray('foo', 'utf-8'), result.result)
199+
self.assertEqual('binary', result.metadata.content_type)
200+
self.assertEqual(bytearray('foo', 'utf-8'), result.result)
189201
try:
190202
os.remove("./test.pem")
191203
except OSError as e:
192204
print(e)
193205

194206
def test_call_binary(self):
195207
result = self.client.algo('quality/echo').pipe(bytearray('foo', 'utf-8'))
196-
self.assertEquals('binary', result.metadata.content_type)
197-
self.assertEquals(bytearray('foo', 'utf-8'), result.result)
208+
self.assertEqual('binary', result.metadata.content_type)
209+
self.assertEqual(bytearray('foo', 'utf-8'), result.result)
198210

199211
def test_async_call(self):
200212
result = self.client.algo('quality/echo').set_options(output=OutputType.void).pipe("foo")
@@ -203,28 +215,28 @@ def test_async_call(self):
203215

204216
def test_raw_call(self):
205217
result = self.client.algo('quality/echo').set_options(output=OutputType.raw).pipe("foo")
206-
self.assertEquals("foo", result)
218+
self.assertEqual("foo", result)
207219

208220
#TODO: add more coverage examples to check kwargs
209221
def test_get_versions(self):
210222
result = self.client.algo('quality/echo').versions()
211223
self.assertTrue('results' in result)
212224
self.assertTrue('version_info' in result['results'][0])
213225
self.assertTrue('semantic_version' in result['results'][0]['version_info'])
214-
self.assertEquals('0.1.0', result['results'][0]['version_info']['semantic_version'])
226+
self.assertEqual('0.1.0', result['results'][0]['version_info']['semantic_version'])
215227

216228
def test_text_unicode(self):
217229
telephone = u"\u260E"
218230

219231
# Unicode input to pipe()
220232
result1 = self.client.algo('quality/echo').pipe(telephone)
221-
self.assertEquals('text', result1.metadata.content_type)
222-
self.assertEquals(telephone, result1.result)
233+
self.assertEqual('text', result1.metadata.content_type)
234+
self.assertEqual(telephone, result1.result)
223235

224236
# Unicode return in .result
225237
result2 = self.client.algo('quality/echo').pipe(result1.result)
226-
self.assertEquals('text', result2.metadata.content_type)
227-
self.assertEquals(telephone, result2.result)
238+
self.assertEqual('text', result2.metadata.content_type)
239+
self.assertEqual(telephone, result2.result)
228240

229241

230242
def test_get_scm_status(self):

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ enum-compat
44
toml
55
argparse
66
algorithmia-api-client==1.5.1
7-
algorithmia-adk>=1.2,<1.3
7+
algorithmia-adk>=1.2,<1.4
88
numpy<2
99
uvicorn==0.14.0
1010
fastapi==0.65.2

requirements27.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ enum-compat
44
toml
55
argparse
66
algorithmia-api-client==1.5.1
7-
algorithmia-adk>=1.2,<1.3
7+
algorithmia-adk>=1.2,<1.4
88
numpy<2

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
'toml',
2323
'argparse',
2424
'algorithmia-api-client==1.5.1',
25-
'algorithmia-adk>=1.2,<1.3'
25+
'algorithmia-adk>=1.2,<1.4'
2626
],
2727
include_package_data=True,
2828
classifiers=[

0 commit comments

Comments
 (0)