proximl 0.5.3__py3-none-any.whl → 0.5.5__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/job/create.py +3 -3
- proximl/cli/volume.py +235 -0
- proximl/exceptions.py +21 -12
- proximl/jobs.py +36 -39
- proximl/proximl.py +7 -15
- proximl/volumes.py +255 -0
- {proximl-0.5.3.dist-info → proximl-0.5.5.dist-info}/METADATA +1 -1
- {proximl-0.5.3.dist-info → proximl-0.5.5.dist-info}/RECORD +23 -18
- 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/conftest.py +82 -9
- tests/unit/test_volumes_unit.py +447 -0
- {proximl-0.5.3.dist-info → proximl-0.5.5.dist-info}/LICENSE +0 -0
- {proximl-0.5.3.dist-info → proximl-0.5.5.dist-info}/WHEEL +0 -0
- {proximl-0.5.3.dist-info → proximl-0.5.5.dist-info}/entry_points.txt +0 -0
- {proximl-0.5.3.dist-info → proximl-0.5.5.dist-info}/top_level.txt +0 -0
proximl/__init__.py
CHANGED
proximl/cli/__init__.py
CHANGED
|
@@ -142,9 +142,7 @@ def configure(config):
|
|
|
142
142
|
project for project in projects if project.id == active_project_id
|
|
143
143
|
]
|
|
144
144
|
|
|
145
|
-
active_project_name = (
|
|
146
|
-
active_project[0].name if len(active_project) else "UNSET"
|
|
147
|
-
)
|
|
145
|
+
active_project_name = active_project[0].name if len(active_project) else "UNSET"
|
|
148
146
|
|
|
149
147
|
click.echo(f"Current Active Project: {active_project_name}")
|
|
150
148
|
|
|
@@ -154,9 +152,7 @@ def configure(config):
|
|
|
154
152
|
show_choices=True,
|
|
155
153
|
default=active_project_name,
|
|
156
154
|
)
|
|
157
|
-
selected_project = [
|
|
158
|
-
project for project in projects if project.name == name
|
|
159
|
-
]
|
|
155
|
+
selected_project = [project for project in projects if project.name == name]
|
|
160
156
|
config.proximl.client.set_active_project(selected_project[0].id)
|
|
161
157
|
|
|
162
158
|
|
|
@@ -164,6 +160,7 @@ from proximl.cli.connection import connection
|
|
|
164
160
|
from proximl.cli.dataset import dataset
|
|
165
161
|
from proximl.cli.model import model
|
|
166
162
|
from proximl.cli.checkpoint import checkpoint
|
|
163
|
+
from proximl.cli.volume import volume
|
|
167
164
|
from proximl.cli.environment import environment
|
|
168
165
|
from proximl.cli.gpu import gpu
|
|
169
166
|
from proximl.cli.job import job
|
proximl/cli/job/create.py
CHANGED
|
@@ -389,7 +389,7 @@ def notebook(
|
|
|
389
389
|
],
|
|
390
390
|
case_sensitive=False,
|
|
391
391
|
),
|
|
392
|
-
default="rtx3090",
|
|
392
|
+
default=["rtx3090"],
|
|
393
393
|
multiple=True,
|
|
394
394
|
show_default=True,
|
|
395
395
|
help="GPU type.",
|
|
@@ -732,7 +732,7 @@ def training(
|
|
|
732
732
|
],
|
|
733
733
|
case_sensitive=False,
|
|
734
734
|
),
|
|
735
|
-
default="rtx3090",
|
|
735
|
+
default=["rtx3090"],
|
|
736
736
|
show_default=True,
|
|
737
737
|
multiple=True,
|
|
738
738
|
help="GPU type.",
|
|
@@ -1099,7 +1099,7 @@ def from_json(config, attach, connect, file):
|
|
|
1099
1099
|
],
|
|
1100
1100
|
case_sensitive=False,
|
|
1101
1101
|
),
|
|
1102
|
-
default="rtx3090",
|
|
1102
|
+
default=["rtx3090"],
|
|
1103
1103
|
show_default=True,
|
|
1104
1104
|
multiple=True,
|
|
1105
1105
|
help="GPU type.",
|
proximl/cli/volume.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from proximl.cli import cli, pass_config, search_by_id_name
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def pretty_size(num):
|
|
6
|
+
if not num:
|
|
7
|
+
num = 0.0
|
|
8
|
+
s = (" B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
|
9
|
+
n = 0
|
|
10
|
+
while num > 1023:
|
|
11
|
+
num = num / 1024
|
|
12
|
+
n += 1
|
|
13
|
+
return f"{num:.2f} {s[n]}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cli.group()
|
|
17
|
+
@pass_config
|
|
18
|
+
def volume(config):
|
|
19
|
+
"""proxiML volume commands."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@volume.command()
|
|
24
|
+
@click.argument("volume", type=click.STRING)
|
|
25
|
+
@pass_config
|
|
26
|
+
def attach(config, volume):
|
|
27
|
+
"""
|
|
28
|
+
Attach to volume and show creation logs.
|
|
29
|
+
|
|
30
|
+
VOLUME may be specified by name or ID, but ID is preferred.
|
|
31
|
+
"""
|
|
32
|
+
volumes = config.proximl.run(config.proximl.client.volumes.list())
|
|
33
|
+
|
|
34
|
+
found = search_by_id_name(volume, volumes)
|
|
35
|
+
if None is found:
|
|
36
|
+
raise click.UsageError("Cannot find specified volume.")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
config.proximl.run(found.attach())
|
|
40
|
+
return config.proximl.run(found.disconnect())
|
|
41
|
+
except:
|
|
42
|
+
try:
|
|
43
|
+
config.proximl.run(found.disconnect())
|
|
44
|
+
except:
|
|
45
|
+
pass
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@volume.command()
|
|
50
|
+
@click.option(
|
|
51
|
+
"--attach/--no-attach",
|
|
52
|
+
default=True,
|
|
53
|
+
show_default=True,
|
|
54
|
+
help="Auto attach to volume and show creation logs.",
|
|
55
|
+
)
|
|
56
|
+
@click.argument("volume", type=click.STRING)
|
|
57
|
+
@pass_config
|
|
58
|
+
def connect(config, volume, attach):
|
|
59
|
+
"""
|
|
60
|
+
Connect local source to volume and begin upload.
|
|
61
|
+
|
|
62
|
+
VOLUME may be specified by name or ID, but ID is preferred.
|
|
63
|
+
"""
|
|
64
|
+
volumes = config.proximl.run(config.proximl.client.volumes.list())
|
|
65
|
+
|
|
66
|
+
found = search_by_id_name(volume, volumes)
|
|
67
|
+
if None is found:
|
|
68
|
+
raise click.UsageError("Cannot find specified volume.")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
if attach:
|
|
72
|
+
config.proximl.run(found.connect(), found.attach())
|
|
73
|
+
return config.proximl.run(found.disconnect())
|
|
74
|
+
else:
|
|
75
|
+
return config.proximl.run(found.connect())
|
|
76
|
+
except:
|
|
77
|
+
try:
|
|
78
|
+
config.proximl.run(found.disconnect())
|
|
79
|
+
except:
|
|
80
|
+
pass
|
|
81
|
+
raise
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@volume.command()
|
|
85
|
+
@click.option(
|
|
86
|
+
"--attach/--no-attach",
|
|
87
|
+
default=True,
|
|
88
|
+
show_default=True,
|
|
89
|
+
help="Auto attach to volume and show creation logs.",
|
|
90
|
+
)
|
|
91
|
+
@click.option(
|
|
92
|
+
"--connect/--no-connect",
|
|
93
|
+
default=True,
|
|
94
|
+
show_default=True,
|
|
95
|
+
help="Auto connect source and start volume creation.",
|
|
96
|
+
)
|
|
97
|
+
@click.option(
|
|
98
|
+
"--source",
|
|
99
|
+
"-s",
|
|
100
|
+
type=click.Choice(["local"], case_sensitive=False),
|
|
101
|
+
default="local",
|
|
102
|
+
show_default=True,
|
|
103
|
+
help="Dataset source type.",
|
|
104
|
+
)
|
|
105
|
+
@click.argument("name", type=click.STRING)
|
|
106
|
+
@click.argument("capacity", type=click.INT)
|
|
107
|
+
@click.argument(
|
|
108
|
+
"path", type=click.Path(exists=True, file_okay=False, resolve_path=True)
|
|
109
|
+
)
|
|
110
|
+
@pass_config
|
|
111
|
+
def create(config, attach, connect, source, name, capacity, path):
|
|
112
|
+
"""
|
|
113
|
+
Create a volume.
|
|
114
|
+
|
|
115
|
+
A volume with maximum size CAPACITY is created with the specified NAME using a local source at the PATH
|
|
116
|
+
specified. PATH should be a local directory containing the source data for
|
|
117
|
+
a local source or a URI for all other source types.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
if source == "local":
|
|
121
|
+
volume = config.proximl.run(
|
|
122
|
+
config.proximl.client.volumes.create(
|
|
123
|
+
name=name, source_type="local", source_uri=path, capacity=capacity
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
if connect and attach:
|
|
129
|
+
config.proximl.run(volume.attach(), volume.connect())
|
|
130
|
+
return config.proximl.run(volume.disconnect())
|
|
131
|
+
elif connect:
|
|
132
|
+
return config.proximl.run(volume.connect())
|
|
133
|
+
else:
|
|
134
|
+
raise click.UsageError(
|
|
135
|
+
"Abort!\n"
|
|
136
|
+
"No logs to show for local sourced volume without connect."
|
|
137
|
+
)
|
|
138
|
+
except:
|
|
139
|
+
try:
|
|
140
|
+
config.proximl.run(volume.disconnect())
|
|
141
|
+
except:
|
|
142
|
+
pass
|
|
143
|
+
raise
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@volume.command()
|
|
147
|
+
@click.argument("volume", type=click.STRING)
|
|
148
|
+
@pass_config
|
|
149
|
+
def disconnect(config, volume):
|
|
150
|
+
"""
|
|
151
|
+
Disconnect and clean-up volume upload.
|
|
152
|
+
|
|
153
|
+
VOLUME may be specified by name or ID, but ID is preferred.
|
|
154
|
+
"""
|
|
155
|
+
volumes = config.proximl.run(config.proximl.client.volumes.list())
|
|
156
|
+
|
|
157
|
+
found = search_by_id_name(volume, volumes)
|
|
158
|
+
if None is found:
|
|
159
|
+
raise click.UsageError("Cannot find specified volume.")
|
|
160
|
+
|
|
161
|
+
return config.proximl.run(found.disconnect())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@volume.command()
|
|
165
|
+
@pass_config
|
|
166
|
+
def list(config):
|
|
167
|
+
"""List volumes."""
|
|
168
|
+
data = [
|
|
169
|
+
["ID", "STATUS", "NAME", "CAPACITY"],
|
|
170
|
+
["-" * 80, "-" * 80, "-" * 80, "-" * 80],
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
volumes = config.proximl.run(config.proximl.client.volumes.list())
|
|
174
|
+
|
|
175
|
+
for volume in volumes:
|
|
176
|
+
data.append(
|
|
177
|
+
[
|
|
178
|
+
volume.id,
|
|
179
|
+
volume.status,
|
|
180
|
+
volume.name,
|
|
181
|
+
volume.capacity,
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
for row in data:
|
|
185
|
+
click.echo(
|
|
186
|
+
"{: >38.36} {: >13.11} {: >40.38} {: >14.12}" "".format(*row),
|
|
187
|
+
file=config.stdout,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@volume.command()
|
|
192
|
+
@click.option(
|
|
193
|
+
"--force/--no-force",
|
|
194
|
+
default=False,
|
|
195
|
+
show_default=True,
|
|
196
|
+
help="Force removal.",
|
|
197
|
+
)
|
|
198
|
+
@click.argument("volume", type=click.STRING)
|
|
199
|
+
@pass_config
|
|
200
|
+
def remove(config, volume, force):
|
|
201
|
+
"""
|
|
202
|
+
Remove a volume.
|
|
203
|
+
|
|
204
|
+
VOLUME may be specified by name or ID, but ID is preferred.
|
|
205
|
+
"""
|
|
206
|
+
volumes = config.proximl.run(config.proximl.client.volumes.list())
|
|
207
|
+
|
|
208
|
+
found = search_by_id_name(volume, volumes)
|
|
209
|
+
if None is found:
|
|
210
|
+
if force:
|
|
211
|
+
config.proximl.run(found.client.volumes.remove(volume))
|
|
212
|
+
else:
|
|
213
|
+
raise click.UsageError("Cannot find specified volume.")
|
|
214
|
+
|
|
215
|
+
return config.proximl.run(found.remove(force=force))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@volume.command()
|
|
219
|
+
@click.argument("volume", type=click.STRING)
|
|
220
|
+
@click.argument("name", type=click.STRING)
|
|
221
|
+
@pass_config
|
|
222
|
+
def rename(config, volume, name):
|
|
223
|
+
"""
|
|
224
|
+
Renames a volume.
|
|
225
|
+
|
|
226
|
+
VOLUME may be specified by name or ID, but ID is preferred.
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
volume = config.proximl.run(config.proximl.client.volumes.get(volume))
|
|
230
|
+
if volume is None:
|
|
231
|
+
raise click.UsageError("Cannot find specified volume.")
|
|
232
|
+
except:
|
|
233
|
+
raise click.UsageError("Cannot find specified volume.")
|
|
234
|
+
|
|
235
|
+
return config.proximl.run(volume.rename(name=name))
|
proximl/exceptions.py
CHANGED
|
@@ -97,14 +97,27 @@ class CheckpointError(ProxiMLException):
|
|
|
97
97
|
return self._status
|
|
98
98
|
|
|
99
99
|
def __repr__(self):
|
|
100
|
-
return "CheckpointError({self.status}, {self.message})".format(
|
|
101
|
-
self=self
|
|
102
|
-
)
|
|
100
|
+
return "CheckpointError({self.status}, {self.message})".format(self=self)
|
|
103
101
|
|
|
104
102
|
def __str__(self):
|
|
105
|
-
return "CheckpointError({self.status}, {self.message})".format(
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
return "CheckpointError({self.status}, {self.message})".format(self=self)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class VolumeError(ProxiMLException):
|
|
107
|
+
def __init__(self, status, data, *args):
|
|
108
|
+
super().__init__(data, *args)
|
|
109
|
+
self._status = status
|
|
110
|
+
self._message = data
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def status(self) -> str:
|
|
114
|
+
return self._status
|
|
115
|
+
|
|
116
|
+
def __repr__(self):
|
|
117
|
+
return "VolumeError({self.status}, {self.message})".format(self=self)
|
|
118
|
+
|
|
119
|
+
def __str__(self):
|
|
120
|
+
return "VolumeError({self.status}, {self.message})".format(self=self)
|
|
108
121
|
|
|
109
122
|
|
|
110
123
|
class ConnectionError(ProxiMLException):
|
|
@@ -130,11 +143,7 @@ class SpecificationError(ProxiMLException):
|
|
|
130
143
|
return self._attribute
|
|
131
144
|
|
|
132
145
|
def __repr__(self):
|
|
133
|
-
return "SpecificationError({self.attribute}, {self.message})".format(
|
|
134
|
-
self=self
|
|
135
|
-
)
|
|
146
|
+
return "SpecificationError({self.attribute}, {self.message})".format(self=self)
|
|
136
147
|
|
|
137
148
|
def __str__(self):
|
|
138
|
-
return "SpecificationError({self.attribute}, {self.message})".format(
|
|
139
|
-
self=self
|
|
140
|
-
)
|
|
149
|
+
return "SpecificationError({self.attribute}, {self.message})".format(self=self)
|
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):
|