proximl 0.5.10__py3-none-any.whl → 0.5.11__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.
Files changed (59) hide show
  1. proximl/__init__.py +1 -1
  2. proximl/checkpoints.py +46 -28
  3. proximl/cli/cloudbender/__init__.py +2 -1
  4. proximl/cli/cloudbender/datastore.py +2 -7
  5. proximl/cli/cloudbender/service.py +19 -2
  6. proximl/cli/project/__init__.py +3 -72
  7. proximl/cli/project/data_connector.py +61 -0
  8. proximl/cli/project/datastore.py +61 -0
  9. proximl/cli/project/service.py +61 -0
  10. proximl/cloudbender/cloudbender.py +4 -2
  11. proximl/cloudbender/data_connectors.py +8 -0
  12. proximl/cloudbender/datastores.py +9 -19
  13. proximl/cloudbender/nodes.py +44 -1
  14. proximl/cloudbender/providers.py +53 -0
  15. proximl/cloudbender/regions.py +48 -0
  16. proximl/cloudbender/services.py +65 -1
  17. proximl/datasets.py +41 -12
  18. proximl/exceptions.py +51 -0
  19. proximl/jobs.py +15 -19
  20. proximl/models.py +41 -22
  21. proximl/volumes.py +24 -5
  22. {proximl-0.5.10.dist-info → proximl-0.5.11.dist-info}/METADATA +1 -1
  23. {proximl-0.5.10.dist-info → proximl-0.5.11.dist-info}/RECORD +48 -46
  24. tests/integration/projects/conftest.py +3 -1
  25. tests/integration/projects/test_projects_data_connectors_integration.py +44 -0
  26. tests/integration/projects/test_projects_datastores_integration.py +42 -0
  27. tests/integration/projects/test_projects_services_integration.py +44 -0
  28. tests/integration/test_checkpoints_integration.py +1 -2
  29. tests/integration/test_jobs_integration.py +13 -0
  30. tests/integration/test_models_integration.py +0 -1
  31. tests/unit/cli/projects/__init__.py +0 -0
  32. tests/unit/cli/projects/test_cli_project_data_connector_unit.py +28 -0
  33. tests/unit/cli/projects/test_cli_project_datastore_unit.py +26 -0
  34. tests/unit/cli/projects/test_cli_project_key_unit.py +26 -0
  35. tests/unit/cli/projects/test_cli_project_secret_unit.py +26 -0
  36. tests/unit/cli/projects/test_cli_project_service_unit.py +26 -0
  37. tests/unit/cli/projects/test_cli_project_unit.py +19 -0
  38. tests/unit/cloudbender/test_datastores_unit.py +1 -5
  39. tests/unit/cloudbender/test_services_unit.py +6 -0
  40. tests/unit/conftest.py +158 -15
  41. tests/unit/test_checkpoints_unit.py +15 -23
  42. tests/unit/test_datasets_unit.py +15 -20
  43. tests/unit/test_models_unit.py +13 -16
  44. tests/unit/test_volumes_unit.py +3 -0
  45. proximl/cli/cloudbender/reservation.py +0 -159
  46. proximl/cli/project.py +0 -154
  47. proximl/cloudbender/reservations.py +0 -126
  48. proximl/projects.py +0 -187
  49. tests/integration/test_projects_integration.py +0 -44
  50. tests/unit/cli/cloudbender/test_cli_reservation_unit.py +0 -38
  51. tests/unit/cli/test_cli_project_unit.py +0 -46
  52. tests/unit/cloudbender/test_reservations_unit.py +0 -173
  53. tests/unit/test_auth.py +0 -30
  54. tests/unit/test_projects_unit.py +0 -294
  55. tests/unit/test_proximl.py +0 -54
  56. {proximl-0.5.10.dist-info → proximl-0.5.11.dist-info}/LICENSE +0 -0
  57. {proximl-0.5.10.dist-info → proximl-0.5.11.dist-info}/WHEEL +0 -0
  58. {proximl-0.5.10.dist-info → proximl-0.5.11.dist-info}/entry_points.txt +0 -0
  59. {proximl-0.5.10.dist-info → proximl-0.5.11.dist-info}/top_level.txt +0 -0
