proximl 0.5.13__py3-none-any.whl → 0.5.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proximl/__init__.py +1 -1
- proximl/cli/job/create.py +46 -16
- proximl/cli/project/__init__.py +1 -1
- proximl/cli/project/secret.py +12 -3
- proximl/jobs.py +9 -0
- proximl/projects/data_connectors.py +16 -0
- proximl/projects/datastores.py +16 -0
- proximl/projects/members.py +98 -0
- proximl/projects/projects.py +9 -4
- proximl/projects/secrets.py +1 -1
- proximl/projects/services.py +16 -0
- proximl/proximl.py +36 -19
- {proximl-0.5.13.dist-info → proximl-0.5.14.dist-info}/METADATA +1 -1
- {proximl-0.5.13.dist-info → proximl-0.5.14.dist-info}/RECORD +25 -27
- tests/integration/projects/conftest.py +1 -1
- tests/integration/projects/test_projects_members_integration.py +49 -0
- tests/integration/projects/test_projects_secrets_integration.py +1 -1
- tests/integration/test_jobs_integration.py +9 -9
- tests/unit/conftest.py +9 -9
- tests/unit/projects/test_project_members_unit.py +107 -0
- tests/unit/projects/test_projects_unit.py +1 -1
- proximl/cli/project/key.py +0 -124
- proximl/projects/keys.py +0 -71
- tests/integration/projects/test_projects_keys_integration.py +0 -43
- tests/unit/cli/projects/test_cli_project_key_unit.py +0 -26
- tests/unit/projects/test_project_keys_unit.py +0 -96
- {proximl-0.5.13.dist-info → proximl-0.5.14.dist-info}/LICENSE +0 -0
- {proximl-0.5.13.dist-info → proximl-0.5.14.dist-info}/WHEEL +0 -0
- {proximl-0.5.13.dist-info → proximl-0.5.14.dist-info}/entry_points.txt +0 -0
- {proximl-0.5.13.dist-info → proximl-0.5.14.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ from pytest import fixture
|
|
|
4
4
|
@fixture(scope="module")
|
|
5
5
|
async def project(proximl):
|
|
6
6
|
project = await proximl.projects.create(
|
|
7
|
-
name="New Project",
|
|
7
|
+
name="New Project", copy_credentials=False, copy_secrets=False
|
|
8
8
|
)
|
|
9
9
|
yield project
|
|
10
10
|
await project.remove()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import asyncio
|
|
4
|
+
from pytest import mark, fixture
|
|
5
|
+
|
|
6
|
+
pytestmark = [mark.sdk, mark.integration, mark.projects]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mark.create
|
|
10
|
+
@mark.asyncio
|
|
11
|
+
class ProjectMembersTests:
|
|
12
|
+
@fixture(scope="class")
|
|
13
|
+
async def project_member(self, project):
|
|
14
|
+
member = await project.members.add("test.account@proximl.ai","read","read","read","read","read")
|
|
15
|
+
yield member
|
|
16
|
+
await project.members.remove("test.account@proximl.ai")
|
|
17
|
+
|
|
18
|
+
async def test_list_project_members(self, project):
|
|
19
|
+
members = await project.members.list()
|
|
20
|
+
assert len(members) > 0
|
|
21
|
+
|
|
22
|
+
async def test_project_member_properties(self, project, project_member):
|
|
23
|
+
assert isinstance(project_member.id, str)
|
|
24
|
+
assert isinstance(project_member.email, str)
|
|
25
|
+
assert isinstance(project_member.project_uuid, str)
|
|
26
|
+
assert isinstance(project_member.owner, bool)
|
|
27
|
+
assert isinstance(project_member.job, str)
|
|
28
|
+
assert isinstance(project_member.dataset, str)
|
|
29
|
+
assert isinstance(project_member.model, str)
|
|
30
|
+
assert isinstance(project_member.checkpoint, str)
|
|
31
|
+
assert isinstance(project_member.volume, str)
|
|
32
|
+
assert project.id == project_member.project_uuid
|
|
33
|
+
assert project_member.id == "test.account@proximl.ai"
|
|
34
|
+
|
|
35
|
+
async def test_project_member_str(self, project_member):
|
|
36
|
+
string = str(project_member)
|
|
37
|
+
regex = r"^{.*\"email\": \"" + project_member.email + r"\".*}$"
|
|
38
|
+
assert isinstance(string, str)
|
|
39
|
+
assert re.match(regex, string)
|
|
40
|
+
|
|
41
|
+
async def test_project_member_repr(self, project_member):
|
|
42
|
+
string = repr(project_member)
|
|
43
|
+
regex = (
|
|
44
|
+
r"^ProjectMember\( proximl , \*\*{.*'email': '"
|
|
45
|
+
+ project_member.email
|
|
46
|
+
+ r"'.*}\)$"
|
|
47
|
+
)
|
|
48
|
+
assert isinstance(string, str)
|
|
49
|
+
assert re.match(regex, string)
|
|
@@ -17,7 +17,7 @@ class ProjectSecretsTests:
|
|
|
17
17
|
yield project_secret
|
|
18
18
|
await project.secrets.remove(name="secret_value")
|
|
19
19
|
|
|
20
|
-
async def
|
|
20
|
+
async def test_list_project_secrets(self, project, project_secret):
|
|
21
21
|
secrets = await project.secrets.list()
|
|
22
22
|
assert len(secrets) > 0
|
|
23
23
|
|
|
@@ -175,7 +175,7 @@ class JobAPIResourceValidationTests:
|
|
|
175
175
|
disk_size=10,
|
|
176
176
|
)
|
|
177
177
|
assert (
|
|
178
|
-
"Invalid Request -
|
|
178
|
+
"Invalid Request - CPU Only may be not be combined with other GPU Types"
|
|
179
179
|
in error.value.message
|
|
180
180
|
)
|
|
181
181
|
|
|
@@ -423,11 +423,11 @@ class JobIOTests:
|
|
|
423
423
|
disk_size=10,
|
|
424
424
|
workers=["python $ML_MODEL_PATH/tensorflow/main.py"],
|
|
425
425
|
environment=dict(
|
|
426
|
-
type="
|
|
426
|
+
type="DEEPLEARNING_PY312",
|
|
427
427
|
env=[
|
|
428
428
|
dict(
|
|
429
429
|
key="CHECKPOINT_FILE",
|
|
430
|
-
value="model.ckpt-0050",
|
|
430
|
+
value="model.ckpt-0050.weights.h5",
|
|
431
431
|
)
|
|
432
432
|
],
|
|
433
433
|
),
|
|
@@ -469,7 +469,7 @@ class JobIOTests:
|
|
|
469
469
|
sys.stderr.write(captured.err)
|
|
470
470
|
assert "Epoch 1/2" in captured.out
|
|
471
471
|
assert "Epoch 2/2" in captured.out
|
|
472
|
-
assert "adding: model.ckpt-0001
|
|
472
|
+
assert "adding: model.ckpt-0001" in captured.out
|
|
473
473
|
assert "Send complete" in captured.out
|
|
474
474
|
|
|
475
475
|
async def test_job_model_input_and_output(self, proximl, capsys):
|
|
@@ -558,7 +558,7 @@ class JobTypeTests:
|
|
|
558
558
|
assert job.url
|
|
559
559
|
assert extract_domain_suffix(urlparse(job.url).hostname) == "proximl.cloud"
|
|
560
560
|
tries = 0
|
|
561
|
-
await asyncio.sleep(
|
|
561
|
+
await asyncio.sleep(180) ## downloading weights can be slow
|
|
562
562
|
async with aiohttp.ClientSession() as session:
|
|
563
563
|
retry = True
|
|
564
564
|
while retry:
|
|
@@ -640,7 +640,7 @@ class JobTypeTests:
|
|
|
640
640
|
assert "Epoch 2/2" in captured.out
|
|
641
641
|
assert "Uploading s3://proximl-example/output/resnet_cifar10" in captured.out
|
|
642
642
|
assert (
|
|
643
|
-
"upload: ./model.ckpt-0002.
|
|
643
|
+
"upload: ./model.ckpt-0002.weights.h5 to s3://proximl-example/output/resnet_cifar10/model.ckpt-0002.weights.h5"
|
|
644
644
|
in captured.out
|
|
645
645
|
)
|
|
646
646
|
assert "Upload complete" in captured.out
|
|
@@ -713,9 +713,9 @@ class JobFeatureTests:
|
|
|
713
713
|
sys.stderr.write(captured.err)
|
|
714
714
|
upload_contents = os.listdir(temp_dir.name)
|
|
715
715
|
temp_dir.cleanup()
|
|
716
|
-
assert len(upload_contents)
|
|
716
|
+
assert len(upload_contents) >= 3
|
|
717
717
|
assert any(
|
|
718
|
-
"model.ckpt-0002
|
|
718
|
+
"model.ckpt-0002" in content
|
|
719
719
|
for content in upload_contents
|
|
720
720
|
)
|
|
721
721
|
|
|
@@ -724,5 +724,5 @@ class JobFeatureTests:
|
|
|
724
724
|
sys.stderr.write(captured.err)
|
|
725
725
|
assert "Epoch 1/2" in captured.out
|
|
726
726
|
assert "Epoch 2/2" in captured.out
|
|
727
|
-
assert "Number of regular files transferred:
|
|
727
|
+
assert "Number of regular files transferred: 4" in captured.out
|
|
728
728
|
assert "Send complete" in captured.out
|
tests/unit/conftest.py
CHANGED
|
@@ -20,7 +20,7 @@ from proximl.projects import (
|
|
|
20
20
|
from proximl.projects.datastores import ProjectDatastores, ProjectDatastore
|
|
21
21
|
from proximl.projects.services import ProjectServices, ProjectService
|
|
22
22
|
from proximl.projects.data_connectors import ProjectDataConnectors, ProjectDataConnector
|
|
23
|
-
from proximl.projects.
|
|
23
|
+
from proximl.projects.credentials import ProjectCredentials, ProjectCredential
|
|
24
24
|
from proximl.projects.secrets import ProjectSecrets, ProjectSecret
|
|
25
25
|
|
|
26
26
|
from proximl.cloudbender import Cloudbender
|
|
@@ -532,7 +532,7 @@ def mock_jobs():
|
|
|
532
532
|
{"value": "env1val", "key": "env1"},
|
|
533
533
|
{"value": "env2val", "key": "env2"},
|
|
534
534
|
],
|
|
535
|
-
"
|
|
535
|
+
"credentials": ["aws", "gcp"],
|
|
536
536
|
"status": "new",
|
|
537
537
|
},
|
|
538
538
|
"vpn": {
|
|
@@ -609,7 +609,7 @@ def mock_jobs():
|
|
|
609
609
|
"type": "DEEPLEARNING_PY37",
|
|
610
610
|
"image_size": 39656398629,
|
|
611
611
|
"env": [],
|
|
612
|
-
"
|
|
612
|
+
"credentials": [],
|
|
613
613
|
"status": "ready",
|
|
614
614
|
},
|
|
615
615
|
"vpn": {
|
|
@@ -1043,10 +1043,10 @@ def mock_device_configs():
|
|
|
1043
1043
|
@fixture(
|
|
1044
1044
|
scope="session",
|
|
1045
1045
|
)
|
|
1046
|
-
def
|
|
1046
|
+
def mock_project_credentials():
|
|
1047
1047
|
proximl = Mock()
|
|
1048
1048
|
yield [
|
|
1049
|
-
|
|
1049
|
+
ProjectCredential(
|
|
1050
1050
|
proximl,
|
|
1051
1051
|
**{
|
|
1052
1052
|
"project_uuid": "proj-id-1",
|
|
@@ -1055,7 +1055,7 @@ def mock_project_keys():
|
|
|
1055
1055
|
"updatedAt": "2023-06-02T21:22:40.084Z",
|
|
1056
1056
|
},
|
|
1057
1057
|
),
|
|
1058
|
-
|
|
1058
|
+
ProjectCredential(
|
|
1059
1059
|
proximl,
|
|
1060
1060
|
**{
|
|
1061
1061
|
"project_uuid": "proj-id-1",
|
|
@@ -1117,7 +1117,7 @@ def mock_proximl(
|
|
|
1117
1117
|
mock_project_datastores,
|
|
1118
1118
|
mock_project_services,
|
|
1119
1119
|
mock_project_data_connectors,
|
|
1120
|
-
|
|
1120
|
+
mock_project_credentials,
|
|
1121
1121
|
mock_project_secrets,
|
|
1122
1122
|
):
|
|
1123
1123
|
proximl = create_autospec(ProxiML)
|
|
@@ -1150,8 +1150,8 @@ def mock_proximl(
|
|
|
1150
1150
|
proximl.projects.data_connectors.list = AsyncMock(
|
|
1151
1151
|
return_value=mock_project_data_connectors
|
|
1152
1152
|
)
|
|
1153
|
-
proximl.projects.
|
|
1154
|
-
proximl.projects.
|
|
1153
|
+
proximl.projects.credentials = create_autospec(ProjectCredentials)
|
|
1154
|
+
proximl.projects.credentials.list = AsyncMock(return_value=mock_project_credentials)
|
|
1155
1155
|
proximl.projects.secrets = create_autospec(ProjectSecrets)
|
|
1156
1156
|
proximl.projects.secrets.list = AsyncMock(return_value=mock_project_secrets)
|
|
1157
1157
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from pytest import mark, fixture, raises
|
|
6
|
+
from aiohttp import WSMessage, WSMsgType
|
|
7
|
+
|
|
8
|
+
import proximl.projects.members as specimen
|
|
9
|
+
from proximl.exceptions import (
|
|
10
|
+
ApiError,
|
|
11
|
+
SpecificationError,
|
|
12
|
+
ProxiMLException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
pytestmark = [mark.sdk, mark.unit, mark.projects]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@fixture
|
|
19
|
+
def project_members(mock_proximl):
|
|
20
|
+
yield specimen.ProjectMembers(mock_proximl, project_id="1")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@fixture
|
|
24
|
+
def project_member(mock_proximl):
|
|
25
|
+
yield specimen.ProjectMember(
|
|
26
|
+
mock_proximl,
|
|
27
|
+
id="owner@gmail.com",
|
|
28
|
+
email="owner@gmail.com",
|
|
29
|
+
project_uuid="proj-id-1",
|
|
30
|
+
owner= True,
|
|
31
|
+
job= "all",
|
|
32
|
+
dataset= "all",
|
|
33
|
+
model= "all",
|
|
34
|
+
checkpoint="all",
|
|
35
|
+
volume= "all"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ProjectMembersTests:
|
|
40
|
+
@mark.asyncio
|
|
41
|
+
async def test_project_members_list(self, project_members, mock_proximl):
|
|
42
|
+
api_response = [
|
|
43
|
+
{
|
|
44
|
+
"project_uuid": "proj-id-1",
|
|
45
|
+
"email": "owner@gmail.com",
|
|
46
|
+
"createdAt": "2024-09-04T00:42:39.529Z",
|
|
47
|
+
"updatedAt": "2024-09-04T00:42:39.529Z",
|
|
48
|
+
"owner": True,
|
|
49
|
+
"job": "all",
|
|
50
|
+
"dataset": "all",
|
|
51
|
+
"model": "all",
|
|
52
|
+
"checkpoint": "all",
|
|
53
|
+
"volume": "all"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"project_uuid": "proj-id-1",
|
|
57
|
+
"email": "non-owner@gmail.com",
|
|
58
|
+
"createdAt": "2024-09-04T00:42:39.529Z",
|
|
59
|
+
"updatedAt": "2024-09-04T00:42:39.529Z",
|
|
60
|
+
"owner": False,
|
|
61
|
+
"job": "all",
|
|
62
|
+
"dataset": "all",
|
|
63
|
+
"model": "all",
|
|
64
|
+
"checkpoint": "read",
|
|
65
|
+
"volume": "read"
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
69
|
+
resp = await project_members.list()
|
|
70
|
+
mock_proximl._query.assert_called_once_with(
|
|
71
|
+
"/project/1/access", "GET", dict()
|
|
72
|
+
)
|
|
73
|
+
assert len(resp) == 2
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ProjectMemberTests:
|
|
77
|
+
def test_project_member_properties(self, project_member):
|
|
78
|
+
assert isinstance(project_member.id, str)
|
|
79
|
+
assert isinstance(project_member.email, str)
|
|
80
|
+
assert isinstance(project_member.project_uuid, str)
|
|
81
|
+
assert isinstance(project_member.owner, bool)
|
|
82
|
+
assert isinstance(project_member.job, str)
|
|
83
|
+
assert isinstance(project_member.dataset, str)
|
|
84
|
+
assert isinstance(project_member.model, str)
|
|
85
|
+
assert isinstance(project_member.checkpoint, str)
|
|
86
|
+
assert isinstance(project_member.volume, str)
|
|
87
|
+
|
|
88
|
+
def test_project_member_str(self, project_member):
|
|
89
|
+
string = str(project_member)
|
|
90
|
+
regex = r"^{.*\"id\": \"" + project_member.id + r"\".*}$"
|
|
91
|
+
assert isinstance(string, str)
|
|
92
|
+
assert re.match(regex, string)
|
|
93
|
+
|
|
94
|
+
def test_project_member_repr(self, project_member):
|
|
95
|
+
string = repr(project_member)
|
|
96
|
+
regex = (
|
|
97
|
+
r"^ProjectMember\( proximl , \*\*{.*'id': '"
|
|
98
|
+
+ project_member.id
|
|
99
|
+
+ r"'.*}\)$"
|
|
100
|
+
)
|
|
101
|
+
assert isinstance(string, str)
|
|
102
|
+
assert re.match(regex, string)
|
|
103
|
+
|
|
104
|
+
def test_project_member_bool(self, project_member, mock_proximl):
|
|
105
|
+
empty_project_member = specimen.ProjectMember(mock_proximl)
|
|
106
|
+
assert bool(project_member)
|
|
107
|
+
assert not bool(empty_project_member)
|
|
@@ -75,7 +75,7 @@ class ProjectsTests:
|
|
|
75
75
|
requested_config = dict(
|
|
76
76
|
name="new project",
|
|
77
77
|
)
|
|
78
|
-
expected_payload = dict(name="new project",
|
|
78
|
+
expected_payload = dict(name="new project", copy_credentials=False)
|
|
79
79
|
api_response = {
|
|
80
80
|
"id": "project-id-1",
|
|
81
81
|
"name": "new project",
|
proximl/cli/project/key.py
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
import os
|
|
3
|
-
import json
|
|
4
|
-
import base64
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from proximl.cli import pass_config
|
|
7
|
-
from proximl.cli.project import project
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@project.group()
|
|
11
|
-
@pass_config
|
|
12
|
-
def key(config):
|
|
13
|
-
"""proxiML project key commands."""
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@key.command()
|
|
18
|
-
@pass_config
|
|
19
|
-
def list(config):
|
|
20
|
-
"""List keys."""
|
|
21
|
-
data = [
|
|
22
|
-
["TYPE", "KEY ID", "UPDATED AT"],
|
|
23
|
-
[
|
|
24
|
-
"-" * 80,
|
|
25
|
-
"-" * 80,
|
|
26
|
-
"-" * 80,
|
|
27
|
-
],
|
|
28
|
-
]
|
|
29
|
-
project = config.proximl.run(config.proximl.client.projects.get_current())
|
|
30
|
-
keys = config.proximl.run(project.keys.list())
|
|
31
|
-
|
|
32
|
-
for key in keys:
|
|
33
|
-
data.append(
|
|
34
|
-
[
|
|
35
|
-
key.type,
|
|
36
|
-
key.key_id,
|
|
37
|
-
key.updated_at.isoformat(timespec="seconds"),
|
|
38
|
-
]
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
for row in data:
|
|
42
|
-
click.echo(
|
|
43
|
-
"{: >13.11} {: >37.35} {: >28.26}" "".format(*row),
|
|
44
|
-
file=config.stdout,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@key.command()
|
|
49
|
-
@click.argument(
|
|
50
|
-
"type",
|
|
51
|
-
type=click.Choice(
|
|
52
|
-
[
|
|
53
|
-
"aws",
|
|
54
|
-
"azure",
|
|
55
|
-
"docker",
|
|
56
|
-
"gcp",
|
|
57
|
-
"huggingface",
|
|
58
|
-
"kaggle",
|
|
59
|
-
"ngc",
|
|
60
|
-
"wasabi",
|
|
61
|
-
],
|
|
62
|
-
case_sensitive=False,
|
|
63
|
-
),
|
|
64
|
-
)
|
|
65
|
-
@pass_config
|
|
66
|
-
def put(config, type):
|
|
67
|
-
"""
|
|
68
|
-
Set a key.
|
|
69
|
-
|
|
70
|
-
A key is uploaded.
|
|
71
|
-
"""
|
|
72
|
-
project = config.proximl.run(config.proximl.client.projects.get_current())
|
|
73
|
-
|
|
74
|
-
tenant = None
|
|
75
|
-
|
|
76
|
-
if type in ["aws", "wasabi"]:
|
|
77
|
-
key_id = click.prompt("Enter the key ID", type=str, hide_input=False)
|
|
78
|
-
secret = click.prompt("Enter the secret key", type=str, hide_input=True)
|
|
79
|
-
elif type == "azure":
|
|
80
|
-
key_id = click.prompt(
|
|
81
|
-
"Enter the Application (client) ID", type=str, hide_input=False
|
|
82
|
-
)
|
|
83
|
-
tenant = click.prompt(
|
|
84
|
-
"Enter the Directory (tenant) ley", type=str, hide_input=False
|
|
85
|
-
)
|
|
86
|
-
secret = click.prompt("Enter the client secret", type=str, hide_input=True)
|
|
87
|
-
elif type in ["docker", "huggingface"]:
|
|
88
|
-
key_id = click.prompt("Enter the username", type=str, hide_input=False)
|
|
89
|
-
secret = click.prompt("Enter the access token", type=str, hide_input=True)
|
|
90
|
-
elif type in ["gcp", "kaggle"]:
|
|
91
|
-
file_name = click.prompt(
|
|
92
|
-
"Enter the path of the credentials file",
|
|
93
|
-
type=click.Path(
|
|
94
|
-
exists=True, file_okay=True, dir_okay=False, resolve_path=True
|
|
95
|
-
),
|
|
96
|
-
hide_input=False,
|
|
97
|
-
)
|
|
98
|
-
key_id = os.path.basename(file_name)
|
|
99
|
-
with open(file_name) as f:
|
|
100
|
-
secret = json.load(f)
|
|
101
|
-
secret = json.dumps(secret)
|
|
102
|
-
elif type == "ngc":
|
|
103
|
-
key_id = "$oauthtoken"
|
|
104
|
-
secret = click.prompt("Enter the access token", type=str, hide_input=True)
|
|
105
|
-
else:
|
|
106
|
-
raise click.UsageError("Unsupported key type")
|
|
107
|
-
|
|
108
|
-
return config.proximl.run(
|
|
109
|
-
project.keys.put(type=type, key_id=key_id, secret=secret, tenant=tenant)
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@key.command()
|
|
114
|
-
@click.argument("name", type=click.STRING)
|
|
115
|
-
@pass_config
|
|
116
|
-
def remove(config, name):
|
|
117
|
-
"""
|
|
118
|
-
Remove a key.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"""
|
|
122
|
-
project = config.proximl.run(config.proximl.client.projects.get_current())
|
|
123
|
-
|
|
124
|
-
return config.proximl.run(project.key.remove(name))
|
proximl/projects/keys.py
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from dateutil import parser, tz
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ProjectKeys(object):
|
|
8
|
-
def __init__(self, proximl, project_id):
|
|
9
|
-
self.proximl = proximl
|
|
10
|
-
self.project_id = project_id
|
|
11
|
-
|
|
12
|
-
async def list(self, **kwargs):
|
|
13
|
-
resp = await self.proximl._query(
|
|
14
|
-
f"/project/{self.project_id}/keys", "GET", kwargs
|
|
15
|
-
)
|
|
16
|
-
keys = [ProjectKey(self.proximl, **service) for service in resp]
|
|
17
|
-
return keys
|
|
18
|
-
|
|
19
|
-
async def put(self, type, key_id, secret, tenant=None, **kwargs):
|
|
20
|
-
data = dict(key_id=key_id, secret=secret, tenant=tenant)
|
|
21
|
-
payload = {k: v for k, v in data.items() if v is not None}
|
|
22
|
-
logging.info(f"Creating Project Key {type}")
|
|
23
|
-
resp = await self.proximl._query(
|
|
24
|
-
f"/project/{self.project_id}/key/{type}", "PUT", None, payload
|
|
25
|
-
)
|
|
26
|
-
key = ProjectKey(self.proximl, **resp)
|
|
27
|
-
logging.info(f"Created Project Key {type} in project {self.project_id}")
|
|
28
|
-
|
|
29
|
-
return key
|
|
30
|
-
|
|
31
|
-
async def remove(self, type, **kwargs):
|
|
32
|
-
await self.proximl._query(
|
|
33
|
-
f"/project/{self.project_id}/key/{type}", "DELETE", kwargs
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class ProjectKey:
|
|
38
|
-
def __init__(self, proximl, **kwargs):
|
|
39
|
-
self.proximl = proximl
|
|
40
|
-
self._entity = kwargs
|
|
41
|
-
self._type = self._entity.get("type")
|
|
42
|
-
self._project_uuid = self._entity.get("project_uuid")
|
|
43
|
-
self._key_id = self._entity.get("key_id")
|
|
44
|
-
self._updated_at = self._entity.get("updatedAt")
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def type(self) -> str:
|
|
48
|
-
return self._type
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def project_uuid(self) -> str:
|
|
52
|
-
return self._project_uuid
|
|
53
|
-
|
|
54
|
-
@property
|
|
55
|
-
def key_id(self) -> str:
|
|
56
|
-
return self._key_id
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def updated_at(self) -> datetime:
|
|
60
|
-
timestamp = parser.isoparse(self._updated_at)
|
|
61
|
-
timezone = tz.tzlocal()
|
|
62
|
-
return timestamp.astimezone(timezone)
|
|
63
|
-
|
|
64
|
-
def __str__(self):
|
|
65
|
-
return json.dumps({k: v for k, v in self._entity.items()})
|
|
66
|
-
|
|
67
|
-
def __repr__(self):
|
|
68
|
-
return f"ProjectKey( proximl , **{self._entity.__repr__()})"
|
|
69
|
-
|
|
70
|
-
def __bool__(self):
|
|
71
|
-
return bool(self._type)
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import sys
|
|
3
|
-
import asyncio
|
|
4
|
-
from pytest import mark, fixture
|
|
5
|
-
|
|
6
|
-
pytestmark = [mark.sdk, mark.integration, mark.projects]
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@mark.create
|
|
10
|
-
@mark.asyncio
|
|
11
|
-
class ProjectKeysTests:
|
|
12
|
-
@fixture(scope="class")
|
|
13
|
-
async def project_key(self, project):
|
|
14
|
-
project_key = await project.keys.put(
|
|
15
|
-
type="aws", key_id="ASFHALKF", secret="IUHKLHKAHF"
|
|
16
|
-
)
|
|
17
|
-
yield project_key
|
|
18
|
-
await project.keys.remove(type="aws")
|
|
19
|
-
|
|
20
|
-
async def test_list_project_keys(self, project, project_key):
|
|
21
|
-
keys = await project.keys.list()
|
|
22
|
-
assert len(keys) > 0
|
|
23
|
-
|
|
24
|
-
async def test_project_key_properties(self, project, project_key):
|
|
25
|
-
assert isinstance(project_key.project_uuid, str)
|
|
26
|
-
assert isinstance(project_key.type, str)
|
|
27
|
-
assert isinstance(project_key.key_id, str)
|
|
28
|
-
assert project_key.type == "aws"
|
|
29
|
-
assert project.id == project_key.project_uuid
|
|
30
|
-
|
|
31
|
-
async def test_project_key_str(self, project_key):
|
|
32
|
-
string = str(project_key)
|
|
33
|
-
regex = r"^{.*\"type\": \"" + project_key.type + r"\".*}$"
|
|
34
|
-
assert isinstance(string, str)
|
|
35
|
-
assert re.match(regex, string)
|
|
36
|
-
|
|
37
|
-
async def test_project_key_repr(self, project_key):
|
|
38
|
-
string = repr(project_key)
|
|
39
|
-
regex = (
|
|
40
|
-
r"^ProjectKey\( proximl , \*\*{.*'type': '" + project_key.type + r"'.*}\)$"
|
|
41
|
-
)
|
|
42
|
-
assert isinstance(string, str)
|
|
43
|
-
assert re.match(regex, string)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import json
|
|
3
|
-
import click
|
|
4
|
-
from unittest.mock import AsyncMock, patch, create_autospec
|
|
5
|
-
from pytest import mark, fixture, raises
|
|
6
|
-
|
|
7
|
-
pytestmark = [mark.cli, mark.unit, mark.projects]
|
|
8
|
-
|
|
9
|
-
from proximl.cli.project import key as specimen
|
|
10
|
-
from proximl.projects import (
|
|
11
|
-
Project,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_list_keys(runner, mock_project_keys):
|
|
16
|
-
with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
|
|
17
|
-
project = create_autospec(Project)
|
|
18
|
-
mock_proximl.projects = AsyncMock()
|
|
19
|
-
mock_proximl.projects.get = AsyncMock(return_value=project)
|
|
20
|
-
mock_proximl.projects.get_current = AsyncMock(return_value=project)
|
|
21
|
-
project.keys = AsyncMock()
|
|
22
|
-
project.keys.list = AsyncMock(return_value=mock_project_keys)
|
|
23
|
-
result = runner.invoke(specimen, ["list"])
|
|
24
|
-
print(result)
|
|
25
|
-
assert result.exit_code == 0
|
|
26
|
-
project.keys.list.assert_called_once()
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import json
|
|
3
|
-
import logging
|
|
4
|
-
from unittest.mock import AsyncMock, patch
|
|
5
|
-
from pytest import mark, fixture, raises
|
|
6
|
-
from aiohttp import WSMessage, WSMsgType
|
|
7
|
-
|
|
8
|
-
import proximl.projects.keys as specimen
|
|
9
|
-
from proximl.exceptions import (
|
|
10
|
-
ApiError,
|
|
11
|
-
SpecificationError,
|
|
12
|
-
ProxiMLException,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
pytestmark = [mark.sdk, mark.unit, mark.projects]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@fixture
|
|
19
|
-
def project_keys(mock_proximl):
|
|
20
|
-
yield specimen.ProjectKeys(mock_proximl, project_id="1")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@fixture
|
|
24
|
-
def project_key(mock_proximl):
|
|
25
|
-
yield specimen.ProjectKey(
|
|
26
|
-
mock_proximl, project_uuid="proj-id-1", type="aws", key_id="AIYHGFSDLK"
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class ProjectKeysTests:
|
|
31
|
-
@mark.asyncio
|
|
32
|
-
async def test_project_keys_list(self, project_keys, mock_proximl):
|
|
33
|
-
api_response = [
|
|
34
|
-
{"project_uuid": "proj-id-1", "type": "aws", "key_id": "AIYHGFSDLK"},
|
|
35
|
-
{"project_uuid": "proj-id-1", "type": "gcp", "key_id": "credentials.json"},
|
|
36
|
-
]
|
|
37
|
-
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
38
|
-
resp = await project_keys.list()
|
|
39
|
-
mock_proximl._query.assert_called_once_with("/project/1/keys", "GET", dict())
|
|
40
|
-
assert len(resp) == 2
|
|
41
|
-
|
|
42
|
-
@mark.asyncio
|
|
43
|
-
async def test_remove_project_key(
|
|
44
|
-
self,
|
|
45
|
-
project_keys,
|
|
46
|
-
mock_proximl,
|
|
47
|
-
):
|
|
48
|
-
api_response = dict()
|
|
49
|
-
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
50
|
-
await project_keys.remove("aws")
|
|
51
|
-
mock_proximl._query.assert_called_once_with(
|
|
52
|
-
"/project/1/key/aws", "DELETE", dict()
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
@mark.asyncio
|
|
56
|
-
async def test_put_project_key(self, project_keys, mock_proximl):
|
|
57
|
-
requested_config = dict(type="aws", key_id="AIUDHADA", secret="ASKHJSLKF")
|
|
58
|
-
expected_payload = dict(key_id="AIUDHADA", secret="ASKHJSLKF")
|
|
59
|
-
api_response = {
|
|
60
|
-
"project_uuid": "project-id-1",
|
|
61
|
-
"type": "aws",
|
|
62
|
-
"key_id": "AIUDHADA",
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
66
|
-
response = await project_keys.put(**requested_config)
|
|
67
|
-
mock_proximl._query.assert_called_once_with(
|
|
68
|
-
"/project/1/key/aws", "PUT", None, expected_payload
|
|
69
|
-
)
|
|
70
|
-
assert response.type == "aws"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class ProjectKeyTests:
|
|
74
|
-
def test_project_key_properties(self, project_key):
|
|
75
|
-
assert isinstance(project_key.type, str)
|
|
76
|
-
assert isinstance(project_key.key_id, str)
|
|
77
|
-
assert isinstance(project_key.project_uuid, str)
|
|
78
|
-
|
|
79
|
-
def test_project_key_str(self, project_key):
|
|
80
|
-
string = str(project_key)
|
|
81
|
-
regex = r"^{.*\"type\": \"" + project_key.type + r"\".*}$"
|
|
82
|
-
assert isinstance(string, str)
|
|
83
|
-
assert re.match(regex, string)
|
|
84
|
-
|
|
85
|
-
def test_project_key_repr(self, project_key):
|
|
86
|
-
string = repr(project_key)
|
|
87
|
-
regex = (
|
|
88
|
-
r"^ProjectKey\( proximl , \*\*{.*'type': '" + project_key.type + r"'.*}\)$"
|
|
89
|
-
)
|
|
90
|
-
assert isinstance(string, str)
|
|
91
|
-
assert re.match(regex, string)
|
|
92
|
-
|
|
93
|
-
def test_project_key_bool(self, project_key, mock_proximl):
|
|
94
|
-
empty_project_key = specimen.ProjectKey(mock_proximl)
|
|
95
|
-
assert bool(project_key)
|
|
96
|
-
assert not bool(empty_project_key)
|
|
File without changes
|