trainml 0.5.5__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.
@@ -4,35 +4,31 @@ import click
4
4
  from unittest.mock import AsyncMock, patch
5
5
  from pytest import mark, fixture, raises
6
6
 
7
- pytestmark = [mark.cli, mark.unit, mark.cloudbender, mark.reservations]
7
+ pytestmark = [mark.cli, mark.unit, mark.cloudbender, mark.services]
8
8
 
9
- from trainml.cli.cloudbender import reservation as specimen
10
- from trainml.cloudbender.reservations import Reservation
9
+ from trainml.cli.cloudbender import service as specimen
10
+ from trainml.cloudbender.services import Service
11
11
 
12
12
 
13
- def test_list(runner, mock_reservations):
13
+ def test_list(runner, mock_services):
14
14
  with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
15
15
  mock_trainml.cloudbender = AsyncMock()
16
- mock_trainml.cloudbender.reservations = AsyncMock()
17
- mock_trainml.cloudbender.reservations.list = AsyncMock(
18
- return_value=mock_reservations
19
- )
16
+ mock_trainml.cloudbender.services = AsyncMock()
17
+ mock_trainml.cloudbender.services.list = AsyncMock(return_value=mock_services)
20
18
  result = runner.invoke(
21
19
  specimen,
22
20
  args=["list", "--provider=prov-id-1", "--region=reg-id-1"],
23
21
  )
24
22
  assert result.exit_code == 0
