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.
Files changed (33) hide show
  1. proximl/__init__.py +1 -1
  2. proximl/cli/job/create.py +32 -2
  3. proximl/cloudbender/services.py +19 -1
  4. proximl/jobs.py +9 -0
  5. proximl/projects/members.py +98 -0
  6. proximl/projects/projects.py +2 -0
  7. proximl/projects/services.py +17 -0
  8. proximl/proximl.py +36 -19
  9. {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/METADATA +1 -1
  10. {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/RECORD +33 -27
  11. tests/integration/cloudbender/conftest.py +28 -0
  12. tests/integration/cloudbender/test_providers_integration.py +3 -8
  13. tests/integration/cloudbender/test_regions_integration.py +42 -0
  14. tests/integration/cloudbender/test_services_integration.py +87 -0
  15. tests/integration/conftest.py +1 -1
  16. tests/integration/projects/conftest.py +2 -1
  17. tests/integration/projects/test_projects_credentials_integration.py +1 -0
  18. tests/integration/projects/test_projects_data_connectors_integration.py +1 -0
  19. tests/integration/projects/test_projects_datastores_integration.py +1 -0
  20. tests/integration/projects/test_projects_integration.py +1 -0
  21. tests/integration/projects/test_projects_members_integration.py +50 -0
  22. tests/integration/projects/test_projects_secrets_integration.py +1 -0
  23. tests/integration/projects/test_projects_services_integration.py +1 -0
  24. tests/integration/test_checkpoints_integration.py +1 -0
  25. tests/integration/test_datasets_integration.py +1 -0
  26. tests/integration/test_jobs_integration.py +13 -8
  27. tests/integration/test_models_integration.py +1 -0
  28. tests/integration/test_volumes_integration.py +1 -0
  29. tests/unit/projects/test_project_members_unit.py +107 -0
  30. {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/LICENSE +0 -0
  31. {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/WHEEL +0 -0
  32. {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/entry_points.txt +0 -0
  33. {proximl-0.5.13.post1.dist-info → proximl-0.5.15.dist-info}/top_level.txt +0 -0
proximl/__init__.py CHANGED
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.13.post1"
16
+ __version__ = "0.5.15"
17
17
  __all__ = "ProxiML"
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
 
@@ -67,7 +67,7 @@ class Services(object):
67
67
 
68
68
 
69
69
  class Service:
70
- def __init__(self, proximl, **kwargs):
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
+
@@ -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:
@@ -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
- 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 ProxiMLException("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: proximl
3
- Version: 0.5.13.post1
3
+ Version: 0.5.15
4
4
  Summary: proxiML client SDK and command line utilities
5
5
  Home-page: https://github.com/proxiML/python-sdk
6
6
  Author: proxiML
@@ -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=H24uX4w44gBopXgz6-IQz2EmtKokV3dNMKqD9KEsAKM,439
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=DySH1hzLiYFdE5IhAX5ppISXQTK-hB1dKtWOgrTenEM,17632
14
+ proximl/jobs.py,sha256=HiGs2gRKPFZReHLg9DLPsDWEecxZpafWv6XFBSGXYj0,17947
15
15
  proximl/models.py,sha256=gJQ1C3HGEHScW41PAQS3Q8IKSQSauIUIJxOZvkDzizQ,8284
16
- proximl/proximl.py,sha256=yzKbJ7ak8YiVv6q7CEagPufECNU4YTNeul6N3OuyH1M,10864
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=0EsICgLxP5EniWxN-eTKnJtMoKJJXcfgB5myfNzcDrs,34380
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=km3KcIgaRRVb3iuZEIn6ONIekuyXhBgk_brAFJcMMl4,5126
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/projects.py,sha256=V86mKR5USaLaqcKf7XcGIXeH5qmIL0bYITvnwqSTwzI,2792
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=C1YUxixkio8n4AlgmGqZJ93hP3VKOoTXVXTNCkRVd9Y,2206
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=zRWpherX-yfbpk7xqZk9bIZCyJ-dwVeszY_7kekn2M4,1134
61
- tests/integration/test_checkpoints_integration.py,sha256=m-m6KM5Iy99bbCyA6VQVBFWc9MI7VItuoAPb_Q6Q0Fk,3206
62
- tests/integration/test_datasets_integration.py,sha256=Ndp8itnncDSjVH0t5BM5R_-_yL4qt6JrkQAVOTMi1R8,3499
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=9wY6ZfCv9FkafV0FJ5QRIwO0-8JR-81iB6uLT9C3pQ8,25110
66
- tests/integration/test_models_integration.py,sha256=cnsao7Zo4PwVCbdAdcBs0xombAslI_X_JOw6ipxdzOA,2878
67
- tests/integration/test_volumes_integration.py,sha256=Xo2Whw2U-7jyvESIkyex3f0SMXKlExe3kLmsbpXTHhQ,3270
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/test_providers_integration.py,sha256=gFqPQom-Cn1iZC50_ChQ2us2_f4tIPATQSAUcWdf7ss,1473
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=4WbknSwdJgzPAr5qumCJD3Cl1rQFWgGZ5-Wro1ka0xA,249
72
- tests/integration/projects/test_projects_credentials_integration.py,sha256=_JqE2sGN09gZpvhtMRFjBPXumJ-PqKl8iXOgcYHlvSY,1636
73
- tests/integration/projects/test_projects_data_connectors_integration.py,sha256=ag6w2FFQYNlAdV83rOqat0FVZnP4JFhDunCvRwMJdzo,1630
74
- tests/integration/projects/test_projects_datastores_integration.py,sha256=DCHFnCYguhtrZvfTeWgKpPxlkeAXxVfE3IWkM4KjgAc,1469
75
- tests/integration/projects/test_projects_integration.py,sha256=wlRLxZkMnJZHlNPaeRFjAeyUlMfbgdrrFEI2eMBcoUI,1241
76
- tests/integration/projects/test_projects_secrets_integration.py,sha256=wRkcVkaf-zLWvCdJx9ikJYAoiJXU1esv0diRxwwRFdc,1481
77
- tests/integration/projects/test_projects_services_integration.py,sha256=MV9tZFTgrUtzImNrc-ZAoHYLbf_ZxAAAeVpxxr5AGdg,1530
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.13.post1.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
131
- proximl-0.5.13.post1.dist-info/METADATA,sha256=BvyKWNRgTQNT4Wg2eqJeEwbjcsJWsPoANfYhJUVkfNU,7351
132
- proximl-0.5.13.post1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
133
- proximl-0.5.13.post1.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
134
- proximl-0.5.13.post1.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
135
- proximl-0.5.13.post1.dist-info/RECORD,,
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
- class GetProvidersTests:
12
- @fixture(scope="class")
13
- async def provider(self, proximl):
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)
@@ -40,7 +40,7 @@ def env(request):
40
40
  yield ENVS[env]
41
41
 
42
42
 
43
- @fixture(scope="module")
43
+ @fixture(scope="session")
44
44
  def proximl(env):
45
45
  proximl = ProxiML(**env)
46
46
  yield proximl
@@ -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
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectCredentialsTests:
12
13
  @fixture(scope="class")
13
14
  async def project_credential(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectDataConnectorsTests:
12
13
  @fixture(scope="class")
13
14
  async def project_data_connector(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectDatastoresTests:
12
13
  @fixture(scope="class")
13
14
  async def project_datastore(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class GetProjectsTests:
12
13
  async def test_get_projects(self, proximl):
13
14
  projects = await proximl.projects.list()
@@ -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)
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectSecretsTests:
12
13
  @fixture(scope="class")
13
14
  async def project_secret(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("project_resources")
11
12
  class ProjectServicesTests:
12
13
  @fixture(scope="class")
13
14
  async def project_service(self, project):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.checkpoints]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("checkpoints")
11
12
  class GetCheckpointTests:
12
13
  @fixture(scope="class")
13
14
  async def checkpoint(self, proximl):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.datasets]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("datasets")
11
12
  class GetDatasetTests:
12
13
  @fixture(scope="class")
13
14
  async def dataset(self, proximl):
@@ -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="DEEPLEARNING_PY310",
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.data-00000-of-00001" in captured.out
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(30)
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.data-00000-of-00001 to s3://proximl-example/output/resnet_cifar10/model.ckpt-0002.data-00000-of-00001"
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) > 4
721
+ assert len(upload_contents) >= 3
717
722
  assert any(
718
- "model.ckpt-0002.data-00000-of-00001" in content
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: 7" in captured.out
732
+ assert "Number of regular files transferred: 4" in captured.out
728
733
  assert "Send complete" in captured.out
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.models]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("models")
11
12
  class GetModelTests:
12
13
  @fixture(scope="class")
13
14
  async def model(self, proximl):
@@ -8,6 +8,7 @@ pytestmark = [mark.sdk, mark.integration, mark.volumes]
8
8
 
9
9
  @mark.create
10
10
  @mark.asyncio
11
+ @mark.xdist_group("volumes")
11
12
  class GetVolumeTests:
12
13
  @fixture(scope="class")
13
14
  async def volume(self, proximl):
@@ -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)