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.
- modelscope_api/__init__.py +1 -0
- modelscope_api/config.py +23 -0
- modelscope_api/data_models/__init__.py +3 -0
- modelscope_api/data_models/base.py +22 -0
- modelscope_api/data_models/collection/__init__.py +6 -0
- modelscope_api/data_models/collection/collection.py +110 -0
- modelscope_api/data_models/collection/collection_item.py +92 -0
- modelscope_api/data_models/magicube/__init__.py +5 -0
- modelscope_api/data_models/magicube/magicube_blance.py +31 -0
- modelscope_api/data_models/studio/__init__.py +13 -0
- modelscope_api/data_models/studio/base_image.py +26 -0
- modelscope_api/data_models/studio/environment_variable.py +38 -0
- modelscope_api/data_models/studio/hardware.py +87 -0
- modelscope_api/data_models/studio/logs.py +54 -0
- modelscope_api/data_models/studio/sdk.py +41 -0
- modelscope_api/data_models/studio/studio.py +207 -0
- modelscope_api/data_models/user/__init__.py +5 -0
- modelscope_api/data_models/user/user.py +47 -0
- modelscope_api/exceptions.py +59 -0
- modelscope_api/models/__init__.py +9 -0
- modelscope_api/models/_sub_client.py +71 -0
- modelscope_api/models/collection/__init__.py +8 -0
- modelscope_api/models/collection/collection.py +122 -0
- modelscope_api/models/collection/collection_client.py +132 -0
- modelscope_api/models/collection/collection_item.py +92 -0
- modelscope_api/models/collection/collection_item_client.py +247 -0
- modelscope_api/models/magicube/__init__.py +5 -0
- modelscope_api/models/magicube/magicube_client.py +46 -0
- modelscope_api/models/modelscope_client.py +206 -0
- modelscope_api/models/studio/__init__.py +7 -0
- modelscope_api/models/studio/environment_variable_client.py +107 -0
- modelscope_api/models/studio/studio.py +197 -0
- modelscope_api/models/studio/studio_client.py +237 -0
- modelscope_api/models/user/__init__.py +5 -0
- modelscope_api/models/user/user_client.py +46 -0
- modelscope_api/utils/regex.py +12 -0
- modelscope_api/utils/typing.py +12 -0
- modelscope_api-0.0.1.dist-info/METADATA +20 -0
- modelscope_api-0.0.1.dist-info/RECORD +42 -0
- modelscope_api-0.0.1.dist-info/WHEEL +5 -0
- modelscope_api-0.0.1.dist-info/licenses/LICENSE +21 -0
- 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,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)
|