proximl 0.5.11__py3-none-any.whl → 0.5.12__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.
@@ -0,0 +1,128 @@
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 credential(config):
13
+ """proxiML project credential commands."""
14
+ pass
15
+
16
+
17
+ @credential.command()
18
+ @pass_config
19
+ def list(config):
20
+ """List credentials."""
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
+ credentials = config.proximl.run(project.credentials.list())
31
+
32
+ for credential in credentials:
33
+ data.append(
34
+ [
35
+ credential.type,
36
+ credential.key_id,
37
+ credential.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
+ @credential.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 credential.
69
+
70
+ A credential 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
+ credential_id = click.prompt(
78
+ "Enter the credential ID", type=str, hide_input=False
79
+ )
80
+ secret = click.prompt("Enter the secret credential", type=str, hide_input=True)
81
+ elif type == "azure":
82
+ credential_id = click.prompt(
83
+ "Enter the Application (client) ID", type=str, hide_input=False
84
+ )
85
+ tenant = click.prompt(
86
+ "Enter the Directory (tenant) ley", type=str, hide_input=False
87
+ )
88
+ secret = click.prompt("Enter the client secret", type=str, hide_input=True)
89
+ elif type in ["docker", "huggingface"]:
90
+ credential_id = click.prompt("Enter the username", type=str, hide_input=False)
91
+ secret = click.prompt("Enter the access token", type=str, hide_input=True)
92
+ elif type in ["gcp", "kaggle"]:
93
+ file_name = click.prompt(
94
+ "Enter the path of the credentials file",
95
+ type=click.Path(
96
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True
97
+ ),
98
+ hide_input=False,
99
+ )
100
+ credential_id = os.path.basename(file_name)
101
+ with open(file_name) as f:
102
+ secret = json.load(f)
103
+ secret = json.dumps(secret)
104
+ elif type == "ngc":
105
+ credential_id = "$oauthtoken"
106
+ secret = click.prompt("Enter the access token", type=str, hide_input=True)
107
+ else:
108
+ raise click.UsageError("Unsupported credential type")
109
+
110
+ return config.proximl.run(
111
+ project.credentials.put(
112
+ type=type, credential_id=credential_id, secret=secret, tenant=tenant
113
+ )
114
+ )
115
+
116
+
117
+ @credential.command()
118
+ @click.argument("name", type=click.STRING)
119
+ @pass_config
120
+ def remove(config, name):
121
+ """
122
+ Remove a credential.
123
+
124
+
125
+ """
126
+ project = config.proximl.run(config.proximl.client.projects.get_current())
127
+
128
+ return config.proximl.run(project.credential.remove(name))
@@ -0,0 +1,71 @@
1
+ import json
2
+ import logging
3
+ from datetime import datetime
4
+ from dateutil import parser, tz
5
+
6
+
7
+ class ProjectCredentials(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}/credentials", "GET", kwargs
15
+ )
16
+ credentials = [ProjectCredential(self.proximl, **service) for service in resp]
17
+ return credentials
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 Credential {type}")
23
+ resp = await self.proximl._query(
24
+ f"/project/{self.project_id}/credential/{type}", "PUT", None, payload
25
+ )
26
+ credential = ProjectCredential(self.proximl, **resp)
27
+ logging.info(f"Created Project Credential {type} in project {self.project_id}")
28
+
29
+ return credential
30
+
31
+ async def remove(self, type, **kwargs):
32
+ await self.proximl._query(
33
+ f"/project/{self.project_id}/credential/{type}", "DELETE", kwargs
34
+ )
35
+
36
+
37
+ class ProjectCredential:
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"ProjectCredential( proximl , **{self._entity.__repr__()})"
69
+
70
+ def __bool__(self):
71
+ return bool(self._type)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proximl
3
- Version: 0.5.11
3
+ Version: 0.5.12
4
4
  Summary: proxiML client SDK and command line utilities
5
5
  Home-page: https://github.com/proxiML/python-sdk
6
6
  Author: proxiML
@@ -34,6 +34,7 @@ proximl/cli/cloudbender/service.py,sha256=6NuwlFULJLtOmMy9OFA8GkW4MLvNemAcd_KitQ
34
34
  proximl/cli/job/__init__.py,sha256=s8mU2PvCWDcv4gGT3EmjHn8MIZlXBAoayoZKmnKpXnY,6545
35
35
  proximl/cli/job/create.py,sha256=sGvbenY0yxvxHo-FZVbdw8FaZx5D4ekTCjD7P4YHG4g,34288
36
36
  proximl/cli/project/__init__.py,sha256=nSU_qBNVPezVA_Uu54ha37PAfCmTsIclXptEvIwiRME,1904
37
+ proximl/cli/project/credential.py,sha256=5vPI6VBIo_YyvAzfUa9ARmz-LUnkdIVW0zbko9t0Q38,3443
37
38
  proximl/cli/project/data_connector.py,sha256=CKF4snfrzDmeDXb4au8nV2W9jTweRMDZ89U2GAlBedQ,1411
38
39
  proximl/cli/project/datastore.py,sha256=YusrUTKY_qIwEOBN2gXOUoKCUp2qbWTKzquKc0DcUsE,1336
39
40
  proximl/cli/project/key.py,sha256=23ugcWOAxNJvC8VzG3FsEUWJ1_FZANnhq66cO04ZEVY,3203
@@ -50,6 +51,7 @@ proximl/cloudbender/providers.py,sha256=KLO4Pc8yeW8TpSDICgTw3NxN44_0s4HptVsR2hHc
50
51
  proximl/cloudbender/regions.py,sha256=6doBdfMXIQI-uexzvwmNg2ATDzruatw-ixviZSMjonU,5157
51
52
  proximl/cloudbender/services.py,sha256=km3KcIgaRRVb3iuZEIn6ONIekuyXhBgk_brAFJcMMl4,5126
52
53
  proximl/projects/__init__.py,sha256=6NKCcHtQMeGB1IyU-djANphfnDX6MEkrXUM5Fyq9fWg,75
54
+ proximl/projects/credentials.py,sha256=hWz6EUEAgltkAQMDjQVsFlVvfWUbZfY3KoWpldnCrXY,2255
53
55
  proximl/projects/data_connectors.py,sha256=559yJ-9qmva2JcNReuCyUQxdrSRIEROcGX5BDVr8ySs,1662
54
56
  proximl/projects/datastores.py,sha256=zAmKX9CibeS7PSXVvExk0SVrEo9Zq83kz6CQw0GoHck,1560
55
57
  proximl/projects/keys.py,sha256=yT5pgv1mGm50CaORf-HnsbDULad69G9I_QpVMN0zcuE,2157
@@ -69,6 +71,7 @@ tests/integration/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
69
71
  tests/integration/cloudbender/test_providers_integration.py,sha256=gFqPQom-Cn1iZC50_ChQ2us2_f4tIPATQSAUcWdf7ss,1473
70
72
  tests/integration/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
73
  tests/integration/projects/conftest.py,sha256=o7rBOFGvcmZ3q0fy7jCIm2CBqn_ULojPP1XFBBUPEVQ,242
74
+ tests/integration/projects/test_projects_credentials_integration.py,sha256=_JqE2sGN09gZpvhtMRFjBPXumJ-PqKl8iXOgcYHlvSY,1636
72
75
  tests/integration/projects/test_projects_data_connectors_integration.py,sha256=ag6w2FFQYNlAdV83rOqat0FVZnP4JFhDunCvRwMJdzo,1630
73
76
  tests/integration/projects/test_projects_datastores_integration.py,sha256=DCHFnCYguhtrZvfTeWgKpPxlkeAXxVfE3IWkM4KjgAc,1469
74
77
  tests/integration/projects/test_projects_integration.py,sha256=wlRLxZkMnJZHlNPaeRFjAeyUlMfbgdrrFEI2eMBcoUI,1241
@@ -105,6 +108,7 @@ tests/unit/cli/cloudbender/test_cli_provider_unit.py,sha256=jCnFnqZuLzuDx9u3kLyj
105
108
  tests/unit/cli/cloudbender/test_cli_region_unit.py,sha256=mEAU0z_gKDM-e5J_V8igXmiU4qjrOfzJJRtKRNWdeBs,1262
106
109
  tests/unit/cli/cloudbender/test_cli_service_unit.py,sha256=7gFaD-Ox22Gevvk6Sn8zGNp_k3jemwgDX3Yqx8yYN3I,1311
107
110
  tests/unit/cli/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
+ tests/unit/cli/projects/test_cli_project_credential_unit.py,sha256=wehwquMGwUzwc8iH0bWletS3-kwIXKshoOuXHhJxRUc,943
108
112
  tests/unit/cli/projects/test_cli_project_data_connector_unit.py,sha256=RlqgHOUkFv3bmc19pUtGNVD0W1UWBJSDfaI_lzN0ZEA,993
109
113
  tests/unit/cli/projects/test_cli_project_datastore_unit.py,sha256=6UMgFwqxBtH65VeWTzXzaXhVfCRoG3Tef6aflqcDlV4,936
110
114
  tests/unit/cli/projects/test_cli_project_key_unit.py,sha256=Xb-O4cltVMNvHpSoPtIdfY4hmXUiMv-z5ixY49FKKyM,894
@@ -121,15 +125,16 @@ tests/unit/cloudbender/test_providers_unit.py,sha256=y63VCqHXb4Yu8sh0kW30-ojRvv9
121
125
  tests/unit/cloudbender/test_regions_unit.py,sha256=9bvP268gpNyygjh1IEpSSiUt2aP6okv7QOsV1XoaIS0,6299
122
126
  tests/unit/cloudbender/test_services_unit.py,sha256=V1JHfqNcLjz7d3qtVxPBCKzd5_lcL5iwhvWqQ0kw0J0,5220
123
127
  tests/unit/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
+ tests/unit/projects/test_project_credentials_unit.py,sha256=y528DyvIm7uc5mFuwiUPjOMO1dkVcMEeYQqzMmKLOuk,3480
124
129
  tests/unit/projects/test_project_data_connectors_unit.py,sha256=Yx2yCUgcuN_KfTKynZxse0Nx8jRrSlMygI7R8a-NjE4,3385
125
130
  tests/unit/projects/test_project_datastores_unit.py,sha256=WZMKkhpEg2dcevJqT7k6QRcYJF6FJfq177BDk2GWOBk,3129
126
131
  tests/unit/projects/test_project_keys_unit.py,sha256=n5Y2rKaenHA6JQFlViE1_dZ00FZ0PhM3C7rQtkuiRGM,3161
127
132
  tests/unit/projects/test_project_secrets_unit.py,sha256=VE9L91FJodcwVGizfF65WYMiHZaF0s2AdW1aiJ3z7xA,3276
128
133
  tests/unit/projects/test_project_services_unit.py,sha256=PzeNuJRuAG7RkrPWX0FfgFTt6-63FviecrDY06rLQ6A,3331
129
134
  tests/unit/projects/test_projects_unit.py,sha256=lTGdKOCN2bjzGORhaDVVlnzJBuEItUDGN36MsyQjDo4,3823
130
- proximl-0.5.11.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
131
- proximl-0.5.11.dist-info/METADATA,sha256=mc59YfzmMo_-ejBfTKF9HZjOFKyDo2E3G0zV1uCE84M,7345
132
- proximl-0.5.11.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
133
- proximl-0.5.11.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
134
- proximl-0.5.11.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
135
- proximl-0.5.11.dist-info/RECORD,,
135
+ proximl-0.5.12.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
136
+ proximl-0.5.12.dist-info/METADATA,sha256=4s3w6gTC6i3GAVUnj4Hw_7NEyO1AH6E4YqkqXo1heXM,7345
137
+ proximl-0.5.12.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
138
+ proximl-0.5.12.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
139
+ proximl-0.5.12.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
140
+ proximl-0.5.12.dist-info/RECORD,,
@@ -0,0 +1,45 @@
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 ProjectCredentialsTests:
12
+ @fixture(scope="class")
13
+ async def project_credential(self, project):
14
+ project_credential = await project.credentials.put(
15
+ type="aws", key_id="ASFHALKF", secret="IUHKLHKAHF"
16
+ )
17
+ yield project_credential
18
+ await project.credentials.remove(type="aws")
19
+
20
+ async def test_list_project_credentials(self, project, project_credential):
21
+ credentials = await project.credentials.list()
22
+ assert len(credentials) > 0
23
+
24
+ async def test_project_credential_properties(self, project, project_credential):
25
+ assert isinstance(project_credential.project_uuid, str)
26
+ assert isinstance(project_credential.type, str)
27
+ assert isinstance(project_credential.key_id, str)
28
+ assert project_credential.type == "aws"
29
+ assert project.id == project_credential.project_uuid
30
+
31
+ async def test_project_credential_str(self, project_credential):
32
+ string = str(project_credential)
33
+ regex = r"^{.*\"type\": \"" + project_credential.type + r"\".*}$"
34
+ assert isinstance(string, str)
35
+ assert re.match(regex, string)
36
+
37
+ async def test_project_credential_repr(self, project_credential):
38
+ string = repr(project_credential)
39
+ regex = (
40
+ r"^ProjectCredential\( proximl , \*\*{.*'type': '"
41
+ + project_credential.type
42
+ + r"'.*}\)$"
43
+ )
44
+ assert isinstance(string, str)
45
+ assert re.match(regex, string)
@@ -0,0 +1,26 @@
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 credential as specimen
10
+ from proximl.projects import (
11
+ Project,
12
+ )
13
+
14
+
15
+ def test_list_credentials(runner, mock_project_credentials):
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.credentials = AsyncMock()
22
+ project.credentials.list = AsyncMock(return_value=mock_project_credentials)
23
+ result = runner.invoke(specimen, ["list"])
24
+ print(result)
25
+ assert result.exit_code == 0
26
+ project.credentials.list.assert_called_once()
@@ -0,0 +1,100 @@
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.credentials 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_credentials(mock_proximl):
20
+ yield specimen.ProjectCredentials(mock_proximl, project_id="1")
21
+
22
+
23
+ @fixture
24
+ def project_credential(mock_proximl):
25
+ yield specimen.ProjectCredential(
26
+ mock_proximl, project_uuid="proj-id-1", type="aws", key_id="AIYHGFSDLK"
27
+ )
28
+
29
+
30
+ class ProjectCredentialsTests:
31
+ @mark.asyncio
32
+ async def test_project_credentials_list(self, project_credentials, 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_credentials.list()
39
+ mock_proximl._query.assert_called_once_with(
40
+ "/project/1/credentials", "GET", dict()
41
+ )
42
+ assert len(resp) == 2
43
+
44
+ @mark.asyncio
45
+ async def test_remove_project_credential(
46
+ self,
47
+ project_credentials,
48
+ mock_proximl,
49
+ ):
50
+ api_response = dict()
51
+ mock_proximl._query = AsyncMock(return_value=api_response)
52
+ await project_credentials.remove("aws")
53
+ mock_proximl._query.assert_called_once_with(
54
+ "/project/1/credential/aws", "DELETE", dict()
55
+ )
56
+
57
+ @mark.asyncio
58
+ async def test_put_project_credential(self, project_credentials, mock_proximl):
59
+ requested_config = dict(type="aws", key_id="AIUDHADA", secret="ASKHJSLKF")
60
+ expected_payload = dict(key_id="AIUDHADA", secret="ASKHJSLKF")
61
+ api_response = {
62
+ "project_uuid": "project-id-1",
63
+ "type": "aws",
64
+ "key_id": "AIUDHADA",
65
+ }
66
+
67
+ mock_proximl._query = AsyncMock(return_value=api_response)
68
+ response = await project_credentials.put(**requested_config)
69
+ mock_proximl._query.assert_called_once_with(
70
+ "/project/1/credential/aws", "PUT", None, expected_payload
71
+ )
72
+ assert response.type == "aws"
73
+
74
+
75
+ class ProjectCredentialTests:
76
+ def test_project_credential_properties(self, project_credential):
77
+ assert isinstance(project_credential.type, str)
78
+ assert isinstance(project_credential.key_id, str)
79
+ assert isinstance(project_credential.project_uuid, str)
80
+
81
+ def test_project_credential_str(self, project_credential):
82
+ string = str(project_credential)
83
+ regex = r"^{.*\"type\": \"" + project_credential.type + r"\".*}$"
84
+ assert isinstance(string, str)
85
+ assert re.match(regex, string)
86
+
87
+ def test_project_credential_repr(self, project_credential):
88
+ string = repr(project_credential)
89
+ regex = (
90
+ r"^ProjectCredential\( proximl , \*\*{.*'type': '"
91
+ + project_credential.type
92
+ + r"'.*}\)$"
93
+ )
94
+ assert isinstance(string, str)
95
+ assert re.match(regex, string)
96
+
97
+ def test_project_credential_bool(self, project_credential, mock_proximl):
98
+ empty_project_credential = specimen.ProjectCredential(mock_proximl)
99
+ assert bool(project_credential)
100
+ assert not bool(empty_project_credential)