trainml 0.5.13__py3-none-any.whl → 0.5.15__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.
- tests/integration/cloudbender/conftest.py +28 -0
- tests/integration/cloudbender/test_providers_integration.py +3 -8
- tests/integration/cloudbender/test_regions_integration.py +42 -0
- tests/integration/cloudbender/test_services_integration.py +87 -0
- tests/integration/conftest.py +1 -1
- tests/integration/projects/conftest.py +2 -1
- tests/integration/projects/test_projects_credentials_integration.py +1 -0
- tests/integration/projects/test_projects_data_connectors_integration.py +1 -0
- tests/integration/projects/test_projects_datastores_integration.py +1 -0
- tests/integration/projects/test_projects_integration.py +1 -0
- tests/integration/projects/test_projects_members_integration.py +50 -0
- tests/integration/projects/test_projects_secrets_integration.py +1 -0
- tests/integration/projects/test_projects_services_integration.py +1 -0
- tests/integration/test_checkpoints_integration.py +1 -0
- tests/integration/test_datasets_integration.py +1 -0
- tests/integration/test_jobs_integration.py +13 -8
- tests/integration/test_models_integration.py +1 -0
- tests/integration/test_volumes_integration.py +1 -0
- tests/unit/projects/test_project_members_unit.py +107 -0
- trainml/__init__.py +1 -1
- trainml/auth.py +1 -1
- trainml/cli/job/create.py +32 -2
- trainml/cloudbender/services.py +19 -1
- trainml/jobs.py +9 -0
- trainml/projects/members.py +98 -0
- trainml/projects/projects.py +2 -0
- trainml/projects/services.py +17 -0
- trainml/trainml.py +37 -20
- {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/METADATA +1 -1
- {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/RECORD +34 -33
- 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
- trainml/cli/project/key.py +0 -124
- trainml/projects/keys.py +0 -71
- {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/LICENSE +0 -0
- {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/WHEEL +0 -0
- {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/entry_points.txt +0 -0
- {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/top_level.txt +0 -0
trainml/cloudbender/services.py
CHANGED
|
@@ -67,7 +67,7 @@ class Services(object):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class Service:
|
|
70
|
-
def __init__(self, trainml,
|
|
70
|
+
def __init__(self, trainml, **kwargs):
|
|
71
71
|
self.trainml = trainml
|
|
72
72
|
self._service = kwargs
|
|
73
73
|
self._id = self._service.get("service_id")
|
|
@@ -177,3 +177,21 @@ class Service:
|
|
|
177
177
|
logging.debug(f"self: {self}, retry count {count}")
|
|
178
178
|
|
|
179
179
|
raise TrainMLException(f"Timeout waiting for {status}")
|
|
180
|
+
|
|
181
|
+
async def generate_certificate(self, **kwargs):
|
|
182
|
+
resp = await self.trainml._query(
|
|
183
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}/certificate",
|
|
184
|
+
"POST",
|
|
185
|
+
kwargs
|
|
186
|
+
)
|
|
187
|
+
self.__init__(self.trainml, **resp)
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
async def sign_client_certificate(self, csr, **kwargs):
|
|
191
|
+
certificate = await self.trainml._query(
|
|
192
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}/certificate/sign",
|
|
193
|
+
"POST",
|
|
194
|
+
kwargs,
|
|
195
|
+
dict(csr=csr)
|
|
196
|
+
)
|
|
197
|
+
return certificate
|
trainml/jobs.py
CHANGED
|
@@ -530,6 +530,15 @@ class Job:
|
|
|
530
530
|
"waiting for data/model download",
|
|
531
531
|
]
|
|
532
532
|
)
|
|
533
|
+
or (
|
|
534
|
+
status
|
|
535
|
+
== "running" ## this status could be too short for polling could miss it
|
|
536
|
+
and self.status
|
|
537
|
+
in [
|
|
538
|
+
"uploading",
|
|
539
|
+
"finished"
|
|
540
|
+
]
|
|
541
|
+
)
|
|
533
542
|
):
|
|
534
543
|
return self
|
|
535
544
|
elif self.status == "failed":
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
class ProjectMembers(object):
|
|
6
|
+
def __init__(self, trainml, project_id):
|
|
7
|
+
self.trainml = trainml
|
|
8
|
+
self.project_id = project_id
|
|
9
|
+
|
|
10
|
+
async def list(self, **kwargs):
|
|
11
|
+
resp = await self.trainml._query(
|
|
12
|
+
f"/project/{self.project_id}/access", "GET", kwargs
|
|
13
|
+
)
|
|
14
|
+
members = [ProjectMember(self.trainml, **member) for member in resp]
|
|
15
|
+
return members
|
|
16
|
+
|
|
17
|
+
async def add(self, email: str, job: Literal["all", "read"], dataset: Literal["all", "read"], model: Literal["all", "read"], checkpoint: Literal["all", "read"], volume: Literal["all", "read"], **kwargs):
|
|
18
|
+
data = dict(
|
|
19
|
+
email=email,
|
|
20
|
+
job=job,
|
|
21
|
+
dataset=dataset,
|
|
22
|
+
model=model,
|
|
23
|
+
checkpoint=checkpoint,
|
|
24
|
+
volume=volume,
|
|
25
|
+
)
|
|
26
|
+
payload = {k: v for k, v in data.items() if v is not None}
|
|
27
|
+
resp = await self.trainml._query(
|
|
28
|
+
f"/project/{self.project_id}/access", "POST",kwargs, payload)
|
|
29
|
+
member = ProjectMember(self.trainml, **resp)
|
|
30
|
+
logging.info(f"Added Project Member {email} to project {self.project_id}")
|
|
31
|
+
return member
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def remove(self, email, **kwargs):
|
|
35
|
+
await self.trainml._query(
|
|
36
|
+
f"/project/{self.project_id}/access", "DELETE", dict(**kwargs, email=email)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ProjectMember:
|
|
41
|
+
def __init__(self, trainml, **kwargs):
|
|
42
|
+
self.trainml = trainml
|
|
43
|
+
self._entity = kwargs
|
|
44
|
+
self._id = self._entity.get("email")
|
|
45
|
+
self._project_uuid = self._entity.get("project_uuid")
|
|
46
|
+
self._owner = self._entity.get("owner")
|
|
47
|
+
self._job = self._entity.get("job")
|
|
48
|
+
self._dataset = self._entity.get("dataset")
|
|
49
|
+
self._model = self._entity.get("model")
|
|
50
|
+
self._checkpoint = self._entity.get("checkpoint")
|
|
51
|
+
self._volume = self._entity.get("volume")
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def id(self) -> str:
|
|
55
|
+
return self._id
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def project_uuid(self) -> str:
|
|
59
|
+
return self._project_uuid
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def email(self) -> str:
|
|
63
|
+
return self._id
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def owner(self) -> bool:
|
|
67
|
+
return self._owner
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def job(self) -> str:
|
|
71
|
+
return self._job
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def dataset(self) -> str:
|
|
75
|
+
return self._dataset
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def model(self) -> str:
|
|
79
|
+
return self._model
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def checkpoint(self) -> str:
|
|
83
|
+
return self._checkpoint
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def volume(self) -> str:
|
|
87
|
+
return self._volume
|
|
88
|
+
|
|
89
|
+
def __str__(self):
|
|
90
|
+
return json.dumps({k: v for k, v in self._entity.items()})
|
|
91
|
+
|
|
92
|
+
def __repr__(self):
|
|
93
|
+
return f"ProjectMember( trainml , **{self._entity.__repr__()})"
|
|
94
|
+
|
|
95
|
+
def __bool__(self):
|
|
96
|
+
return bool(self._id)
|
|
97
|
+
|
|
98
|
+
|
trainml/projects/projects.py
CHANGED
|
@@ -5,6 +5,7 @@ from .data_connectors import ProjectDataConnectors
|
|
|
5
5
|
from .services import ProjectServices
|
|
6
6
|
from .credentials import ProjectCredentials
|
|
7
7
|
from .secrets import ProjectSecrets
|
|
8
|
+
from .members import ProjectMembers
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Projects(object):
|
|
@@ -56,6 +57,7 @@ class Project:
|
|
|
56
57
|
self.services = ProjectServices(self.trainml, self._id)
|
|
57
58
|
self.credentials = ProjectCredentials(self.trainml, self._id)
|
|
58
59
|
self.secrets = ProjectSecrets(self.trainml, self._id)
|
|
60
|
+
self.members = ProjectMembers(self.trainml, self._id)
|
|
59
61
|
|
|
60
62
|
@property
|
|
61
63
|
def id(self) -> str:
|
trainml/projects/services.py
CHANGED
|
@@ -77,3 +77,20 @@ class ProjectService:
|
|
|
77
77
|
await self.trainml._query(
|
|
78
78
|
f"/project/{self._project_uuid}/services/{self._id}/disable", "PATCH"
|
|
79
79
|
)
|
|
80
|
+
|
|
81
|
+
async def get_service_ca_certificate(self, **kwargs):
|
|
82
|
+
certificate = await self.trainml._query(
|
|
83
|
+
f"/project/{self._project_uuid}/services/{self._id}/certificate/ca",
|
|
84
|
+
"GET",
|
|
85
|
+
kwargs,
|
|
86
|
+
)
|
|
87
|
+
return certificate
|
|
88
|
+
|
|
89
|
+
async def sign_client_certificate(self, csr, **kwargs):
|
|
90
|
+
certificate = await self.trainml._query(
|
|
91
|
+
f"/project/{self._project_uuid}/services/{self._id}/certificate/sign",
|
|
92
|
+
"POST",
|
|
93
|
+
kwargs,
|
|
94
|
+
dict(csr=csr)
|
|
95
|
+
)
|
|
96
|
+
return certificate
|
trainml/trainml.py
CHANGED
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import aiohttp
|
|
5
5
|
import logging
|
|
6
6
|
import traceback
|
|
7
|
+
import random
|
|
7
8
|
from importlib.metadata import version
|
|
8
9
|
|
|
9
10
|
from trainml.auth import Auth
|
|
@@ -48,7 +49,7 @@ class TrainML(object):
|
|
|
48
49
|
kwargs.get("domain_suffix")
|
|
49
50
|
or os.environ.get("TRAINML_DOMAIN_SUFFIX")
|
|
50
51
|
or env.get("domain_suffix")
|
|
51
|
-
or "
|
|
52
|
+
or "proximl.ai"
|
|
52
53
|
)
|
|
53
54
|
self.auth = Auth(
|
|
54
55
|
config_dir=CONFIG_DIR,
|
|
@@ -91,7 +92,7 @@ class TrainML(object):
|
|
|
91
92
|
def project(self) -> str:
|
|
92
93
|
return self.active_project
|
|
93
94
|
|
|
94
|
-
async def _query(self, path, method, params=None, data=None, headers=None):
|
|
95
|
+
async def _query(self, path, method, params=None, data=None, headers=None,max_retries=3, backoff_factor=0.5):
|
|
95
96
|
try:
|
|
96
97
|
tokens = self.auth.get_tokens()
|
|
97
98
|
except TrainMLException as e:
|
|
@@ -142,24 +143,40 @@ class TrainML(object):
|
|
|
142
143
|
logging.debug(
|
|
143
144
|
f"Request - Url: {url}, Method: {method}, Params: {params}, Body: {data}, Headers: {headers}"
|
|
144
145
|
)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
146
|
+
for attempt in range(max_retries):
|
|
147
|
+
try:
|
|
148
|
+
async with aiohttp.ClientSession() as session:
|
|
149
|
+
async with session.request(
|
|
150
|
+
method,
|
|
151
|
+
url,
|
|
152
|
+
data=json.dumps(data),
|
|
153
|
+
headers=headers,
|
|
154
|
+
params=params,
|
|
155
|
+
) as resp:
|
|
156
|
+
if (resp.status // 100) in [4, 5]:
|
|
157
|
+
if resp.status == 502 and attempt < max_retries - 1:
|
|
158
|
+
wait_time = (2 ** attempt) * backoff_factor * (random.random() + 0.5)
|
|
159
|
+
await asyncio.sleep(wait_time)
|
|
160
|
+
continue
|
|
161
|
+
else:
|
|
162
|
+
what = await resp.read()
|
|
163
|
+
content_type = resp.headers.get("content-type", "")
|
|
164
|
+
resp.close()
|
|
165
|
+
if content_type == "application/json":
|
|
166
|
+
raise ApiError(resp.status, json.loads(what.decode("utf8")))
|
|
167
|
+
else:
|
|
168
|
+
raise ApiError(resp.status, {"message": what.decode("utf8")})
|
|
169
|
+
results = await resp.json()
|
|
170
|
+
return results
|
|
171
|
+
except aiohttp.ClientResponseError as e:
|
|
172
|
+
if e.status == 502 and attempt < max_retries - 1:
|
|
173
|
+
wait_time = (2 ** attempt) * backoff_factor * (random.random() + 0.5)
|
|
174
|
+
await asyncio.sleep(wait_time)
|
|
175
|
+
continue
|
|
176
|
+
else:
|
|
177
|
+
raise ApiError(e.status, f"Error {e.message}")
|
|
178
|
+
|
|
179
|
+
raise TrainMLException("Unexpected API failure")
|
|
163
180
|
|
|
164
181
|
async def _ws_subscribe(self, entity, project_uuid, id, msg_handler):
|
|
165
182
|
headers = {
|
|
@@ -3,25 +3,28 @@ examples/create_dataset_and_training_job.py,sha256=Ht6soyBZxt8qA9Fp6zAW2syGEdasv
|
|
|
3
3
|
examples/local_storage.py,sha256=w8iAeqr5CLOCOkNrqGzEDtybjDGGY7SQUqeE0ibMUrM,1745
|
|
4
4
|
examples/training_inference_pipeline.py,sha256=SNr4RFT9y69F9G9tMD8ONUbJmXRFrq1yxynq-FbfEf8,2334
|
|
5
5
|
tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
tests/integration/conftest.py,sha256=
|
|
7
|
-
tests/integration/test_checkpoints_integration.py,sha256=
|
|
8
|
-
tests/integration/test_datasets_integration.py,sha256=
|
|
6
|
+
tests/integration/conftest.py,sha256=otgb7lZu62_5zAMAsPpr0U2j0h5lApPlDRCiu7ZfDr8,1135
|
|
7
|
+
tests/integration/test_checkpoints_integration.py,sha256=RbOHZK-fjj2xvYo1XDa6H0war43Qe_1PbiNFndVY87o,3239
|
|
8
|
+
tests/integration/test_datasets_integration.py,sha256=sV88UAMM0IyVVum3zvxfYFbqmR8W4skNWE7jQxbc9ms,3529
|
|
9
9
|
tests/integration/test_environments_integration.py,sha256=0IckhJvQhd8j4Ouiu0hMq2b7iA1dbZpZYmknyfWjsFM,1403
|
|
10
10
|
tests/integration/test_gpu_types_integration.py,sha256=V2OncokZWWVq_l5FSmKEDM4EsWrmpB-zKiVPt-we0aY,1256
|
|
11
|
-
tests/integration/test_jobs_integration.py,sha256=
|
|
12
|
-
tests/integration/test_models_integration.py,sha256=
|
|
13
|
-
tests/integration/test_volumes_integration.py,sha256=
|
|
11
|
+
tests/integration/test_jobs_integration.py,sha256=_c33hYpub3VoWiFY-dAzTVyB66U3n5yvMxoRR-pskHg,25263
|
|
12
|
+
tests/integration/test_models_integration.py,sha256=Kwh8MQ0hOaecclAPq07LBEtAxoZB3rx-yamq0WGtWEA,2906
|
|
13
|
+
tests/integration/test_volumes_integration.py,sha256=W5DeuuKCBx3C89LRWc0jhAZjJEREuPiawTLUnqpPrjg,3299
|
|
14
14
|
tests/integration/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
tests/integration/cloudbender/
|
|
15
|
+
tests/integration/cloudbender/conftest.py,sha256=jBYUHKJioI-WHM-d0Y4lq5pD4LiOPINj6K9kQkgaGWY,777
|
|
16
|
+
tests/integration/cloudbender/test_providers_integration.py,sha256=mQn2dr4oMpctCVCVs53G5P0Raa0U2uBxEYjZtFFSvTg,1327
|
|
17
|
+
tests/integration/cloudbender/test_regions_integration.py,sha256=Ex1SB8xrl3KQ48n-Cka_1Whj8_uFsmhvJuOSdfK3kPk,1406
|
|
18
|
+
tests/integration/cloudbender/test_services_integration.py,sha256=_cGxEtjTDwz4pne6s1gVyEwoRcYYKcJClNUP5Yalrz0,3367
|
|
16
19
|
tests/integration/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
tests/integration/projects/conftest.py,sha256=
|
|
18
|
-
tests/integration/projects/test_projects_credentials_integration.py,sha256=
|
|
19
|
-
tests/integration/projects/test_projects_data_connectors_integration.py,sha256=
|
|
20
|
-
tests/integration/projects/test_projects_datastores_integration.py,sha256=
|
|
21
|
-
tests/integration/projects/test_projects_integration.py,sha256=
|
|
22
|
-
tests/integration/projects/
|
|
23
|
-
tests/integration/projects/test_projects_secrets_integration.py,sha256=
|
|
24
|
-
tests/integration/projects/test_projects_services_integration.py,sha256=
|
|
20
|
+
tests/integration/projects/conftest.py,sha256=v4XqQEmNSmV_hNEjLEIOeGJxFoAl-km8UXO_5jO--6E,294
|
|
21
|
+
tests/integration/projects/test_projects_credentials_integration.py,sha256=DYkd57hxu6wGFAyfgQGQbIR6Ja0501UCsSnjWzxBEkk,1675
|
|
22
|
+
tests/integration/projects/test_projects_data_connectors_integration.py,sha256=0giPCNeMx-Mj6--i0LTEt3m_ZwElSQzhrbxmcpNOthc,1669
|
|
23
|
+
tests/integration/projects/test_projects_datastores_integration.py,sha256=Eln5xPy6qp2JWai97U61fAQa4lGpmYlSTjbuzJl2jYc,1508
|
|
24
|
+
tests/integration/projects/test_projects_integration.py,sha256=1tHXGeF05Bsr5osyMyf4VY8qf1a4xYLUaF7yRaoDoLE,1280
|
|
25
|
+
tests/integration/projects/test_projects_members_integration.py,sha256=bEc9AqC-YZ0hifvVaDmcVGbK3aYuTBNGBj0g4rjBEiQ,1882
|
|
26
|
+
tests/integration/projects/test_projects_secrets_integration.py,sha256=dnIT0KiHnXBVP-azfw8MCspTjX7q1ic03rz8XZNijvU,1520
|
|
27
|
+
tests/integration/projects/test_projects_services_integration.py,sha256=3DpOtVMIdG_Icp4s7JxMT70ZDKC1wMD8imkKjlQChb0,1569
|
|
25
28
|
tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
29
|
tests/unit/conftest.py,sha256=hLfECgT0w4pVZABrpK4UUUeaX1aDzQvj8-erWTGBjHA,35588
|
|
27
30
|
tests/unit/test_auth_unit.py,sha256=nfhlOCR7rUsn_MaD8QQtBc2v0k8pIxqbzGgRAZK1WGc,858
|
|
@@ -55,7 +58,6 @@ tests/unit/cli/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
55
58
|
tests/unit/cli/projects/test_cli_project_credential_unit.py,sha256=gnmEpNbpEG5S9cYQUjSGty2e8mqeC_cCKmIUAJCgRXg,943
|
|
56
59
|
tests/unit/cli/projects/test_cli_project_data_connector_unit.py,sha256=_NgnxV8zTIycpHKe7dyL6EXq774puFHQjvgCKsiEMO0,993
|
|
57
60
|
tests/unit/cli/projects/test_cli_project_datastore_unit.py,sha256=E_kLoIZ5U92LQaPcYAKDyUqpjM1Xe6PVPvofU1Yn6Yk,936
|
|
58
|
-
tests/unit/cli/projects/test_cli_project_key_unit.py,sha256=Ma5HjsZt2RaHKgmMdGY59EIc2WdAgbWKWn2LvlYeDqQ,894
|
|
59
61
|
tests/unit/cli/projects/test_cli_project_secret_unit.py,sha256=QBkq1gVqkjGUni2xCihL994bvseNcA-dyUWaJVU9GjI,915
|
|
60
62
|
tests/unit/cli/projects/test_cli_project_service_unit.py,sha256=TRNOwK3RUCtktYdCGzfhQE3fE_mn8LweCN5E4gRn1h4,922
|
|
61
63
|
tests/unit/cli/projects/test_cli_project_unit.py,sha256=0IynsMwFsXBZn-u0gX2iir7AjPsNraHe68PuAe-Lkaw,627
|
|
@@ -72,22 +74,22 @@ tests/unit/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
72
74
|
tests/unit/projects/test_project_credentials_unit.py,sha256=ObTaJ7_g0MP5N-0qHWdeYips0in1j_4UkOhXXI2FFwo,3480
|
|
73
75
|
tests/unit/projects/test_project_data_connectors_unit.py,sha256=uQPFx6rwYMWZz1IJkns_LdM5gccyHSIaKb3k8zmfSkY,3385
|
|
74
76
|
tests/unit/projects/test_project_datastores_unit.py,sha256=kcoaSs54gGqJ9gwPMpP6KHAPRvDJtdJpm49s4aYYt3E,3129
|
|
75
|
-
tests/unit/projects/
|
|
77
|
+
tests/unit/projects/test_project_members_unit.py,sha256=C5N6jzT7zM3eL3_bXEddddBHPFJ3617T0lYluBjMy78,3466
|
|
76
78
|
tests/unit/projects/test_project_secrets_unit.py,sha256=StVlY-ZR3CKxXAt9NL8hTkXUEdgGESH0m6foEPXKE-4,3276
|
|
77
79
|
tests/unit/projects/test_project_services_unit.py,sha256=ZK5nz78RAmmaCrAwYBd34t87dh6etU-7snLB_xukZHM,3331
|
|
78
80
|
tests/unit/projects/test_projects_unit.py,sha256=8rJ40bJ7YszEsoL6D02Muey-sgRjqFmMoR-R3IiP_Ig,3810
|
|
79
|
-
trainml/__init__.py,sha256=
|
|
81
|
+
trainml/__init__.py,sha256=yxX-2bQsXFk3KgMV8b7day_Wz6qy6Ag4kOHvcMCv15k,433
|
|
80
82
|
trainml/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
|
|
81
|
-
trainml/auth.py,sha256=
|
|
83
|
+
trainml/auth.py,sha256=clbx5S5prJ3u62aEESdBXIHF_HmreQ-L1ShxcyWQNDs,26565
|
|
82
84
|
trainml/checkpoints.py,sha256=Hg3_Si7ohzsv4_8JIjoj9pMRpHNZRWKC5R2gtgmp01s,8790
|
|
83
85
|
trainml/connections.py,sha256=h-S1NZbOkaXpIlpRStA6q-3eXc_OMlFWOLzF8R9SVG8,20029
|
|
84
86
|
trainml/datasets.py,sha256=dxTZbK-cnr8eoq48KAx2TZNK1ndcFveTVBtaNK2QyJ0,8600
|
|
85
87
|
trainml/environments.py,sha256=OH4o08zXZ7IJ2CiA1rPnys2Fl45r8qvQHfM2mCBRAIc,1507
|
|
86
88
|
trainml/exceptions.py,sha256=Qdof2fKRvbMiwarX1VSw1XJXXJjY71H4U3v05nE7-7g,5468
|
|
87
89
|
trainml/gpu_types.py,sha256=mm-dwfYc02192bmYPIJmzesndyBcoOdkKYBaYZXOUwU,1901
|
|
88
|
-
trainml/jobs.py,sha256=
|
|
90
|
+
trainml/jobs.py,sha256=6iaZUz69v6NCSuM0jiSd6j8gElB3JOTuOZuVt2VtAHQ,17947
|
|
89
91
|
trainml/models.py,sha256=SpVNt9oZMu70m_3VjO9j_QYexw_z-yRqMhIuJXPs4Vg,8284
|
|
90
|
-
trainml/trainml.py,sha256=
|
|
92
|
+
trainml/trainml.py,sha256=c-Qxiarv170RaQn0O1wJ6h9RP8e17M15KFg3Rh2ckZc,11902
|
|
91
93
|
trainml/volumes.py,sha256=7T0ZN_Xq3qk4yzw7YYxC0rxH3KqlqADd6HOZCkAfCR4,8443
|
|
92
94
|
trainml/cli/__init__.py,sha256=Gvj6oGSEtgpb40ACtiVeMD93GM-uy15MG6VlX6rwdwA,4346
|
|
93
95
|
trainml/cli/checkpoint.py,sha256=8Rh4bmFwJ4DKlIjHK-FLTeRynABqKCgIUGRtbQhAsX4,7170
|
|
@@ -106,12 +108,11 @@ trainml/cli/cloudbender/provider.py,sha256=oFjZWKfFQjNY7OtDu7nUdfv-RTmQc_Huuug96
|
|
|
106
108
|
trainml/cli/cloudbender/region.py,sha256=X6-FYOb-pGpOEazn-NbsYSwa9ergB7FGATFkTe4a8Pk,2892
|
|
107
109
|
trainml/cli/cloudbender/service.py,sha256=Wh6ycEuECiKL7qpFhc4IyO1rR5lvLtIHk3S475_R6pk,3147
|
|
108
110
|
trainml/cli/job/__init__.py,sha256=ljY-ELeXhXQ7txASbJEKGBom7OXfNyy7sWILz3nxRAE,6545
|
|
109
|
-
trainml/cli/job/create.py,sha256=
|
|
111
|
+
trainml/cli/job/create.py,sha256=MDf53CFua58uJoW4n9FZT1XERJOvAYunEc8tcuO6WQ8,35098
|
|
110
112
|
trainml/cli/project/__init__.py,sha256=HDcJUbKMHhz4Thrvpst5hnywFqzsv0XWmvfKNRi8zuo,1918
|
|
111
113
|
trainml/cli/project/credential.py,sha256=gByXKiYf5sJeNRtuXWcercWv8P2IzO5TjT8Ypp4mCR8,3443
|
|
112
114
|
trainml/cli/project/data_connector.py,sha256=KLDhpNJfwcJkcmyuJRgcVH70Jf73619O9ddYP8vhMvk,1411
|
|
113
115
|
trainml/cli/project/datastore.py,sha256=IwF0LqsSFn7DrKPRdxQs6kturk9MCI52A1aoWeb7ClA,1336
|
|
114
|
-
trainml/cli/project/key.py,sha256=kQlCs_N-5c27hOyGkmT22_J47x8U6CZaSardsaPYGbw,3203
|
|
115
116
|
trainml/cli/project/secret.py,sha256=tHt7bYDt0MNkLTTePTIJFUh9Mn9ogebPcaSCiNRJFNQ,1927
|
|
116
117
|
trainml/cli/project/service.py,sha256=FzcyGI9MN-UvnjdKW7GmXLTXLUIXtuTFAISR_FmCmJo,1310
|
|
117
118
|
trainml/cloudbender/__init__.py,sha256=iE29obtC0_9f0IhRvHQcG5aY58fVhVYipTakpjAhdss,64
|
|
@@ -123,18 +124,18 @@ trainml/cloudbender/devices.py,sha256=QORNmKdLJoqGZmeWXRnivC1JmNBIw-ebvf4bsoem3r
|
|
|
123
124
|
trainml/cloudbender/nodes.py,sha256=U2sTIL2fuBVsNFPsJrvB2JbBcuLULF1-AwJ4dp5ChmM,5924
|
|
124
125
|
trainml/cloudbender/providers.py,sha256=22ymUl1KLC8UlyoWMsIrOKhUDOwnhklhHQ3EZ_V6eWA,3738
|
|
125
126
|
trainml/cloudbender/regions.py,sha256=nfSY9fIWp_AaRE_1Y0qwXX6WVSyPKxpji-zUfM3BNUo,5157
|
|
126
|
-
trainml/cloudbender/services.py,sha256=
|
|
127
|
+
trainml/cloudbender/services.py,sha256=bJtp9ljq8g10r-04phV_XXS8mS8YR3KMbJ4XtHa2SOI,5773
|
|
127
128
|
trainml/projects/__init__.py,sha256=6NKCcHtQMeGB1IyU-djANphfnDX6MEkrXUM5Fyq9fWg,75
|
|
128
129
|
trainml/projects/credentials.py,sha256=6WqHy_-SZZwqE4rULLF8gSyeRVmfsUxqZBuCjBXyxKw,2255
|
|
129
130
|
trainml/projects/data_connectors.py,sha256=WZATdUq4vE3x47Ny5HDwPD7lIUx0syjaSE9BmFWNuEg,2216
|
|
130
131
|
trainml/projects/datastores.py,sha256=qocqD5mGfm2V_wh36CEh3oldnIeJg57ppc6CM129Dkk,2095
|
|
131
|
-
trainml/projects/
|
|
132
|
-
trainml/projects/projects.py,sha256=
|
|
132
|
+
trainml/projects/members.py,sha256=7fptZ2leIQEUiRtvd3z15RTOJyPafzc5wwQLNxICElQ,2826
|
|
133
|
+
trainml/projects/projects.py,sha256=5_6HUg9ZYq8v69QPgC4Yfd_DM0OlZd8tnxh2ahjOgm0,2890
|
|
133
134
|
trainml/projects/secrets.py,sha256=TIvBd3rAvd4lF3pm5qR98UslHjldzlnzn_n9yvpmLgg,2160
|
|
134
|
-
trainml/projects/services.py,sha256=
|
|
135
|
-
trainml-0.5.
|
|
136
|
-
trainml-0.5.
|
|
137
|
-
trainml-0.5.
|
|
138
|
-
trainml-0.5.
|
|
139
|
-
trainml-0.5.
|
|
140
|
-
trainml-0.5.
|
|
135
|
+
trainml/projects/services.py,sha256=rI-uFmojqOTNLbqBeX6gaSwMkI6LKzRuJthQCH0A2h4,2771
|
|
136
|
+
trainml-0.5.15.dist-info/LICENSE,sha256=s0lpBxhSSUEpMavwde-Vb6K_K7xDCTTvSpNznVqVGR0,1069
|
|
137
|
+
trainml-0.5.15.dist-info/METADATA,sha256=kFbUgFw2pcOjJgySdhnKmVrkVpkFMI6mtVrN7t26yXA,7346
|
|
138
|
+
trainml-0.5.15.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
139
|
+
trainml-0.5.15.dist-info/entry_points.txt,sha256=OzBDm2wXby1bSGF02jTVxzRFZLejnbFiLHXhKdW3Bds,63
|
|
140
|
+
trainml-0.5.15.dist-info/top_level.txt,sha256=Y1kLFRWKUW7RG8BX7cvejHF_yW8wBOaRYF1JQHENY4w,23
|
|
141
|
+
trainml-0.5.15.dist-info/RECORD,,
|
|
@@ -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\( trainml , \*\*{.*'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 trainml.cli.project import key as specimen
|
|
10
|
-
from trainml.projects import (
|
|
11
|
-
Project,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_list_keys(runner, mock_project_keys):
|
|
16
|
-
with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
|
|
17
|
-
project = create_autospec(Project)
|
|
18
|
-
mock_trainml.projects = AsyncMock()
|
|
19
|
-
mock_trainml.projects.get = AsyncMock(return_value=project)
|
|
20
|
-
mock_trainml.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 trainml.projects.keys as specimen
|
|
9
|
-
from trainml.exceptions import (
|
|
10
|
-
ApiError,
|
|
11
|
-
SpecificationError,
|
|
12
|
-
TrainMLException,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
pytestmark = [mark.sdk, mark.unit, mark.projects]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@fixture
|
|
19
|
-
def project_keys(mock_trainml):
|
|
20
|
-
yield specimen.ProjectKeys(mock_trainml, project_id="1")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@fixture
|
|
24
|
-
def project_key(mock_trainml):
|
|
25
|
-
yield specimen.ProjectKey(
|
|
26
|
-
mock_trainml, 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_trainml):
|
|
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_trainml._query = AsyncMock(return_value=api_response)
|
|
38
|
-
resp = await project_keys.list()
|
|
39
|
-
mock_trainml._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_trainml,
|
|
47
|
-
):
|
|
48
|
-
api_response = dict()
|
|
49
|
-
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
50
|
-
await project_keys.remove("aws")
|
|
51
|
-
mock_trainml._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_trainml):
|
|
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_trainml._query = AsyncMock(return_value=api_response)
|
|
66
|
-
response = await project_keys.put(**requested_config)
|
|
67
|
-
mock_trainml._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\( trainml , \*\*{.*'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_trainml):
|
|
94
|
-
empty_project_key = specimen.ProjectKey(mock_trainml)
|
|
95
|
-
assert bool(project_key)
|
|
96
|
-
assert not bool(empty_project_key)
|