codecraftai-restclient-runpod 1.2.0__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.
- ccai/restclient/runpod/__init__.py +0 -0
- ccai/restclient/runpod/apigroups/__init__.py +0 -0
- ccai/restclient/runpod/apigroups/datacentersgroup.py +82 -0
- ccai/restclient/runpod/apigroups/gputypesgroup.py +80 -0
- ccai/restclient/runpod/apigroups/groupsupport.py +182 -0
- ccai/restclient/runpod/apigroups/networkvolumesgroup.py +156 -0
- ccai/restclient/runpod/apigroups/podsgroup.py +216 -0
- ccai/restclient/runpod/apigroups/podtemplatesgroup.py +148 -0
- ccai/restclient/runpod/apigroups/tagsgroup.py +74 -0
- ccai/restclient/runpod/aspectsrunpod.py +123 -0
- ccai/restclient/runpod/enums/__init__.py +0 -0
- ccai/restclient/runpod/enums/runpodcloudtype.py +30 -0
- ccai/restclient/runpod/exceptions/__init__.py +0 -0
- ccai/restclient/runpod/exceptions/runpoderrors.py +55 -0
- ccai/restclient/runpod/model/__init__.py +0 -0
- ccai/restclient/runpod/model/runpoddatacenter.py +85 -0
- ccai/restclient/runpod/model/runpodgputype.py +109 -0
- ccai/restclient/runpod/model/runpodnetworkvolume.py +91 -0
- ccai/restclient/runpod/model/runpodpod.py +240 -0
- ccai/restclient/runpod/model/runpodpodtemplate.py +103 -0
- ccai/restclient/runpod/py.typed +0 -0
- ccai/restclient/runpod/runpodclient.py +584 -0
- ccai/restclient/runpod/specs/__init__.py +0 -0
- ccai/restclient/runpod/specs/runpodnetworkvolumespec.py +56 -0
- ccai/restclient/runpod/specs/runpodpodlaunchspec.py +125 -0
- ccai/restclient/runpod/specs/runpodpodtemplatespec.py +65 -0
- codecraftai_restclient_runpod-1.2.0.dist-info/METADATA +86 -0
- codecraftai_restclient_runpod-1.2.0.dist-info/RECORD +30 -0
- codecraftai_restclient_runpod-1.2.0.dist-info/WHEEL +4 -0
- codecraftai_restclient_runpod-1.2.0.dist-info/licenses/LICENSE.md +204 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: datacentersgroup
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Module that contains the :class:`DataCentersGroup` api
|
|
5
|
+
group exposing the RunPod data-center catalog endpoint.
|
|
6
|
+
|
|
7
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Myron Walker"
|
|
11
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
12
|
+
__credits__ = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, List
|
|
16
|
+
|
|
17
|
+
from ccai.restclient.base.restapigroup import RestApiGroup
|
|
18
|
+
|
|
19
|
+
from ccai.restclient.runpod.aspectsrunpod import (
|
|
20
|
+
AspectsRunpod, DEFAULT_RUNPOD_ASPECTS
|
|
21
|
+
)
|
|
22
|
+
from ccai.restclient.runpod.apigroups.groupsupport import raise_for_status
|
|
23
|
+
from ccai.restclient.runpod.model.runpoddatacenter import RunpodDataCenter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DataCentersGroup(RestApiGroup):
|
|
27
|
+
"""
|
|
28
|
+
:class:`DataCentersGroup` exposes the RunPod data-center catalog
|
|
29
|
+
endpoint. The catalog is a small, non-paged collection.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
BASE_LEAF = "/v1"
|
|
33
|
+
|
|
34
|
+
def list(self, *,
|
|
35
|
+
aspects: AspectsRunpod = DEFAULT_RUNPOD_ASPECTS) -> List[RunpodDataCenter]:
|
|
36
|
+
"""
|
|
37
|
+
List every RunPod data center.
|
|
38
|
+
|
|
39
|
+
:param aspects: Behaviour control for the call.
|
|
40
|
+
|
|
41
|
+
:returns: A list of :class:`RunpodDataCenter` records.
|
|
42
|
+
"""
|
|
43
|
+
client = self.client
|
|
44
|
+
leaf = self.build_leaf(parts=["datacenters"])
|
|
45
|
+
response = client.request(method="GET", leaf=leaf, aspects=aspects)
|
|
46
|
+
raise_for_status(response=response, action="list data centers")
|
|
47
|
+
|
|
48
|
+
payload = response.json()
|
|
49
|
+
raw_entries = self._extract_entries(payload=payload)
|
|
50
|
+
|
|
51
|
+
rtnval: List[RunpodDataCenter] = []
|
|
52
|
+
for entry in raw_entries:
|
|
53
|
+
rtnval.append(RunpodDataCenter.from_dict(entry))
|
|
54
|
+
|
|
55
|
+
return rtnval
|
|
56
|
+
|
|
57
|
+
def _extract_entries(self, *, payload: Any) -> List[Dict[str, Any]]:
|
|
58
|
+
"""
|
|
59
|
+
Extract the data-center entry list from a response payload.
|
|
60
|
+
|
|
61
|
+
:param payload: The decoded response payload.
|
|
62
|
+
|
|
63
|
+
:returns: The list of raw data-center dictionaries.
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(payload, list):
|
|
66
|
+
rtnval: List[Dict[str, Any]] = list(payload)
|
|
67
|
+
return rtnval
|
|
68
|
+
|
|
69
|
+
if isinstance(payload, dict):
|
|
70
|
+
entries = payload.get("dataCenters")
|
|
71
|
+
if entries is None:
|
|
72
|
+
entries = payload.get("datacenters")
|
|
73
|
+
if entries is None:
|
|
74
|
+
entries = payload.get("items", [])
|
|
75
|
+
rtnval = list(entries)
|
|
76
|
+
return rtnval
|
|
77
|
+
|
|
78
|
+
rtnval = []
|
|
79
|
+
return rtnval
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ["DataCentersGroup"]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: gputypesgroup
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Module that contains the :class:`GpuTypesGroup` api group
|
|
5
|
+
exposing the RunPod GPU-type catalog endpoint.
|
|
6
|
+
|
|
7
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Myron Walker"
|
|
11
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
12
|
+
__credits__ = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, List
|
|
16
|
+
|
|
17
|
+
from ccai.restclient.base.restapigroup import RestApiGroup
|
|
18
|
+
|
|
19
|
+
from ccai.restclient.runpod.aspectsrunpod import (
|
|
20
|
+
AspectsRunpod, DEFAULT_RUNPOD_ASPECTS
|
|
21
|
+
)
|
|
22
|
+
from ccai.restclient.runpod.apigroups.groupsupport import raise_for_status
|
|
23
|
+
from ccai.restclient.runpod.model.runpodgputype import RunpodGpuType
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GpuTypesGroup(RestApiGroup):
|
|
27
|
+
"""
|
|
28
|
+
:class:`GpuTypesGroup` exposes the RunPod GPU-type catalog endpoint.
|
|
29
|
+
The catalog is a small, non-paged collection.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
BASE_LEAF = "/v1"
|
|
33
|
+
|
|
34
|
+
def list(self, *,
|
|
35
|
+
aspects: AspectsRunpod = DEFAULT_RUNPOD_ASPECTS) -> List[RunpodGpuType]:
|
|
36
|
+
"""
|
|
37
|
+
List every GPU type RunPod offers.
|
|
38
|
+
|
|
39
|
+
:param aspects: Behaviour control for the call.
|
|
40
|
+
|
|
41
|
+
:returns: A list of :class:`RunpodGpuType` records.
|
|
42
|
+
"""
|
|
43
|
+
client = self.client
|
|
44
|
+
leaf = self.build_leaf(parts=["gpuTypes"])
|
|
45
|
+
response = client.request(method="GET", leaf=leaf, aspects=aspects)
|
|
46
|
+
raise_for_status(response=response, action="list gpu types")
|
|
47
|
+
|
|
48
|
+
payload = response.json()
|
|
49
|
+
raw_entries = self._extract_entries(payload=payload)
|
|
50
|
+
|
|
51
|
+
rtnval: List[RunpodGpuType] = []
|
|
52
|
+
for entry in raw_entries:
|
|
53
|
+
rtnval.append(RunpodGpuType.from_dict(entry))
|
|
54
|
+
|
|
55
|
+
return rtnval
|
|
56
|
+
|
|
57
|
+
def _extract_entries(self, *, payload: Any) -> List[Dict[str, Any]]:
|
|
58
|
+
"""
|
|
59
|
+
Extract the GPU-type entry list from a response payload.
|
|
60
|
+
|
|
61
|
+
:param payload: The decoded response payload.
|
|
62
|
+
|
|
63
|
+
:returns: The list of raw GPU-type dictionaries.
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(payload, list):
|
|
66
|
+
rtnval: List[Dict[str, Any]] = list(payload)
|
|
67
|
+
return rtnval
|
|
68
|
+
|
|
69
|
+
if isinstance(payload, dict):
|
|
70
|
+
entries = payload.get("gpuTypes")
|
|
71
|
+
if entries is None:
|
|
72
|
+
entries = payload.get("items", [])
|
|
73
|
+
rtnval = list(entries)
|
|
74
|
+
return rtnval
|
|
75
|
+
|
|
76
|
+
rtnval = []
|
|
77
|
+
return rtnval
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = ["GpuTypesGroup"]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: groupsupport
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Module that contains shared helpers used by the RunPod
|
|
5
|
+
api-group classes — URL composition and HTTP-error
|
|
6
|
+
reclassification.
|
|
7
|
+
|
|
8
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__author__ = "Myron Walker"
|
|
12
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
13
|
+
__credits__ = []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from typing import Any, Callable, Dict, Iterator, List, Optional
|
|
19
|
+
from urllib.parse import quote
|
|
20
|
+
|
|
21
|
+
from requests import Response
|
|
22
|
+
|
|
23
|
+
from ccai.errors.exceptions import SemanticError
|
|
24
|
+
|
|
25
|
+
from ccai.restclient.runpod.exceptions.runpoderrors import (
|
|
26
|
+
RunpodAuthenticationError, RunpodError
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def encode_segment(*, segment: str) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Percent-encode a single URL path segment.
|
|
33
|
+
|
|
34
|
+
:param segment: The path segment to encode. Must be a non-empty
|
|
35
|
+
string.
|
|
36
|
+
|
|
37
|
+
:returns: The percent-encoded segment.
|
|
38
|
+
|
|
39
|
+
:raises: :class:`SemanticError` when ``segment`` is ``None`` or
|
|
40
|
+
empty.
|
|
41
|
+
"""
|
|
42
|
+
if segment is None:
|
|
43
|
+
raise SemanticError("A RunPod URL path segment must not be None.")
|
|
44
|
+
if not isinstance(segment, str):
|
|
45
|
+
raise SemanticError("A RunPod URL path segment must be a string.")
|
|
46
|
+
if len(segment) == 0:
|
|
47
|
+
raise SemanticError("A RunPod URL path segment must not be empty.")
|
|
48
|
+
|
|
49
|
+
rtnval = quote(segment, safe="")
|
|
50
|
+
return rtnval
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def compose_leaf(*, base: str, parts: List[str]) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Compose a REST leaf path by joining ``base`` with per-segment
|
|
56
|
+
percent-encoded ``parts``.
|
|
57
|
+
|
|
58
|
+
:param base: The base leaf path (e.g. ``"/v1"``).
|
|
59
|
+
:param parts: The path segments to append. Each segment is
|
|
60
|
+
percent-encoded; empty / ``None`` segments are rejected.
|
|
61
|
+
|
|
62
|
+
:returns: The composed leaf path.
|
|
63
|
+
|
|
64
|
+
:raises: :class:`SemanticError` when ``parts`` is ``None`` or holds
|
|
65
|
+
an empty / ``None`` segment.
|
|
66
|
+
"""
|
|
67
|
+
if parts is None:
|
|
68
|
+
raise SemanticError("compose_leaf requires a non-None parts list.")
|
|
69
|
+
|
|
70
|
+
encoded_parts: List[str] = []
|
|
71
|
+
for part in parts:
|
|
72
|
+
encoded_parts.append(encode_segment(segment=part))
|
|
73
|
+
|
|
74
|
+
trimmed_base = base
|
|
75
|
+
if trimmed_base.endswith("/"):
|
|
76
|
+
trimmed_base = trimmed_base[:-1]
|
|
77
|
+
|
|
78
|
+
joined = "/".join(encoded_parts)
|
|
79
|
+
rtnval = "{}/{}".format(trimmed_base, joined)
|
|
80
|
+
return rtnval
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def raise_for_status(*, response: Response, action: str) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Reclassify a non-2xx RunPod response into a typed exception.
|
|
86
|
+
|
|
87
|
+
:param response: The :class:`Response` to inspect.
|
|
88
|
+
:param action: A short description of the attempted action.
|
|
89
|
+
|
|
90
|
+
:returns: ``None``.
|
|
91
|
+
|
|
92
|
+
:raises: :class:`RunpodAuthenticationError` on HTTP 401 / 403;
|
|
93
|
+
:class:`RunpodError` on every other non-2xx status.
|
|
94
|
+
"""
|
|
95
|
+
status = response.status_code
|
|
96
|
+
if 200 <= status < 300:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
err_msg_lines = ["RunPod API call failed: {}.".format(action)]
|
|
100
|
+
err_msg_lines.append("STATUS: {}".format(status))
|
|
101
|
+
err_msg_lines.append("RESPONSE:")
|
|
102
|
+
err_msg_lines.append(response.text)
|
|
103
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
104
|
+
|
|
105
|
+
if status == 401 or status == 403:
|
|
106
|
+
raise RunpodAuthenticationError(err_msg)
|
|
107
|
+
|
|
108
|
+
raise RunpodError(err_msg)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def safe_json(*, response: Response) -> Dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Parse a response body as a JSON object, degrading to an empty dict.
|
|
114
|
+
|
|
115
|
+
:param response: The :class:`Response` to parse.
|
|
116
|
+
|
|
117
|
+
:returns: The decoded object, or an empty dict.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
payload = response.json()
|
|
121
|
+
except ValueError:
|
|
122
|
+
rtnval: Dict[str, Any] = {}
|
|
123
|
+
return rtnval
|
|
124
|
+
|
|
125
|
+
if not isinstance(payload, dict):
|
|
126
|
+
rtnval = {}
|
|
127
|
+
return rtnval
|
|
128
|
+
|
|
129
|
+
rtnval = payload
|
|
130
|
+
return rtnval
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def iter_cursor_pages(*, fetch_page: Callable[[Optional[str]], Dict[str, Any]],
|
|
134
|
+
items_field: str = "items",
|
|
135
|
+
cursor_field: str = "next") -> Iterator[Dict[str, Any]]:
|
|
136
|
+
"""
|
|
137
|
+
Iterate a cursor-paginated RunPod endpoint, yielding each raw item
|
|
138
|
+
dictionary across every page.
|
|
139
|
+
|
|
140
|
+
:param fetch_page: Callable accepting the current cursor token
|
|
141
|
+
(``None`` on the first call) and returning a page dictionary.
|
|
142
|
+
:param items_field: Name of the field holding the page item list.
|
|
143
|
+
:param cursor_field: Name of the field holding the next-page cursor
|
|
144
|
+
token (``None`` / absent / empty-string indicates the final
|
|
145
|
+
page).
|
|
146
|
+
|
|
147
|
+
:returns: An iterator over the raw item dictionaries.
|
|
148
|
+
|
|
149
|
+
:raises: :class:`RunpodError` when the endpoint returns a cursor
|
|
150
|
+
token that has already been seen (cursor cycle).
|
|
151
|
+
"""
|
|
152
|
+
cursor: Optional[str] = None
|
|
153
|
+
seen_cursors: List[str] = []
|
|
154
|
+
while True:
|
|
155
|
+
page = fetch_page(cursor)
|
|
156
|
+
|
|
157
|
+
items = page.get(items_field)
|
|
158
|
+
if items is None:
|
|
159
|
+
items = []
|
|
160
|
+
for item in items:
|
|
161
|
+
yield item
|
|
162
|
+
|
|
163
|
+
next_cursor = page.get(cursor_field)
|
|
164
|
+
if next_cursor is None:
|
|
165
|
+
break
|
|
166
|
+
if not isinstance(next_cursor, str):
|
|
167
|
+
break
|
|
168
|
+
if len(next_cursor) == 0:
|
|
169
|
+
break
|
|
170
|
+
if next_cursor in seen_cursors:
|
|
171
|
+
err_msg_lines = ["RunPod cursor pagination detected a cursor cycle."]
|
|
172
|
+
err_msg_lines.append("REPEATED CURSOR: {}".format(next_cursor))
|
|
173
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
174
|
+
raise RunpodError(err_msg)
|
|
175
|
+
|
|
176
|
+
seen_cursors.append(next_cursor)
|
|
177
|
+
cursor = next_cursor
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
__all__ = ["encode_segment", "compose_leaf", "raise_for_status", "safe_json",
|
|
182
|
+
"iter_cursor_pages"]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: networkvolumesgroup
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Module that contains the :class:`NetworkVolumesGroup` api
|
|
5
|
+
group exposing the RunPod network-volume endpoints.
|
|
6
|
+
|
|
7
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Myron Walker"
|
|
11
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
12
|
+
__credits__ = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from ccai.errors.exceptions import SemanticError
|
|
18
|
+
|
|
19
|
+
from ccai.restclient.base.restapigroup import RestApiGroup
|
|
20
|
+
|
|
21
|
+
from ccai.restclient.runpod.aspectsrunpod import (
|
|
22
|
+
AspectsRunpod, DEFAULT_RUNPOD_ASPECTS
|
|
23
|
+
)
|
|
24
|
+
from ccai.restclient.runpod.apigroups.groupsupport import (
|
|
25
|
+
iter_cursor_pages, raise_for_status, safe_json
|
|
26
|
+
)
|
|
27
|
+
from ccai.restclient.runpod.model.runpodnetworkvolume import (
|
|
28
|
+
RunpodNetworkVolume
|
|
29
|
+
)
|
|
30
|
+
from ccai.restclient.runpod.specs.runpodnetworkvolumespec import (
|
|
31
|
+
RunpodNetworkVolumeSpec
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NetworkVolumesGroup(RestApiGroup):
|
|
36
|
+
"""
|
|
37
|
+
:class:`NetworkVolumesGroup` exposes the RunPod network-volume
|
|
38
|
+
endpoints — idempotent create, cursor-paged list, and idempotent
|
|
39
|
+
delete.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
BASE_LEAF = "/v1"
|
|
43
|
+
|
|
44
|
+
def create(self, *, spec: RunpodNetworkVolumeSpec,
|
|
45
|
+
aspects: AspectsRunpod = DEFAULT_RUNPOD_ASPECTS) -> RunpodNetworkVolume:
|
|
46
|
+
"""
|
|
47
|
+
Create a network volume. An existing volume carrying the same
|
|
48
|
+
name in the same data center is returned unchanged (idempotent
|
|
49
|
+
create).
|
|
50
|
+
|
|
51
|
+
:param spec: The :class:`RunpodNetworkVolumeSpec` to realise.
|
|
52
|
+
:param aspects: Behaviour control for the call.
|
|
53
|
+
|
|
54
|
+
:returns: A :class:`RunpodNetworkVolume` record.
|
|
55
|
+
|
|
56
|
+
:raises: :class:`SemanticError` when ``spec`` is ``None``.
|
|
57
|
+
"""
|
|
58
|
+
if spec is None:
|
|
59
|
+
raise SemanticError("NetworkVolumesGroup.create requires a spec.")
|
|
60
|
+
|
|
61
|
+
existing = self._find_by_name(name=spec.name,
|
|
62
|
+
data_center_id=spec.data_center_id,
|
|
63
|
+
aspects=aspects)
|
|
64
|
+
if existing is not None:
|
|
65
|
+
rtnval = existing
|
|
66
|
+
return rtnval
|
|
67
|
+
|
|
68
|
+
client = self.client
|
|
69
|
+
leaf = self.build_leaf(parts=["networkvolumes"])
|
|
70
|
+
response = client.request(method="POST", leaf=leaf,
|
|
71
|
+
json_body=spec.to_payload(),
|
|
72
|
+
aspects=aspects)
|
|
73
|
+
raise_for_status(response=response, action="create network volume")
|
|
74
|
+
|
|
75
|
+
rtnval = RunpodNetworkVolume.from_dict(safe_json(response=response))
|
|
76
|
+
return rtnval
|
|
77
|
+
|
|
78
|
+
def list(self, *,
|
|
79
|
+
aspects: AspectsRunpod = DEFAULT_RUNPOD_ASPECTS) -> List[RunpodNetworkVolume]:
|
|
80
|
+
"""
|
|
81
|
+
List every network volume, following cursor pages transparently.
|
|
82
|
+
|
|
83
|
+
:param aspects: Behaviour control for the call.
|
|
84
|
+
|
|
85
|
+
:returns: A list of :class:`RunpodNetworkVolume` records.
|
|
86
|
+
"""
|
|
87
|
+
client = self.client
|
|
88
|
+
leaf = self.build_leaf(parts=["networkvolumes"])
|
|
89
|
+
|
|
90
|
+
def fetch_page(cursor: Optional[str]) -> Dict[str, Any]:
|
|
91
|
+
params: Dict[str, str] = {"limit": str(aspects.page_size)}
|
|
92
|
+
if cursor is not None:
|
|
93
|
+
params["cursor"] = cursor
|
|
94
|
+
response = client.request(method="GET", leaf=leaf,
|
|
95
|
+
params=params, aspects=aspects)
|
|
96
|
+
raise_for_status(response=response, action="list network volumes")
|
|
97
|
+
page = safe_json(response=response)
|
|
98
|
+
return page
|
|
99
|
+
|
|
100
|
+
rtnval: List[RunpodNetworkVolume] = []
|
|
101
|
+
for entry in iter_cursor_pages(fetch_page=fetch_page):
|
|
102
|
+
rtnval.append(RunpodNetworkVolume.from_dict(entry))
|
|
103
|
+
|
|
104
|
+
return rtnval
|
|
105
|
+
|
|
106
|
+
def delete(self, *, volume_id: str,
|
|
107
|
+
aspects: AspectsRunpod = DEFAULT_RUNPOD_ASPECTS) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Delete a network volume. Deleting an unknown id is a no-op.
|
|
110
|
+
|
|
111
|
+
:param volume_id: The network-volume identifier to delete.
|
|
112
|
+
:param aspects: Behaviour control for the call.
|
|
113
|
+
|
|
114
|
+
:returns: ``None``.
|
|
115
|
+
|
|
116
|
+
:raises: :class:`SemanticError` when ``volume_id`` is empty.
|
|
117
|
+
"""
|
|
118
|
+
if volume_id is None or len(volume_id) == 0:
|
|
119
|
+
raise SemanticError("'volume_id' must be a non-empty string.")
|
|
120
|
+
|
|
121
|
+
client = self.client
|
|
122
|
+
leaf = self.build_leaf(parts=["networkvolumes", volume_id])
|
|
123
|
+
response = client.request(method="DELETE", leaf=leaf, aspects=aspects)
|
|
124
|
+
|
|
125
|
+
status = response.status_code
|
|
126
|
+
if 200 <= status < 300:
|
|
127
|
+
return
|
|
128
|
+
if status == 404:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
raise_for_status(response=response, action="delete network volume")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
def _find_by_name(self, *, name: str, data_center_id: str,
|
|
135
|
+
aspects: AspectsRunpod) -> Optional[RunpodNetworkVolume]:
|
|
136
|
+
"""
|
|
137
|
+
Find an existing network volume by name and data center.
|
|
138
|
+
|
|
139
|
+
:param name: The volume name to match.
|
|
140
|
+
:param data_center_id: The data center to match.
|
|
141
|
+
:param aspects: Behaviour control for the call.
|
|
142
|
+
|
|
143
|
+
:returns: The matching :class:`RunpodNetworkVolume`, or ``None``.
|
|
144
|
+
"""
|
|
145
|
+
volumes = self.list(aspects=aspects)
|
|
146
|
+
for volume in volumes:
|
|
147
|
+
if volume.name == name:
|
|
148
|
+
if volume.data_center_id == data_center_id:
|
|
149
|
+
rtnval: Optional[RunpodNetworkVolume] = volume
|
|
150
|
+
return rtnval
|
|
151
|
+
|
|
152
|
+
rtnval = None
|
|
153
|
+
return rtnval
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
__all__ = ["NetworkVolumesGroup"]
|