modelscope-api 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. modelscope_api/__init__.py +1 -0
  2. modelscope_api/config.py +23 -0
  3. modelscope_api/data_models/__init__.py +3 -0
  4. modelscope_api/data_models/base.py +22 -0
  5. modelscope_api/data_models/collection/__init__.py +6 -0
  6. modelscope_api/data_models/collection/collection.py +110 -0
  7. modelscope_api/data_models/collection/collection_item.py +92 -0
  8. modelscope_api/data_models/magicube/__init__.py +5 -0
  9. modelscope_api/data_models/magicube/magicube_blance.py +31 -0
  10. modelscope_api/data_models/studio/__init__.py +13 -0
  11. modelscope_api/data_models/studio/base_image.py +26 -0
  12. modelscope_api/data_models/studio/environment_variable.py +38 -0
  13. modelscope_api/data_models/studio/hardware.py +87 -0
  14. modelscope_api/data_models/studio/logs.py +54 -0
  15. modelscope_api/data_models/studio/sdk.py +41 -0
  16. modelscope_api/data_models/studio/studio.py +207 -0
  17. modelscope_api/data_models/user/__init__.py +5 -0
  18. modelscope_api/data_models/user/user.py +47 -0
  19. modelscope_api/exceptions.py +59 -0
  20. modelscope_api/models/__init__.py +9 -0
  21. modelscope_api/models/_sub_client.py +71 -0
  22. modelscope_api/models/collection/__init__.py +8 -0
  23. modelscope_api/models/collection/collection.py +122 -0
  24. modelscope_api/models/collection/collection_client.py +132 -0
  25. modelscope_api/models/collection/collection_item.py +92 -0
  26. modelscope_api/models/collection/collection_item_client.py +247 -0
  27. modelscope_api/models/magicube/__init__.py +5 -0
  28. modelscope_api/models/magicube/magicube_client.py +46 -0
  29. modelscope_api/models/modelscope_client.py +206 -0
  30. modelscope_api/models/studio/__init__.py +7 -0
  31. modelscope_api/models/studio/environment_variable_client.py +107 -0
  32. modelscope_api/models/studio/studio.py +197 -0
  33. modelscope_api/models/studio/studio_client.py +237 -0
  34. modelscope_api/models/user/__init__.py +5 -0
  35. modelscope_api/models/user/user_client.py +46 -0
  36. modelscope_api/utils/regex.py +12 -0
  37. modelscope_api/utils/typing.py +12 -0
  38. modelscope_api-0.0.1.dist-info/METADATA +20 -0
  39. modelscope_api-0.0.1.dist-info/RECORD +42 -0
  40. modelscope_api-0.0.1.dist-info/WHEEL +5 -0
  41. modelscope_api-0.0.1.dist-info/licenses/LICENSE +21 -0
  42. modelscope_api-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,92 @@
