gogpu 0.1.0__tar.gz

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.
gogpu-0.1.0/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ gogpu.egg-info/
2
+ dist/
3
+ build/
4
+ .venv/
5
+ venv/
6
+ __pycache__/
7
+ *.py[cod]
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ *.egg
12
+ .DS_Store
13
+ swagger/*.txt
14
+ swagger/_*.py
15
+ swagger/swagger.*
16
+ getselfinfo.json
gogpu-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GoGPU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gogpu-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.5
2
+ Name: gogpu
3
+ Version: 0.1.0
4
+ Summary: Python SDK for GoGPU / 捷智算 cloud GPU instances (create, start, stop, reboot, delete).
5
+ Project-URL: Homepage, https://github.com/gogpu/gogpu-sdk
6
+ Project-URL: Documentation, https://github.com/gogpu/gogpu-sdk
7
+ Author: GoGPU
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: cloud,cvm,gogpu,gpu,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: httpx>=0.27
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # gogpu
29
+
30
+ 云主机(CVM)Python SDK:选购、下单、开关机、运维。
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install gogpu
36
+ # 开发中:
37
+ # pip install -e .
38
+ ```
39
+
40
+ ## Quick start
41
+
42
+ ```python
43
+ from gogpu import Client
44
+
45
+ client = Client(
46
+ base_url="http://192.168.0.17:3001/api",
47
+ api_key="ak-xxxxxxxx",
48
+ )
49
+
50
+ # 1) 可购买资源(charging 必填语义,默认 hour)
51
+ offers = client.instances.list_offers(page=1, page_size=20, charging="hour")
52
+ good_id = offers["list"][0]["id"]
53
+
54
+ # 2) 下单 + 余额支付
55
+ result = client.instances.create_and_pay(
56
+ good_id=good_id,
57
+ charging="hour",
58
+ image_id=28, # 来自 list_os_images / 控制台
59
+ image_type=3, # 3=系统镜像
60
+ pay_type=1,
61
+ )
62
+
63
+ # 3) 已购实例生命周期
64
+ vms = client.instances.list(page=1, page_size=10)
65
+ vm_id = vms["list"][0]["id"]
66
+ client.instances.start(vm_id)
67
+ client.instances.stop(vm_id)
68
+ client.instances.reboot(vm_id)
69
+ client.instances.get_ssh_password(vm_id)
70
+ ```
71
+
72
+ ## 云主机方法一览
73
+
74
+ | 类别 | 方法 |
75
+ |---|---|
76
+ | 选购 | `list_offers` `get_offer` `list_gpu_types` `list_regions` `list_os_images` `list_public_images` `list_usable_images` |
77
+ | 订单 | `create` `pay` `create_and_pay` `renew` |
78
+ | 实例 | `list` `get` `statistics` `list_instance_gpu_types` `list_cluster` |
79
+ | 电源 | `start` `stop` `reboot` `delete` `reset_system` `convert_cardless` `stop_cardless` `convert_charging` |
80
+ | 配置 | `edit` `change_password` `change_renew` `change_reserve_disk` `expand` `get_expand_amount` `get_refund_amount` `refund` |
81
+ | 凭据 | `get_ssh_password` `get_vnc_password` `get_rdp_password` `get_jupyter_password` `get_vscode_password` `generate_key` `download_key` |
82
+ | 运维 | `list_ports` `list_default_ports` `update_ports` `get_monitor` `take_screenshot` `create_image` `get_software_config` |
83
+
84
+ 常量见 `gogpu.resources.instances`(如 `STATUS_RUNNING`、`ORDER_TYPE_CVM`)。
85
+
86
+ ## Auth
87
+
88
+ - `api_key` → 请求头 `x-api-key`
89
+ - `token` → 请求头 `x-token`(JWT)
90
+
91
+ ## Response
92
+
93
+ 接口统一 `{code, data, msg}`。`code==0` 时方法返回 `data`,否则抛 `GoGPUAPIError`。
gogpu-0.1.0/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # gogpu
2
+
3
+ 云主机(CVM)Python SDK:选购、下单、开关机、运维。
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install gogpu
9
+ # 开发中:
10
+ # pip install -e .
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ from gogpu import Client
17
+
18
+ client = Client(
19
+ base_url="http://192.168.0.17:3001/api",
20
+ api_key="ak-xxxxxxxx",
21
+ )
22
+
23
+ # 1) 可购买资源(charging 必填语义,默认 hour)
24
+ offers = client.instances.list_offers(page=1, page_size=20, charging="hour")
25
+ good_id = offers["list"][0]["id"]
26
+
27
+ # 2) 下单 + 余额支付
28
+ result = client.instances.create_and_pay(
29
+ good_id=good_id,
30
+ charging="hour",
31
+ image_id=28, # 来自 list_os_images / 控制台
32
+ image_type=3, # 3=系统镜像
33
+ pay_type=1,
34
+ )
35
+
36
+ # 3) 已购实例生命周期
37
+ vms = client.instances.list(page=1, page_size=10)
38
+ vm_id = vms["list"][0]["id"]
39
+ client.instances.start(vm_id)
40
+ client.instances.stop(vm_id)
41
+ client.instances.reboot(vm_id)
42
+ client.instances.get_ssh_password(vm_id)
43
+ ```
44
+
45
+ ## 云主机方法一览
46
+
47
+ | 类别 | 方法 |
48
+ |---|---|
49
+ | 选购 | `list_offers` `get_offer` `list_gpu_types` `list_regions` `list_os_images` `list_public_images` `list_usable_images` |
50
+ | 订单 | `create` `pay` `create_and_pay` `renew` |
51
+ | 实例 | `list` `get` `statistics` `list_instance_gpu_types` `list_cluster` |
52
+ | 电源 | `start` `stop` `reboot` `delete` `reset_system` `convert_cardless` `stop_cardless` `convert_charging` |
53
+ | 配置 | `edit` `change_password` `change_renew` `change_reserve_disk` `expand` `get_expand_amount` `get_refund_amount` `refund` |
54
+ | 凭据 | `get_ssh_password` `get_vnc_password` `get_rdp_password` `get_jupyter_password` `get_vscode_password` `generate_key` `download_key` |
55
+ | 运维 | `list_ports` `list_default_ports` `update_ports` `get_monitor` `take_screenshot` `create_image` `get_software_config` |
56
+
57
+ 常量见 `gogpu.resources.instances`(如 `STATUS_RUNNING`、`ORDER_TYPE_CVM`)。
58
+
59
+ ## Auth
60
+
61
+ - `api_key` → 请求头 `x-api-key`
62
+ - `token` → 请求头 `x-token`(JWT)
63
+
64
+ ## Response
65
+
66
+ 接口统一 `{code, data, msg}`。`code==0` 时方法返回 `data`,否则抛 `GoGPUAPIError`。
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "gogpu"
7
+ version = "0.1.0"
8
+ description = "Python SDK for GoGPU / 捷智算 cloud GPU instances (create, start, stop, reboot, delete)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "GoGPU" }]
13
+ keywords = ["gpu", "cloud", "cvm", "sdk", "gogpu"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.27",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=8.0", "ruff>=0.6"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/gogpu/gogpu-sdk"
35
+ Documentation = "https://github.com/gogpu/gogpu-sdk"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/gogpu"]
39
+
40
+ [tool.hatch.build.targets.wheel.sources]
41
+ "src/gogpu" = "gogpu"
42
+
43
+ [tool.hatch.build.targets.sdist]
44
+ include = ["src/gogpu", "README.md", "LICENSE", "pyproject.toml"]
@@ -0,0 +1,14 @@
1
+ """gogpu — Python SDK for GoGPU cloud GPU instances."""
2
+
3
+ from gogpu._version import __version__
4
+ from gogpu.client import Client
5
+ from gogpu.exceptions import GoGPUAPIError, GoGPUError
6
+ from gogpu.resources import instances as cvm
7
+
8
+ __all__ = [
9
+ "Client",
10
+ "GoGPUAPIError",
11
+ "GoGPUError",
12
+ "cvm",
13
+ "__version__",
14
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping, MutableMapping, Optional, Union
4
+
5
+ import httpx
6
+
7
+ from gogpu.exceptions import GoGPUAPIError
8
+ from gogpu.resources.account import AccountResource
9
+ from gogpu.resources.instances import InstancesResource
10
+
11
+
12
+ class Client:
13
+ """GoGPU API client.
14
+
15
+ Parameters
16
+ ----------
17
+ base_url:
18
+ API root, e.g. ``http://host:3001/api`` or ``http://host:9021``.
19
+ api_key:
20
+ Account API key, sent as ``x-api-key``.
21
+ token:
22
+ Login JWT, sent as ``x-token``.
23
+ timeout:
24
+ Request timeout in seconds.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ base_url: str,
30
+ *,
31
+ api_key: Optional[str] = None,
32
+ token: Optional[str] = None,
33
+ timeout: float = 60.0,
34
+ transport: Optional[httpx.BaseTransport] = None,
35
+ ) -> None:
36
+ if not api_key and not token:
37
+ raise ValueError("Provide at least one of api_key or token")
38
+
39
+ self.base_url = base_url.rstrip("/")
40
+ self.api_key = api_key
41
+ self.token = token
42
+ self.timeout = timeout
43
+
44
+ headers: MutableMapping[str, str] = {
45
+ "Accept": "application/json",
46
+ "Content-Type": "application/json",
47
+ }
48
+ if api_key:
49
+ headers["x-api-key"] = api_key
50
+ if token:
51
+ headers["x-token"] = token
52
+
53
+ self._http = httpx.Client(
54
+ base_url=self.base_url,
55
+ headers=headers,
56
+ timeout=timeout,
57
+ transport=transport,
58
+ )
59
+
60
+ self.instances = InstancesResource(self)
61
+ self.account = AccountResource(self)
62
+
63
+ def close(self) -> None:
64
+ self._http.close()
65
+
66
+ def __enter__(self) -> "Client":
67
+ return self
68
+
69
+ def __exit__(self, *args: object) -> None:
70
+ self.close()
71
+
72
+ def request(
73
+ self,
74
+ path: str,
75
+ body: Optional[Mapping[str, Any]] = None,
76
+ *,
77
+ raw: bool = False,
78
+ ) -> Any:
79
+ """POST JSON to ``path`` and unwrap ``{code, data, msg}``.
80
+
81
+ Returns ``data`` when ``code == 0``. Set ``raw=True`` to get the full
82
+ response dict instead.
83
+ """
84
+ url_path = path if path.startswith("/") else f"/{path}"
85
+ try:
86
+ resp = self._http.post(url_path, json=dict(body or {}))
87
+ except httpx.HTTPError as exc:
88
+ raise GoGPUAPIError(f"HTTP request failed: {exc}", path=url_path) from exc
89
+
90
+ try:
91
+ payload: Union[dict[str, Any], Any] = resp.json()
92
+ except ValueError as exc:
93
+ raise GoGPUAPIError(
94
+ f"Invalid JSON response: {resp.text[:200]}",
95
+ path=url_path,
96
+ status_code=resp.status_code,
97
+ ) from exc
98
+
99
+ if resp.status_code >= 400:
100
+ msg = (
101
+ payload.get("msg")
102
+ if isinstance(payload, dict)
103
+ else None
104
+ ) or resp.reason_phrase
105
+ raise GoGPUAPIError(
106
+ str(msg),
107
+ code=payload.get("code") if isinstance(payload, dict) else None,
108
+ data=payload.get("data") if isinstance(payload, dict) else payload,
109
+ path=url_path,
110
+ status_code=resp.status_code,
111
+ )
112
+
113
+ if not isinstance(payload, dict) or "code" not in payload:
114
+ if raw:
115
+ return payload
116
+ return payload
117
+
118
+ code = payload.get("code")
119
+ if code != 0:
120
+ raise GoGPUAPIError(
121
+ str(payload.get("msg") or "API error"),
122
+ code=code,
123
+ data=payload.get("data"),
124
+ path=url_path,
125
+ status_code=resp.status_code,
126
+ )
127
+
128
+ if raw:
129
+ return payload
130
+ return payload.get("data")
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class GoGPUError(Exception):
7
+ """Base error for the gogpu SDK."""
8
+
9
+
10
+ class GoGPUAPIError(GoGPUError):
11
+ """Raised when the API returns a non-success business code or HTTP error."""
12
+
13
+ def __init__(
14
+ self,
15
+ message: str,
16
+ *,
17
+ code: Optional[int] = None,
18
+ data: Any = None,
19
+ path: Optional[str] = None,
20
+ status_code: Optional[int] = None,
21
+ ) -> None:
22
+ super().__init__(message)
23
+ self.message = message
24
+ self.code = code
25
+ self.data = data
26
+ self.path = path
27
+ self.status_code = status_code
28
+
29
+ def __str__(self) -> str:
30
+ parts = [self.message]
31
+ if self.code is not None:
32
+ parts.append(f"code={self.code}")
33
+ if self.path:
34
+ parts.append(f"path={self.path}")
35
+ if self.status_code is not None:
36
+ parts.append(f"http={self.status_code}")
37
+ return " | ".join(parts)
@@ -0,0 +1 @@
1
+ """Resource modules for the gogpu SDK."""
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+
5
+ from gogpu.resources.base import BaseResource
6
+
7
+
8
+ class AccountResource(BaseResource):
9
+ """User account helpers."""
10
+
11
+ def get_self_info(self, *, id: int = 0) -> Any:
12
+ """GET current user profile via ``/user/account/GetSelfInfo``."""
13
+ return self._post("/user/account/GetSelfInfo", {"id": id})
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Mapping, Optional
4
+
5
+ if TYPE_CHECKING:
6
+ from gogpu.client import Client
7
+
8
+
9
+ class BaseResource:
10
+ def __init__(self, client: "Client") -> None:
11
+ self._client = client
12
+
13
+ def _post(self, path: str, body: Optional[Mapping[str, Any]] = None, **kwargs: Any) -> Any:
14
+ return self._client.request(path, body, **kwargs)
@@ -0,0 +1,561 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping, Optional, Sequence
4
+
5
+ from gogpu.resources.base import BaseResource
6
+
7
+ # order_type: 1 容器 / 2 云主机 / 5 裸金属 / 7 商品 / 8 Token套餐
8
+ ORDER_TYPE_CVM = 2
9
+
10
+ # CVM status: 1创建中 2开机中 3运行中 4异常 5已关机 6重置中 7已过期 9已释放
11
+ STATUS_CREATING = 1
12
+ STATUS_STARTING = 2
13
+ STATUS_RUNNING = 3
14
+ STATUS_ERROR = 4
15
+ STATUS_STOPPED = 5
16
+ STATUS_RESETTING = 6
17
+ STATUS_EXPIRED = 7
18
+ STATUS_RELEASED = 9
19
+
20
+ # image_type: 1 基础 / 2 模型 / 3 系统 / 7 拷贝云主机 / 9 用户镜像
21
+ IMAGE_TYPE_BASE = 1
22
+ IMAGE_TYPE_MODEL = 2
23
+ IMAGE_TYPE_OS = 3
24
+ IMAGE_TYPE_CVM_COPY = 7
25
+ IMAGE_TYPE_USER = 9
26
+
27
+ # renew mode: 1 自动 / 2 手动
28
+ RENEW_AUTO = 1
29
+ RENEW_MANUAL = 2
30
+
31
+ # reserve disk: 1 保留 / 2 不保留
32
+ RESERVE_DISK_YES = 1
33
+ RESERVE_DISK_NO = 2
34
+
35
+ # monitor mon_type: 1 cpu / 2 内存 / 3 网络 / 4 硬盘
36
+ MON_CPU = 1
37
+ MON_MEMORY = 2
38
+ MON_NETWORK = 3
39
+ MON_DISK = 4
40
+
41
+
42
+ class InstancesResource(BaseResource):
43
+ """云主机(CVM)完整能力:选购 → 下单 → 生命周期 → 运维。"""
44
+
45
+ def _by_id(self, path: str, instance_id: int, **extra: Any) -> Any:
46
+ return self._post(path, {"id": instance_id, **extra})
47
+
48
+ # ----- 选购:可购买资源 / 镜像 / 区域 -----
49
+
50
+ def list_offers(
51
+ self,
52
+ *,
53
+ page: int = 1,
54
+ page_size: int = 10,
55
+ charging: str = "hour",
56
+ region_id: Optional[int] = None,
57
+ gpu_id: Optional[int] = None,
58
+ gpu_number: Optional[int] = None,
59
+ keyword: Optional[str] = None,
60
+ server_id: Optional[int] = None,
61
+ web: bool = False,
62
+ **extra: Any,
63
+ ) -> Any:
64
+ """获取可购买的云主机资源列表。
65
+
66
+ 返回 ``{"list": [...], "page", "pageSize", "total"}``。
67
+ 列表项 ``id`` 即下单 ``good_id``;``inventory==0`` 表示暂无库存。
68
+
69
+ ``charging`` 对可筛选接口是必填语义(默认 ``hour``),可选
70
+ ``hour`` / ``day`` / ``week`` / ``month`` / ``year``。
71
+ ``web=True`` 走官网列表(仅分页,无筛选)。
72
+ """
73
+ if web:
74
+ return self._post(
75
+ "/common/product/GetWebVmServerList",
76
+ {"page": page, "pageSize": page_size, **extra},
77
+ )
78
+
79
+ body: dict[str, Any] = {
80
+ "page": page,
81
+ "pageSize": page_size,
82
+ "charging": charging,
83
+ **extra,
84
+ }
85
+ if region_id is not None:
86
+ body["region_id"] = region_id
87
+ if gpu_id is not None:
88
+ body["gpu_id"] = gpu_id
89
+ if gpu_number is not None:
90
+ body["gpu_number"] = gpu_number
91
+ if keyword is not None:
92
+ body["keyword"] = keyword
93
+ if server_id is not None:
94
+ body["server_id"] = server_id
95
+ return self._post("/common/product/GetVmServerList", body)
96
+
97
+ def get_offer(self, server_id: int) -> Any:
98
+ """单个可购买资源详情。"""
99
+ return self._by_id("/common/product/GetVmServerInfo", server_id)
100
+
101
+ def list_gpu_types(self, *, page: int = 1, page_size: int = 100, **extra: Any) -> Any:
102
+ """云主机可选 GPU 型号(筛选 ``list_offers(gpu_id=...)``)。"""
103
+ return self._post(
104
+ "/common/product/GetVmServerGpuTypeList",
105
+ {"page": page, "pageSize": page_size, **extra},
106
+ )
107
+
108
+ def list_regions(self, *, parent_id: Optional[int] = None, **extra: Any) -> Any:
109
+ """区域树;传 ``parent_id`` 则查子区域。"""
110
+ if parent_id is None and not extra:
111
+ return self._post("/common/product/GetRegionTree", {})
112
+ body: dict[str, Any] = dict(extra)
113
+ if parent_id is not None:
114
+ body["id"] = parent_id
115
+ return self._post("/common/product/GetRegionChildrenList", body)
116
+
117
+ def list_os_images(
118
+ self,
119
+ *,
120
+ image_os_type: int,
121
+ server_id: Optional[int] = None,
122
+ server_type: int = 2,
123
+ scene: Optional[int] = None,
124
+ **extra: Any,
125
+ ) -> Any:
126
+ """系统镜像列表(购买 / 重装时选 ``image_id``)。
127
+
128
+ ``image_os_type``:1 windows / 2 ubuntu。
129
+ ``server_type``:1 容器 / 2 虚拟机(云主机用 2)。
130
+ """
131
+ body: dict[str, Any] = {
132
+ "type": image_os_type,
133
+ "server_type": server_type,
134
+ **extra,
135
+ }
136
+ if server_id is not None:
137
+ body["server_id"] = server_id
138
+ if scene is not None:
139
+ body["scene"] = scene
140
+ return self._post("/common/product/GetOsImageList", body)
141
+
142
+ def list_public_images(self, **extra: Any) -> Any:
143
+ """公共镜像列表。"""
144
+ return self._post("/common/product/GetPublicImageList", dict(extra))
145
+
146
+ def list_usable_images(
147
+ self,
148
+ *,
149
+ object_id: int,
150
+ object_type: int = ORDER_TYPE_CVM,
151
+ page: int = 1,
152
+ page_size: int = 10,
153
+ keyword: Optional[str] = None,
154
+ **extra: Any,
155
+ ) -> Any:
156
+ """创建实例可用的用户镜像列表。``object_type``:1 容器 / 2 云主机。"""
157
+ body: dict[str, Any] = {
158
+ "object_id": object_id,
159
+ "object_type": object_type,
160
+ "page": page,
161
+ "pageSize": page_size,
162
+ **extra,
163
+ }
164
+ if keyword is not None:
165
+ body["keyword"] = keyword
166
+ return self._post("/user/image/GetUsableImageList", body)
167
+
168
+ # ----- 下单 / 支付 / 续费 -----
169
+
170
+ def create(
171
+ self,
172
+ *,
173
+ good_id: int,
174
+ order_type: int = ORDER_TYPE_CVM,
175
+ image_id: Optional[int] = None,
176
+ image_type: Optional[int] = None,
177
+ charging: Optional[str] = None,
178
+ cycle_number: Optional[int] = None,
179
+ number: Optional[int] = None,
180
+ renew: Optional[bool] = None,
181
+ coupon_id: Optional[int] = None,
182
+ notes: Optional[str] = None,
183
+ sub_type: Optional[int] = None,
184
+ **extra: Any,
185
+ ) -> Any:
186
+ """创建云主机新购订单。``order_type`` 默认 2(云主机)。"""
187
+ body: dict[str, Any] = {
188
+ "good_id": good_id,
189
+ "order_type": order_type,
190
+ **extra,
191
+ }
192
+ if image_id is not None:
193
+ body["image_id"] = image_id
194
+ if image_type is not None:
195
+ body["image_type"] = image_type
196
+ if charging is not None:
197
+ body["charging"] = charging
198
+ if cycle_number is not None:
199
+ body["cycle_number"] = cycle_number
200
+ if number is not None:
201
+ body["number"] = number
202
+ if renew is not None:
203
+ body["renew"] = renew
204
+ if coupon_id is not None:
205
+ body["coupon_id"] = coupon_id
206
+ if notes is not None:
207
+ body["notes"] = notes
208
+ if sub_type is not None:
209
+ body["sub_type"] = sub_type
210
+ return self._post("/user/order/CreateOrder", body)
211
+
212
+ def pay(
213
+ self,
214
+ order_id: int,
215
+ *,
216
+ pay_type: int = 1,
217
+ client: int = 1,
218
+ **extra: Any,
219
+ ) -> Any:
220
+ """支付订单。``pay_type``:1 余额 / 2 微信 / 3 支付宝 / 5 stripe。"""
221
+ return self._post(
222
+ "/user/order/PayOrder",
223
+ {
224
+ "order_id": order_id,
225
+ "pay_type": pay_type,
226
+ "client": client,
227
+ **extra,
228
+ },
229
+ )
230
+
231
+ def create_and_pay(
232
+ self,
233
+ *,
234
+ good_id: int,
235
+ pay_type: int = 1,
236
+ order_id_key: str = "id",
237
+ **create_kwargs: Any,
238
+ ) -> dict[str, Any]:
239
+ """下单并支付,返回 ``{"order": ..., "payment": ...}``。"""
240
+ order = self.create(good_id=good_id, **create_kwargs)
241
+ order_id = None
242
+ if isinstance(order, dict):
243
+ order_id = order.get(order_id_key) or order.get("order_id")
244
+ if order_id is None:
245
+ raise ValueError(
246
+ f"Cannot find order id in create response "
247
+ f"(tried '{order_id_key}' / 'order_id'): {order!r}"
248
+ )
249
+ payment = self.pay(int(order_id), pay_type=pay_type)
250
+ return {"order": order, "payment": payment}
251
+
252
+ def renew(
253
+ self,
254
+ product_id: int,
255
+ *,
256
+ charging: str,
257
+ renew: bool = True,
258
+ order_type: int = ORDER_TYPE_CVM,
259
+ cycle_number: Optional[int] = None,
260
+ coupon_id: Optional[int] = None,
261
+ notes: Optional[str] = None,
262
+ **extra: Any,
263
+ ) -> Any:
264
+ """创建续费订单。``product_id`` 为已购云主机实例 id。"""
265
+ body: dict[str, Any] = {
266
+ "product_id": product_id,
267
+ "charging": charging,
268
+ "renew": renew,
269
+ "order_type": order_type,
270
+ **extra,
271
+ }
272
+ if cycle_number is not None:
273
+ body["cycle_number"] = cycle_number
274
+ if coupon_id is not None:
275
+ body["coupon_id"] = coupon_id
276
+ if notes is not None:
277
+ body["notes"] = notes
278
+ return self._post("/user/order/RenewOrder", body)
279
+
280
+ # ----- 已购实例:列表 / 详情 / 统计 -----
281
+
282
+ def list(
283
+ self,
284
+ *,
285
+ page: int = 1,
286
+ page_size: int = 10,
287
+ status: Optional[int] = None,
288
+ keyword: Optional[str] = None,
289
+ charging: Optional[int] = None,
290
+ gpu_number: Optional[int] = None,
291
+ auto_renew: Optional[int] = None,
292
+ stop_range: Optional[str] = None,
293
+ **extra: Any,
294
+ ) -> Any:
295
+ """已购云主机列表。"""
296
+ body: dict[str, Any] = {"page": page, "pageSize": page_size, **extra}
297
+ if status is not None:
298
+ body["status"] = status
299
+ if keyword is not None:
300
+ body["keyword"] = keyword
301
+ if charging is not None:
302
+ body["charging"] = charging
303
+ if gpu_number is not None:
304
+ body["gpu_number"] = gpu_number
305
+ if auto_renew is not None:
306
+ body["auto_renew"] = auto_renew
307
+ if stop_range is not None:
308
+ body["stop_range"] = stop_range
309
+ return self._post("/user/cvm/GetCvmList", body)
310
+
311
+ def get(self, instance_id: int) -> Any:
312
+ """云主机详情。"""
313
+ return self._by_id("/user/cvm/GetCvmInfo", instance_id)
314
+
315
+ def statistics(self) -> Any:
316
+ """云主机统计。"""
317
+ return self._post("/user/cvm/GetCvmStatistics", {})
318
+
319
+ def list_instance_gpu_types(self) -> Any:
320
+ """已购列表可筛选的 GPU 类型。"""
321
+ return self._post("/user/cvm/GetCvmGpuList", {})
322
+
323
+ def list_cluster(
324
+ self,
325
+ *,
326
+ vm_server_id: Optional[int] = None,
327
+ current_cvm_id: Optional[int] = None,
328
+ **extra: Any,
329
+ ) -> Any:
330
+ """同集群内用户云主机。"""
331
+ body: dict[str, Any] = dict(extra)
332
+ if vm_server_id is not None:
333
+ body["vm_server_id"] = vm_server_id
334
+ if current_cvm_id is not None:
335
+ body["current_cvm_id"] = current_cvm_id
336
+ return self._post("/user/cvm/GetClusterCvm", body)
337
+
338
+ # ----- 电源 / 生命周期 -----
339
+
340
+ def start(self, instance_id: int) -> Any:
341
+ """开机。"""
342
+ return self._by_id("/user/cvm/Start", instance_id)
343
+
344
+ def stop(self, instance_id: int) -> Any:
345
+ """关机。"""
346
+ return self._by_id("/user/cvm/Stop", instance_id)
347
+
348
+ def reboot(self, instance_id: int) -> Any:
349
+ """重启。"""
350
+ return self._by_id("/user/cvm/Restart", instance_id)
351
+
352
+ def delete(self, instance_id: int) -> Any:
353
+ """删除 / 释放。"""
354
+ return self._by_id("/user/cvm/Delete", instance_id)
355
+
356
+ def reset_system(
357
+ self,
358
+ instance_id: int,
359
+ *,
360
+ image_id: int,
361
+ image_type: int,
362
+ **extra: Any,
363
+ ) -> Any:
364
+ """重置系统。``image_type``:1 基础 / 2 模型 / 3 系统 / 7 拷贝 / 9 用户镜像。"""
365
+ return self._post(
366
+ "/user/cvm/ResetSystem",
367
+ {
368
+ "id": instance_id,
369
+ "image_id": image_id,
370
+ "image_type": image_type,
371
+ **extra,
372
+ },
373
+ )
374
+
375
+ def convert_cardless(self, instance_id: int) -> Any:
376
+ """转为无卡模式。"""
377
+ return self._by_id("/user/cvm/ConvertCardless", instance_id)
378
+
379
+ def stop_cardless(self, instance_id: int) -> Any:
380
+ """停止无卡模式。"""
381
+ return self._by_id("/user/cvm/StopCardless", instance_id)
382
+
383
+ def convert_charging(self, instance_id: int, **extra: Any) -> Any:
384
+ """包年包月转按量计费。"""
385
+ return self._by_id("/user/cvm/ConvertCharging", instance_id, **extra)
386
+
387
+ # ----- 配置变更 -----
388
+
389
+ def edit(self, instance_id: int, *, name: str, **extra: Any) -> Any:
390
+ """修改云主机名称等信息。"""
391
+ return self._post(
392
+ "/user/cvm/EditCvm",
393
+ {"id": instance_id, "name": name, **extra},
394
+ )
395
+
396
+ def change_password(self, instance_id: int, *, password: str, **extra: Any) -> Any:
397
+ """修改登录密码。"""
398
+ return self._post(
399
+ "/user/cvm/EditPassword",
400
+ {"id": instance_id, "password": password, **extra},
401
+ )
402
+
403
+ def change_renew(self, instance_id: int, *, mode: int, **extra: Any) -> Any:
404
+ """修改续费方式。``mode``:1 自动 / 2 手动。"""
405
+ return self._post(
406
+ "/user/cvm/ChangeRenew",
407
+ {"id": instance_id, "mode": mode, **extra},
408
+ )
409
+
410
+ def change_reserve_disk(self, instance_id: int, *, mode: int, **extra: Any) -> Any:
411
+ """是否保留磁盘。``mode``:1 保留 / 2 不保留。"""
412
+ return self._post(
413
+ "/user/cvm/ChangeReserveDisk",
414
+ {"id": instance_id, "mode": mode, **extra},
415
+ )
416
+
417
+ def expand(
418
+ self,
419
+ instance_id: int,
420
+ *,
421
+ cpu_expansion: Optional[int] = None,
422
+ memory_expansion: Optional[int] = None,
423
+ disk_expansion: Optional[int] = None,
424
+ sys_disk_expansion: Optional[int] = None,
425
+ **extra: Any,
426
+ ) -> Any:
427
+ """云主机扩容。"""
428
+ body: dict[str, Any] = {"id": instance_id, **extra}
429
+ if cpu_expansion is not None:
430
+ body["cpu_expansion"] = cpu_expansion
431
+ if memory_expansion is not None:
432
+ body["memory_expansion"] = memory_expansion
433
+ if disk_expansion is not None:
434
+ body["disk_expansion"] = disk_expansion
435
+ if sys_disk_expansion is not None:
436
+ body["sys_disk_expansion"] = sys_disk_expansion
437
+ return self._post("/user/cvm/CvmExpand", body)
438
+
439
+ def get_expand_amount(
440
+ self,
441
+ instance_id: int,
442
+ *,
443
+ cpu_expansion: Optional[int] = None,
444
+ memory_expansion: Optional[int] = None,
445
+ disk_expansion: Optional[int] = None,
446
+ sys_disk_expansion: Optional[int] = None,
447
+ **extra: Any,
448
+ ) -> Any:
449
+ """查询扩容预计金额。"""
450
+ body: dict[str, Any] = {"id": instance_id, **extra}
451
+ if cpu_expansion is not None:
452
+ body["cpu_expansion"] = cpu_expansion
453
+ if memory_expansion is not None:
454
+ body["memory_expansion"] = memory_expansion
455
+ if disk_expansion is not None:
456
+ body["disk_expansion"] = disk_expansion
457
+ if sys_disk_expansion is not None:
458
+ body["sys_disk_expansion"] = sys_disk_expansion
459
+ return self._post("/user/cvm/GetExpandAmount", body)
460
+
461
+ def get_refund_amount(self, instance_id: int, *, type: int = 1, **extra: Any) -> Any:
462
+ """预计退款金额。``type``:1 退款 / 2 转按量。"""
463
+ return self._post(
464
+ "/user/cvm/GetRefundAmount",
465
+ {"id": instance_id, "type": type, **extra},
466
+ )
467
+
468
+ def refund(self, instance_id: int) -> Any:
469
+ """包年包月退款。"""
470
+ return self._by_id("/user/cvm/Refund", instance_id)
471
+
472
+ # ----- 密码 / 密钥 / 软件 -----
473
+
474
+ def get_ssh_password(self, instance_id: int) -> Any:
475
+ """查看 SSH 密码。"""
476
+ return self._by_id("/user/cvm/CatPasswd", instance_id)
477
+
478
+ def get_vnc_password(self, instance_id: int) -> Any:
479
+ """查看 VNC 密码。"""
480
+ return self._by_id("/user/cvm/CatVncPasswd", instance_id)
481
+
482
+ def get_rdp_password(self, instance_id: int) -> Any:
483
+ """查看 RDP 密码。"""
484
+ return self._by_id("/user/cvm/CatRdpPasswd", instance_id)
485
+
486
+ def get_jupyter_password(self, instance_id: int) -> Any:
487
+ """查看 Jupyter 密码。"""
488
+ return self._by_id("/user/cvm/CatJupyterPasswd", instance_id)
489
+
490
+ def get_vscode_password(self, instance_id: int) -> Any:
491
+ """查看 VS Code 密码。"""
492
+ return self._by_id("/user/cvm/CatVsCodePasswd", instance_id)
493
+
494
+ def generate_key(self, instance_id: int) -> Any:
495
+ """生成 SSH 密钥。"""
496
+ return self._by_id("/user/cvm/GenerateKey", instance_id)
497
+
498
+ def download_key(self, instance_id: int) -> Any:
499
+ """下载 SSH 密钥。"""
500
+ return self._by_id("/user/cvm/DownLoadKey", instance_id)
501
+
502
+ def get_software_config(self, instance_id: int) -> Any:
503
+ """软件配置详情。"""
504
+ return self._by_id("/user/cvm/GetSoftwareConfig", instance_id)
505
+
506
+ # ----- 端口 / 监控 / 截图 / 镜像 -----
507
+
508
+ def list_ports(self, instance_id: int) -> Any:
509
+ """端口列表。"""
510
+ return self._by_id("/user/cvm/GetCvmPortList", instance_id)
511
+
512
+ def list_default_ports(self, instance_id: int) -> Any:
513
+ """默认端口列表。"""
514
+ return self._by_id("/user/cvm/GetCvmDefaultPortList", instance_id)
515
+
516
+ def update_ports(
517
+ self,
518
+ instance_id: int,
519
+ port_list: Sequence[Mapping[str, Any]],
520
+ **extra: Any,
521
+ ) -> Any:
522
+ """更新端口列表。每项可含 ``name`` / ``internal_port`` / ``external_port`` / ``protocol_type``。"""
523
+ return self._post(
524
+ "/user/cvm/UpCvmPortList",
525
+ {"id": instance_id, "port_list": list(port_list), **extra},
526
+ )
527
+
528
+ def get_monitor(
529
+ self,
530
+ instance_id: int,
531
+ *,
532
+ mon_type: int,
533
+ time_type: str,
534
+ time_value: Optional[int] = None,
535
+ **extra: Any,
536
+ ) -> Any:
537
+ """监控数据。
538
+
539
+ ``mon_type``:1 cpu / 2 内存 / 3 网络 / 4 硬盘。
540
+ ``time_type``:``hour`` / ``day`` / ``month``。
541
+ """
542
+ body: dict[str, Any] = {
543
+ "id": instance_id,
544
+ "mon_type": mon_type,
545
+ "time_type": time_type,
546
+ **extra,
547
+ }
548
+ if time_value is not None:
549
+ body["time_value"] = time_value
550
+ return self._post("/user/cvm/GetMonitor", body)
551
+
552
+ def take_screenshot(self, instance_id: int) -> Any:
553
+ """屏幕截图。"""
554
+ return self._by_id("/user/cvm/TakeScreenshot", instance_id)
555
+
556
+ def create_image(self, instance_id: int, *, name: str, **extra: Any) -> Any:
557
+ """从云主机创建镜像。"""
558
+ return self._post(
559
+ "/user/cvm/CreateImage",
560
+ {"cid": instance_id, "name": name, **extra},
561
+ )