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 CHANGED
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.4"
16
+ __version__ = "0.5.5"
17
17
  __all__ = "ProxiML"
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
@@ -0,0 +1,129 @@
1
+ import click
2
+ from proximl.cli import cli, pass_config, search_by_id_name
3
+ from proximl.cli.cloudbender import cloudbender
4
+
5
+
6
+ @cloudbender.group()
7
+ @pass_config
8
+ def service(config):
9
+ """proxiML CloudBender service commands."""
10
+ pass
11
+
12
+
13
+ @service.command()
14
+ @click.option(
15
+ "--provider",
16
+ "-p",
17
+ type=click.STRING,
18
+ required=True,
19
+ help="The provider ID of the region.",
20
+ )
21
+ @click.option(
22
+ "--region",
23
+ "-r",
24
+ type=click.STRING,
25
+ required=True,
26
+ help="The region ID to list services for.",
27
+ )
28
+ @pass_config
29
+ def list(config, provider, region):
30
+ """List services."""
31
+ data = [
32
+ ["ID", "NAME", "HOSTNAME"],
33
+ [
34
+ "-" * 80,
35
+ "-" * 80,
36
+ "-" * 80,
37
+ ],
38
+ ]
39
+
40
+ services = config.proximl.run(
41
+ config.proximl.client.cloudbender.services.list(
42
+ provider_uuid=provider, region_uuid=region
43
+ )
44
+ )
45
+
46
+ for service in services:
47
+ data.append(
48
+ [
49
+ service.id,
50
+ service.name,
51
+ service.hostname,
52
+ ]
53
+ )
54
+
55
+ for row in data:
56
+ click.echo(
57
+ "{: >25.24} {: >29.28} {: >40.39}" "".format(*row),
58
+ file=config.stdout,
59
+ )
60
+
61
+
62
+ @service.command()
63
+ @click.option(
64
+ "--provider",
65
+ "-p",
66
+ type=click.STRING,
67
+ required=True,
68
+ help="The provider ID of the region.",
69
+ )
70
+ @click.option(
71
+ "--region",
72
+ "-r",
73
+ type=click.STRING,
74
+ required=True,
75
+ help="The region ID to create the service in.",
76
+ )
77
+ @click.option(
78
+ "--public/--no-public",
79
+ default=True,
80
+ show_default=True,
81
+ help="Service should be accessible from the public internet.",
82
+ )
83
+ @click.argument("name", type=click.STRING, required=True)
84
+ @pass_config
85
+ def create(config, provider, region, public, name):
86
+ """
87
+ Creates a service.
88
+ """
89
+ return config.proximl.run(
90
+ config.proximl.client.cloudbender.services.create(
91
+ provider_uuid=provider, region_uuid=region, name=name, public=public
92
+ )
93
+ )
94
+
95
+
96
+ @service.command()
97
+ @click.option(
98
+ "--provider",
99
+ "-p",
100
+ type=click.STRING,
101
+ required=True,
102
+ help="The provider ID of the region.",
103
+ )
104
+ @click.option(
105
+ "--region",
106
+ "-r",
107
+ type=click.STRING,
108
+ required=True,
109
+ help="The region ID to remove the service from.",
110
+ )
111
+ @click.argument("service", type=click.STRING)
112
+ @pass_config
113
+ def remove(config, provider, region, service):
114
+ """
115
+ Remove a service.
116
+
117
+ RESERVATION may be specified by name or ID, but ID is preferred.
118
+ """
119
+ services = config.proximl.run(
120
+ config.proximl.client.cloudbender.services.list(
121
+ provider_uuid=provider, region_uuid=region
122
+ )
123
+ )
124
+
125
+ found = search_by_id_name(service, services)
126
+ if None is found:
127
+ raise click.UsageError("Cannot find specified service.")
128
+
129
+ return config.proximl.run(found.remove())
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))
@@ -0,0 +1,115 @@
1
+ import json
2
+ import logging
3
+
4
+
5
+ class Services(object):
6
+ def __init__(self, proximl):
7
+ self.proximl = proximl
8
+
9
+ async def get(self, provider_uuid, region_uuid, id, **kwargs):
10
+ resp = await self.proximl._query(
11
+ f"/provider/{provider_uuid}/region/{region_uuid}/service/{id}",
12
+ "GET",
13
+ kwargs,
14
+ )
15
+ return Service(self.proximl, **resp)
16
+
17
+ async def list(self, provider_uuid, region_uuid, **kwargs):
18
+ resp = await self.proximl._query(
19
+ f"/provider/{provider_uuid}/region/{region_uuid}/service",
20
+ "GET",
21
+ kwargs,
22
+ )
23
+ services = [Service(self.proximl, **service) for service in resp]
24
+ return services
25
+
26
+ async def create(
27
+ self,
28
+ provider_uuid,
29
+ region_uuid,
30
+ name,
31
+ public,
32
+ **kwargs,
33
+ ):
34
+ logging.info(f"Creating Service {name}")
35
+ data = dict(
36
+ name=name,
37
+ public=public,
38
+ **kwargs,
39
+ )
40
+ payload = {k: v for k, v in data.items() if v is not None}
41
+ resp = await self.proximl._query(
42
+ f"/provider/{provider_uuid}/region/{region_uuid}/service",
43
+ "POST",
44
+ None,
45
+ payload,
46
+ )
47
+ service = Service(self.proximl, **resp)
48
+ logging.info(f"Created Service {name} with id {service.id}")
49
+ return service
50
+
51
+ async def remove(self, provider_uuid, region_uuid, id, **kwargs):
52
+ await self.proximl._query(
53
+ f"/provider/{provider_uuid}/region/{region_uuid}/service/{id}",
54
+ "DELETE",
55
+ kwargs,
56
+ )
57
+
58
+
59
+ class Service:
60
+ def __init__(self, proximl, **kwargs):
61
+ self.proximl = proximl
62
+ self._service = kwargs
63
+ self._id = self._service.get("service_id")
64
+ self._provider_uuid = self._service.get("provider_uuid")
65
+ self._region_uuid = self._service.get("region_uuid")
66
+ self._public = self._service.get("public")
67
+ self._name = self._service.get("name")
68
+ self._hostname = self._service.get("hostname")
69
+
70
+ @property
71
+ def id(self) -> str:
72
+ return self._id
73
+
74
+ @property
75
+ def provider_uuid(self) -> str:
76
+ return self._provider_uuid
77
+
78
+ @property
79
+ def region_uuid(self) -> str:
80
+ return self._region_uuid
81
+
82
+ @property
83
+ def public(self) -> bool:
84
+ return self._public
85
+
86
+ @property
87
+ def name(self) -> str:
88
+ return self._name
89
+
90
+ @property
91
+ def hostname(self) -> str:
92
+ return self._hostname
93
+
94
+ def __str__(self):
95
+ return json.dumps({k: v for k, v in self._service.items()})
96
+
97
+ def __repr__(self):
98
+ return f"Service( proximl , **{self._service.__repr__()})"
99
+
100
+ def __bool__(self):
101
+ return bool(self._id)
102
+
103
+ async def remove(self):
104
+ await self.proximl._query(
105
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}",
106
+ "DELETE",
107
+ )
108
+
109
+ async def refresh(self):
110
+ resp = await self.proximl._query(
111
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}",
112
+ "GET",
113
+ )
114
+ self.__init__(self.proximl, **resp)
115
+ return self
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
- self=self
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)