1
+ """
2
+ 对单个合集条目的操作。
3
+ """
4
+
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Optional, TYPE_CHECKING
9
+
10
+ from ...data_models.collection import CollectionItemInfo
11
+ from .._sub_client import SubClient
12
+
13
+ if TYPE_CHECKING:
14
+ from .collection_item_client import CollectionItemClient
15
+
16
+
17
+
18
+ class CollectionItem(SubClient):
19
+ """
20
+ 单个合集。
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ collection_item_client: CollectionItemClient,
26
+ item_type: str,
27
+ item_object_id: str,
28
+ ):
29
+ self._item_type = item_type
30
+ self._item_object_id = item_object_id
31
+ super().__init__(
32
+ super_client=collection_item_client,
33
+ prefix=f"{self.item_type}/{self.item_object_id}"
34
+ )
35
+
36
+
37
+ def __str__(self) -> str:
38
+ return f"{type(self).__name__}<{self.item_type}/{self.item_object_id}>"
39
+
40
+
41
+ # ==== 只读属性 ====
42
+
43
+ @property
44
+ def item_type(self) -> str:
45
+ """
46
+ 资源类型:model / dataset / studio / paper / skill / mcp
47
+ """
48
+ return self._item_type
49
+
50
+
51
+ @property
52
+ def item_object_id(self) -> str:
53
+ """
54
+ 资源标识
55
+ """
56
+ return self._item_object_id
57
+
58
+
59
+ # ==== 对条目的操作 ====
60
+
61
+ async def update(
62
+ self,
63
+ *,
64
+ note: Optional[str] = None,
65
+ position: Optional[int] = None,
66
+ **kwargs
67
+ ) -> CollectionItemInfo:
68
+ """
69
+ 更新单个条目。传入哪个字段即更新哪个字段。
70
+
71
+ Args:
72
+ note: 备注(Markdown)。
73
+ position: 排序位置(从 1 开始)。
74
+ """
75
+ kwargs["method"] = "PATCH"
76
+ kwargs["subpath"] = None
77
+ kwargs.setdefault("json", {}).update({
78
+ "note": note,
79
+ "position": position,
80
+ })
81
+ data = await self.request_openapi_data(**kwargs)
82
+ return CollectionItemInfo.from_json(data)
83
+
84
+
85
+ async def delete(self) -> None:
86
+ """
87
+ 从合集中删除本条目。
88
+ """
89
+ await self.super_client.delete_item(
90
+ item_type=self.item_type,
91
+ item_object_id=self.item_object_id,
92
+ )
@@ -0,0 +1,247 @@
1
+ """
2
+ 集成封装与合集条目(Collection Item)有关的 API。
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Dict, Iterable, List, Optional, TYPE_CHECKING
8
+
9
+ from ...utils.typing import JsonObject
10
+ from ...data_models.collection import CollectionItemType, CollectionItemInfo
11
+ from .._sub_client import SubClient
12
+ from .collection_item import CollectionItem
13
+
14
+ if TYPE_CHECKING:
15
+ from .collection import Collection
16
+
17
+
18
+ class CollectionItemClient(SubClient):
19
+ """
20
+ 集成封装与合集条目(Collection Item)有关的 API。
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ collection: Collection,
26
+ *,
27
+ prefix: str = "items"
28
+ ):
29
+ super().__init__(
30
+ super_client=collection,
31
+ prefix=prefix
32
+ )
33
+
34
+
35
+ # ==== 查看条目信息 ====
36
+
37
+ async def get_item_infos(
38
+ self,
39
+ item_type: Optional[CollectionItemType] = None,
40
+ page_number: int = 1,
41
+ page_size: int = 10,
42
+ **kwargs
43
+ ) -> List[CollectionItemInfo]:
44
+ """
45
+ 分页获取指定 Collection 的条目列表,支持按资源类型过滤。
46
+
47
+ visibility=public 时 Token 可选;private 时必填。
48
+
49
+ Args:
50
+ item_type: 按资源类型过滤:model / dataset / studio / paper / skill / mcp。
51
+ page_number: 页码(≥1)。
52
+ page_size: 每页大小(1~50)。
53
+ """
54
+ kwargs["method"] = "GET"
55
+ kwargs["subpath"] = None
56
+ kwargs.setdefault("params", {}).update({
57
+ "item_type": item_type,
58
+ "page_number": page_number,
59
+ "page_size": page_size,
60
+ })
61
+ data = await self.request_openapi_data(**kwargs)
62
+ collection_item_infos = list(map(
63
+ CollectionItemInfo.from_json,
64
+ data.get("items") or []
65
+ ))
66
+ return collection_item_infos
67
+
68
+
69
+ # ==== 添加条目 ====
70
+
71
+ @staticmethod
72
+ def _extract_item_infos(
73
+ items: Iterable[CollectionItemInfo | JsonObject]
74
+ ) -> List[Dict[str, str | int]]:
75
+ """
76
+ 从给定的 items 列表中提取 item 的主要信息:
77
+ - item_type (必须)
78
+ - item_object_id (必须)
79
+ - note (可选)
80
+ - position (添加时必须,更新时可选)
81
+ """
82
+ item_list = []
83
+ for item in items:
84
+ # 统一转换为 CollectionItemInfo 类型
85
+ if isinstance(item, dict):
86
+ item_info = CollectionItemInfo.from_json(item)
87
+ elif isinstance(item, CollectionItemInfo):
88
+ item_info = item
89
+ else:
90
+ continue
91
+
92
+ # 添加到列表中
93
+ item_to_be_added = {
94
+ "item_type": item_info.item_type,
95
+ "item_object_id": item_info.item_object_id,
96
+ }
97
+ if item_info.note is not None:
98
+ item_to_be_added["note"] = item_info.note
99
+ if item_info.position is not None:
100
+ item_to_be_added["position"] = item_info.position
101
+ item_list.append(item_to_be_added)
102
+
103
+ return item_list
104
+
105
+
106
+ async def add_items(
107
+ self,
108
+ items: Iterable[CollectionItemInfo | JsonObject],
109
+ **kwargs
110
+ ) -> List[CollectionItemInfo]:
111
+ """
112
+ 向 Collection 批量添加条目。传入 items 数组。
113
+
114
+ Args:
115
+ items: 条目信息列表。
116
+ 对于其中的每个条目,需要有 item_type, item_object_id, position 字段;note 字段是可选的。
117
+
118
+ Returns:
119
+ 添加失败的条目列表。每个条目有 reason 属性指示其失败原因。
120
+ """
121
+ kwargs["method"] = "POST"
122
+ kwargs["subpath"] = None
123
+
124
+ # 往请求体中添加 items 列表
125
+ item_list = kwargs.setdefault("json", {}).setdefault("items", [])
126
+ item_list.extend(self._extract_item_infos(items))
127
+
128
+ # 发送请求
129
+ data = await self.request_openapi_data(**kwargs)
130
+
131
+ # 失败的条目
132
+ failed_item_infos = list(map(
133
+ CollectionItemInfo.from_json,
134
+ data.get("failed_items") or []
135
+ ))
136
+ return failed_item_infos
137
+
138
+
139
+ # ==== 修改条目 ====
140
+
141
+ async def update_items(
142
+ self,
143
+ items: Iterable[CollectionItemInfo | JsonObject],
144
+ **kwargs
145
+ ) -> List[CollectionItemInfo]:
146
+ """
147
+ 批量更新 Collection 条目。
148
+
149
+ 通过每个条目的 item_type + item_object_id 定位,传入哪个字段就修改哪个字段。
150
+
151
+ Args:
152
+ items: 条目信息列表。
153
+ 对于其中的每个条目,需要有 item_type 和 item_object_id 字段;note 和 position 字段是可选的。
154
+
155
+ Returns:
156
+ 更新失败的条目列表。每个条目有 reason 属性指示其失败原因。
157
+ """
158
+ kwargs["method"] = "PATCH"
159
+ kwargs["subpath"] = None
160
+
161
+ # 往请求体中添加 items 列表
162
+ item_list = kwargs.setdefault("json", {}).setdefault("items", [])
163
+ item_list.extend(self._extract_item_infos(items))
164
+
165
+ # 如果没有要修改的条目,则直接返回
166
+ if not item_list:
167
+ return []
168
+
169
+ # 发送请求
170
+ data = await self.request_openapi_data(**kwargs)
171
+
172
+ # 失败的条目
173
+ failed_item_infos = list(map(
174
+ CollectionItemInfo.from_json,
175
+ data.get("failed_items") or []
176
+ ))
177
+ return failed_item_infos
178
+
179
+
180
+ # ==== 删除条目 ====
181
+
182
+ async def delete_item(
183
+ self,
184
+ item_type: str,
185
+ item_object_id: str,
186
+ **kwargs
187
+ ) -> None:
188
+ """
189
+ 通过 item_type + item_object_id 定位并从 Collection 中移除单个条目。
190
+
191
+ Args:
192
+ item_type: 资源类型:model / dataset / studio / paper / skill / mcp
193
+ item_object_id: 资源标识,如 damo/nlp_bert_base。
194
+ 可包含 /(作为 items/{item_type}/ 之后的整段剩余路径),无需 URL 编码。
195
+ """
196
+ kwargs["method"] = "DELETE"
197
+ kwargs["subpath"] = f"{item_type}/{item_object_id}"
198
+ await self.request_openapi_data(**kwargs)
199
+
200
+
201
+ # ==== 获得条目对象 ====
202
+
203
+ def get_item(
204
+ self,
205
+ item_type: str,
206
+ item_object_id: str,
207
+ ) -> CollectionItem:
208
+ """
209
+ 构造 CollectionItem 对象。
210
+ """
211
+ return CollectionItem(
212
+ collection_item_client=self,
213
+ item_type=item_type,
214
+ item_object_id=item_object_id,
215
+ )
216
+
217
+
218
+ async def get_items(
219
+ self,
220
+ item_type: Optional[CollectionItemType] = None,
221
+ page_number: int = 1,
222
+ page_size: int = 10,
223
+ **kwargs
224
+ ) -> List[CollectionItem]:
225
+ """
226
+ 分页获取指定 Collection 的条目列表,支持按资源类型过滤。
227
+
228
+ visibility=public 时 Token 可选;private 时必填。
229
+
230
+ Args:
231
+ item_type: 按资源类型过滤:model / dataset / studio / paper / skill / mcp。
232
+ page_number: 页码(≥1)。
233
+ page_size: 每页大小(1~50)。
234
+ """
235
+ collection_item_infos = await self.get_item_infos(
236
+ item_type=item_type,
237
+ page_number=page_number,
238
+ page_size=page_size,
239
+ **kwargs
240
+ )
241
+ return [
242
+ self.get_item(
243
+ item_type=item_info.item_type,
244
+ item_object_id=item_info.item_object_id
245
+ )
246
+ for item_info in collection_item_infos
247
+ ]
@@ -0,0 +1,5 @@
1
+ """
2
+ 与魔粒(Magicube)有关的 API 模型。
3
+ """
4
+
5
+ from .magicube_client import MagicubeClient
@@ -0,0 +1,46 @@
1
+ """
2
+ 集成封装与魔粒体系有关的 API。
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ from ...data_models.magicube import MagicubeBalanceInfo
10
+ from .._sub_client import SubClient
11
+
12
+ if TYPE_CHECKING:
13
+ from ..modelscope_client import ModelScopeClient
14
+
15
+
16
+
17
+ class MagicubeClient(SubClient):
18
+ """
19
+ 集成封装与魔粒(Magicube)有关的 API。
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ modelscope_client: ModelScopeClient,
25
+ *,
26
+ prefix: str = "magicubes"
27
+ ):
28
+ super().__init__(
29
+ super_client=modelscope_client,
30
+ prefix=prefix
31
+ )
32
+
33
+
34
+ # ==== 查询魔粒余额 ====
35
+
36
+ async def query_magicube_balance(self, **kwargs) -> MagicubeBalanceInfo:
37
+ """
38
+ 查询当前用户的魔粒余额信息。
39
+
40
+ Raises:
41
+ ModelScopeException: 如果未登录。
42
+ """
43
+ kwargs["method"] = "GET"
44
+ kwargs["subpath"] = "balance"
45
+ data = await self.request_openapi_data(**kwargs)
46
+ return MagicubeBalanceInfo.from_json(data)
@@ -0,0 +1,206 @@
1
+ """
2
+ 对所有 API 的聚合。
3
+ """
4
+
5
+ import json
6
+ from typing import Any, Dict, List, Optional, Union
7
+
8
+ from fake_useragent import UserAgent
9
+ import httpx
10
+ from typing_extensions import Self
11
+ from yarl import URL
12
+
13
+ from ..config import (
14
+ MODELSCOPE_API_TOKEN,
15
+ MODELSCOPE_OPENAPI_BASE_URL,
16
+ MODELSCOPE_OPENAPI_VERSION,
17
+ )
18
+ from ..exceptions import ParseException, ModelScopeException
19
+ from ..utils.typing import JsonObject
20
+ from .collection import CollectionClient
21
+ from .magicube import MagicubeClient
22
+ from .studio import StudioClient
23
+ from .user import UserClient
24
+
25
+
26
+ class ModelScopeClient:
27
+ """Client that requests APIs of ModelScope."""
28
+
29
+ def __init__(
30
+ self,
31
+ *,
32
+ api_key: Optional[str] = None,
33
+ openapi_base_url: Optional[Union[str, URL]] = None,
34
+ openapi_version: Optional[str] = None,
35
+ http_client: Optional[httpx.AsyncClient] = None,
36
+ **kwargs
37
+ ):
38
+ """
39
+ Args:
40
+ api_key (str): to obtain: https://www.modelscope.cn/my/settings/token
41
+ It can be passed in through the environment variable `MODELSCOPE_API_TOKEN`.
42
+ This will overwrite the header::Authorization in http_cient.
43
+ openapi_base_url (str): to obtain: https://www.modelscope.cn/docs/openapi
44
+ The base URL of ModelScope OpenAPI,
45
+ defaults to `"https://modelscope.cn/openapi"`.
46
+ openapi_version (str): The version of the API, defaults to `"v1"`.
47
+ http_client (httpx.AsyncClient): An HTTP‑client object with the same methods
48
+ as `httpx.AsyncClient.request`, used for sending network requests.
49
+ kwargs: The initialization parameters passed to `http_client`.
50
+ """
51
+ # API Token
52
+ if api_key is None:
53
+ api_key = MODELSCOPE_API_TOKEN
54
+
55
+ self.api_key: Optional[str] = api_key
56
+
57
+ # URL
58
+ if openapi_base_url is None:
59
+ openapi_base_url = MODELSCOPE_OPENAPI_BASE_URL
60
+ if openapi_version is None:
61
+ openapi_version = MODELSCOPE_OPENAPI_VERSION
62
+
63
+ self.openapi_base_url: URL = URL(openapi_base_url)
64
+ self.openapi_version: str = openapi_version
65
+
66
+ # HTTP Client
67
+ self._http_client_is_local: bool = http_client is None
68
+ if self._http_client_is_local:
69
+ http_client = httpx.AsyncClient(**kwargs)
70
+ self._http_client = http_client
71
+ self._kwargs: Dict[str, Any] = kwargs.copy()
72
+
73
+ # 添加伪造的 User-Agent 头
74
+ headers = self._kwargs.setdefault("headers", {})
75
+ if "user-agent" not in headers:
76
+ headers["user-agent"] = UserAgent().random # 生成随机 UA
77
+
78
+ # 聚合子路由
79
+ self.collection = CollectionClient(self)
80
+ self.magicube = MagicubeClient(self)
81
+ self.studio = StudioClient(self)
82
+ self.user = UserClient(self)
83
+
84
+
85
+ async def __aenter__(self) -> Self:
86
+ if self._http_client_is_local:
87
+ await self._http_client.__aenter__()
88
+ return self
89
+
90
+
91
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
92
+ if self._http_client_is_local:
93
+ await self._http_client.__aexit__(exc_type, exc_val, exc_tb)
94
+
95
+
96
+ async def aclose(self) -> None:
97
+ """Close the client within the instance."""
98
+ if self._http_client_is_local:
99
+ await self._http_client.aclose()
100
+
101
+
102
+ @property
103
+ def openapi_url(self) -> URL:
104
+ """
105
+ The URL of OpenAPI interface.
106
+
107
+ Returns like:
108
+ "https://modelscope.cn/openapi/v1"
109
+ """
110
+ return self.openapi_base_url / self.openapi_version
111
+
112
+
113
+ def get_openapi_url(self, subpath: Optional[str] = None) -> URL:
114
+ """
115
+ 拼接完整的路由。
116
+ """
117
+ if subpath is None:
118
+ return self.openapi_url
119
+ return self.openapi_url / subpath
120
+
121
+
122
+ # ==== 发送请求 ====
123
+
124
+ def _get_kwargs(self, kwargs: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
125
+ """
126
+ 将给定的关键字参数与本客户端的合并并输出。
127
+
128
+ 具体来说,该方法会合并以下内容(按优先级从大到小排序)并返回:
129
+ - 调用本方法时输入的 `kwargs`
130
+ - "headers": {"Authorization": "Bearer api_key"} (如果 self.api_key 存在)
131
+ - 初始化本客户端时输入的 `kwargs`
132
+ """
133
+ result = self._kwargs.copy()
134
+ if self.api_key:
135
+ result.setdefault("headers", {}).update({
136
+ "Authorization": f"Bearer {self.api_key}"
137
+ })
138
+ if kwargs:
139
+ for key, value in kwargs.items():
140
+ if isinstance(value, dict):
141
+ result.setdefault(key, {}).update(value)
142
+ else:
143
+ result[key] = value
144
+ return result
145
+
146
+
147
+ async def request(self, **kwargs) -> httpx.Response:
148
+ """
149
+ 用该客户端发送请求。
150
+ """
151
+ kwargs = self._get_kwargs(kwargs)
152
+ response = await self._http_client.request(**kwargs)
153
+ return response
154
+
155
+
156
+ async def request_data(self, **kwargs) -> Optional[JsonObject | List[JsonObject]]:
157
+ """
158
+ 发送请求,并返回响应体中的 `data` 字段。
159
+ 如果响应体中没有 `data` 字段,则返回整个响应体。
160
+
161
+ Raises:
162
+ ParseException: 如果解析失败。
163
+ ModelScopeException: 如果请求失败。
164
+ """
165
+ response = await self.request(**kwargs)
166
+
167
+ # 解析响应体
168
+ try:
169
+ resp_json = response.json()
170
+ except json.JSONDecodeError as exp:
171
+ raise ParseException(
172
+ code="JSON DECODE ERROR",
173
+ message=f"unexpected json string: {response.text}"
174
+ ) from exp
175
+
176
+ # 判断请求是否失败
177
+ if not resp_json.get("success", True):
178
+ raise ModelScopeException(
179
+ code=resp_json.get("code"),
180
+ message=resp_json.get("message")
181
+ )
182
+
183
+ if "data" in resp_json:
184
+ return resp_json["data"]
185
+ return resp_json
186
+
187
+
188
+ async def request_openapi_data(
189
+ self,
190
+ subpath: Optional[str] = None,
191
+ **kwargs
192
+ ) -> Optional[JsonObject | List[JsonObject]]:
193
+ """
194
+ 向 ModelScope OpenAPI 发送请求,并返回响应体中的 `data` 字段。
195
+ 如果响应体中没有 `data` 字段,则返回整个响应体。
196
+
197
+ Args:
198
+ subpath: 在 `self.openapi_url` 之后要拼接的子路径。
199
+ 不能以 `/` 开头。
200
+
201
+ Raises:
202
+ ParseException: 如果解析失败。
203
+ ModelScopeException: 如果请求失败。
204
+ """
205
+ kwargs["url"] = str(self.get_openapi_url(subpath))
206
+ return await self.request_data(**kwargs)
@@ -0,0 +1,7 @@
1
+ """
2
+ 与创空间(Studio)有关的 API 模型。
3
+ """
4
+
5
+ from .environment_variable_client import EnvironmentVariableClient
6
+ from .studio import Studio
7
+ from .studio_client import StudioClient