intelion-cloud 0.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.
- intelion_cloud/__init__.py +82 -0
- intelion_cloud/_client.py +108 -0
- intelion_cloud/_pagination.py +66 -0
- intelion_cloud/_transport.py +342 -0
- intelion_cloud/constants.py +74 -0
- intelion_cloud/exceptions.py +92 -0
- intelion_cloud/models/__init__.py +33 -0
- intelion_cloud/models/_base.py +39 -0
- intelion_cloud/models/components.py +218 -0
- intelion_cloud/models/flavors.py +44 -0
- intelion_cloud/models/servers.py +247 -0
- intelion_cloud/models/users.py +73 -0
- intelion_cloud/resources/__init__.py +17 -0
- intelion_cloud/resources/_base.py +152 -0
- intelion_cloud/resources/cloud_servers.py +293 -0
- intelion_cloud/resources/flavors.py +42 -0
- intelion_cloud/resources/os_images.py +66 -0
- intelion_cloud/resources/users.py +100 -0
- intelion_cloud-0.2.0.dist-info/METADATA +134 -0
- intelion_cloud-0.2.0.dist-info/RECORD +23 -0
- intelion_cloud-0.2.0.dist-info/WHEEL +5 -0
- intelion_cloud-0.2.0.dist-info/licenses/LICENSE +21 -0
- intelion_cloud-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Exception hierarchy for the Intelion Cloud client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"IntelionCloudError",
|
|
7
|
+
"APIError",
|
|
8
|
+
"AuthenticationError",
|
|
9
|
+
"ForbiddenError",
|
|
10
|
+
"NotFoundError",
|
|
11
|
+
"ConflictError",
|
|
12
|
+
"RateLimitError",
|
|
13
|
+
"ValidationError",
|
|
14
|
+
"ServerError",
|
|
15
|
+
"ConnectionError",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IntelionCloudError(Exception):
|
|
20
|
+
"""Base exception for all Intelion Cloud client errors."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class APIError(IntelionCloudError):
|
|
24
|
+
"""Error returned by the Intelion Cloud API."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
message: str,
|
|
29
|
+
*,
|
|
30
|
+
status_code: int,
|
|
31
|
+
response_body: Any = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
self.status_code = status_code
|
|
34
|
+
self.response_body = response_body
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return f"{self.__class__.__name__}(status_code={self.status_code}, message={str(self)!r})"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AuthenticationError(APIError):
|
|
42
|
+
"""401 Unauthorized — invalid or missing API token."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ForbiddenError(APIError):
|
|
46
|
+
"""403 Forbidden — insufficient permissions."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NotFoundError(APIError):
|
|
50
|
+
"""404 Not Found — resource does not exist."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ConflictError(APIError):
|
|
54
|
+
"""409 Conflict — server is busy with another operation."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RateLimitError(APIError):
|
|
58
|
+
"""429 Too Many Requests — rate limit exceeded."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
message: str,
|
|
63
|
+
*,
|
|
64
|
+
status_code: int = 429,
|
|
65
|
+
response_body: Any = None,
|
|
66
|
+
retry_after: Optional[float] = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
self.retry_after = retry_after
|
|
69
|
+
super().__init__(message, status_code=status_code, response_body=response_body)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ValidationError(APIError):
|
|
73
|
+
"""400 Bad Request — validation failed with field-level errors."""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
message: str,
|
|
78
|
+
*,
|
|
79
|
+
status_code: int = 400,
|
|
80
|
+
response_body: Any = None,
|
|
81
|
+
field_errors: Optional[Dict[str, Any]] = None,
|
|
82
|
+
) -> None:
|
|
83
|
+
self.field_errors = field_errors or {}
|
|
84
|
+
super().__init__(message, status_code=status_code, response_body=response_body)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ServerError(APIError):
|
|
88
|
+
"""5xx Server Error — Intelion Cloud API server error."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ConnectionError(IntelionCloudError):
|
|
92
|
+
"""Network-level error (connection refused, timeout, DNS failure)."""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Data models for the Intelion Cloud API."""
|
|
2
|
+
|
|
3
|
+
from .components import CPU, GPU, OSImage, RAM, SSD
|
|
4
|
+
from .flavors import Flavor
|
|
5
|
+
from .servers import (
|
|
6
|
+
CloudServer,
|
|
7
|
+
DebtInfo,
|
|
8
|
+
PhysicalServer,
|
|
9
|
+
Promocode,
|
|
10
|
+
ServerStatus,
|
|
11
|
+
SoftwareAddonInstance,
|
|
12
|
+
UsageAct,
|
|
13
|
+
WhiteIP,
|
|
14
|
+
)
|
|
15
|
+
from .users import User
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"GPU",
|
|
19
|
+
"CPU",
|
|
20
|
+
"RAM",
|
|
21
|
+
"SSD",
|
|
22
|
+
"OSImage",
|
|
23
|
+
"Flavor",
|
|
24
|
+
"CloudServer",
|
|
25
|
+
"DebtInfo",
|
|
26
|
+
"PhysicalServer",
|
|
27
|
+
"Promocode",
|
|
28
|
+
"ServerStatus",
|
|
29
|
+
"SoftwareAddonInstance",
|
|
30
|
+
"UsageAct",
|
|
31
|
+
"WhiteIP",
|
|
32
|
+
"User",
|
|
33
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Base utilities for model deserialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional, Type, TypeVar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _get(data: Dict[str, Any], key: str, default: Any = None) -> Any:
|
|
11
|
+
"""Get a value from dict, treating empty string as None."""
|
|
12
|
+
val = data.get(key, default)
|
|
13
|
+
if val == "":
|
|
14
|
+
return default
|
|
15
|
+
return val
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_nested(
|
|
19
|
+
data: Dict[str, Any],
|
|
20
|
+
key: str,
|
|
21
|
+
cls: Type[T],
|
|
22
|
+
) -> Optional[T]:
|
|
23
|
+
"""Parse a nested object from a dict if present and non-null."""
|
|
24
|
+
val = data.get(key)
|
|
25
|
+
if val is None or not isinstance(val, dict):
|
|
26
|
+
return None
|
|
27
|
+
return cls.from_dict(val) # type: ignore[attr-defined]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _parse_nested_list(
|
|
31
|
+
data: Dict[str, Any],
|
|
32
|
+
key: str,
|
|
33
|
+
cls: Type[T],
|
|
34
|
+
) -> list:
|
|
35
|
+
"""Parse a list of nested objects."""
|
|
36
|
+
val = data.get(key)
|
|
37
|
+
if val is None or not isinstance(val, list):
|
|
38
|
+
return []
|
|
39
|
+
return [cls.from_dict(item) for item in val if isinstance(item, dict)] # type: ignore[attr-defined]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Hardware component models (GPU, CPU, RAM, SSD, OSImage)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from ._base import _get
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class GPU:
|
|
13
|
+
"""Graphics processing unit specification."""
|
|
14
|
+
|
|
15
|
+
id: int
|
|
16
|
+
name: str
|
|
17
|
+
slug: str
|
|
18
|
+
group_title: str
|
|
19
|
+
cuda_cores: int
|
|
20
|
+
tensor_cores: int
|
|
21
|
+
ram: int
|
|
22
|
+
ram_type: str
|
|
23
|
+
is_cloud: bool
|
|
24
|
+
monthly_price_rub_cents: int
|
|
25
|
+
hourly_price_rub_cents: int
|
|
26
|
+
daily_price_rub_cents: int
|
|
27
|
+
monthly_price_rub: Optional[float] = None
|
|
28
|
+
hourly_price_rub: Optional[float] = None
|
|
29
|
+
daily_price_rub: Optional[float] = None
|
|
30
|
+
old_monthly_price_rub: Optional[float] = None
|
|
31
|
+
discount_percent: int = 0
|
|
32
|
+
tmu: Optional[int] = None
|
|
33
|
+
rop: Optional[int] = None
|
|
34
|
+
ram_capacity: Optional[int] = None
|
|
35
|
+
processor: Optional[str] = None
|
|
36
|
+
cover: Optional[str] = None
|
|
37
|
+
description: Optional[str] = None
|
|
38
|
+
l1_cache: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_dict(cls, data: Dict[str, Any]) -> GPU:
|
|
42
|
+
return cls(
|
|
43
|
+
id=data["id"],
|
|
44
|
+
name=data["name"],
|
|
45
|
+
slug=data.get("slug", ""),
|
|
46
|
+
group_title=data.get("group_title", ""),
|
|
47
|
+
cuda_cores=data.get("cuda_cores", 0),
|
|
48
|
+
tensor_cores=data.get("tensor_cores", 0),
|
|
49
|
+
ram=data.get("ram", 0),
|
|
50
|
+
ram_type=data.get("ram_type", ""),
|
|
51
|
+
is_cloud=data.get("is_cloud", False),
|
|
52
|
+
monthly_price_rub_cents=data.get("monthly_price_rub_cents", 0),
|
|
53
|
+
hourly_price_rub_cents=data.get("hourly_price_rub_cents", 0),
|
|
54
|
+
daily_price_rub_cents=data.get("daily_price_rub_cents", 0),
|
|
55
|
+
monthly_price_rub=_get(data, "monthly_price_rub"),
|
|
56
|
+
hourly_price_rub=_get(data, "hourly_price_rub"),
|
|
57
|
+
daily_price_rub=_get(data, "daily_price_rub"),
|
|
58
|
+
old_monthly_price_rub=_get(data, "old_monthly_price_rub"),
|
|
59
|
+
discount_percent=data.get("discount_percent", 0),
|
|
60
|
+
tmu=_get(data, "tmu"),
|
|
61
|
+
rop=_get(data, "rop"),
|
|
62
|
+
ram_capacity=_get(data, "ram_capacity"),
|
|
63
|
+
processor=_get(data, "processor"),
|
|
64
|
+
cover=_get(data, "cover"),
|
|
65
|
+
description=_get(data, "description"),
|
|
66
|
+
l1_cache=_get(data, "l1_cache"),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class CPU:
|
|
72
|
+
"""CPU specification."""
|
|
73
|
+
|
|
74
|
+
id: int
|
|
75
|
+
name: str
|
|
76
|
+
cores: int
|
|
77
|
+
threads: int
|
|
78
|
+
monthly_price_rub_cents: int
|
|
79
|
+
hourly_price_rub_cents: int
|
|
80
|
+
daily_price_rub_cents: int
|
|
81
|
+
group_title: str = ""
|
|
82
|
+
freq_base: Optional[float] = None
|
|
83
|
+
freq_turbo: Optional[float] = None
|
|
84
|
+
l3_cache: Optional[float] = None
|
|
85
|
+
ram_channels: Optional[int] = None
|
|
86
|
+
ram_max_size: Optional[int] = None
|
|
87
|
+
has_ecc_ram_support: bool = False
|
|
88
|
+
cover: Optional[str] = None
|
|
89
|
+
min_count: int = 1
|
|
90
|
+
max_count: int = 1
|
|
91
|
+
monthly_price_rub: Optional[float] = None
|
|
92
|
+
hourly_price_rub: Optional[float] = None
|
|
93
|
+
daily_price_rub: Optional[float] = None
|
|
94
|
+
old_monthly_price_rub: Optional[float] = None
|
|
95
|
+
discount_percent: int = 0
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_dict(cls, data: Dict[str, Any]) -> CPU:
|
|
99
|
+
return cls(
|
|
100
|
+
id=data["id"],
|
|
101
|
+
name=data["name"],
|
|
102
|
+
cores=data.get("cores", 0),
|
|
103
|
+
threads=data.get("threads", 0),
|
|
104
|
+
monthly_price_rub_cents=data.get("monthly_price_rub_cents", 0),
|
|
105
|
+
hourly_price_rub_cents=data.get("hourly_price_rub_cents", 0),
|
|
106
|
+
daily_price_rub_cents=data.get("daily_price_rub_cents", 0),
|
|
107
|
+
group_title=data.get("group_title", ""),
|
|
108
|
+
freq_base=_get(data, "freq_base"),
|
|
109
|
+
freq_turbo=_get(data, "freq_turbo"),
|
|
110
|
+
l3_cache=_get(data, "l3_cache"),
|
|
111
|
+
ram_channels=_get(data, "ram_channels"),
|
|
112
|
+
ram_max_size=_get(data, "ram_max_size"),
|
|
113
|
+
has_ecc_ram_support=data.get("has_ecc_ram_support", False),
|
|
114
|
+
cover=_get(data, "cover"),
|
|
115
|
+
min_count=data.get("min_count", 1),
|
|
116
|
+
max_count=data.get("max_count", 1),
|
|
117
|
+
monthly_price_rub=_get(data, "monthly_price_rub"),
|
|
118
|
+
hourly_price_rub=_get(data, "hourly_price_rub"),
|
|
119
|
+
daily_price_rub=_get(data, "daily_price_rub"),
|
|
120
|
+
old_monthly_price_rub=_get(data, "old_monthly_price_rub"),
|
|
121
|
+
discount_percent=data.get("discount_percent", 0),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class RAM:
|
|
127
|
+
"""RAM module specification."""
|
|
128
|
+
|
|
129
|
+
id: int
|
|
130
|
+
size: int
|
|
131
|
+
is_ecc: bool
|
|
132
|
+
standard: int
|
|
133
|
+
monthly_price_rub_cents: int
|
|
134
|
+
hourly_price_rub_cents: int
|
|
135
|
+
daily_price_rub_cents: int
|
|
136
|
+
group_title: str = ""
|
|
137
|
+
monthly_price_rub: Optional[float] = None
|
|
138
|
+
hourly_price_rub: Optional[float] = None
|
|
139
|
+
daily_price_rub: Optional[float] = None
|
|
140
|
+
old_monthly_price_rub: Optional[float] = None
|
|
141
|
+
discount_percent: int = 0
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_dict(cls, data: Dict[str, Any]) -> RAM:
|
|
145
|
+
return cls(
|
|
146
|
+
id=data["id"],
|
|
147
|
+
size=data.get("size", 0),
|
|
148
|
+
is_ecc=data.get("is_ecc", False),
|
|
149
|
+
standard=data.get("standard", 4),
|
|
150
|
+
monthly_price_rub_cents=data.get("monthly_price_rub_cents", 0),
|
|
151
|
+
hourly_price_rub_cents=data.get("hourly_price_rub_cents", 0),
|
|
152
|
+
daily_price_rub_cents=data.get("daily_price_rub_cents", 0),
|
|
153
|
+
group_title=data.get("group_title", ""),
|
|
154
|
+
monthly_price_rub=_get(data, "monthly_price_rub"),
|
|
155
|
+
hourly_price_rub=_get(data, "hourly_price_rub"),
|
|
156
|
+
daily_price_rub=_get(data, "daily_price_rub"),
|
|
157
|
+
old_monthly_price_rub=_get(data, "old_monthly_price_rub"),
|
|
158
|
+
discount_percent=data.get("discount_percent", 0),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class SSD:
|
|
164
|
+
"""SSD/NVMe drive specification."""
|
|
165
|
+
|
|
166
|
+
id: int
|
|
167
|
+
size: int
|
|
168
|
+
group_title: str
|
|
169
|
+
monthly_price_rub_cents: int
|
|
170
|
+
hourly_price_rub_cents: int
|
|
171
|
+
daily_price_rub_cents: int
|
|
172
|
+
monthly_price_rub: Optional[float] = None
|
|
173
|
+
hourly_price_rub: Optional[float] = None
|
|
174
|
+
daily_price_rub: Optional[float] = None
|
|
175
|
+
old_monthly_price_rub: Optional[float] = None
|
|
176
|
+
discount_percent: int = 0
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def from_dict(cls, data: Dict[str, Any]) -> SSD:
|
|
180
|
+
return cls(
|
|
181
|
+
id=data["id"],
|
|
182
|
+
size=data.get("size", 0),
|
|
183
|
+
group_title=data.get("group_title", ""),
|
|
184
|
+
monthly_price_rub_cents=data.get("monthly_price_rub_cents", 0),
|
|
185
|
+
hourly_price_rub_cents=data.get("hourly_price_rub_cents", 0),
|
|
186
|
+
daily_price_rub_cents=data.get("daily_price_rub_cents", 0),
|
|
187
|
+
monthly_price_rub=_get(data, "monthly_price_rub"),
|
|
188
|
+
hourly_price_rub=_get(data, "hourly_price_rub"),
|
|
189
|
+
daily_price_rub=_get(data, "daily_price_rub"),
|
|
190
|
+
old_monthly_price_rub=_get(data, "old_monthly_price_rub"),
|
|
191
|
+
discount_percent=data.get("discount_percent", 0),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(frozen=True)
|
|
197
|
+
class OSImage:
|
|
198
|
+
"""Operating system image available for server deployment."""
|
|
199
|
+
|
|
200
|
+
id: int
|
|
201
|
+
name: str
|
|
202
|
+
type: str
|
|
203
|
+
description: str = ""
|
|
204
|
+
ssh_enabled: bool = False
|
|
205
|
+
rdp_enabled: bool = False
|
|
206
|
+
compatible_flavor_ids: Optional[List[int]] = None
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def from_dict(cls, data: Dict[str, Any]) -> OSImage:
|
|
210
|
+
return cls(
|
|
211
|
+
id=data["id"],
|
|
212
|
+
name=data["name"],
|
|
213
|
+
type=data.get("type", ""),
|
|
214
|
+
description=data.get("description", ""),
|
|
215
|
+
ssh_enabled=data.get("ssh_enabled", False),
|
|
216
|
+
rdp_enabled=data.get("rdp_enabled", False),
|
|
217
|
+
compatible_flavor_ids=data.get("compatible_flavor_ids"),
|
|
218
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Flavor model — server hardware configuration (FlavorConfig)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
from ._base import _get, _parse_nested
|
|
9
|
+
from .components import CPU, GPU, RAM
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Flavor:
|
|
14
|
+
"""A flavor describing a server hardware configuration template (backed by FlavorConfig)."""
|
|
15
|
+
|
|
16
|
+
id: int
|
|
17
|
+
name: str
|
|
18
|
+
cpu_count: int
|
|
19
|
+
ram_count: int
|
|
20
|
+
gpu_count: Optional[int]
|
|
21
|
+
flavor_monthly_price_rub_cents: int
|
|
22
|
+
flavor_hourly_price_rub_cents: int
|
|
23
|
+
max_available: int
|
|
24
|
+
openstack_id: Optional[str] = None
|
|
25
|
+
cpu: Optional[CPU] = None
|
|
26
|
+
ram: Optional[RAM] = None
|
|
27
|
+
gpu: Optional[GPU] = None
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_dict(cls, data: Dict[str, Any]) -> Flavor:
|
|
31
|
+
return cls(
|
|
32
|
+
id=data["id"],
|
|
33
|
+
name=data.get("name", ""),
|
|
34
|
+
cpu_count=data.get("cpu_count", 0),
|
|
35
|
+
ram_count=data.get("ram_count", 0),
|
|
36
|
+
gpu_count=_get(data, "gpu_count"),
|
|
37
|
+
flavor_monthly_price_rub_cents=data.get("flavor_monthly_price_rub_cents", 0),
|
|
38
|
+
flavor_hourly_price_rub_cents=data.get("flavor_hourly_price_rub_cents", 0),
|
|
39
|
+
max_available=data.get("max_available", 0),
|
|
40
|
+
openstack_id=_get(data, "openstack_id"),
|
|
41
|
+
cpu=_parse_nested(data, "cpu", CPU),
|
|
42
|
+
ram=_parse_nested(data, "ram", RAM),
|
|
43
|
+
gpu=_parse_nested(data, "gpu", GPU),
|
|
44
|
+
)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Cloud server and related billing models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from ._base import _get, _parse_nested, _parse_nested_list
|
|
9
|
+
from .components import CPU, GPU, OSImage, RAM, SSD
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class DebtInfo:
|
|
14
|
+
"""Next payment / debt information for a usage act."""
|
|
15
|
+
|
|
16
|
+
period_price_rub_cents: int
|
|
17
|
+
periods_to_pay: int
|
|
18
|
+
amount_rub_cents: int
|
|
19
|
+
period_end_dt: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_dict(cls, data: Dict[str, Any]) -> DebtInfo:
|
|
23
|
+
return cls(
|
|
24
|
+
period_price_rub_cents=data.get("period_price_rub_cents", 0),
|
|
25
|
+
periods_to_pay=data.get("periods_to_pay", 0),
|
|
26
|
+
amount_rub_cents=data.get("amount_rub_cents", 0),
|
|
27
|
+
period_end_dt=_get(data, "period_end_dt"),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class UsageAct:
|
|
33
|
+
"""A billing period record (snapshot of prices at the time of creation)."""
|
|
34
|
+
|
|
35
|
+
id: int
|
|
36
|
+
user_config_id: int
|
|
37
|
+
start_dt: Optional[str] = None
|
|
38
|
+
stop_dt: Optional[str] = None
|
|
39
|
+
monthly_price_rub: Optional[float] = None
|
|
40
|
+
daily_price_rub: Optional[float] = None
|
|
41
|
+
hourly_price_rub: Optional[float] = None
|
|
42
|
+
period: Optional[int] = None
|
|
43
|
+
periods_planned: Optional[int] = None
|
|
44
|
+
cloud_server_uptime_seconds: int = 0
|
|
45
|
+
cloud_srv_price_rub_cents: int = 0
|
|
46
|
+
actual_cost_rub_cents: int = 0
|
|
47
|
+
next_payment: Optional[DebtInfo] = None
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, data: Dict[str, Any]) -> UsageAct:
|
|
51
|
+
return cls(
|
|
52
|
+
id=data["id"],
|
|
53
|
+
user_config_id=data.get("user_config_id", 0),
|
|
54
|
+
start_dt=_get(data, "start_dt"),
|
|
55
|
+
stop_dt=_get(data, "stop_dt"),
|
|
56
|
+
monthly_price_rub=_get(data, "monthly_price_rub"),
|
|
57
|
+
daily_price_rub=_get(data, "daily_price_rub"),
|
|
58
|
+
hourly_price_rub=_get(data, "hourly_price_rub"),
|
|
59
|
+
period=_get(data, "period"),
|
|
60
|
+
periods_planned=_get(data, "periods_planned"),
|
|
61
|
+
cloud_server_uptime_seconds=data.get("cloud_server_uptime_seconds", 0),
|
|
62
|
+
cloud_srv_price_rub_cents=data.get("cloud_srv_price_rub_cents", 0),
|
|
63
|
+
actual_cost_rub_cents=data.get("actual_cost_rub_cents", 0),
|
|
64
|
+
next_payment=_parse_nested(data, "next_payment", DebtInfo),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class Promocode:
|
|
70
|
+
"""Applied promotional code."""
|
|
71
|
+
|
|
72
|
+
id: int
|
|
73
|
+
code: str
|
|
74
|
+
discount_value: int
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def from_dict(cls, data: Dict[str, Any]) -> Promocode:
|
|
78
|
+
return cls(
|
|
79
|
+
id=data["id"],
|
|
80
|
+
code=data.get("code", ""),
|
|
81
|
+
discount_value=data.get("discount_value", 0),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class WhiteIP:
|
|
87
|
+
"""A public IP address assigned to a server."""
|
|
88
|
+
|
|
89
|
+
address_v4: str
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_dict(cls, data: Dict[str, Any]) -> WhiteIP:
|
|
93
|
+
return cls(address_v4=data.get("address_v4", ""))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class PhysicalServer:
|
|
98
|
+
"""Physical server reference (for dedicated servers only)."""
|
|
99
|
+
|
|
100
|
+
name: str
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def from_dict(cls, data: Dict[str, Any]) -> PhysicalServer:
|
|
104
|
+
return cls(name=data.get("name", ""))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class SoftwareAddonInstance:
|
|
109
|
+
"""Software addon installed (or being installed) on a cloud server."""
|
|
110
|
+
|
|
111
|
+
uca_id: int
|
|
112
|
+
id: int
|
|
113
|
+
name: str
|
|
114
|
+
status: str
|
|
115
|
+
port: Optional[int] = None
|
|
116
|
+
access_token: str = ""
|
|
117
|
+
progress_message: str = ""
|
|
118
|
+
error_message: str = ""
|
|
119
|
+
hardware_mode: str = ""
|
|
120
|
+
last_heartbeat_dt: Optional[str] = None
|
|
121
|
+
has_error: bool = False
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_dict(cls, data: Dict[str, Any]) -> SoftwareAddonInstance:
|
|
125
|
+
return cls(
|
|
126
|
+
uca_id=data["uca_id"],
|
|
127
|
+
id=data["id"],
|
|
128
|
+
name=data.get("name", ""),
|
|
129
|
+
status=data.get("status", ""),
|
|
130
|
+
port=_get(data, "port"),
|
|
131
|
+
access_token=data.get("access_token", "") or "",
|
|
132
|
+
progress_message=data.get("progress_message", "") or "",
|
|
133
|
+
error_message=data.get("error_message", "") or "",
|
|
134
|
+
hardware_mode=data.get("hardware_mode", "") or "",
|
|
135
|
+
last_heartbeat_dt=_get(data, "last_heartbeat_dt"),
|
|
136
|
+
has_error=data.get("has_error", False),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class CloudServer:
|
|
142
|
+
"""A cloud (or dedicated) server configuration.
|
|
143
|
+
|
|
144
|
+
This is the main model returned by the cloud-servers API endpoints.
|
|
145
|
+
Cloud servers have ``flavor_oid`` set; dedicated servers have it as ``None``.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
id: int
|
|
149
|
+
name: str
|
|
150
|
+
status: int
|
|
151
|
+
price_plan: int
|
|
152
|
+
is_auto_renewal: bool
|
|
153
|
+
monthly_price_rub_cents: int
|
|
154
|
+
hourly_price_rub_cents: int
|
|
155
|
+
server_full_rent_price_rub_cents: int
|
|
156
|
+
flavor_oid: Optional[str] = None
|
|
157
|
+
server_state: int = 0
|
|
158
|
+
has_instance: bool = False
|
|
159
|
+
credentials: str = ""
|
|
160
|
+
ip_to_connect: Optional[str] = None
|
|
161
|
+
domain_to_connect: Optional[str] = None
|
|
162
|
+
login: str = ""
|
|
163
|
+
gpu: Optional[GPU] = None
|
|
164
|
+
gpu_count: int = 0
|
|
165
|
+
cpu: Optional[CPU] = None
|
|
166
|
+
cpu_count: int = 0
|
|
167
|
+
ram: Optional[RAM] = None
|
|
168
|
+
ram_count: int = 0
|
|
169
|
+
network_disk: Optional[SSD] = None
|
|
170
|
+
network_disk_count: int = 0
|
|
171
|
+
local_disk: Optional[SSD] = None
|
|
172
|
+
local_disk_count: int = 0
|
|
173
|
+
os: Optional[OSImage] = None
|
|
174
|
+
usage_act: Optional[UsageAct] = None
|
|
175
|
+
last_finished_usage_act: Optional[UsageAct] = None
|
|
176
|
+
last_usage_act: Optional[UsageAct] = None
|
|
177
|
+
promocode: Optional[Promocode] = None
|
|
178
|
+
physical_server: Optional[PhysicalServer] = None
|
|
179
|
+
white_ips: List[WhiteIP] = field(default_factory=list)
|
|
180
|
+
software_addons: List[SoftwareAddonInstance] = field(default_factory=list)
|
|
181
|
+
card_background: Optional[str] = None
|
|
182
|
+
monthly_price_rub: Optional[float] = None
|
|
183
|
+
total_ram_size: int = 0
|
|
184
|
+
total_network_disk_size: int = 0
|
|
185
|
+
total_local_disk_size: int = 0
|
|
186
|
+
max_available: int = 0
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def from_dict(cls, data: Dict[str, Any]) -> CloudServer:
|
|
190
|
+
return cls(
|
|
191
|
+
id=data["id"],
|
|
192
|
+
name=data.get("name", ""),
|
|
193
|
+
status=data.get("status", 0),
|
|
194
|
+
price_plan=data.get("price_plan", 0),
|
|
195
|
+
is_auto_renewal=data.get("is_auto_renewal", False),
|
|
196
|
+
monthly_price_rub_cents=data.get("monthly_price_rub_cents", 0),
|
|
197
|
+
hourly_price_rub_cents=data.get("hourly_price_rub_cents", 0),
|
|
198
|
+
server_full_rent_price_rub_cents=data.get("server_full_rent_price_rub_cents", 0),
|
|
199
|
+
flavor_oid=_get(data, "flavor_oid"),
|
|
200
|
+
server_state=data.get("server_state", 0),
|
|
201
|
+
has_instance=data.get("has_instance", False),
|
|
202
|
+
credentials=data.get("credentials", ""),
|
|
203
|
+
ip_to_connect=_get(data, "ip_to_connect"),
|
|
204
|
+
domain_to_connect=_get(data, "domain_to_connect"),
|
|
205
|
+
login=data.get("login", ""),
|
|
206
|
+
gpu=_parse_nested(data, "gpu", GPU),
|
|
207
|
+
gpu_count=data.get("gpu_count", 0),
|
|
208
|
+
cpu=_parse_nested(data, "cpu", CPU),
|
|
209
|
+
cpu_count=data.get("cpu_count", 0),
|
|
210
|
+
ram=_parse_nested(data, "ram", RAM),
|
|
211
|
+
ram_count=data.get("ram_count", 0),
|
|
212
|
+
network_disk=_parse_nested(data, "network_disk", SSD),
|
|
213
|
+
network_disk_count=data.get("network_disk_count", 0),
|
|
214
|
+
local_disk=_parse_nested(data, "local_disk", SSD),
|
|
215
|
+
local_disk_count=data.get("local_disk_count", 0),
|
|
216
|
+
os=_parse_nested(data, "os", OSImage),
|
|
217
|
+
usage_act=_parse_nested(data, "usage_act", UsageAct),
|
|
218
|
+
last_finished_usage_act=_parse_nested(data, "last_finished_usage_act", UsageAct),
|
|
219
|
+
last_usage_act=_parse_nested(data, "last_usage_act", UsageAct),
|
|
220
|
+
promocode=_parse_nested(data, "promocode", Promocode),
|
|
221
|
+
physical_server=_parse_nested(data, "physical_server", PhysicalServer),
|
|
222
|
+
white_ips=_parse_nested_list(data, "white_ips", WhiteIP),
|
|
223
|
+
software_addons=_parse_nested_list(data, "software_addons", SoftwareAddonInstance),
|
|
224
|
+
card_background=_get(data, "card_background"),
|
|
225
|
+
monthly_price_rub=_get(data, "monthly_price_rub"),
|
|
226
|
+
total_ram_size=data.get("total_ram_size", 0),
|
|
227
|
+
total_network_disk_size=data.get("total_network_disk_size", 0),
|
|
228
|
+
total_local_disk_size=data.get("total_local_disk_size", 0),
|
|
229
|
+
max_available=data.get("max_available", 0),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass(frozen=True)
|
|
234
|
+
class ServerStatus:
|
|
235
|
+
"""Result of the pre-start status check endpoint."""
|
|
236
|
+
|
|
237
|
+
can_start: bool
|
|
238
|
+
affordable_runtime_seconds: Optional[int] = None
|
|
239
|
+
message: Optional[str] = None
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def from_dict(cls, data: Dict[str, Any]) -> ServerStatus:
|
|
243
|
+
return cls(
|
|
244
|
+
can_start=data.get("can_start", False),
|
|
245
|
+
affordable_runtime_seconds=_get(data, "affordable_runtime_seconds"),
|
|
246
|
+
message=_get(data, "message"),
|
|
247
|
+
)
|