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.
Files changed (39) hide show
  1. tests/integration/cloudbender/conftest.py +28 -0
  2. tests/integration/cloudbender/test_providers_integration.py +3 -8
  3. tests/integration/cloudbender/test_regions_integration.py +42 -0
  4. tests/integration/cloudbender/test_services_integration.py +87 -0
  5. tests/integration/conftest.py +1 -1
  6. tests/integration/projects/conftest.py +2 -1
  7. tests/integration/projects/test_projects_credentials_integration.py +1 -0
  8. tests/integration/projects/test_projects_data_connectors_integration.py +1 -0
  9. tests/integration/projects/test_projects_datastores_integration.py +1 -0
  10. tests/integration/projects/test_projects_integration.py +1 -0
  11. tests/integration/projects/test_projects_members_integration.py +50 -0
  12. tests/integration/projects/test_projects_secrets_integration.py +1 -0
  13. tests/integration/projects/test_projects_services_integration.py +1 -0
  14. tests/integration/test_checkpoints_integration.py +1 -0
  15. tests/integration/test_datasets_integration.py +1 -0
  16. tests/integration/test_jobs_integration.py +13 -8
  17. tests/integration/test_models_integration.py +1 -0
  18. tests/integration/test_volumes_integration.py +1 -0
  19. tests/unit/projects/test_project_members_unit.py +107 -0
  20. trainml/__init__.py +1 -1
  21. trainml/auth.py +1 -1
  22. trainml/cli/job/create.py +32 -2
  23. trainml/cloudbender/services.py +19 -1
  24. trainml/jobs.py +9 -0
  25. trainml/projects/members.py +98 -0
  26. trainml/projects/projects.py +2 -0
  27. trainml/projects/services.py +17 -0
  28. trainml/trainml.py +37 -20
  29. {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/METADATA +1 -1
  30. {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/RECORD +34 -33
  31. tests/integration/projects/test_projects_keys_integration.py +0 -43
  32. tests/unit/cli/projects/test_cli_project_key_unit.py +0 -26
  33. tests/unit/projects/test_project_keys_unit.py +0 -96
  34. trainml/cli/project/key.py +0 -124
  35. trainml/projects/keys.py +0 -71
  36. {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/LICENSE +0 -0
  37. {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/WHEEL +0 -0
  38. {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/entry_points.txt +0 -0
  39. {trainml-0.5.13.dist-info → trainml-0.5.15.dist-info}/top_level.txt +0 -0
@@ -67,7 +67,7 @@ class Services(object):
67
67
 
68
68
 
69
69
  class Service:
70
- def __init__(self, trainml, **kwargs):
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
+
@@ -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:
@@ -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 "trainml.ai"
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
- async with aiohttp.ClientSession() as session:
146
- async with session.request(
147
- method,
148
- url,
149
- data=json.dumps(data),
150
- headers=headers,
151
- params=params,
152
- ) as resp:
153
- if (resp.status // 100) in [4, 5]:
154
- what = await resp.read()
155
- content_type = resp.headers.get("content-type", "")
156
- resp.close()
157
- if content_type == "application/json":
158
- raise ApiError(resp.status, json.loads(what.decode("utf8")))
159
- else:
160
- raise ApiError(resp.status, {"message": what.decode("utf8")})
161
- results = await resp.json()
162
- return results
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 = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainml
3
- Version: 0.5.13
3
+ Version: 0.5.15
4
4
  Summary: trainML client SDK and command line utilities
5
5
  Home-page: https://github.com/trainML/trainml-cli
6
6
  Author: trainML
@@ -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=VWWTfofsFcBOdSCXQxNYbMcDEEaErDi2wFFMn--LE4Y,1134
7
- tests/integration/test_checkpoints_integration.py,sha256=Giy5Z6WyselfXzskDGR7Z0ks2KK2U7WGQBDPfyqQ6dk,3206
8
- tests/integration/test_datasets_integration.py,sha256=zdHOevduuMUWvVxaHBslpmH8AdvPdqEJ95MdqCC5_rw,3499
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=vj-lINOzprUBj61F4i4CBe64MmT0HCDDsPneYsulv3I,25110
12
- tests/integration/test_models_integration.py,sha256=u6KVnX-F00TqwiU-gEoZJP1oKfAblqnyRptOc9vNGJ4,2878
13
- tests/integration/test_volumes_integration.py,sha256=gOmZpwwFxqeOAVmfKWSTmuyshx8nb2zu_0xv1RUEepM,3270
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/test_providers_integration.py,sha256=oV8ydFsosDZ_Z1Dkg2IN-ZhWuIl5e_HkHAORMsOsAJc,1473
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=sNN1Svq2IO63Yx3DC7-5ZnksaEifGpDZU-3ti9sZhIU,249
18
- tests/integration/projects/test_projects_credentials_integration.py,sha256=3PnpxNEsedXmyyhqjSb8T4JzupB1Kmwu--3stNTBlN8,1636
19
- tests/integration/projects/test_projects_data_connectors_integration.py,sha256=R90v7HVN4i9k-AOEl17AvnbWIwnD9Fl1OK9IGOV847o,1630
20
- tests/integration/projects/test_projects_datastores_integration.py,sha256=S9aSvryVjrcoEDezr33x4T5aPjEhUwL9O1-1eyzhQ-s,1469
21
- tests/integration/projects/test_projects_integration.py,sha256=HP4jPAyTqGt7pwk42bxQsqGp0olgai6sI-S1XXcXoJs,1241
22
- tests/integration/projects/test_projects_keys_integration.py,sha256=O-a9OwBxIM2l0vi__GMlWP8ncNswxGBJvEhgo8PoalQ,1423
23
- tests/integration/projects/test_projects_secrets_integration.py,sha256=bagLLfSUP805gOOPbn_xCQyifflNzIs8DGUBeUwOhO0,1481
24
- tests/integration/projects/test_projects_services_integration.py,sha256=lBnrqFpjsL3zfaJJmFh2rCWfNmmX-TjyOPCR3KobDAc,1530
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/test_project_keys_unit.py,sha256=2-w_VwmWxWHoLWBCoBAfg5WozyeYJHV361ZndlA0xk4,3161
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=KVgJtJhufpHLxSIxc6VgC9rtBlScxurtZsM8FibfA3o,433
81
+ trainml/__init__.py,sha256=yxX-2bQsXFk3KgMV8b7day_Wz6qy6Ag4kOHvcMCv15k,433
80
82
  trainml/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
81
- trainml/auth.py,sha256=gruZv27nhttrCbhcVQTH9kZkF2uMm1E06SwA_2pQAHQ,26565
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=5wHiEmHfri1btgIn6zbFp9kIO6EU5FCOVkNMP0-WuYM,17632
90
+ trainml/jobs.py,sha256=6iaZUz69v6NCSuM0jiSd6j8gElB3JOTuOZuVt2VtAHQ,17947
89
91
  trainml/models.py,sha256=SpVNt9oZMu70m_3VjO9j_QYexw_z-yRqMhIuJXPs4Vg,8284
90
- trainml/trainml.py,sha256=EBnqQ3Q291xrPKYuN6xKm5yt0mJQOJ3b7GAlR-fl8NI,10864
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=1YGHgl0bN0LC7EtMStukHT6RvnBheaaOnkqoKsAxuco,34380
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=KC3VcyljvnazUUG-Tzwm6Ab6d0--yuccXjOaMgYB5uA,5126
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/keys.py,sha256=qcQAdysTbfVW19tzzxZDvBBWjU9DG0dkViLmG_S0SyM,2157
132
- trainml/projects/projects.py,sha256=0ZghShqjbs5swfn3BsLrMW2NzrdBXpf_M-nyzwYPjpM,2792
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=W2IHgRUT9BemWThsY7uAot2gbmyPaH41WAii-NQ_cT8,2206
135
- trainml-0.5.13.dist-info/LICENSE,sha256=s0lpBxhSSUEpMavwde-Vb6K_K7xDCTTvSpNznVqVGR0,1069
136
- trainml-0.5.13.dist-info/METADATA,sha256=8BjIAywdzYBrUOb_k6PkSdc20fFaVW7pUEKiOPqHzf4,7346
137
- trainml-0.5.13.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
138
- trainml-0.5.13.dist-info/entry_points.txt,sha256=OzBDm2wXby1bSGF02jTVxzRFZLejnbFiLHXhKdW3Bds,63
139
- trainml-0.5.13.dist-info/top_level.txt,sha256=Y1kLFRWKUW7RG8BX7cvejHF_yW8wBOaRYF1JQHENY4w,23
140
- trainml-0.5.13.dist-info/RECORD,,
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)