proximl 0.5.0__py3-none-any.whl → 0.5.2__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.
@@ -0,0 +1,157 @@
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 device(config):
9
+ """proxiML CloudBender device commands."""
10
+ pass
11
+
12
+
13
+ @device.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 devices for.",
27
+ )
28
+ @pass_config
29
+ def list(config, provider, region):
30
+ """List devices."""
31
+ data = [
32
+ [
33
+ "ID",
34
+ "NAME",
35
+ "STATUS",
36
+ "JOB STATUS",
37
+ "ONLINE",
38
+ "MAINTENANCE",
39
+ ],
40
+ [
41
+ "-" * 80,
42
+ "-" * 80,
43
+ "-" * 80,
44
+ "-" * 80,
45
+ "-" * 80,
46
+ "-" * 80,
47
+ "-" * 80,
48
+ ],
49
+ ]
50
+
51
+ devices = config.proximl.run(
52
+ config.proximl.client.cloudbender.devices.list(
53
+ provider_uuid=provider, region_uuid=region
54
+ )
55
+ )
56
+
57
+ for device in devices:
58
+ data.append(
59
+ [
60
+ device.id,
61
+ device.name,
62
+ device.status,
63
+ device.job_status,
64
+ "X" if device.online else "",
65
+ "X" if device.maintenance_mode else "",
66
+ ]
67
+ )
68
+
69
+ for row in data:
70
+ click.echo(
71
+ "{: >37.36} {: >29.28} {: >9.8} {: >11.10} {: >7.6} {: >12.11}"
72
+ "".format(*row),
73
+ file=config.stdout,
74
+ )
75
+
76
+
77
+ @device.command()
78
+ @click.option(
79
+ "--provider",
80
+ "-p",
81
+ type=click.STRING,
82
+ required=True,
83
+ help="The provider ID of the region.",
84
+ )
85
+ @click.option(
86
+ "--region",
87
+ "-r",
88
+ type=click.STRING,
89
+ required=True,
90
+ help="The region ID to create the region in.",
91
+ )
92
+ @click.option(
93
+ "--minion-id",
94
+ "-m",
95
+ type=click.STRING,
96
+ required=True,
97
+ help="The minion_id of the new node.",
98
+ )
99
+ @click.option(
100
+ "--hostname",
101
+ "-h",
102
+ type=click.STRING,
103
+ help="The hostname (if different from name)",
104
+ )
105
+ @click.argument("name", type=click.STRING, required=True)
106
+ @pass_config
107
+ def create(config, provider, region, minion_id, hostname, name):
108
+ """
109
+ Creates a node.
110
+ """
111
+ if not hostname:
112
+ hostname = name
113
+ return config.proximl.run(
114
+ config.proximl.client.cloudbender.devices.create(
115
+ provider_uuid=provider,
116
+ region_uuid=region,
117
+ friendly_name=name,
118
+ hostname=hostname,
119
+ minion_id=minion_id,
120
+ )
121
+ )
122
+
123
+
124
+ @device.command()
125
+ @click.option(
126
+ "--provider",
127
+ "-p",
128
+ type=click.STRING,
129
+ required=True,
130
+ help="The provider ID of the region.",
131
+ )
132
+ @click.option(
133
+ "--region",
134
+ "-r",
135
+ type=click.STRING,
136
+ required=True,
137
+ help="The region ID to delete the node from.",
138
+ )
139
+ @click.argument("device", type=click.STRING)
140
+ @pass_config
141
+ def remove(config, provider, region, node):
142
+ """
143
+ Remove a device.
144
+
145
+ DEVICE may be specified by name or ID, but ID is preferred.
146
+ """
147
+ devices = config.proximl.run(
148
+ config.proximl.client.cloudbender.devices.list(
149
+ provider_uuid=provider, region_uuid=region
150
+ )
151
+ )
152
+
153
+ found = search_by_id_name(device, devices)
154
+ if None is found:
155
+ raise click.UsageError("Cannot find specified device.")
156
+
157
+ return config.proximl.run(found.remove())
@@ -0,0 +1,190 @@
1
+ import json
2
+ import logging
3
+
4
+
5
+ class Devices(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}/device/{id}",
12
+ "GET",
13
+ kwargs,
14
+ )
15
+ return Device(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}/device",
20
+ "GET",
21
+ kwargs,
22
+ )
23
+ devices = [Device(self.proximl, **device) for device in resp]
24
+ return devices
25
+
26
+ async def create(
27
+ self,
28
+ provider_uuid,
29
+ region_uuid,
30
+ friendly_name,
31
+ hostname,
32
+ minion_id,
33
+ **kwargs,
34
+ ):
35
+ logging.info(f"Creating Device {friendly_name}")
36
+ data = dict(
37
+ friendly_name=friendly_name,
38
+ hostname=hostname,
39
+ minion_id=minion_id,
40
+ type="device",
41
+ service="compute",
42
+ **kwargs,
43
+ )
44
+ payload = {k: v for k, v in data.items() if v is not None}
45
+ resp = await self.proximl._query(
46
+ f"/provider/{provider_uuid}/region/{region_uuid}/device",
47
+ "POST",
48
+ None,
49
+ payload,
50
+ )
51
+ device = Device(self.proximl, **resp)
52
+ logging.info(f"Created Device {friendly_name} with id {device.id}")
53
+ return device
54
+
55
+ async def remove(self, provider_uuid, region_uuid, id, **kwargs):
56
+ await self.proximl._query(
57
+ f"/provider/{provider_uuid}/region/{region_uuid}/device/{id}",
58
+ "DELETE",
59
+ kwargs,
60
+ )
61
+
62
+
63
+ class Device:
64
+ def __init__(self, proximl, **kwargs):
65
+ self.proximl = proximl
66
+ self._device = kwargs
67
+ self._id = self._device.get("device_id")
68
+ self._provider_uuid = self._device.get("provider_uuid")
69
+ self._region_uuid = self._device.get("region_uuid")
70
+ self._name = self._device.get("friendly_name")
71
+ self._hostname = self._device.get("hostname")
72
+ self._status = self._device.get("status")
73
+ self._online = self._device.get("online")
74
+ self._maintenance_mode = self._device.get("maintenance_mode")
75
+ self._device_config_id = self._device.get("device_config_id")
76
+ self._job_status = self._device.get("job_status")
77
+ self._job_last_deployed = self._device.get("job_last_deployed")
78
+ self._job_config_id = self._device.get("job_config_id")
79
+ self._job_config_revision = self._device.get("job_config_revision")
80
+
81
+ @property
82
+ def id(self) -> str:
83
+ return self._id
84
+
85
+ @property
86
+ def provider_uuid(self) -> str:
87
+ return self._provider_uuid
88
+
89
+ @property
90
+ def region_uuid(self) -> str:
91
+ return self._region_uuid
92
+
93
+ @property
94
+ def name(self) -> str:
95
+ return self._name
96
+
97
+ @property
98
+ def hostname(self) -> str:
99
+ return self._hostname
100
+
101
+ @property
102
+ def status(self) -> str:
103
+ return self._status
104
+
105
+ @property
106
+ def online(self) -> bool:
107
+ return self._online
108
+
109
+ @property
110
+ def maintenance_mode(self) -> bool:
111
+ return self._maintenance_mode
112
+
113
+ @property
114
+ def device_config_id(self) -> str:
115
+ return self._device_config_id
116
+
117
+ @property
118
+ def job_status(self) -> str:
119
+ return self._job_status
120
+
121
+ @property
122
+ def job_last_deployed(self) -> str:
123
+ return self._job_last_deployed
124
+
125
+ @property
126
+ def job_config_id(self) -> str:
127
+ return self._job_config_id
128
+
129
+ @property
130
+ def job_config_revision(self) -> str:
131
+ return self._job_config_revision
132
+
133
+ def __str__(self):
134
+ return json.dumps({k: v for k, v in self._device.items()})
135
+
136
+ def __repr__(self):
137
+ return f"Device( proximl , **{self._device.__repr__()})"
138
+
139
+ def __bool__(self):
140
+ return bool(self._id)
141
+
142
+ async def remove(self):
143
+ await self.proximl._query(
144
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}",
145
+ "DELETE",
146
+ )
147
+
148
+ async def refresh(self):
149
+ resp = await self.proximl._query(
150
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}",
151
+ "GET",
152
+ )
153
+ self.__init__(self.proximl, **resp)
154
+ return self
155
+
156
+ async def toggle_maintenance(self):
157
+ await self.proximl._query(
158
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/maintenance",
159
+ "PATCH",
160
+ )
161
+
162
+ async def run_action(self, command):
163
+ await self.proximl._query(
164
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/action",
165
+ "POST",
166
+ None,
167
+ dict(command=command),
168
+ )
169
+
170
+ async def set_config(self, device_config_id):
171
+ resp = await self.proximl._query(
172
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}",
173
+ "PATCH",
174
+ None,
175
+ dict(device_config_id=device_config_id),
176
+ )
177
+ self.__init__(self.proximl, **resp)
178
+ return self
179
+
180
+ async def deploy_endpoint(self):
181
+ await self.proximl._query(
182
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/deploy",
183
+ "PUT",
184
+ )
185
+
186
+ async def stop_endpoint(self):
187
+ await self.proximl._query(
188
+ f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/stop",
189
+ "PUT",
190
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proximl
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: proxiML client SDK and command line utilities
5
5
  Home-page: https://github.com/proxiML/python-sdk
6
6
  Author: proxiML
@@ -25,6 +25,7 @@ proximl/cli/model.py,sha256=x1iG41tcYZuQdhA1qa_BQqLmpp0uteW6ieUCKlgzGRc,6071
25
25
  proximl/cli/project.py,sha256=Er1twSiWQSAKir-hBIT9fRo2fc_UGqFoIJOwwjQGmlo,3522
26
26
  proximl/cli/cloudbender/__init__.py,sha256=qWLg19Gq-2WHnBZZ4Un10QyY8ipNOA0_LK-Lm9yeM4w,484
27
27
  proximl/cli/cloudbender/datastore.py,sha256=_vQOj-NfrL_nj4HfxNJL63TJZjLgfDyztRLyaRU58v8,3478
28
+ proximl/cli/cloudbender/device.py,sha256=FdQZPESP6YBfUSzXq1Byu7eNMKi59qSOICONK-TEljI,3453
28
29
  proximl/cli/cloudbender/node.py,sha256=xxzj68YvpRey2vZQasgYTnwv3x7TnwpuPSSf8Ma5a54,3843
29
30
  proximl/cli/cloudbender/provider.py,sha256=qhWbDK1tWi00wQWEYqGw7yGoZx0nEjV40GLHRuuE86c,1726
30
31
  proximl/cli/cloudbender/region.py,sha256=WnSkY4dXKRJ-FNaoxMfmoh6iuUx5dXCNJmEFT34Xtao,2892
@@ -35,6 +36,7 @@ proximl/cloudbender/__init__.py,sha256=iE29obtC0_9f0IhRvHQcG5aY58fVhVYipTakpjAhd
35
36
  proximl/cloudbender/cloudbender.py,sha256=Bn4l7ypAi4HzQbhrL_Ss0MY6S775CNaZ87YUxp9rD8Y,565
36
37
  proximl/cloudbender/datastores.py,sha256=rE_6A2rH-muET3mFus9oXU8jPc2y5poZFs_O7cuWx4M,3245
37
38
  proximl/cloudbender/device_configs.py,sha256=7kvnSZ68kZEkUymPMNlCIJUTg5fMIJCNrODbPZAkLdc,3134
39
+ proximl/cloudbender/devices.py,sha256=vHooaOw2k2Tf99FJHnVZTgggqCTYJg7rq46aUPW0k8M,5660
38
40
  proximl/cloudbender/nodes.py,sha256=85mC7H8MkU4tGiyk51GjKgK7O9XBuKQu94Rg7ECqFnc,3608
39
41
  proximl/cloudbender/providers.py,sha256=-DxBpWgKteBacwNluUxrKST4HuyneNg57bz1y9CcUko,1834
40
42
  proximl/cloudbender/regions.py,sha256=7mkjWjrpJjRjlkzOWZlxzGo4HgrfPvhITwAeBZfXnDo,3339
@@ -74,6 +76,7 @@ tests/unit/cli/test_cli_model_unit.py,sha256=AucngxvYjW6GidDGBPHnKyYOb82ff7xMX5m
74
76
  tests/unit/cli/test_cli_project_unit.py,sha256=ms9gJ8pgMNGeIMdFcvBcwSPmb0i2qo9-rk9CCF53-9M,1756
75
77
  tests/unit/cli/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
78
  tests/unit/cli/cloudbender/test_cli_datastore_unit.py,sha256=sUZWnzzCCG7NdgzEHsQ2zHpEb-ZD6FDIazca6FqMOc0,1381
79
+ tests/unit/cli/cloudbender/test_cli_device_unit.py,sha256=o1vrPlbaanYK1iJG5pE6tDwgmOXDuLUY0VH8DxtaPYI,1342
77
80
  tests/unit/cli/cloudbender/test_cli_node_unit.py,sha256=uh1Nt0ewk0v81iN5wCyBPzSXAhD8clW98HAORsCjqrg,1316
78
81
  tests/unit/cli/cloudbender/test_cli_provider_unit.py,sha256=jCnFnqZuLzuDx9u3kLyjT83nBDWKn7LDCz6ErzCce1g,781
79
82
  tests/unit/cli/cloudbender/test_cli_region_unit.py,sha256=mEAU0z_gKDM-e5J_V8igXmiU4qjrOfzJJRtKRNWdeBs,1262
@@ -81,13 +84,14 @@ tests/unit/cli/cloudbender/test_cli_reservation_unit.py,sha256=Lcr-xeNVAtVLHllQn
81
84
  tests/unit/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
85
  tests/unit/cloudbender/test_datastores_unit.py,sha256=Sk-qg4fpBSkW8D3cnnfjAQdWjADtV9uHfLpqexl0loo,4644
83
86
  tests/unit/cloudbender/test_device_configs_unit.py,sha256=Dp8GaeD0lEt9giVAX6f3r_G0ELLgIZux_887gR32M3M,4858
87
+ tests/unit/cloudbender/test_devices_unit.py,sha256=C2YTnfIpHlxdidgfbTnlzl72r5O7kqKStQUhWcTTXDg,9103
84
88
  tests/unit/cloudbender/test_nodes_unit.py,sha256=vk6lWGr1PBWeJZNIVywPN_UzwuZdRW3xZuh_iT0gCeA,4686
85
89
  tests/unit/cloudbender/test_providers_unit.py,sha256=8KURiz5zz2saBcAYIPPvLBx3wVOUhN5kK36MD64-oyw,3730
86
90
  tests/unit/cloudbender/test_regions_unit.py,sha256=4QivFfejiI8cI47DsmJGgDw-tY9ENol3dKRwcVEDpr0,5635
87
91
  tests/unit/cloudbender/test_reservations_unit.py,sha256=MlUWr8cXM7n-CTqd4sfFaZMEV6jka802Hnhmg6dAeGU,4827
88
- proximl-0.5.0.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
89
- proximl-0.5.0.dist-info/METADATA,sha256=rG8H63Jf_vO5rUI5xdSeDDR9PT9bsGhjgMIx1YLaMgs,7344
90
- proximl-0.5.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
91
- proximl-0.5.0.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
92
- proximl-0.5.0.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
93
- proximl-0.5.0.dist-info/RECORD,,
92
+ proximl-0.5.2.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
93
+ proximl-0.5.2.dist-info/METADATA,sha256=fc1Q36A0tQBo_Gb3KoPlCqfOOopX0WvMOtjXqyAiRC8,7344
94
+ proximl-0.5.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
95
+ proximl-0.5.2.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
96
+ proximl-0.5.2.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
97
+ proximl-0.5.2.dist-info/RECORD,,
@@ -0,0 +1,38 @@
1
+ import re
2
+ import json
3
+ import click
4
+ from unittest.mock import AsyncMock, patch
5
+ from pytest import mark, fixture, raises
6
+
7
+ pytestmark = [mark.cli, mark.unit, mark.cloudbender, mark.devices]
8
+
9
+ from proximl.cli.cloudbender import device as specimen
10
+ from proximl.cloudbender.devices import Device
11
+
12
+
13
+ def test_list(runner, mock_devices):
14
+ with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
15
+ mock_proximl.cloudbender = AsyncMock()
16
+ mock_proximl.cloudbender.devices = AsyncMock()
17
+ mock_proximl.cloudbender.devices.list = AsyncMock(
18
+ return_value=mock_devices
19
+ )
20
+ result = runner.invoke(
21
+ specimen,
22
+ args=["list", "--provider=prov-id-1", "--region=reg-id-1"],
23
+ )
24
+ assert result.exit_code == 0
25
+ mock_proximl.cloudbender.devices.list.assert_called_once_with(
26
+ provider_uuid="prov-id-1", region_uuid="reg-id-1"
27
+ )
28
+
29
+
30
+ def test_list_no_provider(runner, mock_devices):
31
+ with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
32
+ mock_proximl.cloudbender = AsyncMock()
33
+ mock_proximl.cloudbender.devices = AsyncMock()
34
+ mock_proximl.cloudbender.devices.list = AsyncMock(
35
+ return_value=mock_devices
36
+ )
37
+ result = runner.invoke(specimen, ["list"])
38
+ assert result.exit_code != 0
@@ -0,0 +1,270 @@
1
+ import re
2
+ import json
3
+ import logging
4
+ from unittest.mock import AsyncMock, patch
5
+ from pytest import mark, fixture, raises
6
+ from aiohttp import WSMessage, WSMsgType
7
+
8
+ import proximl.cloudbender.devices as specimen
9
+ from proximl.exceptions import (
10
+ ApiError,
11
+ SpecificationError,
12
+ ProxiMLException,
13
+ )
14
+
15
+ pytestmark = [mark.sdk, mark.unit, mark.cloudbender, mark.devices]
16
+
17
+
18
+ @fixture
19
+ def devices(mock_proximl):
20
+ yield specimen.Devices(mock_proximl)
21
+
22
+
23
+ @fixture
24
+ def device(mock_proximl):
25
+ yield specimen.Device(
26
+ mock_proximl,
27
+ provider_uuid="1",
28
+ region_uuid="a",
29
+ device_id="x",
30
+ type="device",
31
+ service="compute",
32
+ friendly_name="hq-orin-01",
33
+ hostname="hq-orin-01",
34
+ status="active",
35
+ online=True,
36
+ maintenance_mode=False,
37
+ job_status="stopped",
38
+ job_last_deployed="2023-06-02T21:22:40.084Z",
39
+ job_config_id="job-id-1",
40
+ job_config_revision="1685740490096",
41
+ device_config_id="conf-id-2",
42
+ )
43
+
44
+
45
+ class RegionsTests:
46
+ @mark.asyncio
47
+ async def test_get_device(
48
+ self,
49
+ devices,
50
+ mock_proximl,
51
+ ):
52
+ api_response = dict()
53
+ mock_proximl._query = AsyncMock(return_value=api_response)
54
+ await devices.get("1234", "5687", "91011")
55
+ mock_proximl._query.assert_called_once_with(
56
+ "/provider/1234/region/5687/device/91011", "GET", {}
57
+ )
58
+
59
+ @mark.asyncio
60
+ async def test_list_devices(
61
+ self,
62
+ devices,
63
+ mock_proximl,
64
+ ):
65
+ api_response = dict()
66
+ mock_proximl._query = AsyncMock(return_value=api_response)
67
+ await devices.list("1234", "5687")
68
+ mock_proximl._query.assert_called_once_with(
69
+ "/provider/1234/region/5687/device", "GET", {}
70
+ )
71
+
72
+ @mark.asyncio
73
+ async def test_remove_device(
74
+ self,
75
+ devices,
76
+ mock_proximl,
77
+ ):
78
+ api_response = dict()
79
+ mock_proximl._query = AsyncMock(return_value=api_response)
80
+ await devices.remove("1234", "4567", "8910")
81
+ mock_proximl._query.assert_called_once_with(
82
+ "/provider/1234/region/4567/device/8910", "DELETE", {}
83
+ )
84
+
85
+ @mark.asyncio
86
+ async def test_create_device(self, devices, mock_proximl):
87
+ requested_config = dict(
88
+ provider_uuid="provider-id-1",
89
+ region_uuid="region-id-1",
90
+ friendly_name="phys-device",
91
+ hostname="phys-device",
92
+ minion_id="asdf",
93
+ )
94
+ expected_payload = dict(
95
+ friendly_name="phys-device",
96
+ hostname="phys-device",
97
+ minion_id="asdf",
98
+ type="device",
99
+ service="compute",
100
+ )
101
+ api_response = {
102
+ "provider_uuid": "provider-id-1",
103
+ "region_uuid": "region-id-1",
104
+ "device_id": "rig-id-1",
105
+ "name": "phys-device",
106
+ "type": "device",
107
+ "service": "compute",
108
+ "status": "new",
109
+ "online": False,
110
+ "maintenance_mode": True,
111
+ "job_status": "stopped",
112
+ "job_last_deployed": "2023-06-02T21:22:40.084Z",
113
+ "job_config_id": "job-id-1",
114
+ "job_config_revision": "1685740490096",
115
+ "device_config_id": "conf-id-1",
116
+ "createdAt": "2020-12-31T23:59:59.000Z",
117
+ }
118
+
119
+ mock_proximl._query = AsyncMock(return_value=api_response)
120
+ response = await devices.create(**requested_config)
121
+ mock_proximl._query.assert_called_once_with(
122
+ "/provider/provider-id-1/region/region-id-1/device",
123
+ "POST",
124
+ None,
125
+ expected_payload,
126
+ )
127
+ assert response.id == "rig-id-1"
128
+
129
+
130
+ class deviceTests:
131
+ def test_device_properties(self, device):
132
+ assert isinstance(device.id, str)
133
+ assert isinstance(device.provider_uuid, str)
134
+ assert isinstance(device.region_uuid, str)
135
+ assert isinstance(device.name, str)
136
+ assert isinstance(device.hostname, str)
137
+ assert isinstance(device.status, str)
138
+ assert isinstance(device.online, bool)
139
+ assert isinstance(device.maintenance_mode, bool)
140
+ assert isinstance(device.device_config_id, str)
141
+ assert isinstance(device.job_status, str)
142
+ assert isinstance(device.job_last_deployed, str)
143
+ assert isinstance(device.job_config_id, str)
144
+ assert isinstance(device.job_config_revision, str)
145
+
146
+ def test_device_str(self, device):
147
+ string = str(device)
148
+ regex = r"^{.*\"device_id\": \"" + device.id + r"\".*}$"
149
+ assert isinstance(string, str)
150
+ assert re.match(regex, string)
151
+
152
+ def test_device_repr(self, device):
153
+ string = repr(device)
154
+ regex = (
155
+ r"^Device\( proximl , \*\*{.*'device_id': '"
156
+ + device.id
157
+ + r"'.*}\)$"
158
+ )
159
+ assert isinstance(string, str)
160
+ assert re.match(regex, string)
161
+
162
+ def test_device_bool(self, device, mock_proximl):
163
+ empty_device = specimen.Device(mock_proximl)
164
+ assert bool(device)
165
+ assert not bool(empty_device)
166
+
167
+ @mark.asyncio
168
+ async def test_device_remove(self, device, mock_proximl):
169
+ api_response = dict()
170
+ mock_proximl._query = AsyncMock(return_value=api_response)
171
+ await device.remove()
172
+ mock_proximl._query.assert_called_once_with(
173
+ "/provider/1/region/a/device/x", "DELETE"
174
+ )
175
+
176
+ @mark.asyncio
177
+ async def test_device_refresh(self, device, mock_proximl):
178
+ api_response = {
179
+ "provider_uuid": "provider-id-1",
180
+ "region_uuid": "region-id-1",
181
+ "device_id": "device-id-1",
182
+ "name": "phys-device",
183
+ "type": "device",
184
+ "service": "compute",
185
+ "status": "new",
186
+ "online": False,
187
+ "maintenance_mode": True,
188
+ "job_status": "stopped",
189
+ "job_last_deployed": "2023-06-02T21:22:40.084Z",
190
+ "job_config_id": "job-id-1",
191
+ "job_config_revision": "1685740490096",
192
+ "device_config_id": "conf-id-1",
193
+ "createdAt": "2020-12-31T23:59:59.000Z",
194
+ }
195
+ mock_proximl._query = AsyncMock(return_value=api_response)
196
+ response = await device.refresh()
197
+ mock_proximl._query.assert_called_once_with(
198
+ f"/provider/1/region/a/device/x", "GET"
199
+ )
200
+ assert device.id == "device-id-1"
201
+ assert response.id == "device-id-1"
202
+
203
+ @mark.asyncio
204
+ async def test_device_toggle_maintenance(self, device, mock_proximl):
205
+ api_response = None
206
+ mock_proximl._query = AsyncMock(return_value=api_response)
207
+ await device.toggle_maintenance()
208
+ mock_proximl._query.assert_called_once_with(
209
+ "/provider/1/region/a/device/x/maintenance", "PATCH"
210
+ )
211
+
212
+ @mark.asyncio
213
+ async def test_device_run_action(self, device, mock_proximl):
214
+ api_response = None
215
+ mock_proximl._query = AsyncMock(return_value=api_response)
216
+ await device.run_action(command="report")
217
+ mock_proximl._query.assert_called_once_with(
218
+ "/provider/1/region/a/device/x/action",
219
+ "POST",
220
+ None,
221
+ dict(command="report"),
222
+ )
223
+
224
+ @mark.asyncio
225
+ async def test_device_set_config(self, device, mock_proximl):
226
+ api_response = {
227
+ "provider_uuid": "provider-id-1",
228
+ "region_uuid": "region-id-1",
229
+ "device_id": "device-id-1",
230
+ "name": "phys-device",
231
+ "type": "device",
232
+ "service": "compute",
233
+ "status": "new",
234
+ "online": False,
235
+ "maintenance_mode": True,
236
+ "job_status": "stopped",
237
+ "job_last_deployed": "2023-06-02T21:22:40.084Z",
238
+ "job_config_id": "job-id-1",
239
+ "job_config_revision": "1685740490096",
240
+ "device_config_id": "config-id-1",
241
+ "createdAt": "2020-12-31T23:59:59.000Z",
242
+ }
243
+ mock_proximl._query = AsyncMock(return_value=api_response)
244
+ response = await device.set_config(device_config_id="config-id-1")
245
+ mock_proximl._query.assert_called_once_with(
246
+ "/provider/1/region/a/device/x",
247
+ "PATCH",
248
+ None,
249
+ dict(device_config_id="config-id-1"),
250
+ )
251
+ assert device.id == "device-id-1"
252
+ assert response.id == "device-id-1"
253
+
254
+ @mark.asyncio
255
+ async def test_device_deploy_endpoint(self, device, mock_proximl):
256
+ api_response = None
257
+ mock_proximl._query = AsyncMock(return_value=api_response)
258
+ await device.deploy_endpoint()
259
+ mock_proximl._query.assert_called_once_with(
260
+ "/provider/1/region/a/device/x/deploy", "PUT"
261
+ )
262
+
263
+ @mark.asyncio
264
+ async def test_device_stop_endpoint(self, device, mock_proximl):
265
+ api_response = None
266
+ mock_proximl._query = AsyncMock(return_value=api_response)
267
+ await device.stop_endpoint()
268
+ mock_proximl._query.assert_called_once_with(
269
+ "/provider/1/region/a/device/x/stop", "PUT"
270
+ )