proximl 0.5.4__py3-none-any.whl → 0.5.6__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/__init__.py +3 -6
- proximl/cli/cloudbender/service.py +129 -0
- proximl/cli/volume.py +235 -0
- proximl/cloudbender/services.py +115 -0
- proximl/exceptions.py +21 -12
- proximl/jobs.py +36 -39
- proximl/proximl.py +7 -15
- proximl/volumes.py +255 -0
- {proximl-0.5.4.dist-info → proximl-0.5.6.dist-info}/METADATA +1 -1
- {proximl-0.5.4.dist-info → proximl-0.5.6.dist-info}/RECORD +25 -17
- tests/integration/test_checkpoints_integration.py +7 -5
- tests/integration/test_datasets_integration.py +4 -5
- tests/integration/test_jobs_integration.py +40 -2
- tests/integration/test_models_integration.py +8 -10
- tests/integration/test_projects_integration.py +2 -6
- tests/integration/test_volumes_integration.py +100 -0
- tests/unit/cli/test_cli_volume_unit.py +20 -0
- tests/unit/cloudbender/test_services_unit.py +161 -0
- tests/unit/conftest.py +82 -9
- tests/unit/test_volumes_unit.py +447 -0
- {proximl-0.5.4.dist-info → proximl-0.5.6.dist-info}/LICENSE +0 -0
- {proximl-0.5.4.dist-info → proximl-0.5.6.dist-info}/WHEEL +0 -0
- {proximl-0.5.4.dist-info → proximl-0.5.6.dist-info}/entry_points.txt +0 -0
- {proximl-0.5.4.dist-info → proximl-0.5.6.dist-info}/top_level.txt +0 -0
proximl/jobs.py
CHANGED
|
@@ -77,8 +77,7 @@ class Jobs(object):
|
|
|
77
77
|
model=model,
|
|
78
78
|
endpoint=endpoint,
|
|
79
79
|
source_job_uuid=kwargs.get("source_job_uuid"),
|
|
80
|
-
project_uuid=kwargs.get("project_uuid")
|
|
81
|
-
or self.proximl.active_project,
|
|
80
|
+
project_uuid=kwargs.get("project_uuid") or self.proximl.active_project,
|
|
82
81
|
)
|
|
83
82
|
payload = {
|
|
84
83
|
k: v
|
|
@@ -103,9 +102,7 @@ class Jobs(object):
|
|
|
103
102
|
return job
|
|
104
103
|
|
|
105
104
|
async def remove(self, id, **kwargs):
|
|
106
|
-
await self.proximl._query(
|
|
107
|
-
f"/job/{id}", "DELETE", dict(**kwargs, force=True)
|
|
108
|
-
)
|
|
105
|
+
await self.proximl._query(f"/job/{id}", "DELETE", dict(**kwargs, force=True))
|
|
109
106
|
|
|
110
107
|
|
|
111
108
|
class Job:
|
|
@@ -308,18 +305,26 @@ class Job:
|
|
|
308
305
|
entity_type="job",
|
|
309
306
|
project_uuid=self._job.get("project_uuid"),
|
|
310
307
|
cidr=self.dict.get("vpn").get("cidr"),
|
|
311
|
-
ssh_port=
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
308
|
+
ssh_port=(
|
|
309
|
+
self._job.get("vpn").get("client").get("ssh_port")
|
|
310
|
+
if self._job.get("vpn").get("client")
|
|
311
|
+
else None
|
|
312
|
+
),
|
|
313
|
+
model_path=(
|
|
314
|
+
self._job.get("model").get("source_uri")
|
|
315
|
+
if self._job.get("model").get("source_type") == "local"
|
|
316
|
+
else None
|
|
317
|
+
),
|
|
318
|
+
input_path=(
|
|
319
|
+
self._job.get("data").get("input_uri")
|
|
320
|
+
if self._job.get("data").get("input_type") == "local"
|
|
321
|
+
else None
|
|
322
|
+
),
|
|
323
|
+
output_path=(
|
|
324
|
+
self._job.get("data").get("output_uri")
|
|
325
|
+
if self._job.get("data").get("output_type") == "local"
|
|
326
|
+
else None
|
|
327
|
+
),
|
|
323
328
|
)
|
|
324
329
|
return details
|
|
325
330
|
|
|
@@ -396,8 +401,7 @@ class Job:
|
|
|
396
401
|
|
|
397
402
|
def _get_msg_handler(self, msg_handler):
|
|
398
403
|
worker_numbers = {
|
|
399
|
-
w.get("job_worker_uuid"): ind + 1
|
|
400
|
-
for ind, w in enumerate(self._workers)
|
|
404
|
+
w.get("job_worker_uuid"): ind + 1 for ind, w in enumerate(self._workers)
|
|
401
405
|
}
|
|
402
406
|
worker_numbers["data_worker"] = 0
|
|
403
407
|
|
|
@@ -407,9 +411,7 @@ class Job:
|
|
|
407
411
|
if msg_handler:
|
|
408
412
|
msg_handler(data)
|
|
409
413
|
else:
|
|
410
|
-
timestamp = datetime.fromtimestamp(
|
|
411
|
-
int(data.get("time")) / 1000
|
|
412
|
-
)
|
|
414
|
+
timestamp = datetime.fromtimestamp(int(data.get("time")) / 1000)
|
|
413
415
|
if len(self._workers) > 1:
|
|
414
416
|
print(
|
|
415
417
|
f"{timestamp.strftime('%m/%d/%Y, %H:%M:%S')}: Worker {data.get('worker_number')} - {data.get('msg').rstrip()}"
|
|
@@ -422,10 +424,7 @@ class Job:
|
|
|
422
424
|
return handler
|
|
423
425
|
|
|
424
426
|
async def attach(self, msg_handler=None):
|
|
425
|
-
if
|
|
426
|
-
self.type == "notebook"
|
|
427
|
-
and self.status != "waiting for data/model download"
|
|
428
|
-
):
|
|
427
|
+
if self.type == "notebook" and self.status != "waiting for data/model download":
|
|
429
428
|
raise SpecificationError(
|
|
430
429
|
"type",
|
|
431
430
|
"Notebooks cannot be attached to after model download is complete. Use open() instead.",
|
|
@@ -442,9 +441,7 @@ class Job:
|
|
|
442
441
|
async def copy(self, name, **kwargs):
|
|
443
442
|
logging.debug(f"copy request - name: {name} ; kwargs: {kwargs}")
|
|
444
443
|
if self.type != "notebook":
|
|
445
|
-
raise SpecificationError(
|
|
446
|
-
"job", "Only notebook job types can be copied"
|
|
447
|
-
)
|
|
444
|
+
raise SpecificationError("job", "Only notebook job types can be copied")
|
|
448
445
|
|
|
449
446
|
job = await self.proximl.jobs.create(
|
|
450
447
|
name,
|
|
@@ -504,9 +501,7 @@ class Job:
|
|
|
504
501
|
|
|
505
502
|
POLL_INTERVAL_MIN = 5
|
|
506
503
|
POLL_INTERVAL_MAX = 60
|
|
507
|
-
POLL_INTERVAL = max(
|
|
508
|
-
min(timeout / 60, POLL_INTERVAL_MAX), POLL_INTERVAL_MIN
|
|
509
|
-
)
|
|
504
|
+
POLL_INTERVAL = max(min(timeout / 60, POLL_INTERVAL_MAX), POLL_INTERVAL_MIN)
|
|
510
505
|
retry_count = math.ceil(timeout / POLL_INTERVAL)
|
|
511
506
|
count = 0
|
|
512
507
|
while count < retry_count:
|
|
@@ -519,23 +514,25 @@ class Job:
|
|
|
519
514
|
raise e
|
|
520
515
|
if (
|
|
521
516
|
self.status == status
|
|
522
|
-
or (
|
|
523
|
-
self.type == "training"
|
|
524
|
-
and status == "finished"
|
|
525
|
-
and self.status == "stopped"
|
|
526
|
-
)
|
|
527
517
|
or (
|
|
528
518
|
status
|
|
529
519
|
in [
|
|
530
520
|
"waiting for GPUs",
|
|
531
521
|
"waiting for resources",
|
|
532
522
|
] ## this status could be very short and the polling could miss it
|
|
533
|
-
and self.status
|
|
523
|
+
and self.status
|
|
524
|
+
not in ["new", "waiting for GPUs", "waiting for resources"]
|
|
534
525
|
)
|
|
535
526
|
or (
|
|
536
527
|
status
|
|
537
528
|
== "waiting for data/model download" ## this status could be very short and the polling could miss it
|
|
538
|
-
and self.status
|
|
529
|
+
and self.status
|
|
530
|
+
not in [
|
|
531
|
+
"new",
|
|
532
|
+
"waiting for GPUs",
|
|
533
|
+
"waiting for resources",
|
|
534
|
+
"waiting for data/model download",
|
|
535
|
+
]
|
|
539
536
|
)
|
|
540
537
|
):
|
|
541
538
|
return self
|
proximl/proximl.py
CHANGED
|
@@ -10,6 +10,7 @@ from proximl.auth import Auth
|
|
|
10
10
|
from proximl.datasets import Datasets
|
|
11
11
|
from proximl.models import Models
|
|
12
12
|
from proximl.checkpoints import Checkpoints
|
|
13
|
+
from proximl.volumes import Volumes
|
|
13
14
|
from proximl.jobs import Jobs
|
|
14
15
|
from proximl.gpu_types import GpuTypes
|
|
15
16
|
from proximl.environments import Environments
|
|
@@ -66,6 +67,7 @@ class ProxiML(object):
|
|
|
66
67
|
self.datasets = Datasets(self)
|
|
67
68
|
self.models = Models(self)
|
|
68
69
|
self.checkpoints = Checkpoints(self)
|
|
70
|
+
self.volumes = Volumes(self)
|
|
69
71
|
self.jobs = Jobs(self)
|
|
70
72
|
self.gpu_types = GpuTypes(self)
|
|
71
73
|
self.environments = Environments(self)
|
|
@@ -117,9 +119,7 @@ class ProxiML(object):
|
|
|
117
119
|
)
|
|
118
120
|
if params:
|
|
119
121
|
if not isinstance(params, dict):
|
|
120
|
-
raise ProxiMLException(
|
|
121
|
-
"Query parameters must be a valid dictionary"
|
|
122
|
-
)
|
|
122
|
+
raise ProxiMLException("Query parameters must be a valid dictionary")
|
|
123
123
|
params = {
|
|
124
124
|
k: (str(v).lower() if isinstance(v, bool) else v)
|
|
125
125
|
for k, v in params.items()
|
|
@@ -155,13 +155,9 @@ class ProxiML(object):
|
|
|
155
155
|
content_type = resp.headers.get("content-type", "")
|
|
156
156
|
resp.close()
|
|
157
157
|
if content_type == "application/json":
|
|
158
|
-
raise ApiError(
|
|
159
|
-
resp.status, json.loads(what.decode("utf8"))
|
|
160
|
-
)
|
|
158
|
+
raise ApiError(resp.status, json.loads(what.decode("utf8")))
|
|
161
159
|
else:
|
|
162
|
-
raise ApiError(
|
|
163
|
-
resp.status, {"message": what.decode("utf8")}
|
|
164
|
-
)
|
|
160
|
+
raise ApiError(resp.status, {"message": what.decode("utf8")})
|
|
165
161
|
results = await resp.json()
|
|
166
162
|
return results
|
|
167
163
|
|
|
@@ -273,15 +269,11 @@ class ProxiML(object):
|
|
|
273
269
|
logging.debug(f"Websocket Disconnected. Done? {done}")
|
|
274
270
|
except Exception as e:
|
|
275
271
|
connection_tries += 1
|
|
276
|
-
logging.debug(
|
|
277
|
-
f"Connection error: {traceback.format_exc()}"
|
|
278
|
-
)
|
|
272
|
+
logging.debug(f"Connection error: {traceback.format_exc()}")
|
|
279
273
|
if connection_tries == 5:
|
|
280
274
|
raise ApiError(
|
|
281
275
|
500,
|
|
282
|
-
{
|
|
283
|
-
"message": f"Connection error: {traceback.format_exc()}"
|
|
284
|
-
},
|
|
276
|
+
{"message": f"Connection error: {traceback.format_exc()}"},
|
|
285
277
|
)
|
|
286
278
|
|
|
287
279
|
def set_active_project(self, project_uuid):
|
proximl/volumes.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import math
|
|
4
|
+
import asyncio
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
VolumeError,
|
|
9
|
+
ApiError,
|
|
10
|
+
SpecificationError,
|
|
11
|
+
ProxiMLException,
|
|
12
|
+
)
|
|
13
|
+
from .connections import Connection
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Volumes(object):
|
|
17
|
+
def __init__(self, proximl):
|
|
18
|
+
self.proximl = proximl
|
|
19
|
+
|
|
20
|
+
async def get(self, id, **kwargs):
|
|
21
|
+
resp = await self.proximl._query(f"/volume/{id}", "GET", kwargs)
|
|
22
|
+
return Volume(self.proximl, **resp)
|
|
23
|
+
|
|
24
|
+
async def list(self, **kwargs):
|
|
25
|
+
resp = await self.proximl._query(f"/volume", "GET", kwargs)
|
|
26
|
+
volumes = [Volume(self.proximl, **volume) for volume in resp]
|
|
27
|
+
return volumes
|
|
28
|
+
|
|
29
|
+
async def create(self, name, source_type, source_uri, capacity, **kwargs):
|
|
30
|
+
data = dict(
|
|
31
|
+
name=name,
|
|
32
|
+
source_type=source_type,
|
|
33
|
+
source_uri=source_uri,
|
|
34
|
+
capacity=capacity,
|
|
35
|
+
source_options=kwargs.get("source_options"),
|
|
36
|
+
project_uuid=kwargs.get("project_uuid") or self.proximl.active_project,
|
|
37
|
+
)
|
|
38
|
+
payload = {k: v for k, v in data.items() if v is not None}
|
|
39
|
+
logging.info(f"Creating Volume {name}")
|
|
40
|
+
resp = await self.proximl._query("/volume", "POST", None, payload)
|
|
41
|
+
volume = Volume(self.proximl, **resp)
|
|
42
|
+
logging.info(f"Created Volume {name} with id {volume.id}")
|
|
43
|
+
|
|
44
|
+
return volume
|
|
45
|
+
|
|
46
|
+
async def remove(self, id, **kwargs):
|
|
47
|
+
await self.proximl._query(f"/volume/{id}", "DELETE", dict(**kwargs, force=True))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Volume:
|
|
51
|
+
def __init__(self, proximl, **kwargs):
|
|
52
|
+
self.proximl = proximl
|
|
53
|
+
self._volume = kwargs
|
|
54
|
+
self._id = self._volume.get("id", self._volume.get("id"))
|
|
55
|
+
self._status = self._volume.get("status")
|
|
56
|
+
self._name = self._volume.get("name")
|
|
57
|
+
self._capacity = self._volume.get("capacity")
|
|
58
|
+
self._used_size = self._volume.get("used_size")
|
|
59
|
+
self._billed_size = self._volume.get("billed_size")
|
|
60
|
+
self._project_uuid = self._volume.get("project_uuid")
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def id(self) -> str:
|
|
64
|
+
return self._id
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def status(self) -> str:
|
|
68
|
+
return self._status
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def name(self) -> str:
|
|
72
|
+
return self._name
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def capacity(self) -> str:
|
|
76
|
+
return self._capacity
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def used_size(self) -> int:
|
|
80
|
+
return self._used_size
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def billed_size(self) -> int:
|
|
84
|
+
return self._billed_size
|
|
85
|
+
|
|
86
|
+
def __str__(self):
|
|
87
|
+
return json.dumps({k: v for k, v in self._volume.items()})
|
|
88
|
+
|
|
89
|
+
def __repr__(self):
|
|
90
|
+
return f"Volume( proximl , **{self._volume.__repr__()})"
|
|
91
|
+
|
|
92
|
+
def __bool__(self):
|
|
93
|
+
return bool(self._id)
|
|
94
|
+
|
|
95
|
+
async def get_log_url(self):
|
|
96
|
+
resp = await self.proximl._query(
|
|
97
|
+
f"/volume/{self._id}/logs",
|
|
98
|
+
"GET",
|
|
99
|
+
dict(project_uuid=self._project_uuid),
|
|
100
|
+
)
|
|
101
|
+
return resp
|
|
102
|
+
|
|
103
|
+
async def get_details(self):
|
|
104
|
+
resp = await self.proximl._query(
|
|
105
|
+
f"/volume/{self._id}/details",
|
|
106
|
+
"GET",
|
|
107
|
+
dict(project_uuid=self._project_uuid),
|
|
108
|
+
)
|
|
109
|
+
return resp
|
|
110
|
+
|
|
111
|
+
async def get_connection_utility_url(self):
|
|
112
|
+
resp = await self.proximl._query(
|
|
113
|
+
f"/volume/{self._id}/download",
|
|
114
|
+
"GET",
|
|
115
|
+
dict(project_uuid=self._project_uuid),
|
|
116
|
+
)
|
|
117
|
+
return resp
|
|
118
|
+
|
|
119
|
+
def get_connection_details(self):
|
|
120
|
+
if self._volume.get("vpn"):
|
|
121
|
+
details = dict(
|
|
122
|
+
entity_type="volume",
|
|
123
|
+
project_uuid=self._volume.get("project_uuid"),
|
|
124
|
+
cidr=self._volume.get("vpn").get("cidr"),
|
|
125
|
+
ssh_port=self._volume.get("vpn").get("client").get("ssh_port"),
|
|
126
|
+
input_path=(
|
|
127
|
+
self._volume.get("source_uri")
|
|
128
|
+
if self.status in ["new", "downloading"]
|
|
129
|
+
else None
|
|
130
|
+
),
|
|
131
|
+
output_path=(
|
|
132
|
+
self._volume.get("output_uri")
|
|
133
|
+
if self.status == "exporting"
|
|
134
|
+
else None
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
details = dict()
|
|
139
|
+
return details
|
|
140
|
+
|
|
141
|
+
async def connect(self):
|
|
142
|
+
if self.status in ["ready", "failed"]:
|
|
143
|
+
raise SpecificationError(
|
|
144
|
+
"status",
|
|
145
|
+
f"You can only connect to downloading or exporting volumes.",
|
|
146
|
+
)
|
|
147
|
+
if self.status == "new":
|
|
148
|
+
await self.wait_for("downloading")
|
|
149
|
+
connection = Connection(
|
|
150
|
+
self.proximl, entity_type="volume", id=self.id, entity=self
|
|
151
|
+
)
|
|
152
|
+
await connection.start()
|
|
153
|
+
return connection.status
|
|
154
|
+
|
|
155
|
+
async def disconnect(self):
|
|
156
|
+
connection = Connection(
|
|
157
|
+
self.proximl, entity_type="volume", id=self.id, entity=self
|
|
158
|
+
)
|
|
159
|
+
await connection.stop()
|
|
160
|
+
return connection.status
|
|
161
|
+
|
|
162
|
+
async def remove(self, force=False):
|
|
163
|
+
await self.proximl._query(
|
|
164
|
+
f"/volume/{self._id}",
|
|
165
|
+
"DELETE",
|
|
166
|
+
dict(project_uuid=self._project_uuid, force=force),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
async def rename(self, name):
|
|
170
|
+
resp = await self.proximl._query(
|
|
171
|
+
f"/volume/{self._id}",
|
|
172
|
+
"PATCH",
|
|
173
|
+
dict(project_uuid=self._project_uuid),
|
|
174
|
+
dict(name=name),
|
|
175
|
+
)
|
|
176
|
+
self.__init__(self.proximl, **resp)
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
async def export(self, output_type, output_uri, output_options=dict()):
|
|
180
|
+
resp = await self.proximl._query(
|
|
181
|
+
f"/volume/{self._id}/export",
|
|
182
|
+
"POST",
|
|
183
|
+
dict(project_uuid=self._project_uuid),
|
|
184
|
+
dict(
|
|
185
|
+
output_type=output_type,
|
|
186
|
+
output_uri=output_uri,
|
|
187
|
+
output_options=output_options,
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
self.__init__(self.proximl, **resp)
|
|
191
|
+
return self
|
|
192
|
+
|
|
193
|
+
def _get_msg_handler(self, msg_handler):
|
|
194
|
+
def handler(data):
|
|
195
|
+
if data.get("type") == "subscription":
|
|
196
|
+
if msg_handler:
|
|
197
|
+
msg_handler(data)
|
|
198
|
+
else:
|
|
199
|
+
timestamp = datetime.fromtimestamp(int(data.get("time")) / 1000)
|
|
200
|
+
print(
|
|
201
|
+
f"{timestamp.strftime('%m/%d/%Y, %H:%M:%S')}: {data.get('msg').rstrip()}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return handler
|
|
205
|
+
|
|
206
|
+
async def attach(self, msg_handler=None):
|
|
207
|
+
await self.refresh()
|
|
208
|
+
if self.status not in ["ready", "failed"]:
|
|
209
|
+
await self.proximl._ws_subscribe(
|
|
210
|
+
"volume",
|
|
211
|
+
self._project_uuid,
|
|
212
|
+
self.id,
|
|
213
|
+
self._get_msg_handler(msg_handler),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
async def refresh(self):
|
|
217
|
+
resp = await self.proximl._query(
|
|
218
|
+
f"/volume/{self.id}",
|
|
219
|
+
"GET",
|
|
220
|
+
dict(project_uuid=self._project_uuid),
|
|
221
|
+
)
|
|
222
|
+
self.__init__(self.proximl, **resp)
|
|
223
|
+
return self
|
|
224
|
+
|
|
225
|
+
async def wait_for(self, status, timeout=300):
|
|
226
|
+
valid_statuses = ["downloading", "ready", "archived"]
|
|
227
|
+
if not status in valid_statuses:
|
|
228
|
+
raise SpecificationError(
|
|
229
|
+
"status",
|
|
230
|
+
f"Invalid wait_for status {status}. Valid statuses are: {valid_statuses}",
|
|
231
|
+
)
|
|
232
|
+
if self.status == status:
|
|
233
|
+
return
|
|
234
|
+
POLL_INTERVAL_MIN = 5
|
|
235
|
+
POLL_INTERVAL_MAX = 60
|
|
236
|
+
POLL_INTERVAL = max(min(timeout / 60, POLL_INTERVAL_MAX), POLL_INTERVAL_MIN)
|
|
237
|
+
retry_count = math.ceil(timeout / POLL_INTERVAL)
|
|
238
|
+
count = 0
|
|
239
|
+
while count < retry_count:
|
|
240
|
+
await asyncio.sleep(POLL_INTERVAL)
|
|
241
|
+
try:
|
|
242
|
+
await self.refresh()
|
|
243
|
+
except ApiError as e:
|
|
244
|
+
if status == "archived" and e.status == 404:
|
|
245
|
+
return
|
|
246
|
+
raise e
|
|
247
|
+
if self.status == status:
|
|
248
|
+
return self
|
|
249
|
+
elif self.status == "failed":
|
|
250
|
+
raise VolumeError(self.status, self)
|
|
251
|
+
else:
|
|
252
|
+
count += 1
|
|
253
|
+
logging.debug(f"self: {self}, retry count {count}")
|
|
254
|
+
|
|
255
|
+
raise ProxiMLException(f"Timeout waiting for {status}")
|
|
@@ -2,20 +2,21 @@ 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=3gq9ZsAAIsysDGCFRY4rF-eaMA6i7uPpZDDQ39rjBKM,432
|
|
6
6
|
proximl/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
|
|
7
7
|
proximl/auth.py,sha256=LacGBDAVel5HcJx7JXp1wVn3s0gZc7nf--vDfSFOXYU,26565
|
|
8
8
|
proximl/checkpoints.py,sha256=JPSq9iTSb6dBu0JFKJZgMqDBt9kSGxfl2OaxKehk4QY,8274
|
|
9
9
|
proximl/connections.py,sha256=0C8VJSkDturQVlzg_3yAkQz8W9NAmgzDCBRK_SrzeUI,20035
|
|
10
10
|
proximl/datasets.py,sha256=RB7IiDkWZPuwFw1o0xKQn10HS_3ui_BZf7tVlAvCIHA,7935
|
|
11
11
|
proximl/environments.py,sha256=L_cRmau1wJxpGvnJAqgms-GiNdDhiuOntrlBqsdoE3A,1507
|
|
12
|
-
proximl/exceptions.py,sha256=
|
|
12
|
+
proximl/exceptions.py,sha256=3q8qj-sahwEEHeYyjY89GrytVAgHDApN8HSCB3dhnf4,4094
|
|
13
13
|
proximl/gpu_types.py,sha256=V-EZzE-hDLi5eVQ2_9yGLTm8-Qk1AnnzctfSVC44yLY,1901
|
|
14
|
-
proximl/jobs.py,sha256=
|
|
14
|
+
proximl/jobs.py,sha256=kQbC9ojrMEvRAysFfftwLdVtNDjpiFBfji4bz1To82E,17838
|
|
15
15
|
proximl/models.py,sha256=QLtkIoznww5cZE_Eqoy7IaEgCEfNbJOe_21hdoExO-k,7750
|
|
16
16
|
proximl/projects.py,sha256=87Fsce8BkPJLN_cEsZ3FLequpBgmd8WtjY7Vs7uZkO4,5209
|
|
17
|
-
proximl/proximl.py,sha256=
|
|
18
|
-
proximl/
|
|
17
|
+
proximl/proximl.py,sha256=yzKbJ7ak8YiVv6q7CEagPufECNU4YTNeul6N3OuyH1M,10864
|
|
18
|
+
proximl/volumes.py,sha256=VnlgTeQwoDKGpZRhMrSKtxe4EWzb4qQ-OdDyaJohH5Y,8094
|
|
19
|
+
proximl/cli/__init__.py,sha256=R_8ExAKQp67N2xtwGM00gtK3zSoWPGruHAP_XFLddSI,4346
|
|
19
20
|
proximl/cli/checkpoint.py,sha256=Iv1i1EAt2LJey2wy2ioQ6-ZysqwRG4kFj0lnE6INZCM,7170
|
|
20
21
|
proximl/cli/connection.py,sha256=YiWqRIB9ZfTl30DjDFaJEpXuDrA-Ldl9PEzFFdZ_hFI,1700
|
|
21
22
|
proximl/cli/dataset.py,sha256=ueoeicBY8aMLpvpKUIBICnS9GsEnDOj7ZlFmOfjjY4c,6871
|
|
@@ -23,6 +24,7 @@ proximl/cli/environment.py,sha256=nh7oYbG5oOrZEpZkMkKgvzFXmQJWnFTMw1-YbuvkdFU,10
|
|
|
23
24
|
proximl/cli/gpu.py,sha256=xL8eqM5ca_Ueaj8cWit1iKn34KhaR0StrubVeRU2YQY,883
|
|
24
25
|
proximl/cli/model.py,sha256=xdjveIaRPK7MdfrnFygPEuwYRJRW9VqheZ-11XnXDcE,6111
|
|
25
26
|
proximl/cli/project.py,sha256=Er1twSiWQSAKir-hBIT9fRo2fc_UGqFoIJOwwjQGmlo,3522
|
|
27
|
+
proximl/cli/volume.py,sha256=uyIrKov4zwCjyLyZrEJYoEbIkS0zdU3xSyWZk2BM1kA,6246
|
|
26
28
|
proximl/cli/cloudbender/__init__.py,sha256=vxj62MyM3sC9h8M4ii3szH4s9JvEhicOQ0D0m7eNwPA,534
|
|
27
29
|
proximl/cli/cloudbender/datastore.py,sha256=_vQOj-NfrL_nj4HfxNJL63TJZjLgfDyztRLyaRU58v8,3478
|
|
28
30
|
proximl/cli/cloudbender/device.py,sha256=FdQZPESP6YBfUSzXq1Byu7eNMKi59qSOICONK-TEljI,3453
|
|
@@ -30,6 +32,7 @@ proximl/cli/cloudbender/node.py,sha256=xxzj68YvpRey2vZQasgYTnwv3x7TnwpuPSSf8Ma5a
|
|
|
30
32
|
proximl/cli/cloudbender/provider.py,sha256=qhWbDK1tWi00wQWEYqGw7yGoZx0nEjV40GLHRuuE86c,1726
|
|
31
33
|
proximl/cli/cloudbender/region.py,sha256=WnSkY4dXKRJ-FNaoxMfmoh6iuUx5dXCNJmEFT34Xtao,2892
|
|
32
34
|
proximl/cli/cloudbender/reservation.py,sha256=xzHs5l8BbmYgKUq6kfFU-jEtRQY0j_vYnmRVcL4wwDo,3569
|
|
35
|
+
proximl/cli/cloudbender/service.py,sha256=YimwXQml82-PKKQIqu9Cvggo9wOKLVTJPxMvDf4pgn8,2869
|
|
33
36
|
proximl/cli/job/__init__.py,sha256=s8mU2PvCWDcv4gGT3EmjHn8MIZlXBAoayoZKmnKpXnY,6545
|
|
34
37
|
proximl/cli/job/create.py,sha256=sGvbenY0yxvxHo-FZVbdw8FaZx5D4ekTCjD7P4YHG4g,34288
|
|
35
38
|
proximl/cloudbender/__init__.py,sha256=iE29obtC0_9f0IhRvHQcG5aY58fVhVYipTakpjAhdss,64
|
|
@@ -41,19 +44,21 @@ proximl/cloudbender/nodes.py,sha256=QeWUaWW1HNvCune1lhakcve6LJyMzOy7cjCtvyOiaTs,
|
|
|
41
44
|
proximl/cloudbender/providers.py,sha256=cH5lCew5WCFpXYS93vuoGNWkZyx7T_mdMH6YNWp2QGs,2036
|
|
42
45
|
proximl/cloudbender/regions.py,sha256=Nu1LT6nuLD8Nt-5-7_FLlxDNZoDDAY6QduTdEBqfxJA,3570
|
|
43
46
|
proximl/cloudbender/reservations.py,sha256=14ImJRLWQGG7CXDYhDnOI2W8pnP6CVVG2aVpysQVN0E,3586
|
|
47
|
+
proximl/cloudbender/services.py,sha256=KR0EQ-BUVk1ov9Q195NsnfiSiMb9vOYD8XyxD87RRvw,3222
|
|
44
48
|
tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
49
|
tests/integration/conftest.py,sha256=zRWpherX-yfbpk7xqZk9bIZCyJ-dwVeszY_7kekn2M4,1134
|
|
46
|
-
tests/integration/test_checkpoints_integration.py,sha256=
|
|
47
|
-
tests/integration/test_datasets_integration.py,sha256=
|
|
50
|
+
tests/integration/test_checkpoints_integration.py,sha256=DUA6ZlX0gsnKCEvFmWCKbKkfJG3dQG6uD8T3fgo7y0M,3230
|
|
51
|
+
tests/integration/test_datasets_integration.py,sha256=Ndp8itnncDSjVH0t5BM5R_-_yL4qt6JrkQAVOTMi1R8,3499
|
|
48
52
|
tests/integration/test_environments_integration.py,sha256=7P6pKSyxA7rTwyNCD9HEaM2ablMG8WcBesOzkG-BgsQ,1403
|
|
49
53
|
tests/integration/test_gpu_types_integration.py,sha256=Zv-yrHcAgKau9BJQvzi92bdrRHhLPl_hbhhzNLEWJ9w,1256
|
|
50
|
-
tests/integration/test_jobs_integration.py,sha256=
|
|
51
|
-
tests/integration/test_models_integration.py,sha256=
|
|
52
|
-
tests/integration/test_projects_integration.py,sha256=
|
|
54
|
+
tests/integration/test_jobs_integration.py,sha256=fMqYfdzHOxUxtRGe0XZlsFhjUGoeo0Prxbq5_yQvroc,24730
|
|
55
|
+
tests/integration/test_models_integration.py,sha256=JFgX6V652iMobpNKfn0XdP9_TDg5pnVtG8lL6yrEOZk,2902
|
|
56
|
+
tests/integration/test_projects_integration.py,sha256=XdGGqvOQby77kMfoD_zAukKvN6tN7v1QydZ4hFp7u_s,1446
|
|
57
|
+
tests/integration/test_volumes_integration.py,sha256=Xo2Whw2U-7jyvESIkyex3f0SMXKlExe3kLmsbpXTHhQ,3270
|
|
53
58
|
tests/integration/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
59
|
tests/integration/cloudbender/test_providers_integration.py,sha256=gFqPQom-Cn1iZC50_ChQ2us2_f4tIPATQSAUcWdf7ss,1473
|
|
55
60
|
tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
|
-
tests/unit/conftest.py,sha256=
|
|
61
|
+
tests/unit/conftest.py,sha256=dilDp7ZSmyg9tJbkJoGcqUF07JBTdkNoULOR6qK7NsU,31197
|
|
57
62
|
tests/unit/test_auth.py,sha256=IJZHT5CZLNfu3MybTdEWIsKlvxNfFYpZ6oWYzbS856g,858
|
|
58
63
|
tests/unit/test_checkpoints_unit.py,sha256=WfFE6HvQHglBKfcJ3B0IYXdXpaNmcL2BDurRQmIbFAU,16008
|
|
59
64
|
tests/unit/test_connections_unit.py,sha256=LFAZzlrvL9oM8rZJTiC1oA9quw1KA2vMUCc3LV6SjXs,5507
|
|
@@ -65,6 +70,7 @@ tests/unit/test_jobs_unit.py,sha256=kCrm2FdogqYgji0Coe9KGaODYEsXmvuV46FN2mUE1Ts,
|
|
|
65
70
|
tests/unit/test_models_unit.py,sha256=wgwdvJLt6QZ_IKCvp6Kmt2Z4b_CvIN8KCuFqlBaxDe8,15064
|
|
66
71
|
tests/unit/test_projects_unit.py,sha256=_SwPFhy2Xl6HRt6w5_HYrqtZJHAzFzoCZ3kqAS1Zn3c,9530
|
|
67
72
|
tests/unit/test_proximl.py,sha256=E-er5V1O-4ZVfTO2R-2wGsKN80q0_BVG5ho9Ab9lnE4,1701
|
|
73
|
+
tests/unit/test_volumes_unit.py,sha256=YcyrGPCq7XfAtCdKdp44723o7mLGIu5_hKOdhAFVYV4,15305
|
|
68
74
|
tests/unit/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
75
|
tests/unit/cli/conftest.py,sha256=w6p_2URicywJKUCtY79tSD_mx8cwJtxHbK_Lu3grOYs,236
|
|
70
76
|
tests/unit/cli/test_cli_checkpoint_unit.py,sha256=Z4v80C2gqzRXdHons3_82lNC_VqL_YOt53HrUQnzReI,702
|
|
@@ -74,6 +80,7 @@ tests/unit/cli/test_cli_gpu_unit.py,sha256=xdwIZEZJcJlWSuLBEcLhZXXH9EogZoKRiJlMR
|
|
|
74
80
|
tests/unit/cli/test_cli_job_unit.py,sha256=UioSx_ZRY0qh4AJf90rIepputVUPBZ0KSqNR_u592UY,611
|
|
75
81
|
tests/unit/cli/test_cli_model_unit.py,sha256=AucngxvYjW6GidDGBPHnKyYOb82ff7xMX5mVVUcbrCA,629
|
|
76
82
|
tests/unit/cli/test_cli_project_unit.py,sha256=ms9gJ8pgMNGeIMdFcvBcwSPmb0i2qo9-rk9CCF53-9M,1756
|
|
83
|
+
tests/unit/cli/test_cli_volume_unit.py,sha256=VeYb9PHuD45E23JCybF-C7ZaHsuERw7me2OUmYnmAAg,644
|
|
77
84
|
tests/unit/cli/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
85
|
tests/unit/cli/cloudbender/test_cli_datastore_unit.py,sha256=sUZWnzzCCG7NdgzEHsQ2zHpEb-ZD6FDIazca6FqMOc0,1381
|
|
79
86
|
tests/unit/cli/cloudbender/test_cli_device_unit.py,sha256=o1vrPlbaanYK1iJG5pE6tDwgmOXDuLUY0VH8DxtaPYI,1342
|
|
@@ -89,9 +96,10 @@ tests/unit/cloudbender/test_nodes_unit.py,sha256=ehOHkNroiLKNTR09SbnBPpwELE72GcG
|
|
|
89
96
|
tests/unit/cloudbender/test_providers_unit.py,sha256=y63VCqHXb4Yu8sh0kW30-ojRvv9aUa5j1jNkmb46KTc,4373
|
|
90
97
|
tests/unit/cloudbender/test_regions_unit.py,sha256=9bvP268gpNyygjh1IEpSSiUt2aP6okv7QOsV1XoaIS0,6299
|
|
91
98
|
tests/unit/cloudbender/test_reservations_unit.py,sha256=ICuFT5sexnLvS7taoC18yQYuDZHpBRrNuCj3Uq_Arwo,5624
|
|
92
|
-
|
|
93
|
-
proximl-0.5.
|
|
94
|
-
proximl-0.5.
|
|
95
|
-
proximl-0.5.
|
|
96
|
-
proximl-0.5.
|
|
97
|
-
proximl-0.5.
|
|
99
|
+
tests/unit/cloudbender/test_services_unit.py,sha256=iYaQpyCXDg77GQEIhmgiVwKX83jyvIf-4-4oya5WA_o,5043
|
|
100
|
+
proximl-0.5.6.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
|
|
101
|
+
proximl-0.5.6.dist-info/METADATA,sha256=wS2y66puGjq7ZkG2GsyFIJWCiSjbDiA5uUdA8PzlIqw,7344
|
|
102
|
+
proximl-0.5.6.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
103
|
+
proximl-0.5.6.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
|
|
104
|
+
proximl-0.5.6.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
|
|
105
|
+
proximl-0.5.6.dist-info/RECORD,,
|
|
@@ -54,18 +54,20 @@ class GetCheckpointTests:
|
|
|
54
54
|
|
|
55
55
|
@mark.create
|
|
56
56
|
@mark.asyncio
|
|
57
|
-
async def
|
|
57
|
+
async def test_checkpoint_wasabi(proximl, capsys):
|
|
58
58
|
checkpoint = await proximl.checkpoints.create(
|
|
59
|
-
name="CLI Automated
|
|
60
|
-
source_type="
|
|
61
|
-
source_uri="s3://proximl-
|
|
59
|
+
name="CLI Automated Wasabi",
|
|
60
|
+
source_type="wasabi",
|
|
61
|
+
source_uri="s3://proximl-example/models/proximl-examples",
|
|
62
|
+
capacity="10G",
|
|
63
|
+
source_options=dict(endpoint_url="https://s3.wasabisys.com"),
|
|
62
64
|
)
|
|
63
65
|
checkpoint = await checkpoint.wait_for("ready", 300)
|
|
64
66
|
status = checkpoint.status
|
|
65
67
|
size = checkpoint.size
|
|
66
68
|
await checkpoint.remove()
|
|
67
69
|
assert status == "ready"
|
|
68
|
-
assert size >=
|
|
70
|
+
assert size >= 500000
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
@mark.create
|
|
@@ -13,8 +13,9 @@ class GetDatasetTests:
|
|
|
13
13
|
async def dataset(self, proximl):
|
|
14
14
|
dataset = await proximl.datasets.create(
|
|
15
15
|
name="CLI Automated",
|
|
16
|
-
source_type="
|
|
17
|
-
source_uri="s3://proximl-
|
|
16
|
+
source_type="wasabi",
|
|
17
|
+
source_uri="s3://proximl-example/input/cifar-10/cifar-10-batches-bin",
|
|
18
|
+
source_options=dict(endpoint_url="https://s3.wasabisys.com"),
|
|
18
19
|
)
|
|
19
20
|
dataset = await dataset.wait_for("ready", 300)
|
|
20
21
|
yield dataset
|
|
@@ -48,9 +49,7 @@ class GetDatasetTests:
|
|
|
48
49
|
async def test_dataset_repr(self, dataset):
|
|
49
50
|
string = repr(dataset)
|
|
50
51
|
regex = (
|
|
51
|
-
r"^Dataset\( proximl , \*\*{.*'dataset_uuid': '"
|
|
52
|
-
+ dataset.id
|
|
53
|
-
+ r"'.*}\)$"
|
|
52
|
+
r"^Dataset\( proximl , \*\*{.*'dataset_uuid': '" + dataset.id + r"'.*}\)$"
|
|
54
53
|
)
|
|
55
54
|
assert isinstance(string, str)
|
|
56
55
|
assert re.match(regex, string)
|