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.
@@ -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
+ )