25
- mock_trainml.cloudbender.reservations.list.assert_called_once_with(
23
+ mock_trainml.cloudbender.services.list.assert_called_once_with(
26
24
  provider_uuid="prov-id-1", region_uuid="reg-id-1"
27
25
  )
28
26
 
29
27
 
30
- def test_list_no_provider(runner, mock_reservations):
28
+ def test_list_no_provider(runner, mock_services):
31
29
  with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
32
30
  mock_trainml.cloudbender = AsyncMock()
33
- mock_trainml.cloudbender.reservations = AsyncMock()
34
- mock_trainml.cloudbender.reservations.list = AsyncMock(
35
- return_value=mock_reservations
36
- )
31
+ mock_trainml.cloudbender.services = AsyncMock()
32
+ mock_trainml.cloudbender.services.list = AsyncMock(return_value=mock_services)
37
33
  result = runner.invoke(specimen, ["list"])
38
34
  assert result.exit_code != 0
@@ -23,9 +23,7 @@ def test_list(runner, mock_projects):
23
23
  def test_list_datastores(runner, mock_project_datastores):
24
24
  with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
25
25
  mock_project = create_autospec(Project)
26
- mock_project.list_datastores = AsyncMock(
27
- return_value=mock_project_datastores
28
- )
26
+ mock_project.list_datastores = AsyncMock(return_value=mock_project_datastores)
29
27
  mock_trainml.projects.get = AsyncMock(return_value=mock_project)
30
28
  result = runner.invoke(specimen, ["list-datastores"])
31
29
  print(result)
@@ -33,14 +31,12 @@ def test_list_datastores(runner, mock_project_datastores):
33
31
  mock_project.list_datastores.assert_called_once()
34
32
 
35
33
 
36
- def test_list_reservations(runner, mock_project_reservations):
34
+ def test_list_services(runner, mock_project_services):
37
35
  with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
38
36
  mock_project = create_autospec(Project)
39
- mock_project.list_reservations = AsyncMock(
40
- return_value=mock_project_reservations
41
- )
37
+ mock_project.list_services = AsyncMock(return_value=mock_project_services)
42
38
  mock_trainml.projects.get = AsyncMock(return_value=mock_project)
43
- result = runner.invoke(specimen, ["list-reservations"])
39
+ result = runner.invoke(specimen, ["list-services"])
44
40
  print(result)
45
41
  assert result.exit_code == 0
46
- mock_project.list_reservations.assert_called_once()
42
+ mock_project.list_services.assert_called_once()
@@ -0,0 +1,161 @@
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 trainml.cloudbender.services as specimen
9
+ from trainml.exceptions import (
10
+ ApiError,
11
+ SpecificationError,
12
+ TrainMLException,
13
+ )
14
+
15
+ pytestmark = [mark.sdk, mark.unit, mark.cloudbender, mark.services]
16
+
17
+
18
+ @fixture
19
+ def services(mock_trainml):
20
+ yield specimen.Services(mock_trainml)
21
+
22
+
23
+ @fixture
24
+ def service(mock_trainml):
25
+ yield specimen.Service(
26
+ mock_trainml,
27
+ provider_uuid="1",
28
+ region_uuid="a",
29
+ service_id="x",
30
+ name="On-Prem Service",
31
+ public=False,
32
+ hostname="app1.proximl.cloud",
33
+ )
34
+
35
+
36
+ class RegionsTests:
37
+ @mark.asyncio
38
+ async def test_get_service(
39
+ self,
40
+ services,
41
+ mock_trainml,
42
+ ):
43
+ api_response = dict()
44
+ mock_trainml._query = AsyncMock(return_value=api_response)
45
+ await services.get("1234", "5687", "91011")
46
+ mock_trainml._query.assert_called_once_with(
47
+ "/provider/1234/region/5687/service/91011", "GET", {}
48
+ )
49
+
50
+ @mark.asyncio
51
+ async def test_list_services(
52
+ self,
53
+ services,
54
+ mock_trainml,
55
+ ):
56
+ api_response = dict()
57
+ mock_trainml._query = AsyncMock(return_value=api_response)
58
+ await services.list("1234", "5687")
59
+ mock_trainml._query.assert_called_once_with(
60
+ "/provider/1234/region/5687/service", "GET", {}
61
+ )
62
+
63
+ @mark.asyncio
64
+ async def test_remove_service(
65
+ self,
66
+ services,
67
+ mock_trainml,
68
+ ):
69
+ api_response = dict()
70
+ mock_trainml._query = AsyncMock(return_value=api_response)
71
+ await services.remove("1234", "4567", "8910")
72
+ mock_trainml._query.assert_called_once_with(
73
+ "/provider/1234/region/4567/service/8910", "DELETE", {}
74
+ )
75
+
76
+ @mark.asyncio
77
+ async def test_create_service(self, services, mock_trainml):
78
+ requested_config = dict(
79
+ provider_uuid="provider-id-1",
80
+ region_uuid="region-id-1",
81
+ name="On-Prem Service",
82
+ public=False,
83
+ )
84
+ expected_payload = dict(
85
+ name="On-Prem Service",
86
+ public=False,
87
+ )
88
+ api_response = {
89
+ "provider_uuid": "provider-id-1",
90
+ "region_uuid": "region-id-1",
91
+ "service_id": "service-id-1",
92
+ "name": "On-Prem Service",
93
+ "public": False,
94
+ "hostname": "app1.proximl.cloud",
95
+ "createdAt": "2020-12-31T23:59:59.000Z",
96
+ }
97
+
98
+ mock_trainml._query = AsyncMock(return_value=api_response)
99
+ response = await services.create(**requested_config)
100
+ mock_trainml._query.assert_called_once_with(
101
+ "/provider/provider-id-1/region/region-id-1/service",
102
+ "POST",
103
+ None,
104
+ expected_payload,
105
+ )
106
+ assert response.id == "service-id-1"
107
+
108
+
109
+ class serviceTests:
110
+ def test_service_properties(self, service):
111
+ assert isinstance(service.id, str)
112
+ assert isinstance(service.provider_uuid, str)
113
+ assert isinstance(service.region_uuid, str)
114
+ assert isinstance(service.public, bool)
115
+ assert isinstance(service.name, str)
116
+ assert isinstance(service.hostname, str)
117
+
118
+ def test_service_str(self, service):
119
+ string = str(service)
120
+ regex = r"^{.*\"service_id\": \"" + service.id + r"\".*}$"
121
+ assert isinstance(string, str)
122
+ assert re.match(regex, string)
123
+
124
+ def test_service_repr(self, service):
125
+ string = repr(service)
126
+ regex = r"^Service\( trainml , \*\*{.*'service_id': '" + service.id + r"'.*}\)$"
127
+ assert isinstance(string, str)
128
+ assert re.match(regex, string)
129
+
130
+ def test_service_bool(self, service, mock_trainml):
131
+ empty_service = specimen.Service(mock_trainml)
132
+ assert bool(service)
133
+ assert not bool(empty_service)
134
+
135
+ @mark.asyncio
136
+ async def test_service_remove(self, service, mock_trainml):
137
+ api_response = dict()
138
+ mock_trainml._query = AsyncMock(return_value=api_response)
139
+ await service.remove()
140
+ mock_trainml._query.assert_called_once_with(
141
+ "/provider/1/region/a/service/x", "DELETE"
142
+ )
143
+
144
+ @mark.asyncio
145
+ async def test_service_refresh(self, service, mock_trainml):
146
+ api_response = {
147
+ "provider_uuid": "provider-id-1",
148
+ "region_uuid": "region-id-1",
149
+ "service_id": "service-id-1",
150
+ "name": "On-Prem Service",
151
+ "public": False,
152
+ "hostname": "app1.proximl.cloud",
153
+ "createdAt": "2020-12-31T23:59:59.000Z",
154
+ }
155
+ mock_trainml._query = AsyncMock(return_value=api_response)
156
+ response = await service.refresh()
157
+ mock_trainml._query.assert_called_once_with(
158
+ f"/provider/1/region/a/service/x", "GET"
159
+ )
160
+ assert service.id == "service-id-1"
161
+ assert response.id == "service-id-1"
tests/unit/conftest.py CHANGED
@@ -17,7 +17,7 @@ from trainml.projects import (
17
17
  Projects,
18
18
  Project,
19
19
  ProjectDatastore,
20
- ProjectReservation,
20
+ ProjectService,
21
21
  )
22
22
  from trainml.cloudbender import Cloudbender
23
23
  from trainml.cloudbender.providers import Provider, Providers
@@ -25,7 +25,7 @@ from trainml.cloudbender.regions import Region, Regions
25
25
  from trainml.cloudbender.nodes import Node, Nodes
26
26
  from trainml.cloudbender.devices import Device, Devices
27
27
  from trainml.cloudbender.datastores import Datastore, Datastores
28
- from trainml.cloudbender.reservations import Reservation, Reservations
28
+ from trainml.cloudbender.services import Service, Services
29
29
  from trainml.cloudbender.device_configs import DeviceConfig, DeviceConfigs
30
30
 
31
31
 
@@ -887,27 +887,27 @@ def mock_project_datastores():
887
887
 
888
888
 
889
889
  @fixture(scope="session")
890
- def mock_reservations():
890
+ def mock_services():
891
891
  trainml = Mock()
892
892
  yield [
893
- Reservation(
893
+ Service(
894
894
  trainml,
895
895
  **{
896
896
  "provider_uuid": "prov-id-1",
897
897
  "region_uuid": "reg-id-1",
898
- "reservation_id": "res-id-1",
898
+ "service_id": "res-id-1",
899
899
  "type": "port",
900
900
  "name": "On-Prem Service A",
901
901
  "resource": "8001",
902
902
  "hostname": "service-a.local",
903
903
  },
904
904
  ),
905
- Reservation(
905
+ Service(
906
906
  trainml,
907
907
  **{
908
908
  "provider_uuid": "prov-id-2",
909
909
  "region_uuid": "reg-id-2",
910
- "reservation_id": "res-id-2",
910
+ "service_id": "res-id-2",
911
911
  "type": "port",
912
912
  "name": "Cloud Service B",
913
913
  "resource": "8001",
@@ -918,10 +918,10 @@ def mock_reservations():
918
918
 
919
919
 
920
920
  @fixture(scope="session")
921
- def mock_project_reservations():
921
+ def mock_project_services():
922
922
  trainml = Mock()
923
923
  yield [
924
- ProjectReservation(
924
+ ProjectService(
925
925
  trainml,
926
926
  **{
927
927
  "project_uuid": "proj-id-1",
@@ -933,7 +933,7 @@ def mock_project_reservations():
933
933
  "hostname": "service-a.local",
934
934
  },
935
935
  ),
936
- ProjectReservation(
936
+ ProjectService(
937
937
  trainml,
938
938
  **{
939
939
  "project_uuid": "proj-id-1",
@@ -990,7 +990,7 @@ def mock_trainml(
990
990
  mock_nodes,
991
991
  mock_devices,
992
992
  mock_datastores,
993
- mock_reservations,
993
+ mock_services,
994
994
  mock_device_configs,
995
995
  ):
996
996
  trainml = create_autospec(TrainML)
@@ -1028,8 +1028,8 @@ def mock_trainml(
1028
1028
  trainml.cloudbender.devices.list = AsyncMock(return_value=mock_devices)
1029
1029
  trainml.cloudbender.datastores = create_autospec(Datastores)
1030
1030
  trainml.cloudbender.datastores.list = AsyncMock(return_value=mock_datastores)
1031
- trainml.cloudbender.reservations = create_autospec(Reservations)
1032
- trainml.cloudbender.reservations.list = AsyncMock(return_value=mock_reservations)
1031
+ trainml.cloudbender.services = create_autospec(Services)
1032
+ trainml.cloudbender.services.list = AsyncMock(return_value=mock_services)
1033
1033
  trainml.cloudbender.device_configs = create_autospec(DeviceConfigs)
1034
1034
  trainml.cloudbender.device_configs.list = AsyncMock(
1035
1035
  return_value=mock_device_configs
@@ -49,11 +49,11 @@ def project_datastore(mock_trainml):
49
49
 
50
50
 
51
51
  @fixture
52
- def project_reservation(mock_trainml):
53
- yield specimen.ProjectReservation(
52
+ def project_service(mock_trainml):
53
+ yield specimen.ProjectService(
54
54
  mock_trainml,
55
55
  id="res-id-1",
56
- name="reservation 1",
56
+ name="service 1",
57
57
  project_uuid="proj-id-1",
58
58
  region_uuid="reg-id-1",
59
59
  type="port",
@@ -72,9 +72,7 @@ class ProjectsTests:
72
72
  api_response = dict()
73
73
  mock_trainml._query = AsyncMock(return_value=api_response)
74
74
  await projects.get("1234")
75
- mock_trainml._query.assert_called_once_with(
76
- "/project/1234", "GET", dict()
77
- )
75
+ mock_trainml._query.assert_called_once_with("/project/1234", "GET", dict())
78
76
 
79
77
  @mark.asyncio
80
78
  async def test_list_projects(
@@ -96,9 +94,7 @@ class ProjectsTests:
96
94
  api_response = dict()
97
95
  mock_trainml._query = AsyncMock(return_value=api_response)
98
96
  await projects.remove("4567")
99
- mock_trainml._query.assert_called_once_with(
100
- "/project/4567", "DELETE", dict()
101
- )
97
+ mock_trainml._query.assert_called_once_with("/project/4567", "DELETE", dict())
102
98
 
103
99
  @mark.asyncio
104
100
  async def test_create_project_simple(self, projects, mock_trainml):
@@ -156,36 +152,36 @@ class ProjectDatastoreTests:
156
152
  assert not bool(empty_project_datastore)
157
153
 
158
154
 
159
- class ProjectReservationTests:
160
- def test_project_reservation_properties(self, project_reservation):
161
- assert isinstance(project_reservation.id, str)
162
- assert isinstance(project_reservation.name, str)
163
- assert isinstance(project_reservation.project_uuid, str)
164
- assert isinstance(project_reservation.type, str)
165
- assert isinstance(project_reservation.hostname, str)
166
- assert isinstance(project_reservation.resource, str)
167
- assert isinstance(project_reservation.region_uuid, str)
155
+ class ProjectServiceTests:
156
+ def test_project_service_properties(self, project_service):
157
+ assert isinstance(project_service.id, str)
158
+ assert isinstance(project_service.name, str)
159
+ assert isinstance(project_service.project_uuid, str)
160
+ assert isinstance(project_service.type, str)
161
+ assert isinstance(project_service.hostname, str)
162
+ assert isinstance(project_service.resource, str)
163
+ assert isinstance(project_service.region_uuid, str)
168
164
 
169
- def test_project_reservation_str(self, project_reservation):
170
- string = str(project_reservation)
171
- regex = r"^{.*\"id\": \"" + project_reservation.id + r"\".*}$"
165
+ def test_project_service_str(self, project_service):
166
+ string = str(project_service)
167
+ regex = r"^{.*\"id\": \"" + project_service.id + r"\".*}$"
172
168
  assert isinstance(string, str)
173
169
  assert re.match(regex, string)
174
170
 
175
- def test_project_reservation_repr(self, project_reservation):
176
- string = repr(project_reservation)
171
+ def test_project_service_repr(self, project_service):
172
+ string = repr(project_service)
177
173
  regex = (
178
- r"^ProjectReservation\( trainml , \*\*{.*'id': '"
179
- + project_reservation.id
174
+ r"^ProjectService\( trainml , \*\*{.*'id': '"
175
+ + project_service.id
180
176
  + r"'.*}\)$"
181
177
  )
182
178
  assert isinstance(string, str)
183
179
  assert re.match(regex, string)
184
180
 
185
- def test_project_reservation_bool(self, project_reservation, mock_trainml):
186
- empty_project_reservation = specimen.ProjectReservation(mock_trainml)
187
- assert bool(project_reservation)
188
- assert not bool(empty_project_reservation)
181
+ def test_project_service_bool(self, project_service, mock_trainml):
182
+ empty_project_service = specimen.ProjectService(mock_trainml)
183
+ assert bool(project_service)
184
+ assert not bool(empty_project_service)
189
185
 
190
186
 
191
187
  class ProjectTests:
@@ -203,9 +199,7 @@ class ProjectTests:
203
199
 
204
200
  def test_project_repr(self, project):
205
201
  string = repr(project)
206
- regex = (
207
- r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
208
- )
202
+ regex = r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
209
203
  assert isinstance(string, str)
210
204
  assert re.match(regex, string)
211
205
 
@@ -226,18 +220,14 @@ class ProjectTests:
226
220
  api_response = dict()
227
221
  mock_trainml._query = AsyncMock(return_value=api_response)
228
222
  await project.refresh_datastores()
229
- mock_trainml._query.assert_called_once_with(
230
- "/project/1/datastores", "PATCH"
231
- )
223
+ mock_trainml._query.assert_called_once_with("/project/1/datastores", "PATCH")
232
224
 
233
225
  @mark.asyncio
234
- async def test_project_refresh_reservations(self, project, mock_trainml):
226
+ async def test_project_refresh_services(self, project, mock_trainml):
235
227
  api_response = dict()
236
228
  mock_trainml._query = AsyncMock(return_value=api_response)
237
- await project.refresh_reservations()
238
- mock_trainml._query.assert_called_once_with(
239
- "/project/1/reservations", "PATCH"
240
- )
229
+ await project.refresh_services()
230
+ mock_trainml._query.assert_called_once_with("/project/1/services", "PATCH")
241
231
 
242
232
  @mark.asyncio
243
233
  async def test_project_list_datastores(self, project, mock_trainml):
@@ -259,13 +249,11 @@ class ProjectTests:
259
249
  ]
260
250
  mock_trainml._query = AsyncMock(return_value=api_response)
261
251
  resp = await project.list_datastores()
262
- mock_trainml._query.assert_called_once_with(
263
- "/project/1/datastores", "GET"
264
- )
252
+ mock_trainml._query.assert_called_once_with("/project/1/datastores", "GET")
265
253
  assert len(resp) == 2
266
254
 
267
255
  @mark.asyncio
268
- async def test_project_list_reservations(self, project, mock_trainml):
256
+ async def test_project_list_services(self, project, mock_trainml):
269
257
  api_response = [
270
258
  {
271
259
  "project_uuid": "proj-id-1",
@@ -287,8 +275,6 @@ class ProjectTests:
287
275
  },
288
276
  ]
289
277
  mock_trainml._query = AsyncMock(return_value=api_response)
290
- resp = await project.list_reservations()
291
- mock_trainml._query.assert_called_once_with(
292
- "/project/1/reservations", "GET"
293
- )
278
+ resp = await project.list_services()
279
+ mock_trainml._query.assert_called_once_with("/project/1/services", "GET")
294
280
  assert len(resp) == 2
trainml/__init__.py CHANGED
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.5"
16
+ __version__ = "0.5.6"
17
17
  __all__ = "TrainML"
@@ -15,4 +15,4 @@ from trainml.cli.cloudbender.region import region
15
15
  from trainml.cli.cloudbender.node import node
16
16
  from trainml.cli.cloudbender.device import device
17
17
  from trainml.cli.cloudbender.datastore import datastore
18
- from trainml.cli.cloudbender.reservation import reservation
18
+ from trainml.cli.cloudbender.service import service
@@ -0,0 +1,129 @@
1
+ import click
2
+ from trainml.cli import cli, pass_config, search_by_id_name
3
+ from trainml.cli.cloudbender import cloudbender
4
+
5
+
6
+ @cloudbender.group()
7
+ @pass_config
8
+ def service(config):
9
+ """trainML 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.trainml.run(
41
+ config.trainml.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.trainml.run(
90
+ config.trainml.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.trainml.run(
120
+ config.trainml.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.trainml.run(found.remove())
trainml/cli/project.py CHANGED
@@ -115,40 +115,35 @@ def list_datastores(config):
115
115
 
116
116
  @project.command()
117
117
  @pass_config
118
- def list_reservations(config):
119
- """List project reservations."""
118
+ def list_services(config):
119
+ """List project services."""
120
120
  data = [
121
- ["ID", "NAME", "TYPE", "RESOURCE", "HOSTNAME", "REGION_UUID"],
121
+ ["ID", "NAME", "HOSTNAME", "REGION_UUID"],
122
122
  [
123
123
  "-" * 80,
124
124
  "-" * 80,
125
125
  "-" * 80,
126
126
  "-" * 80,
127
- "-" * 80,
128
- "-" * 80,
129
127
  ],
130
128
  ]
131
129
  project = config.trainml.run(
132
130
  config.trainml.client.projects.get(config.trainml.client.project)
133
131
  )
134
132
 
135
- reservations = config.trainml.run(project.list_reservations())
133
+ services = config.trainml.run(project.list_services())
136
134
 
137
- for reservation in reservations:
135
+ for service in services:
138
136
  data.append(
139
137
  [
140
- reservation.id,
141
- reservation.name,
142
- reservation.type,
143
- reservation.resource,
144
- reservation.hostname,
145
- reservation.region_uuid,
138
+ service.id,
139
+ service.name,
140
+ service.hostname,
141
+ service.region_uuid,
146
142
  ]
147
143
  )
148
144
 
149
145
  for row in data:
150
146
  click.echo(
151
- "{: >38.36} {: >30.28} {: >8.6} {: >15.13} {: >30.28} {: >38.36}"
152
- "".format(*row),
147
+ "{: >38.36} {: >30.28} {: >30.28} {: >38.36}" "".format(*row),
153
148
  file=config.stdout,
154
149
  )
@@ -3,7 +3,7 @@ from .regions import Regions
3
3
  from .nodes import Nodes
4
4
  from .devices import Devices
5
5
  from .datastores import Datastores
6
- from .reservations import Reservations
6
+ from .services import Services
7
7
  from .device_configs import DeviceConfigs
8
8
 
9
9
 
@@ -15,5 +15,5 @@ class Cloudbender(object):
15
15
  self.nodes = Nodes(trainml)
16
16
  self.devices = Devices(trainml)
17
17
  self.datastores = Datastores(trainml)
18
- self.reservations = Reservations(trainml)
18
+ self.services = Services(trainml)
19
19
  self.device_configs = DeviceConfigs(trainml)
@@ -0,0 +1,115 @@
1
+ import json
2
+ import logging
3
+
4
+
5
+ class Services(object):
6
+ def __init__(self, trainml):
7
+ self.trainml = trainml
8
+
9
+ async def get(self, provider_uuid, region_uuid, id, **kwargs):
10
+ resp = await self.trainml._query(
11
+ f"/provider/{provider_uuid}/region/{region_uuid}/service/{id}",
12
+ "GET",
13
+ kwargs,
14
+ )
15
+ return Service(self.trainml, **resp)
16
+
17
+ async def list(self, provider_uuid, region_uuid, **kwargs):
18
+ resp = await self.trainml._query(
19
+ f"/provider/{provider_uuid}/region/{region_uuid}/service",
20
+ "GET",
21
+ kwargs,
22
+ )
23
+ services = [Service(self.trainml, **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.trainml._query(
42
+ f"/provider/{provider_uuid}/region/{region_uuid}/service",
43
+ "POST",
44
+ None,
45
+ payload,
46
+ )
47
+ service = Service(self.trainml, **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.trainml._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, trainml, **kwargs):
61
+ self.trainml = trainml
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( trainml , **{self._service.__repr__()})"
99
+
100
+ def __bool__(self):
101
+ return bool(self._id)
102
+
103
+ async def remove(self):
104
+ await self.trainml._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.trainml._query(
111
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/service/{self._id}",
112
+ "GET",
113
+ )
114
+ self.__init__(self.trainml, **resp)
115
+ return self
trainml/projects.py CHANGED
@@ -72,17 +72,17 @@ class ProjectDatastore:
72
72
  return bool(self._id)
73
73
 
74
74
 
75
- class ProjectReservation:
75
+ class ProjectService:
76
76
  def __init__(self, trainml, **kwargs):
77
77
  self.trainml = trainml
78
- self._reservation = kwargs
79
- self._id = self._reservation.get("id")
80
- self._project_uuid = self._reservation.get("project_uuid")
81
- self._name = self._reservation.get("name")
82
- self._type = self._reservation.get("type")
83
- self._hostname = self._reservation.get("hostname")
84
- self._resource = self._reservation.get("resource")
85
- self._region_uuid = self._reservation.get("region_uuid")
78
+ self._service = kwargs
79
+ self._id = self._service.get("id")
80
+ self._project_uuid = self._service.get("project_uuid")
81
+ self._name = self._service.get("name")
82
+ self._type = self._service.get("type")
83
+ self._hostname = self._service.get("hostname")
84
+ self._resource = self._service.get("resource")
85
+ self._region_uuid = self._service.get("region_uuid")
86
86
 
87
87
  @property
88
88
  def id(self) -> str:
@@ -113,12 +113,10 @@ class ProjectReservation:
113
113
  return self._region_uuid
114
114
 
115
115
  def __str__(self):
116
- return json.dumps({k: v for k, v in self._reservation.items()})
116
+ return json.dumps({k: v for k, v in self._service.items()})
117
117
 
118
118
  def __repr__(self):
119
- return (
120
- f"ProjectReservation( trainml , **{self._reservation.__repr__()})"
121
- )
119
+ return f"ProjectService( trainml , **{self._service.__repr__()})"
122
120
 
123
121
  def __bool__(self):
124
122
  return bool(self._id)
@@ -162,26 +160,17 @@ class Project:
162
160
  await self.trainml._query(f"/project/{self._id}", "DELETE")
163
161
 
164
162
  async def list_datastores(self):
165
- resp = await self.trainml._query(
166
- f"/project/{self._id}/datastores", "GET"
167
- )
168
- datastores = [
169
- ProjectDatastore(self.trainml, **datastore) for datastore in resp
170
- ]
163
+ resp = await self.trainml._query(f"/project/{self._id}/datastores", "GET")
164
+ datastores = [ProjectDatastore(self.trainml, **datastore) for datastore in resp]
171
165
  return datastores
172
166
 
173
- async def list_reservations(self):
174
- resp = await self.trainml._query(
175
- f"/project/{self._id}/reservations", "GET"
176
- )
177
- reservations = [
178
- ProjectReservation(self.trainml, **reservation)
179
- for reservation in resp
180
- ]
181
- return reservations
167
+ async def list_services(self):
168
+ resp = await self.trainml._query(f"/project/{self._id}/services", "GET")
169
+ services = [ProjectService(self.trainml, **service) for service in resp]
170
+ return services
182
171
 
183
172
  async def refresh_datastores(self):
184
173
  await self.trainml._query(f"/project/{self._id}/datastores", "PATCH")
185
174
 
186
- async def refresh_reservations(self):
187
- await self.trainml._query(f"/project/{self._id}/reservations", "PATCH")
175
+ async def refresh_services(self):
176
+ await self.trainml._query(f"/project/{self._id}/services", "PATCH")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainml
3
- Version: 0.5.5
3
+ Version: 0.5.6
4
4
  Summary: trainML client SDK and command line utilities
5
5
  Home-page: https://github.com/trainML/trainml-cli
6
6
  Author: trainML
@@ -15,7 +15,7 @@ tests/integration/test_volumes_integration.py,sha256=gOmZpwwFxqeOAVmfKWSTmuyshx8
15
15
  tests/integration/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  tests/integration/cloudbender/test_providers_integration.py,sha256=oV8ydFsosDZ_Z1Dkg2IN-ZhWuIl5e_HkHAORMsOsAJc,1473
17
17
  tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- tests/unit/conftest.py,sha256=fbID1lG1FZ-bywEjM7kEriyN_SUqPasRJVIhIqqsf98,31197
18
+ tests/unit/conftest.py,sha256=QW9K3c-rEB8epqOW0tUwTGqcfJv14AW0ovLNqdstpAc,31129
19
19
  tests/unit/test_auth.py,sha256=nfhlOCR7rUsn_MaD8QQtBc2v0k8pIxqbzGgRAZK1WGc,858
20
20
  tests/unit/test_checkpoints_unit.py,sha256=4Add2DXZCuriSZ0atvOXc8fsEGMaEfPhYmT8Q3UgP5E,16008
21
21
  tests/unit/test_connections_unit.py,sha256=FzN2ddQxNpjxzNGUsXhjTk0HnD24wSPelPTL4o_r-Ho,5507
@@ -25,7 +25,7 @@ tests/unit/test_exceptions.py,sha256=3tAok6kAU1QRjN7qTNVYuSGWDg7IEoK__OXFLyzLr7k
25
25
  tests/unit/test_gpu_types_unit.py,sha256=c9ie6YSYT5onBnlmHvHWON9WgQiJ1eO2C-4Tk-UPQHg,2054
26
26
  tests/unit/test_jobs_unit.py,sha256=bZxN9HUfHCyQCjZCZGn6WFIhu8S5FU1z5ZG9sgH2XEg,26835
27
27
  tests/unit/test_models_unit.py,sha256=uezWF7FUHGmCSQBtpyyKhBttTnCTRjxU22NsHdJLYYg,15064
28
- tests/unit/test_projects_unit.py,sha256=iyqYntMj1UivqpL8GUnJeX2p4m9coEG8qL4c0iSfCCA,9530
28
+ tests/unit/test_projects_unit.py,sha256=V7PvCFiXoWI_3dyGJnruBzNbNsolBHdUpaR_1XtHmzI,9238
29
29
  tests/unit/test_trainml.py,sha256=8vAKvFD1xYsx_VY4HFVa0b1MUlMoNApY6TO8r7vI-UQ,1701
30
30
  tests/unit/test_volumes_unit.py,sha256=KHVmdbQIiX8tEE09U-XsH-vl6wfYGVoRzR_UQJlhOVE,15305
31
31
  tests/unit/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -36,7 +36,7 @@ tests/unit/cli/test_cli_environment_unit.py,sha256=7FFKFPVa6yqJqujTXD_tsSUDruW5K
36
36
  tests/unit/cli/test_cli_gpu_unit.py,sha256=FIq3tQIDmeD-pvxkhJKMykfnlWcVxho2vRkoCMXgQxE,650
37
37
  tests/unit/cli/test_cli_job_unit.py,sha256=xUqkLFDIyI1ExiVVgr-218gQSlFSYCm4RRX_eyipJhY,611
38
38
  tests/unit/cli/test_cli_model_unit.py,sha256=fE-CRVg8gbtDlwrKBkf-hc9x7EhFlYeE3jlum1E27EA,629
39
- tests/unit/cli/test_cli_project_unit.py,sha256=1tEfwXJqnac41bDtvYcN_gpScnb4UY6s5dHfTl-0YoQ,1756
39
+ tests/unit/cli/test_cli_project_unit.py,sha256=pi0N-XVH5wsJQ6idFcRsIofWSAdj8KtK1gwf1J3l2JY,1688
40
40
  tests/unit/cli/test_cli_volume_unit.py,sha256=oggGL2eLiaExP6rSdFmQevxLp6nw5o7SKUEqMKBmy_A,644
41
41
  tests/unit/cli/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  tests/unit/cli/cloudbender/test_cli_datastore_unit.py,sha256=DQWDjqg4viBZRONi00nVzqF9rJ5qKOKRub9pKbTmMWU,1381
@@ -44,7 +44,7 @@ tests/unit/cli/cloudbender/test_cli_device_unit.py,sha256=2BSMyXQ8fOzNKh_-pa_tx7
44
44
  tests/unit/cli/cloudbender/test_cli_node_unit.py,sha256=KbK7axJ1L4y4sN7KQRpOVIqphnNpi0aFW4-HlYLtwnI,1316
45
45
  tests/unit/cli/cloudbender/test_cli_provider_unit.py,sha256=Rm-tRNPbTTB7ZzkkIpLfDp_pEYfqihjB0ZYk_EPQUfs,781
46
46
  tests/unit/cli/cloudbender/test_cli_region_unit.py,sha256=iH5AbrzZ-R2EJ-Bd2HFN7FN2lTpkr3-pCLR59ZVvdQU,1262
47
- tests/unit/cli/cloudbender/test_cli_reservation_unit.py,sha256=9y4FhcUwti7OzKYaJ23YAgOpx-dBIYj_0i--xaIk8YU,1407
47
+ tests/unit/cli/cloudbender/test_cli_reservation_unit.py,sha256=4LDOJDXygMuho2cdI2K59eq4oyiry9hNaG0avEr0_tw,1311
48
48
  tests/unit/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  tests/unit/cloudbender/test_datastores_unit.py,sha256=54mPokxhrRjlkBfqpmeA_q-PLml-HUNNit91aQVTpCg,5398
50
50
  tests/unit/cloudbender/test_device_configs_unit.py,sha256=lzyCuF7MRoQrtJVTQFL27lqPnRwQFv25htPgJqDuQI8,5714
@@ -53,7 +53,8 @@ tests/unit/cloudbender/test_nodes_unit.py,sha256=BDpfJXCBNNpLt5rhJMk2BVXDQ_4QSmx
53
53
  tests/unit/cloudbender/test_providers_unit.py,sha256=OgxifgC1IqLH8DNMKXy1Ne9_7a75ea6kHEOfRSRoQuQ,4373
54
54
  tests/unit/cloudbender/test_regions_unit.py,sha256=BbJICLIQmlotpA1UmLD0KTW_H9g2UW0J8ZYzQk1_Xjc,6299
55
55
  tests/unit/cloudbender/test_reservations_unit.py,sha256=nWEZ_p9EF2C49nbgL7Dt4NG2Irmyt94ZqJJQDyNfGFI,5624
56
- trainml/__init__.py,sha256=ZVeJu_28DU8_PtbnRLowX5jQ6IxkuYUpQHov-bC8Bt0,432
56
+ tests/unit/cloudbender/test_services_unit.py,sha256=zhYfCHSaWN6GzLTmt3RuXZCr2bZjptUEakzE4UK4Rw4,5043
57
+ trainml/__init__.py,sha256=VWx1-OcS6OKjpjgZwXYaDSluXSUTDqT-xV9rj07ufnc,432
57
58
  trainml/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
58
59
  trainml/auth.py,sha256=gruZv27nhttrCbhcVQTH9kZkF2uMm1E06SwA_2pQAHQ,26565
59
60
  trainml/checkpoints.py,sha256=726yaeTHzIPvkQ-BhqmvJ6u0s1Oh732N6UAFbuWEv0Q,8274
@@ -64,7 +65,7 @@ trainml/exceptions.py,sha256=MG1FkcjRacv3HoPuBS1IWLCUk0wGHEQ6DaOzXNymsNI,4094
64
65
  trainml/gpu_types.py,sha256=mm-dwfYc02192bmYPIJmzesndyBcoOdkKYBaYZXOUwU,1901
65
66
  trainml/jobs.py,sha256=iuSKkZDK908K0JwZjSbEk1G6IzdKp7vGkXsAUfih6R8,17838
66
67
  trainml/models.py,sha256=Lqs3OJMuOZXx8cfGFNC3JZ8nVK6l9_jU1xM1Uw1c5UQ,7750
67
- trainml/projects.py,sha256=Jk-0xhEgBFPIVMAK_Szxp3B-h-tJJSIEtTF8JWAYzo8,5209
68
+ trainml/projects.py,sha256=11as6-XwSabaWrkVc7UqnT2izJYDbp6BdcOdgr3z3Dc,5001
68
69
  trainml/trainml.py,sha256=EBnqQ3Q291xrPKYuN6xKm5yt0mJQOJ3b7GAlR-fl8NI,10864
69
70
  trainml/volumes.py,sha256=x4_QLPnPCuqEWR9FjwotCVPYFhdiZuWjp4tKKEU-Ne4,8094
70
71
  trainml/cli/__init__.py,sha256=Gvj6oGSEtgpb40ACtiVeMD93GM-uy15MG6VlX6rwdwA,4346
@@ -74,19 +75,20 @@ trainml/cli/dataset.py,sha256=Pc00M6t7hGoRzCxznmmkijsWhG4PhIfG7UkrwtwykTY,6871
74
75
  trainml/cli/environment.py,sha256=dfm_T8OlCM5M8dLyOQBapJl3eFuVIku2P4JO6V0cYVQ,1019
75
76
  trainml/cli/gpu.py,sha256=CMcQyl2qbUgc2bc-gvUVT6X7bq2-sgiCHl3hyZ6kFWM,883
76
77
  trainml/cli/model.py,sha256=hR23E6ttRXcLk-RofkPK6wUXMO7OU6sT6jTEHTmUg9Q,6111
77
- trainml/cli/project.py,sha256=iar4-Aic6k0O6HWfxB5vJ5EtjS4aAaw2g8wW7H-3Bjk,3522
78
+ trainml/cli/project.py,sha256=f772bHs68AVRY60l7dbVKgeDmDC3u2bZjqrz7zm7xvQ,3314
78
79
  trainml/cli/volume.py,sha256=kDUss93N78DT-YlLjC6I3jEq5nBWfRNNR5M4tY_F_Zg,6246
79
- trainml/cli/cloudbender/__init__.py,sha256=ewat7I7PvHQEW0a8zuky28kuMwtjL-gFQOAQmG0mmJA,534
80
+ trainml/cli/cloudbender/__init__.py,sha256=mnHLTfXv3yxgk77rhq9ie7BAkLpXqc3hPlCkOxVfIQ4,526
80
81
  trainml/cli/cloudbender/datastore.py,sha256=gJ-comfAq65uiPoONQ35UIDLNVN7QKMf3l_2EcTN6zY,3478
81
82
  trainml/cli/cloudbender/device.py,sha256=KGZCFwwvS4tWsWuudrhlvquu_IFtV7LCUAOmCajicic,3453
82
83
  trainml/cli/cloudbender/node.py,sha256=iN_WaPCxOhtgDtnSsIFAEMGADG4MKiLjWoez6YSYwZI,3843
83
84
  trainml/cli/cloudbender/provider.py,sha256=oFjZWKfFQjNY7OtDu7nUdfv-RTmQc_Huuug963D3BdA,1726
84
85
  trainml/cli/cloudbender/region.py,sha256=X6-FYOb-pGpOEazn-NbsYSwa9ergB7FGATFkTe4a8Pk,2892
85
86
  trainml/cli/cloudbender/reservation.py,sha256=z2oMYwp-w_Keo1DepKUtuRnwiGz2VscVHDYWEFap1gs,3569
87
+ trainml/cli/cloudbender/service.py,sha256=lHTYkUUtFMtrGgaAPoGAHaRtJq5HuwRKapx5PEl_Slk,2869
86
88
  trainml/cli/job/__init__.py,sha256=ljY-ELeXhXQ7txASbJEKGBom7OXfNyy7sWILz3nxRAE,6545
87
89
  trainml/cli/job/create.py,sha256=pfOCqs5Vfk4PAI5KZpXHJ1vp3DDe4ccvYzieh0oFexY,34288
88
90
  trainml/cloudbender/__init__.py,sha256=iE29obtC0_9f0IhRvHQcG5aY58fVhVYipTakpjAhdss,64
89
- trainml/cloudbender/cloudbender.py,sha256=rAc93mtMaa3tWLi-NT4tiqO664MCPkhlGG9oNYSmimQ,634
91
+ trainml/cloudbender/cloudbender.py,sha256=7qIClWo5uvXm8U_u0_nrs7rNUIegyj9QzvktX10HxSE,618
90
92
  trainml/cloudbender/datastores.py,sha256=biVGifedc3r1DcuxsfCQh-f1Tw4HcJMMJfdgHxPfkKM,3506
91
93
  trainml/cloudbender/device_configs.py,sha256=DJWiGFaOE4C4xLE1BLDAiEjeL4T00R3FA_pb1xnSOr4,3399
92
94
  trainml/cloudbender/devices.py,sha256=QORNmKdLJoqGZmeWXRnivC1JmNBIw-ebvf4bsoem3r8,5660
@@ -94,9 +96,10 @@ trainml/cloudbender/nodes.py,sha256=7HV2VLmxiUcJ-Kc6AAXS3M8C_XO-HKmaVgJpPdVnBQk,
94
96
  trainml/cloudbender/providers.py,sha256=-gkdiTu6Ah2znUuyyc3ZuRALagW8s1-OgqVjtlvc1AU,2036
95
97
  trainml/cloudbender/regions.py,sha256=Aqc_MeLVAeEv21e-lR5u8x1eintqUhZT2DBiQG3AcEE,3570
96
98
  trainml/cloudbender/reservations.py,sha256=rOrGXWIUHON4ad2aufEcvK4Yv_Mv3dDoScUtLJE8LWw,3586
97
- trainml-0.5.5.dist-info/LICENSE,sha256=s0lpBxhSSUEpMavwde-Vb6K_K7xDCTTvSpNznVqVGR0,1069
98
- trainml-0.5.5.dist-info/METADATA,sha256=1yX0kzexWk9MrF3vMdP87PRTbmV34UgsgF-zV-MHAg4,7345
99
- trainml-0.5.5.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
100
- trainml-0.5.5.dist-info/entry_points.txt,sha256=OzBDm2wXby1bSGF02jTVxzRFZLejnbFiLHXhKdW3Bds,63
101
- trainml-0.5.5.dist-info/top_level.txt,sha256=Y1kLFRWKUW7RG8BX7cvejHF_yW8wBOaRYF1JQHENY4w,23
102
- trainml-0.5.5.dist-info/RECORD,,
99
+ trainml/cloudbender/services.py,sha256=mXCdXwWgV1uhPQ05e0qwnqUBrx3gM4sdCnIiiK4j9Ic,3222
100
+ trainml-0.5.6.dist-info/LICENSE,sha256=s0lpBxhSSUEpMavwde-Vb6K_K7xDCTTvSpNznVqVGR0,1069
101
+ trainml-0.5.6.dist-info/METADATA,sha256=TziEO1YevXuR39PH2doZ1rho_6HYgiPUCm0oKXunemU,7345
102
+ trainml-0.5.6.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
103
+ trainml-0.5.6.dist-info/entry_points.txt,sha256=OzBDm2wXby1bSGF02jTVxzRFZLejnbFiLHXhKdW3Bds,63
104
+ trainml-0.5.6.dist-info/top_level.txt,sha256=Y1kLFRWKUW7RG8BX7cvejHF_yW8wBOaRYF1JQHENY4w,23
105
+ trainml-0.5.6.dist-info/RECORD,,