@@ -1,159 +0,0 @@
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 reservation(config):
9
- """proxiML CloudBender reservation commands."""
10
- pass
11
-
12
-
13
- @reservation.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 reservations for.",
27
- )
28
- @pass_config
29
- def list(config, provider, region):
30
- """List reservations."""
31
- data = [
32
- ["ID", "NAME", "TYPE", "RESOURCE", "HOSTNAME"],
33
- [
34
- "-" * 80,
35
- "-" * 80,
36
- "-" * 80,
37
- "-" * 80,
38
- "-" * 80,
39
- ],
40
- ]
41
-
42
- reservations = config.proximl.run(
43
- config.proximl.client.cloudbender.reservations.list(
44
- provider_uuid=provider, region_uuid=region
45
- )
46
- )
47
-
48
- for reservation in reservations:
49
- data.append(
50
- [
51
- reservation.id,
52
- reservation.name,
53
- reservation.type,
54
- reservation.resource,
55
- reservation.hostname,
56
- ]
57
- )
58
-
59
- for row in data:
60
- click.echo(
61
- "{: >37.36} {: >29.28} {: >9.8} {: >9.8} {: >29.28}"
62
- "".format(*row),
63
- file=config.stdout,
64
- )
65
-
66
-
67
- @reservation.command()
68
- @click.option(
69
- "--provider",
70
- "-p",
71
- type=click.STRING,
72
- required=True,
73
- help="The provider ID of the region.",
74
- )
75
- @click.option(
76
- "--region",
77
- "-r",
78
- type=click.STRING,
79
- required=True,
80
- help="The region ID to create the reservation in.",
81
- )
82
- @click.option(
83
- "--type",
84
- "-t",
85
- type=click.Choice(
86
- [
87
- "port",
88
- ],
89
- case_sensitive=False,
90
- ),
91
- required=True,
92
- help="The type of reservation to create.",
93
- )
94
- @click.option(
95
- "--hostname",
96
- "-h",
97
- type=click.STRING,
98
- required=True,
99
- help="The hostname to make the reservation on",
100
- )
101
- @click.option(
102
- "--resource",
103
- "-r",
104
- type=click.STRING,
105
- required=True,
106
- help="The resource to reserve",
107
- )
108
- @click.argument("name", type=click.STRING, required=True)
109
- @pass_config
110
- def create(config, provider, region, type, hostname, resource, name):
111
- """
112
- Creates a reservation.
113
- """
114
- return config.proximl.run(
115
- config.proximl.client.cloudbender.reservations.create(
116
- provider_uuid=provider,
117
- region_uuid=region,
118
- name=name,
119
- hostname=hostname,
120
- resource=resource,
121
- type=type,
122
- )
123
- )
124
-
125
-
126
- @reservation.command()
127
- @click.option(
128
- "--provider",
129
- "-p",
130
- type=click.STRING,
131
- required=True,
132
- help="The provider ID of the region.",
133
- )
134
- @click.option(
135
- "--region",
136
- "-r",
137
- type=click.STRING,
138
- required=True,
139
- help="The region ID to remove the reservation from.",
140
- )
141
- @click.argument("reservation", type=click.STRING)
142
- @pass_config
143
- def remove(config, provider, region, reservation):
144
- """
145
- Remove a reservation.
146
-
147
- RESERVATION may be specified by name or ID, but ID is preferred.
148
- """
149
- reservations = config.proximl.run(
150
- config.proximl.client.cloudbender.reservations.list(
151
- provider_uuid=provider, region_uuid=region
152
- )
153
- )
154
-
155
- found = search_by_id_name(reservation, reservations)
156
- if None is found:
157
- raise click.UsageError("Cannot find specified reservation.")
158
-
159
- return config.proximl.run(found.remove())
proximl/cli/project.py DELETED
@@ -1,154 +0,0 @@
1
- import click
2
- from proximl.cli import cli, pass_config, search_by_id_name
3
-
4
-
5
- @cli.group()
6
- @pass_config
7
- def project(config):
8
- """proxiML project commands."""
9
- pass
10
-
11
-
12
- @project.command()
13
- @pass_config
14
- def list(config):
15
- """List projects."""
16
- data = [
17
- ["ID", "NAME", "OWNER", "MINE"],
18
- [
19
- "-" * 80,
20
- "-" * 80,
21
- "-" * 80,
22
- "-" * 80,
23
- ],
24
- ]
25
-
26
- projects = config.proximl.run(config.proximl.client.projects.list())
27
-
28
- for project in projects:
29
- data.append(
30
- [
31
- project.id,
32
- project.name,
33
- project.owner_name,
34
- "X" if project.is_owner else "",
35
- ]
36
- )
37
-
38
- for row in data:
39
- click.echo(
40
- "{: >38.36} {: >30.28} {: >15.13} {: >4.4}" "".format(*row),
41
- file=config.stdout,
42
- )
43
-
44
-
45
- @project.command()
46
- @click.argument("name", type=click.STRING)
47
- @pass_config
48
- def create(config, name):
49
- """
50
- Create a project.
51
-
52
- Project is created with the specified NAME.
53
- """
54
-
55
- return config.proximl.run(
56
- config.proximl.client.projects.create(
57
- name=name,
58
- )
59
- )
60
-
61
-
62
- @project.command()
63
- @click.argument("project", type=click.STRING)
64
- @pass_config
65
- def remove(config, project):
66
- """
67
- Remove a project.
68
-
69
- PROJECT may be specified by name or ID, but ID is preferred.
70
- """
71
- projects = config.proximl.run(config.proximl.client.projects.list())
72
-
73
- found = search_by_id_name(project, projects)
74
- if None is found:
75
- raise click.UsageError("Cannot find specified project.")
76
-
77
- return config.proximl.run(found.remove())
78
-
79
-
80
- @project.command()
81
- @pass_config
82
- def list_datastores(config):
83
- """List project datastores."""
84
- data = [
85
- ["ID", "NAME", "TYPE", "REGION_UUID"],
86
- [
87
- "-" * 80,
88
- "-" * 80,
89
- "-" * 80,
90
- "-" * 80,
91
- ],
92
- ]
93
- project = config.proximl.run(
94
- config.proximl.client.projects.get(config.proximl.client.project)
95
- )
96
-
97
- datastores = config.proximl.run(project.list_datastores())
98
-
99
- for datastore in datastores:
100
- data.append(
101
- [
102
- datastore.id,
103
- datastore.name,
104
- datastore.type,
105
- datastore.region_uuid,
106
- ]
107
- )
108
-
109
- for row in data:
110
- click.echo(
111
- "{: >38.36} {: >30.28} {: >15.13} {: >38.36}" "".format(*row),
112
- file=config.stdout,
113
- )
114
-
115
-
116
- @project.command()
117
- @pass_config
118
- def list_reservations(config):
119
- """List project reservations."""
120
- data = [
121
- ["ID", "NAME", "TYPE", "RESOURCE", "HOSTNAME", "REGION_UUID"],
122
- [
123
- "-" * 80,
124
- "-" * 80,
125
- "-" * 80,
126
- "-" * 80,
127
- "-" * 80,
128
- "-" * 80,
129
- ],
130
- ]
131
- project = config.proximl.run(
132
- config.proximl.client.projects.get(config.proximl.client.project)
133
- )
134
-
135
- reservations = config.proximl.run(project.list_reservations())
136
-
137
- for reservation in reservations:
138
- data.append(
139
- [
140
- reservation.id,
141
- reservation.name,
142
- reservation.type,
143
- reservation.resource,
144
- reservation.hostname,
145
- reservation.region_uuid,
146
- ]
147
- )
148
-
149
- for row in data:
150
- click.echo(
151
- "{: >38.36} {: >30.28} {: >8.6} {: >15.13} {: >30.28} {: >38.36}"
152
- "".format(*row),
153
- file=config.stdout,
154
- )
@@ -1,126 +0,0 @@
1
- import json
2
- import logging
3
-
4
-
5
- class Reservations(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}/reservation/{id}",
12
- "GET",
13
- kwargs,
14
- )
15
- return Reservation(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}/reservation",
20
- "GET",
21
- kwargs,
22
- )
23
- reservations = [
24
- Reservation(self.proximl, **reservation) for reservation in resp
25
- ]
26
- return reservations
27
-
28
- async def create(
29
- self,
30
- provider_uuid,
31
- region_uuid,
32
- name,
33
- type,
34
- resource,
35
- hostname,
36
- **kwargs,
37
- ):
38
- logging.info(f"Creating Reservation {name}")
39
- data = dict(
40
- name=name,
41
- type=type,
42
- resource=resource,
43
- hostname=hostname,
44
- **kwargs,
45
- )
46
- payload = {k: v for k, v in data.items() if v is not None}
47
- resp = await self.proximl._query(
48
- f"/provider/{provider_uuid}/region/{region_uuid}/reservation",
49
- "POST",
50
- None,
51
- payload,
52
- )
53
- reservation = Reservation(self.proximl, **resp)
54
- logging.info(f"Created Reservation {name} with id {reservation.id}")
55
- return reservation
56
-
57
- async def remove(self, provider_uuid, region_uuid, id, **kwargs):
58
- await self.proximl._query(
59
- f"/provider/{provider_uuid}/region/{region_uuid}/reservation/{id}",
60
- "DELETE",
61
- kwargs,
62
- )
63
-
64
-
65
- class Reservation:
66
- def __init__(self, proximl, **kwargs):
67
- self.proximl = proximl
68
- self._reservation = kwargs
69
- self._id = self._reservation.get("reservation_id")
70
- self._provider_uuid = self._reservation.get("provider_uuid")
71
- self._region_uuid = self._reservation.get("region_uuid")
72
- self._type = self._reservation.get("type")
73
- self._name = self._reservation.get("name")
74
- self._resource = self._reservation.get("resource")
75
- self._hostname = self._reservation.get("hostname")
76
-
77
- @property
78
- def id(self) -> str:
79
- return self._id
80
-
81
- @property
82
- def provider_uuid(self) -> str:
83
- return self._provider_uuid
84
-
85
- @property
86
- def region_uuid(self) -> str:
87
- return self._region_uuid
88
-
89
- @property
90
- def type(self) -> str:
91
- return self._type
92
-
93
- @property
94
- def name(self) -> str:
95
- return self._name
96
-
97
- @property
98
- def resource(self) -> str:
99
- return self._resource
100
-
101
- @property
102
- def hostname(self) -> str:
103
- return self._hostname
104
-
105
- def __str__(self):
106
- return json.dumps({k: v for k, v in self._reservation.items()})
107
-
108
- def __repr__(self):
109
- return f"Reservation( proximl , **{self._reservation.__repr__()})"
110
-
111
- def __bool__(self):
112
- return bool(self._id)
113
-
114
- async def remove(self):
115
- await self.proximl._query(
116
- f"/provider/{self._provider_uuid}/region/{self._region_uuid}/reservation/{self._id}",
117
- "DELETE",
118
- )
119
-
120
- async def refresh(self):
121
- resp = await self.proximl._query(
122
- f"/provider/{self._provider_uuid}/region/{self._region_uuid}/reservation/{self._id}",
123
- "GET",
124
- )
125
- self.__init__(self.proximl, **resp)
126
- return self
proximl/projects.py DELETED
@@ -1,187 +0,0 @@
1
- import json
2
- import logging
3
-
4
-
5
- class Projects(object):
6
- def __init__(self, proximl):
7
- self.proximl = proximl
8
-
9
- async def get(self, id, **kwargs):
10
- resp = await self.proximl._query(f"/project/{id}", "GET", kwargs)
11
- return Project(self.proximl, **resp)
12
-
13
- async def list(self, **kwargs):
14
- resp = await self.proximl._query(f"/project", "GET", kwargs)
15
- projects = [Project(self.proximl, **project) for project in resp]
16
- return projects
17
-
18
- async def create(self, name, copy_keys=False, **kwargs):
19
- data = dict(
20
- name=name,
21
- copy_keys=copy_keys,
22
- )
23
- payload = {k: v for k, v in data.items() if v is not None}
24
- logging.info(f"Creating Project {name}")
25
- resp = await self.proximl._query("/project", "POST", None, payload)
26
- project = Project(self.proximl, **resp)
27
- logging.info(f"Created Project {name} with id {project.id}")
28
-
29
- return project
30
-
31
- async def remove(self, id, **kwargs):
32
- await self.proximl._query(f"/project/{id}", "DELETE", kwargs)
33
-
34
-
35
- class ProjectDatastore:
36
- def __init__(self, proximl, **kwargs):
37
- self.proximl = proximl
38
- self._datastore = kwargs
39
- self._id = self._datastore.get("id")
40
- self._project_uuid = self._datastore.get("project_uuid")
41
- self._name = self._datastore.get("name")
42
- self._type = self._datastore.get("type")
43
- self._region_uuid = self._datastore.get("region_uuid")
44
-
45
- @property
46
- def id(self) -> str:
47
- return self._id
48
-
49
- @property
50
- def project_uuid(self) -> str:
51
- return self._project_uuid
52
-
53
- @property
54
- def name(self) -> str:
55
- return self._name
56
-
57
- @property
58
- def type(self) -> str:
59
- return self._type
60
-
61
- @property
62
- def region_uuid(self) -> str:
63
- return self._region_uuid
64
-
65
- def __str__(self):
66
- return json.dumps({k: v for k, v in self._datastore.items()})
67
-
68
- def __repr__(self):
69
- return f"ProjectDatastore( proximl , **{self._datastore.__repr__()})"
70
-
71
- def __bool__(self):
72
- return bool(self._id)
73
-
74
-
75
- class ProjectReservation:
76
- def __init__(self, proximl, **kwargs):
77
- self.proximl = proximl
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")
86
-
87
- @property
88
- def id(self) -> str:
89
- return self._id
90
-
91
- @property
92
- def project_uuid(self) -> str:
93
- return self._project_uuid
94
-
95
- @property
96
- def name(self) -> str:
97
- return self._name
98
-
99
- @property
100
- def type(self) -> str:
101
- return self._type
102
-
103
- @property
104
- def hostname(self) -> str:
105
- return self._hostname
106
-
107
- @property
108
- def resource(self) -> str:
109
- return self._resource
110
-
111
- @property
112
- def region_uuid(self) -> str:
113
- return self._region_uuid
114
-
115
- def __str__(self):
116
- return json.dumps({k: v for k, v in self._reservation.items()})
117
-
118
- def __repr__(self):
119
- return (
120
- f"ProjectReservation( proximl , **{self._reservation.__repr__()})"
121
- )
122
-
123
- def __bool__(self):
124
- return bool(self._id)
125
-
126
-
127
- class Project:
128
- def __init__(self, proximl, **kwargs):
129
- self.proximl = proximl
130
- self._project = kwargs
131
- self._id = self._project.get("id")
132
- self._name = self._project.get("name")
133
- self._is_owner = self._project.get("owner")
134
- self._owner_name = self._project.get("owner_name")
135
-
136
- @property
137
- def id(self) -> str:
138
- return self._id
139
-
140
- @property
141
- def name(self) -> str:
142
- return self._name
143
-
144
- @property
145
- def is_owner(self) -> bool:
146
- return self._is_owner
147
-
148
- @property
149
- def owner_name(self) -> str:
150
- return self._owner_name
151
-
152
- def __str__(self):
153
- return json.dumps({k: v for k, v in self._project.items()})
154
-
155
- def __repr__(self):
156
- return f"Project( proximl , **{self._project.__repr__()})"
157
-
158
- def __bool__(self):
159
- return bool(self._id)
160
-
161
- async def remove(self):
162
- await self.proximl._query(f"/project/{self._id}", "DELETE")
163
-
164
- async def list_datastores(self):
165
- resp = await self.proximl._query(
166
- f"/project/{self._id}/datastores", "GET"
167
- )
168
- datastores = [
169
- ProjectDatastore(self.proximl, **datastore) for datastore in resp
170
- ]
171
- return datastores
172
-
173
- async def list_reservations(self):
174
- resp = await self.proximl._query(
175
- f"/project/{self._id}/reservations", "GET"
176
- )
177
- reservations = [
178
- ProjectReservation(self.proximl, **reservation)
179
- for reservation in resp
180
- ]
181
- return reservations
182
-
183
- async def refresh_datastores(self):
184
- await self.proximl._query(f"/project/{self._id}/datastores", "PATCH")
185
-
186
- async def refresh_reservations(self):
187
- await self.proximl._query(f"/project/{self._id}/reservations", "PATCH")
@@ -1,44 +0,0 @@
1
- import re
2
- import sys
3
- import asyncio
4
- from pytest import mark, fixture
5
-
6
- pytestmark = [mark.sdk, mark.integration, mark.projects]
7
-
8
-
9
- @mark.create
10
- @mark.asyncio
11
- class GetProjectsTests:
12
- @fixture(scope="class")
13
- async def project(self, proximl):
14
- project = await proximl.projects.create(name="New Project", copy_keys=False)
15
- yield project
16
- await project.remove()
17
-
18
- async def test_get_projects(self, proximl):
19
- projects = await proximl.projects.list()
20
- assert len(projects) > 0
21
-
22
- async def test_get_project(self, proximl, project):
23
- response = await proximl.projects.get(project.id)
24
- assert response.id == project.id
25
-
26
- async def test_project_properties(self, project):
27
- assert isinstance(project.id, str)
28
- assert isinstance(project.name, str)
29
- assert isinstance(project.owner_name, str)
30
- assert isinstance(project.is_owner, bool)
31
- assert project.name == "New Project"
32
- assert project.is_owner
33
-
34
- async def test_project_str(self, project):
35
- string = str(project)
36
- regex = r"^{.*\"id\": \"" + project.id + r"\".*}$"
37
- assert isinstance(string, str)
38
- assert re.match(regex, string)
39
-
40
- async def test_project_repr(self, project):
41
- string = repr(project)
42
- regex = r"^Project\( proximl , \*\*{.*'id': '" + project.id + r"'.*}\)$"
43
- assert isinstance(string, str)
44
- assert re.match(regex, string)
@@ -1,38 +0,0 @@
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.reservations]
8
-
9
- from proximl.cli.cloudbender import reservation as specimen
10
- from proximl.cloudbender.reservations import Reservation
11
-
12
-
13
- def test_list(runner, mock_reservations):
14
- with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
15
- mock_proximl.cloudbender = AsyncMock()
16
- mock_proximl.cloudbender.reservations = AsyncMock()
17
- mock_proximl.cloudbender.reservations.list = AsyncMock(
18
- return_value=mock_reservations
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.reservations.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_reservations):
31
- with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
32
- mock_proximl.cloudbender = AsyncMock()
33
- mock_proximl.cloudbender.reservations = AsyncMock()
34
- mock_proximl.cloudbender.reservations.list = AsyncMock(
35
- return_value=mock_reservations
36
- )
37
- result = runner.invoke(specimen, ["list"])
38
- assert result.exit_code != 0