proximl 0.5.13.post1__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.
- proximl/__init__.py +1 -1
- proximl/cli/job/create.py +32 -2
- proximl/cloudbender/services.py +19 -1
- proximl/jobs.py +9 -0
- proximl/projects/members.py +98 -0
- proximl/projects/projects.py +2 -0
- proximl/projects/services.py +17 -0
- proximl/proximl.py +36 -19
- {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/METADATA +1 -1
- {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/RECORD +33 -27
- 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
- {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/LICENSE +0 -0
- {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/WHEEL +0 -0
- {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/entry_points.txt +0 -0
- {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/top_level.txt +0 -0
proximl/__init__.py
CHANGED
proximl/cli/job/create.py
CHANGED
|
@@ -180,6 +180,12 @@ def create(config):
|
|
|
180
180
|
help="Third Party Credentials to add to the job environment",
|
|
181
181
|
multiple=True,
|
|
182
182
|
)
|
|
183
|
+
@click.option(
|
|
184
|
+
"--secret",
|
|
185
|
+
type=click.STRING,
|
|
186
|
+
help="Project secrets to add to the job environment",
|
|
187
|
+
multiple=True,
|
|
188
|
+
)
|
|
183
189
|
@click.option(
|
|
184
190
|
"--git-uri",
|
|
185
191
|
type=click.STRING,
|
|
@@ -222,6 +228,7 @@ def notebook(
|
|
|
222
228
|
custom_image,
|
|
223
229
|
env,
|
|
224
230
|
credential,
|
|
231
|
+
secret,
|
|
225
232
|
apt_packages,
|
|
226
233
|
pip_packages,
|
|
227
234
|
conda_packages,
|
|
@@ -254,7 +261,7 @@ def notebook(
|
|
|
254
261
|
data=dict(datasets=datasets),
|
|
255
262
|
model=dict(checkpoints=checkpoints),
|
|
256
263
|
environment=dict(
|
|
257
|
-
credentials=[k for k in credential],
|
|
264
|
+
credentials=[k for k in credential], secrets=[s for s in secret]
|
|
258
265
|
),
|
|
259
266
|
)
|
|
260
267
|
|
|
@@ -508,6 +515,12 @@ def notebook(
|
|
|
508
515
|
help="Third Party Credentials to add to the job environment",
|
|
509
516
|
multiple=True,
|
|
510
517
|
)
|
|
518
|
+
@click.option(
|
|
519
|
+
"--secret",
|
|
520
|
+
type=click.STRING,
|
|
521
|
+
help="Project secrets to add to the job environment",
|
|
522
|
+
multiple=True,
|
|
523
|
+
)
|
|
511
524
|
@click.option(
|
|
512
525
|
"--apt-packages",
|
|
513
526
|
type=click.STRING,
|
|
@@ -564,6 +577,7 @@ def training(
|
|
|
564
577
|
custom_image,
|
|
565
578
|
env,
|
|
566
579
|
credential,
|
|
580
|
+
secret,
|
|
567
581
|
apt_packages,
|
|
568
582
|
pip_packages,
|
|
569
583
|
conda_packages,
|
|
@@ -596,7 +610,7 @@ def training(
|
|
|
596
610
|
data=dict(datasets=datasets),
|
|
597
611
|
model=dict(checkpoints=checkpoints),
|
|
598
612
|
environment=dict(
|
|
599
|
-
credentials=[k for k in credential],
|
|
613
|
+
credentials=[k for k in credential], secrets=[s for s in secret]
|
|
600
614
|
),
|
|
601
615
|
)
|
|
602
616
|
|
|
@@ -852,6 +866,12 @@ def training(
|
|
|
852
866
|
help="Third Party Credentials to add to the job environment",
|
|
853
867
|
multiple=True,
|
|
854
868
|
)
|
|
869
|
+
@click.option(
|
|
870
|
+
"--secret",
|
|
871
|
+
type=click.STRING,
|
|
872
|
+
help="Project secrets to add to the job environment",
|
|
873
|
+
multiple=True,
|
|
874
|
+
)
|
|
855
875
|
@click.option(
|
|
856
876
|
"--apt-packages",
|
|
857
877
|
type=click.STRING,
|
|
@@ -908,6 +928,7 @@ def inference(
|
|
|
908
928
|
custom_image,
|
|
909
929
|
env,
|
|
910
930
|
credential,
|
|
931
|
+
secret,
|
|
911
932
|
apt_packages,
|
|
912
933
|
pip_packages,
|
|
913
934
|
conda_packages,
|
|
@@ -934,6 +955,7 @@ def inference(
|
|
|
934
955
|
model=dict(checkpoints=checkpoints),
|
|
935
956
|
environment=dict(
|
|
936
957
|
credentials=[k for k in credential],
|
|
958
|
+
secrets=[s for s in secret],
|
|
937
959
|
),
|
|
938
960
|
)
|
|
939
961
|
|
|
@@ -1171,6 +1193,12 @@ def from_json(config, attach, connect, file):
|
|
|
1171
1193
|
help="Third Party Credentials to add to the job environment",
|
|
1172
1194
|
multiple=True,
|
|
1173
1195
|
)
|
|
1196
|
+
@click.option(
|
|
1197
|
+
"--secret",
|
|
1198
|
+
type=click.STRING,
|
|
1199
|
+
help="Project secrets to add to the job environment",
|
|
1200
|
+
multiple=True,
|
|
1201
|
+
)
|
|
1174
1202
|
@click.option(
|
|
1175
1203
|
"--apt-packages",
|
|
1176
1204
|
type=click.STRING,
|
|
@@ -1231,6 +1259,7 @@ def endpoint(
|
|
|
1231
1259
|
custom_image,
|
|
1232
1260
|
env,
|
|
1233
1261
|
credential,
|
|
1262
|
+
secret,
|
|
1234
1263
|
apt_packages,
|
|
1235
1264
|
pip_packages,
|
|
1236
1265
|
conda_packages,
|
|
@@ -1258,6 +1287,7 @@ def endpoint(
|
|
|
1258
1287
|
model=dict(checkpoints=checkpoints),
|
|
1259
1288
|
environment=dict(
|
|
1260
1289
|
credentials=[k for k in credential],
|
|
1290
|
+
secrets=[s for s in secret],
|
|
1261
1291
|
),
|
|
1262
1292
|
)
|
|
1263
1293
|
|
proximl/cloudbender/services.py
CHANGED
|
@@ -67,7 +67,7 @@ class Services(object):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class Service:
|
|
70
|
-
def __init__(self, proximl,
|
|
70
|
+
def __init__(self, proximl, **kwargs):
|
|
71
71
|
self.proximl = proximl
|
|
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 ProxiMLException(f"Timeout waiting for {status}")
|
|
180
|
+
|
|
181
|
+
async def generate_certificate(self, **kwargs):
|
|
182
|
+
resp = await self.proximl._query(
|
|
183
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}/certificate",
|
|
184
|
+
"POST",
|
|
185
|
+
kwargs
|
|
186
|
+
)
|
|
187
|
+
self.__init__(self.proximl, **resp)
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
async def sign_client_certificate(self, csr, **kwargs):
|
|
191
|
+
certificate = await self.proximl._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
|
proximl/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, proximl, project_id):
|
|
7
|
+
self.proximl = proximl
|
|
8
|
+
self.project_id = project_id
|
|
9
|
+
|
|
10
|
+
async def list(self, **kwargs):
|
|
11
|
+
resp = await self.proximl._query(
|
|
12
|
+
f"/project/{self.project_id}/access", "GET", kwargs
|
|
13
|
+
)
|
|
14
|
+
members = [ProjectMember(self.proximl, **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.proximl._query(
|
|
28
|
+
f"/project/{self.project_id}/access", "POST",kwargs, payload)
|
|
29
|
+
member = ProjectMember(self.proximl, **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.proximl._query(
|
|
36
|
+
f"/project/{self.project_id}/access", "DELETE", dict(**kwargs, email=email)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ProjectMember:
|
|
41
|
+
def __init__(self, proximl, **kwargs):
|
|
42
|
+
self.proximl = proximl
|
|
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( proximl , **{self._entity.__repr__()})"
|
|
94
|
+
|
|
95
|
+
def __bool__(self):
|
|
96
|
+
return bool(self._id)
|
|
97
|
+
|
|
98
|
+
|
proximl/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.proximl, self._id)
|
|
57
58
|
self.credentials = ProjectCredentials(self.proximl, self._id)
|
|
58
59
|
self.secrets = ProjectSecrets(self.proximl, self._id)
|
|
60
|
+
self.members = ProjectMembers(self.proximl, self._id)
|
|
59
61
|
|
|
60
62
|
@property
|
|
61
63
|
def id(self) -> str:
|
proximl/projects/services.py
CHANGED
|
@@ -77,3 +77,20 @@ class ProjectService:
|
|
|
77
77
|
await self.proximl._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.proximl._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.proximl._query(
|
|
91
|
+
f"/project/{self._project_uuid}/services/{self._id}/certificate/sign",
|
|
92
|
+
"POST",
|
|
93
|
+
kwargs,
|
|
94
|
+
dict(csr=csr)
|
|
95
|
+
)
|
|
96
|
+
return certificate
|
proximl/proximl.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 proximl.auth import Auth
|
|
@@ -91,7 +92,7 @@ class ProxiML(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 ProxiMLException as e:
|
|
@@ -142,24 +143,40 @@ class ProxiML(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 ProxiMLException("Unexpected API failure")
|
|
163
180
|
|
|
164
181
|
async def _ws_subscribe(self, entity, project_uuid, id, msg_handler):
|
|
165
182
|
headers = {
|
|
@@ -2,7 +2,7 @@ examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
examples/create_dataset_and_training_job.py,sha256=Fqueoz2KD1MTxnU960iqHUdxvo68Xe64HT5XS55lI9w,1178
|
|
3
3
|
examples/local_storage.py,sha256=6K6LMO7ZPI7N2KdBcgqXSvdsqJfjISzN4yRO9YrJqbA,1745
|
|
4
4
|
examples/training_inference_pipeline.py,sha256=pxux0QUUtRXxKj2rX-6fPEKBIi43mhRd1zJ-Lf1ZJGI,2334
|
|
5
|
-
proximl/__init__.py,sha256=
|
|
5
|
+
proximl/__init__.py,sha256=cS84qd0OsBVm8H9pmmykeIzYpGPYj5wbaGNpQezWMdI,433
|
|
6
6
|
proximl/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
|
|
7
7
|
proximl/auth.py,sha256=LacGBDAVel5HcJx7JXp1wVn3s0gZc7nf--vDfSFOXYU,26565
|
|
8
8
|
proximl/checkpoints.py,sha256=Ezpiab9Wfcmd2h5Slm9U5lekZGXiPzvhDKxXXgla1yo,8790
|
|
@@ -11,9 +11,9 @@ proximl/datasets.py,sha256=dBDzf7DZ1rS5LmJPy5oV-JC0PXGTf-NZuBDG1KPyoA4,8600
|
|
|
11
11
|
proximl/environments.py,sha256=L_cRmau1wJxpGvnJAqgms-GiNdDhiuOntrlBqsdoE3A,1507
|
|
12
12
|
proximl/exceptions.py,sha256=q1YfsqbLw3y1DJHP1FcMM8xG5CEhQsTIvRfq4v5PyVw,5468
|
|
13
13
|
proximl/gpu_types.py,sha256=V-EZzE-hDLi5eVQ2_9yGLTm8-Qk1AnnzctfSVC44yLY,1901
|
|
14
|
-
proximl/jobs.py,sha256=
|
|
14
|
+
proximl/jobs.py,sha256=HiGs2gRKPFZReHLg9DLPsDWEecxZpafWv6XFBSGXYj0,17947
|
|
15
15
|
proximl/models.py,sha256=gJQ1C3HGEHScW41PAQS3Q8IKSQSauIUIJxOZvkDzizQ,8284
|
|
16
|
-
proximl/proximl.py,sha256=
|
|
16
|
+
proximl/proximl.py,sha256=7UyqYbGO9edFJec54xqk_wwahKldXQlY5112Izg3saA,11902
|
|
17
17
|
proximl/volumes.py,sha256=qRIgzvJ4NTHuLi7QN_6v61Urns8l4Y9KjWladCIQ2gk,8443
|
|
18
18
|
proximl/cli/__init__.py,sha256=R_8ExAKQp67N2xtwGM00gtK3zSoWPGruHAP_XFLddSI,4346
|
|
19
19
|
proximl/cli/checkpoint.py,sha256=Iv1i1EAt2LJey2wy2ioQ6-ZysqwRG4kFj0lnE6INZCM,7170
|
|
@@ -32,7 +32,7 @@ proximl/cli/cloudbender/provider.py,sha256=qhWbDK1tWi00wQWEYqGw7yGoZx0nEjV40GLHR
|
|
|
32
32
|
proximl/cli/cloudbender/region.py,sha256=WnSkY4dXKRJ-FNaoxMfmoh6iuUx5dXCNJmEFT34Xtao,2892
|
|
33
33
|
proximl/cli/cloudbender/service.py,sha256=6NuwlFULJLtOmMy9OFA8GkW4MLvNemAcd_KitQyDXV8,3147
|
|
34
34
|
proximl/cli/job/__init__.py,sha256=s8mU2PvCWDcv4gGT3EmjHn8MIZlXBAoayoZKmnKpXnY,6545
|
|
35
|
-
proximl/cli/job/create.py,sha256=
|
|
35
|
+
proximl/cli/job/create.py,sha256=plRiX9lRqt6lrjWvTIVbLC-EoDgSL0-oYqwIfuWb_ek,35098
|
|
36
36
|
proximl/cli/project/__init__.py,sha256=ZSiXhsq7O1JwGecsBKBOD1W0VuiwErs-FFsfrXMOiFs,1918
|
|
37
37
|
proximl/cli/project/credential.py,sha256=5vPI6VBIo_YyvAzfUa9ARmz-LUnkdIVW0zbko9t0Q38,3443
|
|
38
38
|
proximl/cli/project/data_connector.py,sha256=CKF4snfrzDmeDXb4au8nV2W9jTweRMDZ89U2GAlBedQ,1411
|
|
@@ -48,33 +48,38 @@ proximl/cloudbender/devices.py,sha256=vHooaOw2k2Tf99FJHnVZTgggqCTYJg7rq46aUPW0k8
|
|
|
48
48
|
proximl/cloudbender/nodes.py,sha256=T7aCh2O6sF2tFX4D5a7wDW8hJmEgHoCV9So1kg70tdg,5924
|
|
49
49
|
proximl/cloudbender/providers.py,sha256=KLO4Pc8yeW8TpSDICgTw3NxN44_0s4HptVsR2hHc28w,3738
|
|
50
50
|
proximl/cloudbender/regions.py,sha256=6doBdfMXIQI-uexzvwmNg2ATDzruatw-ixviZSMjonU,5157
|
|
51
|
-
proximl/cloudbender/services.py,sha256=
|
|
51
|
+
proximl/cloudbender/services.py,sha256=FOhtRozqNroO9Ru6gtsKgjumGREiLCooXMXchDh5cNM,5773
|
|
52
52
|
proximl/projects/__init__.py,sha256=6NKCcHtQMeGB1IyU-djANphfnDX6MEkrXUM5Fyq9fWg,75
|
|
53
53
|
proximl/projects/credentials.py,sha256=hWz6EUEAgltkAQMDjQVsFlVvfWUbZfY3KoWpldnCrXY,2255
|
|
54
54
|
proximl/projects/data_connectors.py,sha256=o2-K40BdF85TBXJgj1UMsznBmNHmnYh1qbZtHmmOa2Y,2216
|
|
55
55
|
proximl/projects/datastores.py,sha256=8nHpMEHn3UzsIhmPMoXympbRRJ10XkHdyRhBXUVXyeE,2095
|
|
56
|
-
proximl/projects/
|
|
56
|
+
proximl/projects/members.py,sha256=siREAa-Oac1pLGRHMXmj3ShoB0DVl8uY34eFdTlSezA,2826
|
|
57
|
+
proximl/projects/projects.py,sha256=PJYUaXbYMINcvlDQePKUSAz6uO11jg9tj5gJW-cNjGQ,2890
|
|
57
58
|
proximl/projects/secrets.py,sha256=xlfzRONjT2ySJDcq8FYRQmSU2ZJMdPLS8NbePWYNCeM,2160
|
|
58
|
-
proximl/projects/services.py,sha256=
|
|
59
|
+
proximl/projects/services.py,sha256=aCESZjqfI9K-iXl8uJQsjEyoX29LuPaNv_TFjK6B2Ts,2771
|
|
59
60
|
tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
tests/integration/conftest.py,sha256=
|
|
61
|
-
tests/integration/test_checkpoints_integration.py,sha256=
|
|
62
|
-
tests/integration/test_datasets_integration.py,sha256=
|
|
61
|
+
tests/integration/conftest.py,sha256=rqOKgDZ37SZH6CxXZIQSHtx8SF5GC1MlC49Zb5xrzsk,1135
|
|
62
|
+
tests/integration/test_checkpoints_integration.py,sha256=2gKhbax96ePIEZqcM4IeqSB0fTOYPh9nexNJtC96LuU,3239
|
|
63
|
+
tests/integration/test_datasets_integration.py,sha256=y8NL7fKQvZhJs1r9UAcLjXI0832tHr9XfIaRhHX9aOk,3529
|
|
63
64
|
tests/integration/test_environments_integration.py,sha256=7P6pKSyxA7rTwyNCD9HEaM2ablMG8WcBesOzkG-BgsQ,1403
|
|
64
65
|
tests/integration/test_gpu_types_integration.py,sha256=Zv-yrHcAgKau9BJQvzi92bdrRHhLPl_hbhhzNLEWJ9w,1256
|
|
65
|
-
tests/integration/test_jobs_integration.py,sha256=
|
|
66
|
-
tests/integration/test_models_integration.py,sha256=
|
|
67
|
-
tests/integration/test_volumes_integration.py,sha256=
|
|
66
|
+
tests/integration/test_jobs_integration.py,sha256=mSgPERDycfphLuLd7Hp3tSo-SpnbACp963x99K3XbtU,25263
|
|
67
|
+
tests/integration/test_models_integration.py,sha256=du9i3GsWjwEWei_-5BzfoBneBNcZ-9NQsDdOfn4dzd8,2906
|
|
68
|
+
tests/integration/test_volumes_integration.py,sha256=fTsP5_wyspzR_DX_ArXz5Fflq2dEmXtJbhwqCjSQDOA,3299
|
|
68
69
|
tests/integration/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
-
tests/integration/cloudbender/
|
|
70
|
+
tests/integration/cloudbender/conftest.py,sha256=K1EQC7U7aBnywJq-QvBZINqHtQm7kq5feDWVKAtp22E,777
|
|
71
|
+
tests/integration/cloudbender/test_providers_integration.py,sha256=8_1TBxcIpQcET7VStHIsi_-_3j5hs8H-icsKN3qnNa8,1327
|
|
72
|
+
tests/integration/cloudbender/test_regions_integration.py,sha256=9Cj2s2cX07iUnhYvFyrNxLxYHCPVjAA9Gfes7Xf7OV0,1406
|
|
73
|
+
tests/integration/cloudbender/test_services_integration.py,sha256=ziw3K-48Yl2Rzy-rCNw9QZ5m3TtfmQecdgdTpzSuUVA,3367
|
|
70
74
|
tests/integration/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
|
-
tests/integration/projects/conftest.py,sha256=
|
|
72
|
-
tests/integration/projects/test_projects_credentials_integration.py,sha256
|
|
73
|
-
tests/integration/projects/test_projects_data_connectors_integration.py,sha256=
|
|
74
|
-
tests/integration/projects/test_projects_datastores_integration.py,sha256=
|
|
75
|
-
tests/integration/projects/test_projects_integration.py,sha256=
|
|
76
|
-
tests/integration/projects/
|
|
77
|
-
tests/integration/projects/
|
|
75
|
+
tests/integration/projects/conftest.py,sha256=C_UumszWK-Es39ULv-Lz4TvbMdXXKQvZV6OwFSX8Mn0,294
|
|
76
|
+
tests/integration/projects/test_projects_credentials_integration.py,sha256=-OXDLaaWm_W0-AHDEHbMATjPGkyoAd_Vf3UsVJBg0_A,1675
|
|
77
|
+
tests/integration/projects/test_projects_data_connectors_integration.py,sha256=UBeqWbGoVZCH4BIVm4t9_L4mwzbN7oqncZyxcuwEnZQ,1669
|
|
78
|
+
tests/integration/projects/test_projects_datastores_integration.py,sha256=M-Hv3bSgG6FZap-WFA2DavqUaNJ4K9W4Du5214dURjQ,1508
|
|
79
|
+
tests/integration/projects/test_projects_integration.py,sha256=s5_M04wIKh4VHEdcc6FImUz5KhTS0-Wgxd6CMvWNmy8,1280
|
|
80
|
+
tests/integration/projects/test_projects_members_integration.py,sha256=4i4SeDCDZwmG82tjQEDyyFU-U52aBE5TeSXIP3RW8sA,1882
|
|
81
|
+
tests/integration/projects/test_projects_secrets_integration.py,sha256=TyqItJs-SBzOUwDy3vs_-yrDXeDF8r1_bAovr8C1a_s,1520
|
|
82
|
+
tests/integration/projects/test_projects_services_integration.py,sha256=cHOGSUpl3rqYJnCrbYp-2Zmn1ngFJTDV9Io7K8fDOp0,1569
|
|
78
83
|
tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
84
|
tests/unit/conftest.py,sha256=11Z49O4mKXtm6xl18kWPomZvbZJQpqdaDYeJHFQbO1A,35588
|
|
80
85
|
tests/unit/test_auth_unit.py,sha256=IJZHT5CZLNfu3MybTdEWIsKlvxNfFYpZ6oWYzbS856g,858
|
|
@@ -124,12 +129,13 @@ tests/unit/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
124
129
|
tests/unit/projects/test_project_credentials_unit.py,sha256=y528DyvIm7uc5mFuwiUPjOMO1dkVcMEeYQqzMmKLOuk,3480
|
|
125
130
|
tests/unit/projects/test_project_data_connectors_unit.py,sha256=Yx2yCUgcuN_KfTKynZxse0Nx8jRrSlMygI7R8a-NjE4,3385
|
|
126
131
|
tests/unit/projects/test_project_datastores_unit.py,sha256=WZMKkhpEg2dcevJqT7k6QRcYJF6FJfq177BDk2GWOBk,3129
|
|
132
|
+
tests/unit/projects/test_project_members_unit.py,sha256=dtbRfP454mviQYn6Dc0Uzyb3BBIuR3Ive6g3mnUAR6M,3466
|
|
127
133
|
tests/unit/projects/test_project_secrets_unit.py,sha256=VE9L91FJodcwVGizfF65WYMiHZaF0s2AdW1aiJ3z7xA,3276
|
|
128
134
|
tests/unit/projects/test_project_services_unit.py,sha256=PzeNuJRuAG7RkrPWX0FfgFTt6-63FviecrDY06rLQ6A,3331
|
|
129
135
|
tests/unit/projects/test_projects_unit.py,sha256=4vMo7TF-SjduoT2ejpuyM3dULslpdqE8-c25M9X-iYc,3810
|
|
130
|
-
proximl-0.5.
|
|
131
|
-
proximl-0.5.
|
|
132
|
-
proximl-0.5.
|
|
133
|
-
proximl-0.5.
|
|
134
|
-
proximl-0.5.
|
|
135
|
-
proximl-0.5.
|
|
136
|
+
proximl-0.5.15.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
|
|
137
|
+
proximl-0.5.15.dist-info/METADATA,sha256=9lwoFniw9bBF974xxoeOv2j89XLjBGienszKZwhh-9c,7345
|
|
138
|
+
proximl-0.5.15.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
139
|
+
proximl-0.5.15.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
|
|
140
|
+
proximl-0.5.15.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
|
|
141
|
+
proximl-0.5.15.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
from pytest import fixture, mark
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
pytestmark = [mark.integration,mark.cloudbender]
|
|
6
|
+
|
|
7
|
+
@fixture(scope="session")
|
|
8
|
+
@mark.create
|
|
9
|
+
@mark.asyncio
|
|
10
|
+
@mark.xdist_group("cloudbender_resources")
|
|
11
|
+
async def provider( proximl):
|
|
12
|
+
provider = await proximl.cloudbender.providers.enable(type="test")
|
|
13
|
+
await provider.wait_for("ready")
|
|
14
|
+
yield provider
|
|
15
|
+
await provider.remove()
|
|
16
|
+
|
|
17
|
+
@fixture(scope="session")
|
|
18
|
+
@mark.create
|
|
19
|
+
@mark.asyncio
|
|
20
|
+
@mark.xdist_group("cloudbender_resources")
|
|
21
|
+
async def region(proximl, provider):
|
|
22
|
+
region = await proximl.cloudbender.regions.create(provider_uuid=provider.id,name="test-region",
|
|
23
|
+
public=False,
|
|
24
|
+
storage=dict(mode="local"),)
|
|
25
|
+
await region.wait_for("healthy")
|
|
26
|
+
yield region
|
|
27
|
+
await region.remove()
|
|
28
|
+
await region.wait_for("archived")
|
|
@@ -8,14 +8,9 @@ pytestmark = [mark.sdk, mark.integration, mark.cloudbender, mark.providers]
|
|
|
8
8
|
|
|
9
9
|
@mark.create
|
|
10
10
|
@mark.asyncio
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async def
|
|
14
|
-
provider = await proximl.cloudbender.providers.enable(type="test")
|
|
15
|
-
yield provider
|
|
16
|
-
await provider.remove()
|
|
17
|
-
|
|
18
|
-
async def test_get_providers(self, proximl):
|
|
11
|
+
@mark.xdist_group("cloudbender_resources")
|
|
12
|
+
class GetProviderTests:
|
|
13
|
+
async def test_get_providers(self, proximl, provider):
|
|
19
14
|
providers = await proximl.cloudbender.providers.list()
|
|
20
15
|
assert len(providers) > 0
|
|
21
16
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import asyncio
|
|
4
|
+
from pytest import mark, fixture
|
|
5
|
+
|
|
6
|
+
pytestmark = [mark.sdk, mark.integration, mark.cloudbender, mark.regions]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mark.create
|
|
10
|
+
@mark.asyncio
|
|
11
|
+
@mark.xdist_group("cloudbender_resources")
|
|
12
|
+
class GetRegionTests:
|
|
13
|
+
async def test_get_regions(self, proximl, provider, region):
|
|
14
|
+
regions = await proximl.cloudbender.regions.list(provider_uuid=provider.id)
|
|
15
|
+
assert len(regions) > 0
|
|
16
|
+
|
|
17
|
+
async def test_get_region(self, proximl, provider, region):
|
|
18
|
+
response = await proximl.cloudbender.regions.get(provider.id, region.id)
|
|
19
|
+
assert response.id == region.id
|
|
20
|
+
|
|
21
|
+
async def test_region_properties(self, provider, region):
|
|
22
|
+
assert isinstance(region.id, str)
|
|
23
|
+
assert isinstance(region.provider_uuid, str)
|
|
24
|
+
assert isinstance(region.type, str)
|
|
25
|
+
assert region.type == "test"
|
|
26
|
+
assert region.provider_uuid == provider.id
|
|
27
|
+
|
|
28
|
+
async def test_region_str(self, region):
|
|
29
|
+
string = str(region)
|
|
30
|
+
regex = r"^{.*\"region_uuid\": \"" + region.id + r"\".*}$"
|
|
31
|
+
assert isinstance(string, str)
|
|
32
|
+
assert re.match(regex, string)
|
|
33
|
+
|
|
34
|
+
async def test_region_repr(self, region):
|
|
35
|
+
string = repr(region)
|
|
36
|
+
regex = (
|
|
37
|
+
r"^Region\( proximl , \*\*{.*'region_uuid': '"
|
|
38
|
+
+ region.id
|
|
39
|
+
+ r"'.*}\)$"
|
|
40
|
+
)
|
|
41
|
+
assert isinstance(string, str)
|
|
42
|
+
assert re.match(regex, string)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import asyncio
|
|
4
|
+
from pytest import mark, fixture
|
|
5
|
+
from cryptography import x509
|
|
6
|
+
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
|
|
7
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
8
|
+
from cryptography.hazmat.primitives import serialization, hashes
|
|
9
|
+
|
|
10
|
+
pytestmark = [mark.sdk, mark.integration, mark.cloudbender, mark.regions]
|
|
11
|
+
|
|
12
|
+
def get_csr(service_id):
|
|
13
|
+
private_key = rsa.generate_private_key(
|
|
14
|
+
public_exponent=65537,
|
|
15
|
+
key_size=4096
|
|
16
|
+
)
|
|
17
|
+
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
|
18
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
|
19
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "proxiML"),
|
|
20
|
+
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, service_id),
|
|
21
|
+
x509.NameAttribute(NameOID.COMMON_NAME, "test-client"), # Client identity
|
|
22
|
+
])).add_extension(
|
|
23
|
+
x509.ExtendedKeyUsage([
|
|
24
|
+
ExtendedKeyUsageOID.CLIENT_AUTH # Client authentication usage
|
|
25
|
+
]),
|
|
26
|
+
critical=True
|
|
27
|
+
).sign(private_key, hashes.SHA256())
|
|
28
|
+
return csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
|
29
|
+
|
|
30
|
+
@mark.create
|
|
31
|
+
@mark.asyncio
|
|
32
|
+
@mark.xdist_group("cloudbender_resources")
|
|
33
|
+
class GetServiceTests:
|
|
34
|
+
@fixture(scope="class")
|
|
35
|
+
async def service(self, proximl, region):
|
|
36
|
+
service = await proximl.cloudbender.services.create(
|
|
37
|
+
provider_uuid=region.provider_uuid,
|
|
38
|
+
region_uuid=region.id,
|
|
39
|
+
name="CLI Automated Service",
|
|
40
|
+
type="tcp",
|
|
41
|
+
port="8989",
|
|
42
|
+
public=False,
|
|
43
|
+
)
|
|
44
|
+
await service.wait_for("active")
|
|
45
|
+
yield service
|
|
46
|
+
await service.remove()
|
|
47
|
+
await service.wait_for("archived")
|
|
48
|
+
|
|
49
|
+
async def test_get_services(self, proximl, region,service):
|
|
50
|
+
services = await proximl.cloudbender.services.list(provider_uuid=region.provider_uuid, region_uuid=region.id)
|
|
51
|
+
assert len(services) > 0
|
|
52
|
+
|
|
53
|
+
async def test_get_service(self, proximl, provider, region, service):
|
|
54
|
+
response = await proximl.cloudbender.services.get(provider.id, region.id, service.id)
|
|
55
|
+
assert response.id == service.id
|
|
56
|
+
|
|
57
|
+
async def test_service_properties(self, region, service):
|
|
58
|
+
assert isinstance(service.id, str)
|
|
59
|
+
assert isinstance(service.provider_uuid, str)
|
|
60
|
+
assert isinstance(service.region_uuid, str)
|
|
61
|
+
assert isinstance(service.public, bool)
|
|
62
|
+
assert service.port == "8989"
|
|
63
|
+
assert service.provider_uuid == region.provider_uuid
|
|
64
|
+
assert service.region_uuid == region.id
|
|
65
|
+
|
|
66
|
+
async def test_service_str(self, service):
|
|
67
|
+
string = str(service)
|
|
68
|
+
regex = r"^{.*\"service_id\": \"" + service.id + r"\".*}$"
|
|
69
|
+
assert isinstance(string, str)
|
|
70
|
+
assert re.match(regex, string)
|
|
71
|
+
|
|
72
|
+
async def test_service_repr(self, service):
|
|
73
|
+
string = repr(service)
|
|
74
|
+
regex = (
|
|
75
|
+
r"^Service\( proximl , \*\*{.*'service_id': '"
|
|
76
|
+
+ service.id
|
|
77
|
+
+ r"'.*}\)$"
|
|
78
|
+
)
|
|
79
|
+
assert isinstance(string, str)
|
|
80
|
+
assert re.match(regex, string)
|
|
81
|
+
|
|
82
|
+
async def test_service_certificate(self, service):
|
|
83
|
+
service = await service.generate_certificate()
|
|
84
|
+
assert isinstance(service._service.get("auth_cert"), str)
|
|
85
|
+
csr = get_csr(service.id)
|
|
86
|
+
certificate = await service.sign_client_certificate(csr)
|
|
87
|
+
assert isinstance(certificate, str)
|
tests/integration/conftest.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from pytest import fixture
|
|
1
|
+
from pytest import fixture, mark
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
@fixture(scope="module")
|
|
5
|
+
@mark.xdist_group("project_resources")
|
|
5
6
|
async def project(proximl):
|
|
6
7
|
project = await proximl.projects.create(
|
|
7
8
|
name="New Project", copy_credentials=False, copy_secrets=False
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
@mark.xdist_group("project_resources")
|
|
12
|
+
class ProjectMembersTests:
|
|
13
|
+
@fixture(scope="class")
|
|
14
|
+
async def project_member(self, project):
|
|
15
|
+
member = await project.members.add("test.account@proximl.ai","read","read","read","read","read")
|
|
16
|
+
yield member
|
|
17
|
+
await project.members.remove("test.account@proximl.ai")
|
|
18
|
+
|
|
19
|
+
async def test_list_project_members(self, project):
|
|
20
|
+
members = await project.members.list()
|
|
21
|
+
assert len(members) > 0
|
|
22
|
+
|
|
23
|
+
async def test_project_member_properties(self, project, project_member):
|
|
24
|
+
assert isinstance(project_member.id, str)
|
|
25
|
+
assert isinstance(project_member.email, str)
|
|
26
|
+
assert isinstance(project_member.project_uuid, str)
|
|
27
|
+
assert isinstance(project_member.owner, bool)
|
|
28
|
+
assert isinstance(project_member.job, str)
|
|
29
|
+
assert isinstance(project_member.dataset, str)
|
|
30
|
+
assert isinstance(project_member.model, str)
|
|
31
|
+
assert isinstance(project_member.checkpoint, str)
|
|
32
|
+
assert isinstance(project_member.volume, str)
|
|
33
|
+
assert project.id == project_member.project_uuid
|
|
34
|
+
assert project_member.id == "test.account@proximl.ai"
|
|
35
|
+
|
|
36
|
+
async def test_project_member_str(self, project_member):
|
|
37
|
+
string = str(project_member)
|
|
38
|
+
regex = r"^{.*\"email\": \"" + project_member.email + r"\".*}$"
|
|
39
|
+
assert isinstance(string, str)
|
|
40
|
+
assert re.match(regex, string)
|
|
41
|
+
|
|
42
|
+
async def test_project_member_repr(self, project_member):
|
|
43
|
+
string = repr(project_member)
|
|
44
|
+
regex = (
|
|
45
|
+
r"^ProjectMember\( proximl , \*\*{.*'email': '"
|
|
46
|
+
+ project_member.email
|
|
47
|
+
+ r"'.*}\)$"
|
|
48
|
+
)
|
|
49
|
+
assert isinstance(string, str)
|
|
50
|
+
assert re.match(regex, string)
|
|
@@ -20,6 +20,7 @@ def extract_domain_suffix(hostname):
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@fixture(scope="class")
|
|
23
|
+
@mark.xdist_group("job_lifecycle")
|
|
23
24
|
async def job(proximl):
|
|
24
25
|
job = await proximl.jobs.create(
|
|
25
26
|
name="CLI Automated Tests - Job Lifecycle",
|
|
@@ -38,6 +39,7 @@ async def job(proximl):
|
|
|
38
39
|
|
|
39
40
|
@mark.create
|
|
40
41
|
@mark.asyncio
|
|
42
|
+
@mark.xdist_group("job_lifecycle")
|
|
41
43
|
class JobLifeCycleTests:
|
|
42
44
|
async def test_wait_for_running(self, job):
|
|
43
45
|
assert job.status != "running"
|
|
@@ -413,6 +415,7 @@ class JobAPIWorkerValidationTests:
|
|
|
413
415
|
|
|
414
416
|
@mark.create
|
|
415
417
|
@mark.asyncio
|
|
418
|
+
@mark.xdist_group("job_io")
|
|
416
419
|
class JobIOTests:
|
|
417
420
|
async def test_job_local_output(self, proximl, capsys):
|
|
418
421
|
temp_dir = tempfile.TemporaryDirectory()
|
|
@@ -423,11 +426,11 @@ class JobIOTests:
|
|
|
423
426
|
disk_size=10,
|
|
424
427
|
workers=["python $ML_MODEL_PATH/tensorflow/main.py"],
|
|
425
428
|
environment=dict(
|
|
426
|
-
type="
|
|
429
|
+
type="DEEPLEARNING_PY312",
|
|
427
430
|
env=[
|
|
428
431
|
dict(
|
|
429
432
|
key="CHECKPOINT_FILE",
|
|
430
|
-
value="model.ckpt-0050",
|
|
433
|
+
value="model.ckpt-0050.weights.h5",
|
|
431
434
|
)
|
|
432
435
|
],
|
|
433
436
|
),
|
|
@@ -469,7 +472,7 @@ class JobIOTests:
|
|
|
469
472
|
sys.stderr.write(captured.err)
|
|
470
473
|
assert "Epoch 1/2" in captured.out
|
|
471
474
|
assert "Epoch 2/2" in captured.out
|
|
472
|
-
assert "adding: model.ckpt-0001
|
|
475
|
+
assert "adding: model.ckpt-0001" in captured.out
|
|
473
476
|
assert "Send complete" in captured.out
|
|
474
477
|
|
|
475
478
|
async def test_job_model_input_and_output(self, proximl, capsys):
|
|
@@ -527,6 +530,7 @@ class JobIOTests:
|
|
|
527
530
|
|
|
528
531
|
@mark.create
|
|
529
532
|
@mark.asyncio
|
|
533
|
+
@mark.xdist_group("job_types")
|
|
530
534
|
class JobTypeTests:
|
|
531
535
|
async def test_endpoint(self, proximl):
|
|
532
536
|
|
|
@@ -558,7 +562,7 @@ class JobTypeTests:
|
|
|
558
562
|
assert job.url
|
|
559
563
|
assert extract_domain_suffix(urlparse(job.url).hostname) == "proximl.cloud"
|
|
560
564
|
tries = 0
|
|
561
|
-
await asyncio.sleep(
|
|
565
|
+
await asyncio.sleep(180) ## downloading weights can be slow
|
|
562
566
|
async with aiohttp.ClientSession() as session:
|
|
563
567
|
retry = True
|
|
564
568
|
while retry:
|
|
@@ -640,7 +644,7 @@ class JobTypeTests:
|
|
|
640
644
|
assert "Epoch 2/2" in captured.out
|
|
641
645
|
assert "Uploading s3://proximl-example/output/resnet_cifar10" in captured.out
|
|
642
646
|
assert (
|
|
643
|
-
"upload: ./model.ckpt-0002.
|
|
647
|
+
"upload: ./model.ckpt-0002.weights.h5 to s3://proximl-example/output/resnet_cifar10/model.ckpt-0002.weights.h5"
|
|
644
648
|
in captured.out
|
|
645
649
|
)
|
|
646
650
|
assert "Upload complete" in captured.out
|
|
@@ -648,6 +652,7 @@ class JobTypeTests:
|
|
|
648
652
|
|
|
649
653
|
@mark.create
|
|
650
654
|
@mark.asyncio
|
|
655
|
+
@mark.xdist_group("job_features")
|
|
651
656
|
class JobFeatureTests:
|
|
652
657
|
async def test_cpu_instance(self, proximl, capsys):
|
|
653
658
|
job = await proximl.jobs.create(
|
|
@@ -713,9 +718,9 @@ class JobFeatureTests:
|
|
|
713
718
|
sys.stderr.write(captured.err)
|
|
714
719
|
upload_contents = os.listdir(temp_dir.name)
|
|
715
720
|
temp_dir.cleanup()
|
|
716
|
-
assert len(upload_contents)
|
|
721
|
+
assert len(upload_contents) >= 3
|
|
717
722
|
assert any(
|
|
718
|
-
"model.ckpt-0002
|
|
723
|
+
"model.ckpt-0002" in content
|
|
719
724
|
for content in upload_contents
|
|
720
725
|
)
|
|
721
726
|
|
|
@@ -724,5 +729,5 @@ class JobFeatureTests:
|
|
|
724
729
|
sys.stderr.write(captured.err)
|
|
725
730
|
assert "Epoch 1/2" in captured.out
|
|
726
731
|
assert "Epoch 2/2" in captured.out
|
|
727
|
-
assert "Number of regular files transferred:
|
|
732
|
+
assert "Number of regular files transferred: 4" in captured.out
|
|
728
733
|
assert "Send complete" in captured.out
|
|
@@ -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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|