modelscope-api 0.0.3__tar.gz → 0.0.4__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.
- {modelscope_api-0.0.3/src/modelscope_api.egg-info → modelscope_api-0.0.4}/PKG-INFO +3 -1
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/pyproject.toml +3 -1
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/exceptions.py +2 -2
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/_sub_client.py +9 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/modelscope_client.py +12 -0
- modelscope_api-0.0.4/src/modelscope_api/models/studio/studio.py +430 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4/src/modelscope_api.egg-info}/PKG-INFO +3 -1
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api.egg-info/requires.txt +2 -0
- modelscope_api-0.0.3/src/modelscope_api/models/studio/studio.py +0 -236
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/LICENSE +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/README.md +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/setup.cfg +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/config.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/base.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/collection/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/collection/collection.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/collection/collection_item.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/magicube/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/magicube/magicube_blance.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/base_image.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/environment_variable.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/hardware.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/logs.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/sdk.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/studio.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/user/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/user/user.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/collection.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/collection_client.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/collection_item.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/collection_item_client.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/magicube/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/magicube/magicube_client.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/studio/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/studio/environment_variable_client.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/studio/studio_client.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/user/__init__.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/user/user_client.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/utils/regex.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/utils/typing.py +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api.egg-info/SOURCES.txt +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api.egg-info/dependency_links.txt +0 -0
- {modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelscope-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A Python async SDK that wraps the ModelScope API into a clean, type-safe interface.
|
|
5
5
|
Author-email: Jerry <wujr24@m.fudan.edu.cn>
|
|
6
6
|
License: MIT
|
|
@@ -18,8 +18,10 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: aiofiles
|
|
21
22
|
Requires-Dist: fake-useragent
|
|
22
23
|
Requires-Dist: httpx
|
|
24
|
+
Requires-Dist: modelscope-hub
|
|
23
25
|
Requires-Dist: python-dotenv
|
|
24
26
|
Requires-Dist: typing-extensions
|
|
25
27
|
Requires-Dist: yarl
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modelscope-api" # 包名,保持与项目仓库名一致
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4" # 当前版本
|
|
8
8
|
description = "A Python async SDK that wraps the ModelScope API into a clean, type-safe interface." # 简短描述
|
|
9
9
|
readme = "README.md" # 自述文件,PyPI 会将其渲染为项目首页
|
|
10
10
|
authors = [{name = "Jerry", email = "wujr24@m.fudan.edu.cn"}] # 作者信息
|
|
@@ -31,8 +31,10 @@ classifiers = [ # 帮助用户在 PyPI 上找到你的项目
|
|
|
31
31
|
]
|
|
32
32
|
requires-python = ">=3.11"
|
|
33
33
|
dependencies = [ # 项目的运行时依赖,从 requirements.txt 移植
|
|
34
|
+
"aiofiles",
|
|
34
35
|
"fake-useragent",
|
|
35
36
|
"httpx",
|
|
37
|
+
"modelscope-hub",
|
|
36
38
|
"python-dotenv",
|
|
37
39
|
"typing-extensions",
|
|
38
40
|
"yarl",
|
|
@@ -16,10 +16,10 @@ class BusinessException(Exception):
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
# 默认消息
|
|
19
|
-
DEFAULT_MESSAGE
|
|
19
|
+
DEFAULT_MESSAGE: ClassVar[str] = "Business Error"
|
|
20
20
|
|
|
21
21
|
# 默认错误代码
|
|
22
|
-
DEFAULT_CODE
|
|
22
|
+
DEFAULT_CODE: ClassVar[str] = "ERROR"
|
|
23
23
|
|
|
24
24
|
def __init__(
|
|
25
25
|
self,
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from typing import List, Optional, Union, TYPE_CHECKING
|
|
8
8
|
|
|
9
|
+
from modelscope_hub import HubApi
|
|
9
10
|
from yarl import URL
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
@@ -35,6 +36,14 @@ class SubClient:
|
|
|
35
36
|
self.openapi_prefix: Optional[str] = openapi_prefix
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
@property
|
|
40
|
+
def hub_api(self) -> HubApi:
|
|
41
|
+
"""
|
|
42
|
+
返回一个 HubApi 对象,用于实现 OpenAPI 中未实现的操作。
|
|
43
|
+
"""
|
|
44
|
+
return self.super_client.hub_api
|
|
45
|
+
|
|
46
|
+
|
|
38
47
|
# ==== API ====
|
|
39
48
|
|
|
40
49
|
@property
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/modelscope_client.py
RENAMED
|
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Union
|
|
|
7
7
|
|
|
8
8
|
from fake_useragent import UserAgent
|
|
9
9
|
import httpx
|
|
10
|
+
from modelscope_hub import HubApi
|
|
10
11
|
from typing_extensions import Self
|
|
11
12
|
from yarl import URL
|
|
12
13
|
|
|
@@ -115,6 +116,17 @@ class ModelScopeClient:
|
|
|
115
116
|
await self._http_client.aclose()
|
|
116
117
|
|
|
117
118
|
|
|
119
|
+
@property
|
|
120
|
+
def hub_api(self) -> HubApi:
|
|
121
|
+
"""
|
|
122
|
+
将 api_key 注入 HubApi 并返回 HubApi 对象。
|
|
123
|
+
"""
|
|
124
|
+
return HubApi(
|
|
125
|
+
endpoint=str(self.openapi_base_url.origin()),
|
|
126
|
+
token=self.api_key
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
118
130
|
@property
|
|
119
131
|
def api_url(self) -> URL:
|
|
120
132
|
"""
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"""
|
|
2
|
+
对单个创空间的操作。
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import BinaryIO, List, Optional, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import aiofiles
|
|
12
|
+
import httpx
|
|
13
|
+
from yarl import URL
|
|
14
|
+
|
|
15
|
+
from ...config import STUDIO_API_DOMAIN
|
|
16
|
+
from ...utils.regex import STUDIO_ID_PATTERN
|
|
17
|
+
from ...data_models.studio import (
|
|
18
|
+
StudioInfo, StudioRuntimeInfo, StudioVisibility,
|
|
19
|
+
EnvironmentVariableType,
|
|
20
|
+
SDKType,
|
|
21
|
+
LogsInfo, LogsType
|
|
22
|
+
)
|
|
23
|
+
from .._sub_client import SubClient
|
|
24
|
+
from .environment_variable_client import EnvironmentVariableClient
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .studio_client import StudioClient
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Studio(SubClient):
|
|
32
|
+
"""
|
|
33
|
+
单个创空间。
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, studio_client: StudioClient, id: str):
|
|
37
|
+
self._id = id
|
|
38
|
+
assert STUDIO_ID_PATTERN.fullmatch(self.id), f"Invalid studio ID: {self.id}"
|
|
39
|
+
super().__init__(
|
|
40
|
+
super_client=studio_client,
|
|
41
|
+
openapi_prefix=self.id
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# 明文变量
|
|
45
|
+
self.variables = EnvironmentVariableClient(
|
|
46
|
+
studio=self,
|
|
47
|
+
type=EnvironmentVariableType.VARIABLE
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# 密文变量
|
|
51
|
+
self.secrets = EnvironmentVariableClient(
|
|
52
|
+
studio=self,
|
|
53
|
+
type=EnvironmentVariableType.SECRET
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def __str__(self) -> str:
|
|
58
|
+
return f"{type(self).__name__}<{self.id}>"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ==== 只读属性 ====
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def id(self) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Studio ID (owner/repo_name)
|
|
67
|
+
"""
|
|
68
|
+
return self._id
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def owner(self) -> str:
|
|
73
|
+
"""
|
|
74
|
+
拥有者(个人用户名或组织名)
|
|
75
|
+
"""
|
|
76
|
+
return self._id.split("/")[0]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def repo_name(self) -> str:
|
|
81
|
+
"""
|
|
82
|
+
仓库名。
|
|
83
|
+
"""
|
|
84
|
+
return self._id.split("/")[1]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ==== 创空间操作 ====
|
|
88
|
+
|
|
89
|
+
async def get_info(self, **kwargs) -> StudioInfo:
|
|
90
|
+
"""
|
|
91
|
+
获取当前 Studio 的详细信息。
|
|
92
|
+
|
|
93
|
+
公开(public)和公开体验(protected)类型的创空间无需认证即可访问;私有(private)创空间需要认证。
|
|
94
|
+
"""
|
|
95
|
+
kwargs["method"] = "GET"
|
|
96
|
+
kwargs["subpath"] = None
|
|
97
|
+
data = await self.request_openapi_data(**kwargs)
|
|
98
|
+
return StudioInfo.from_json(data)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def update_settings(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
display_name: Optional[str] = None,
|
|
105
|
+
license: Optional[str] = None,
|
|
106
|
+
visibility: Optional[StudioVisibility] = None,
|
|
107
|
+
description: Optional[str] = None,
|
|
108
|
+
cover_image: Optional[str] = None,
|
|
109
|
+
sdk_type: Optional[SDKType] = None,
|
|
110
|
+
sdk_version: Optional[str] = None,
|
|
111
|
+
base_image: Optional[str] = None,
|
|
112
|
+
hardware: Optional[str] = None,
|
|
113
|
+
**kwargs
|
|
114
|
+
) -> StudioInfo:
|
|
115
|
+
"""
|
|
116
|
+
更新当前 Studio 的设置,传入哪个字段修改哪个字段,不传的字段不修改。
|
|
117
|
+
|
|
118
|
+
注意:sdk_type、sdk_version、base_image、hardware 修改后需重新部署才能生效。
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
display_name: 显示名称。
|
|
122
|
+
display_name.length <= 128
|
|
123
|
+
license: 许可证。
|
|
124
|
+
visibility: 创空间可见性。
|
|
125
|
+
description: 描述。
|
|
126
|
+
description.length <= 2000。
|
|
127
|
+
cover_image: 封面图 URL。
|
|
128
|
+
sdk_type: SDK 类型,修改后需重新部署才能生效。
|
|
129
|
+
sdk_version: SDK 版本,仅对 Gradio 类型生效,默认最新版,建议选用最新版本,修改后需重新部署才能生效。
|
|
130
|
+
base_image: 基础镜像,仅 Docker 类型不支持,建议选用最新版本,修改后需重新部署才能生效。
|
|
131
|
+
hardware: 硬件配置,修改后需重新部署才能生效。默认为 "platform/2v-cpu-16g-mem"。
|
|
132
|
+
免费资源常见格式为 platform/...、xgpu/...、amd/...;
|
|
133
|
+
付费资源格式为 paid/<InstanceType>,例如 paid/ecs.gn7i-c8g1.2xlarge。
|
|
134
|
+
"""
|
|
135
|
+
kwargs["method"] = "PATCH"
|
|
136
|
+
kwargs["subpath"] = "settings"
|
|
137
|
+
kwargs.setdefault("json", {}).update({
|
|
138
|
+
"display_name": display_name,
|
|
139
|
+
"license": license,
|
|
140
|
+
"visibility": visibility,
|
|
141
|
+
"description": description,
|
|
142
|
+
"cover_image": cover_image,
|
|
143
|
+
"sdk_type": sdk_type,
|
|
144
|
+
"sdk_version": sdk_version,
|
|
145
|
+
"base_image": base_image,
|
|
146
|
+
"hardware": hardware,
|
|
147
|
+
})
|
|
148
|
+
data = await self.request_openapi_data(**kwargs)
|
|
149
|
+
return StudioInfo.from_json(data)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def deploy(self, **kwargs) -> StudioRuntimeInfo:
|
|
153
|
+
"""
|
|
154
|
+
部署当前的 Studio(会重新拉取代码并重建),无论当前状态是停止还是运行中均可调用。
|
|
155
|
+
"""
|
|
156
|
+
kwargs["method"] = "POST"
|
|
157
|
+
kwargs["subpath"] = "deploy"
|
|
158
|
+
data = await self.request_openapi_data(**kwargs)
|
|
159
|
+
return StudioRuntimeInfo.from_json(data)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def stop(self, **kwargs) -> StudioRuntimeInfo:
|
|
163
|
+
"""
|
|
164
|
+
停止当前的 Studio。
|
|
165
|
+
"""
|
|
166
|
+
kwargs["method"] = "POST"
|
|
167
|
+
kwargs["subpath"] = "stop"
|
|
168
|
+
data = await self.request_openapi_data(**kwargs)
|
|
169
|
+
return StudioRuntimeInfo.from_json(data)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def get_logs(
|
|
173
|
+
self,
|
|
174
|
+
log_type: LogsType,
|
|
175
|
+
*,
|
|
176
|
+
page_num: int = 1,
|
|
177
|
+
page_size: int = 100,
|
|
178
|
+
keyword: Optional[str] = None,
|
|
179
|
+
start_timestamp: Optional[int] = None,
|
|
180
|
+
end_timestamp: Optional[int] = None,
|
|
181
|
+
**kwargs
|
|
182
|
+
) -> LogsInfo:
|
|
183
|
+
"""
|
|
184
|
+
获取当前 Studio 的运行日志。
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
log_type: 日志类型:build(构建日志)或 run(运行日志)。
|
|
188
|
+
page_num: 页码,默认 1。
|
|
189
|
+
page_size: 每页数量,默认 100,最大 500。
|
|
190
|
+
keyword: 关键字过滤,可选。
|
|
191
|
+
start_timestamp: 开始时间戳(秒),可选,自动根据 end_timestamp 计算。
|
|
192
|
+
end_timestamp: 结束时间戳(秒),可选,默认当前时间。
|
|
193
|
+
"""
|
|
194
|
+
kwargs["method"] = "GET"
|
|
195
|
+
kwargs["subpath"] = f"logs/{log_type}"
|
|
196
|
+
kwargs.setdefault("params", {}).update({
|
|
197
|
+
"page_num": page_num,
|
|
198
|
+
"page_size": page_size,
|
|
199
|
+
"keyword": keyword,
|
|
200
|
+
"start_timestamp": start_timestamp,
|
|
201
|
+
"end_timestamp": end_timestamp,
|
|
202
|
+
})
|
|
203
|
+
data = await self.request_openapi_data(**kwargs)
|
|
204
|
+
return LogsInfo.from_json(data)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def delete(self) -> None:
|
|
208
|
+
"""
|
|
209
|
+
删除当前创空间。
|
|
210
|
+
"""
|
|
211
|
+
await self.super_client.delete_studio(self.id)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ==== 访问创空间 API ====
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def base_url(self) -> URL:
|
|
218
|
+
"""
|
|
219
|
+
创空间的 API 调用根域名,服务的基础访问入口,不带接口路径。
|
|
220
|
+
"""
|
|
221
|
+
host = (
|
|
222
|
+
"studio-"
|
|
223
|
+
f"{self.owner.lower()}-"
|
|
224
|
+
f"{self.repo_name.lower()}."
|
|
225
|
+
f"{STUDIO_API_DOMAIN}"
|
|
226
|
+
).replace('_', '-')
|
|
227
|
+
return URL.build(scheme="https", host=host)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def request_studio_api(self, subpath: Optional[str] = None, **kwargs) -> httpx.Response:
|
|
231
|
+
"""
|
|
232
|
+
调用当前创空间的 API。
|
|
233
|
+
"""
|
|
234
|
+
if subpath is None:
|
|
235
|
+
api_url = self.base_url
|
|
236
|
+
else:
|
|
237
|
+
api_url = self.base_url / subpath
|
|
238
|
+
kwargs["url"] = str(api_url)
|
|
239
|
+
return await self.super_client.modelscope_client.request(**kwargs)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ==== 操作创空间文件 ====
|
|
243
|
+
|
|
244
|
+
async def upload_file(
|
|
245
|
+
self,
|
|
246
|
+
path_or_fileobj: str | Path | bytes | BinaryIO,
|
|
247
|
+
path_in_repo: str,
|
|
248
|
+
*,
|
|
249
|
+
commit_message: str | None = None,
|
|
250
|
+
commit_description: str | None = None,
|
|
251
|
+
revision: str | None = None,
|
|
252
|
+
buffer_size_mb: int = 16,
|
|
253
|
+
disable_tqdm: bool = False,
|
|
254
|
+
) -> dict:
|
|
255
|
+
"""Upload a single file to a repository.
|
|
256
|
+
|
|
257
|
+
LFS files upload/reuse their blob first, then commit an LFS pointer.
|
|
258
|
+
Normal files are committed directly with inline base64 content.
|
|
259
|
+
LFS mode is determined by file suffix and size threshold.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
path_or_fileobj : str, Path, bytes or BinaryIO
|
|
264
|
+
Local path, raw bytes, or a binary file-like object.
|
|
265
|
+
path_in_repo : str
|
|
266
|
+
Destination path inside the repository.
|
|
267
|
+
commit_message : str, optional
|
|
268
|
+
Commit message. Defaults to ``"Upload file"``.
|
|
269
|
+
commit_description : str, optional
|
|
270
|
+
Extended commit description.
|
|
271
|
+
revision : str, optional
|
|
272
|
+
Branch to commit on. Defaults to ``"master"``.
|
|
273
|
+
buffer_size_mb : int, optional
|
|
274
|
+
Buffer size in MiB for reading file data. Default 16.
|
|
275
|
+
disable_tqdm : bool, optional
|
|
276
|
+
Disable progress bar. Default False.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
dict
|
|
281
|
+
Commit info from the server.
|
|
282
|
+
|
|
283
|
+
Raises
|
|
284
|
+
------
|
|
285
|
+
AuthenticationError
|
|
286
|
+
When the token is missing or invalid.
|
|
287
|
+
NotExistError
|
|
288
|
+
When the target repository does not exist.
|
|
289
|
+
|
|
290
|
+
Examples
|
|
291
|
+
--------
|
|
292
|
+
>>> studio.upload_file(
|
|
293
|
+
... path_or_fileobj="./pytorch_model.bin",
|
|
294
|
+
... path_in_repo="pytorch_model.bin",
|
|
295
|
+
... commit_message="Add fine-tuned weights",
|
|
296
|
+
... )
|
|
297
|
+
"""
|
|
298
|
+
return await asyncio.to_thread(
|
|
299
|
+
self.hub_api.upload_file,
|
|
300
|
+
repo_id=self.id,
|
|
301
|
+
repo_type="studio",
|
|
302
|
+
path_or_fileobj=path_or_fileobj,
|
|
303
|
+
path_in_repo=path_in_repo,
|
|
304
|
+
commit_message=commit_message,
|
|
305
|
+
commit_description=commit_description,
|
|
306
|
+
revision=revision,
|
|
307
|
+
buffer_size_mb=buffer_size_mb,
|
|
308
|
+
disable_tqdm=disable_tqdm,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
async def _load_ignore_file(path: str | Path, encoding: str = "utf-8") -> List[str]:
|
|
314
|
+
"""
|
|
315
|
+
异步读取 ignore 文件中的 ignore_patterns。
|
|
316
|
+
|
|
317
|
+
如果文件不存在,不会报错。
|
|
318
|
+
"""
|
|
319
|
+
patterns = []
|
|
320
|
+
try:
|
|
321
|
+
async with aiofiles.open(path, mode="r", encoding=encoding) as file:
|
|
322
|
+
# 异步读取所有行
|
|
323
|
+
async for line in file:
|
|
324
|
+
line = line.strip()
|
|
325
|
+
# 跳过空行和注释行
|
|
326
|
+
if line and not line.startswith("#"):
|
|
327
|
+
patterns.append(line)
|
|
328
|
+
except FileNotFoundError:
|
|
329
|
+
pass
|
|
330
|
+
return patterns
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
async def upload_folder(
|
|
334
|
+
self,
|
|
335
|
+
folder_path: str | Path,
|
|
336
|
+
*,
|
|
337
|
+
path_in_repo: str = "",
|
|
338
|
+
commit_message: str | None = None,
|
|
339
|
+
commit_description: str | None = None,
|
|
340
|
+
revision: str | None = None,
|
|
341
|
+
allow_patterns: List[str] | None = None,
|
|
342
|
+
ignore_patterns: List[str] | None = None,
|
|
343
|
+
max_workers: int | None = None,
|
|
344
|
+
use_cache: bool | None = None,
|
|
345
|
+
disable_tqdm: bool = False,
|
|
346
|
+
sync_remote_repo: bool = False,
|
|
347
|
+
load_ignore: Optional[List[str]] = None,
|
|
348
|
+
) -> dict | List[dict] | None:
|
|
349
|
+
"""Upload an entire folder to a repository with resumable support.
|
|
350
|
+
|
|
351
|
+
Files are walked recursively from ``folder_path`` and uploaded in
|
|
352
|
+
parallel with adaptive batching, per-file retry, and ReAct progressive
|
|
353
|
+
retry fallback.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
folder_path : str or Path
|
|
358
|
+
Local directory whose contents will be uploaded.
|
|
359
|
+
path_in_repo : str, optional
|
|
360
|
+
Destination prefix inside the repository. Defaults to the repo root.
|
|
361
|
+
commit_message : str, optional
|
|
362
|
+
Commit message. Defaults to ``"Upload folder"``.
|
|
363
|
+
commit_description : str, optional
|
|
364
|
+
Extended commit description.
|
|
365
|
+
revision : str, optional
|
|
366
|
+
Branch to commit on. Defaults to ``"master"``.
|
|
367
|
+
allow_patterns : list of str, optional
|
|
368
|
+
If given, only files matching at least one pattern are uploaded.
|
|
369
|
+
ignore_patterns : list of str, optional
|
|
370
|
+
Files matching any pattern are skipped.
|
|
371
|
+
max_workers : int, optional
|
|
372
|
+
Concurrency for parallel uploads. Defaults to adaptive.
|
|
373
|
+
use_cache : bool, optional
|
|
374
|
+
Use resumable upload caching. When omitted, read
|
|
375
|
+
``MODELSCOPE_UPLOAD_CACHE_ENABLED``. Explicit ``True`` or ``False``
|
|
376
|
+
overrides the environment configuration.
|
|
377
|
+
disable_tqdm : bool, optional
|
|
378
|
+
Disable progress bars. Default False.
|
|
379
|
+
sync_remote_repo : bool, optional
|
|
380
|
+
If True, delete remote files that are not present locally after
|
|
381
|
+
a successful upload (sync semantics). Default False.
|
|
382
|
+
load_ignore: list[str], optional
|
|
383
|
+
Each element is a relative path to ``folder_path``.
|
|
384
|
+
The contents of this files will be imported and added to ``ignore_matterns``.
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
None
|
|
389
|
+
If all files were already committed (nothing to do).
|
|
390
|
+
dict
|
|
391
|
+
If only one batch was committed.
|
|
392
|
+
list of dict
|
|
393
|
+
If multiple batches were committed.
|
|
394
|
+
|
|
395
|
+
Examples
|
|
396
|
+
--------
|
|
397
|
+
>>> studio.upload_folder(
|
|
398
|
+
... folder_path="./checkpoint-1000",
|
|
399
|
+
... ignore_patterns=["*.optim", "events.out.*"],
|
|
400
|
+
... max_workers=8,
|
|
401
|
+
... )
|
|
402
|
+
"""
|
|
403
|
+
# Fill `ignore_patterns`
|
|
404
|
+
if load_ignore:
|
|
405
|
+
folder_path = Path(folder_path)
|
|
406
|
+
ignore_patterns = ignore_patterns or []
|
|
407
|
+
tasks = []
|
|
408
|
+
for ignore_file_relative_path in load_ignore:
|
|
409
|
+
ignore_file_path = folder_path / ignore_file_relative_path
|
|
410
|
+
tasks.append(self._load_ignore_file(ignore_file_path))
|
|
411
|
+
for patterns in await asyncio.gather(*tasks):
|
|
412
|
+
ignore_patterns.extend(patterns)
|
|
413
|
+
|
|
414
|
+
# Request
|
|
415
|
+
return await asyncio.to_thread(
|
|
416
|
+
self.hub_api.upload_folder,
|
|
417
|
+
repo_id=self.id,
|
|
418
|
+
repo_type="studio",
|
|
419
|
+
folder_path=folder_path,
|
|
420
|
+
path_in_repo=path_in_repo,
|
|
421
|
+
commit_message=commit_message,
|
|
422
|
+
commit_description=commit_description,
|
|
423
|
+
revision=revision,
|
|
424
|
+
allow_patterns=allow_patterns,
|
|
425
|
+
ignore_patterns=ignore_patterns,
|
|
426
|
+
max_workers=max_workers,
|
|
427
|
+
use_cache=use_cache,
|
|
428
|
+
disable_tqdm=disable_tqdm,
|
|
429
|
+
sync_remote_repo=sync_remote_repo,
|
|
430
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelscope-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A Python async SDK that wraps the ModelScope API into a clean, type-safe interface.
|
|
5
5
|
Author-email: Jerry <wujr24@m.fudan.edu.cn>
|
|
6
6
|
License: MIT
|
|
@@ -18,8 +18,10 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: aiofiles
|
|
21
22
|
Requires-Dist: fake-useragent
|
|
22
23
|
Requires-Dist: httpx
|
|
24
|
+
Requires-Dist: modelscope-hub
|
|
23
25
|
Requires-Dist: python-dotenv
|
|
24
26
|
Requires-Dist: typing-extensions
|
|
25
27
|
Requires-Dist: yarl
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
对单个创空间的操作。
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
from typing import Optional, TYPE_CHECKING
|
|
8
|
-
|
|
9
|
-
import httpx
|
|
10
|
-
from yarl import URL
|
|
11
|
-
|
|
12
|
-
from ...config import STUDIO_API_DOMAIN
|
|
13
|
-
from ...utils.regex import STUDIO_ID_PATTERN
|
|
14
|
-
from ...data_models.studio import (
|
|
15
|
-
StudioInfo, StudioRuntimeInfo, StudioVisibility,
|
|
16
|
-
EnvironmentVariableType,
|
|
17
|
-
SDKType,
|
|
18
|
-
LogsInfo, LogsType
|
|
19
|
-
)
|
|
20
|
-
from .._sub_client import SubClient
|
|
21
|
-
from .environment_variable_client import EnvironmentVariableClient
|
|
22
|
-
|
|
23
|
-
if TYPE_CHECKING:
|
|
24
|
-
from .studio_client import StudioClient
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class Studio(SubClient):
|
|
29
|
-
"""
|
|
30
|
-
单个创空间。
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
def __init__(self, studio_client: StudioClient, id: str):
|
|
34
|
-
self._id = id
|
|
35
|
-
assert STUDIO_ID_PATTERN.fullmatch(self.id), f"Invalid studio ID: {self.id}"
|
|
36
|
-
super().__init__(
|
|
37
|
-
super_client=studio_client,
|
|
38
|
-
openapi_prefix=self.id
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# 明文变量
|
|
42
|
-
self.variables = EnvironmentVariableClient(
|
|
43
|
-
studio=self,
|
|
44
|
-
type=EnvironmentVariableType.VARIABLE
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# 密文变量
|
|
48
|
-
self.secrets = EnvironmentVariableClient(
|
|
49
|
-
studio=self,
|
|
50
|
-
type=EnvironmentVariableType.SECRET
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def __str__(self) -> str:
|
|
55
|
-
return f"{type(self).__name__}<{self.id}>"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# ==== 只读属性 ====
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def id(self) -> str:
|
|
62
|
-
"""
|
|
63
|
-
Studio ID (owner/repo_name)
|
|
64
|
-
"""
|
|
65
|
-
return self._id
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def owner(self) -> str:
|
|
70
|
-
"""
|
|
71
|
-
拥有者(个人用户名或组织名)
|
|
72
|
-
"""
|
|
73
|
-
return self._id.split("/")[0]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def repo_name(self) -> str:
|
|
78
|
-
"""
|
|
79
|
-
仓库名。
|
|
80
|
-
"""
|
|
81
|
-
return self._id.split("/")[1]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# ==== 创空间操作 ====
|
|
85
|
-
|
|
86
|
-
async def get_info(self, **kwargs) -> StudioInfo:
|
|
87
|
-
"""
|
|
88
|
-
获取当前 Studio 的详细信息。
|
|
89
|
-
|
|
90
|
-
公开(public)和公开体验(protected)类型的创空间无需认证即可访问;私有(private)创空间需要认证。
|
|
91
|
-
"""
|
|
92
|
-
kwargs["method"] = "GET"
|
|
93
|
-
kwargs["subpath"] = None
|
|
94
|
-
data = await self.request_openapi_data(**kwargs)
|
|
95
|
-
return StudioInfo.from_json(data)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
async def update_settings(
|
|
99
|
-
self,
|
|
100
|
-
*,
|
|
101
|
-
display_name: Optional[str] = None,
|
|
102
|
-
license: Optional[str] = None,
|
|
103
|
-
visibility: Optional[StudioVisibility] = None,
|
|
104
|
-
description: Optional[str] = None,
|
|
105
|
-
cover_image: Optional[str] = None,
|
|
106
|
-
sdk_type: Optional[SDKType] = None,
|
|
107
|
-
sdk_version: Optional[str] = None,
|
|
108
|
-
base_image: Optional[str] = None,
|
|
109
|
-
hardware: Optional[str] = None,
|
|
110
|
-
**kwargs
|
|
111
|
-
) -> StudioInfo:
|
|
112
|
-
"""
|
|
113
|
-
更新当前 Studio 的设置,传入哪个字段修改哪个字段,不传的字段不修改。
|
|
114
|
-
|
|
115
|
-
注意:sdk_type、sdk_version、base_image、hardware 修改后需重新部署才能生效。
|
|
116
|
-
|
|
117
|
-
Args:
|
|
118
|
-
display_name: 显示名称。
|
|
119
|
-
display_name.length <= 128
|
|
120
|
-
license: 许可证。
|
|
121
|
-
visibility: 创空间可见性。
|
|
122
|
-
description: 描述。
|
|
123
|
-
description.length <= 2000。
|
|
124
|
-
cover_image: 封面图 URL。
|
|
125
|
-
sdk_type: SDK 类型,修改后需重新部署才能生效。
|
|
126
|
-
sdk_version: SDK 版本,仅对 Gradio 类型生效,默认最新版,建议选用最新版本,修改后需重新部署才能生效。
|
|
127
|
-
base_image: 基础镜像,仅 Docker 类型不支持,建议选用最新版本,修改后需重新部署才能生效。
|
|
128
|
-
hardware: 硬件配置,修改后需重新部署才能生效。默认为 "platform/2v-cpu-16g-mem"。
|
|
129
|
-
免费资源常见格式为 platform/...、xgpu/...、amd/...;
|
|
130
|
-
付费资源格式为 paid/<InstanceType>,例如 paid/ecs.gn7i-c8g1.2xlarge。
|
|
131
|
-
"""
|
|
132
|
-
kwargs["method"] = "PATCH"
|
|
133
|
-
kwargs["subpath"] = "settings"
|
|
134
|
-
kwargs.setdefault("json", {}).update({
|
|
135
|
-
"display_name": display_name,
|
|
136
|
-
"license": license,
|
|
137
|
-
"visibility": visibility,
|
|
138
|
-
"description": description,
|
|
139
|
-
"cover_image": cover_image,
|
|
140
|
-
"sdk_type": sdk_type,
|
|
141
|
-
"sdk_version": sdk_version,
|
|
142
|
-
"base_image": base_image,
|
|
143
|
-
"hardware": hardware,
|
|
144
|
-
})
|
|
145
|
-
data = await self.request_openapi_data(**kwargs)
|
|
146
|
-
return StudioInfo.from_json(data)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
async def deploy(self, **kwargs) -> StudioRuntimeInfo:
|
|
150
|
-
"""
|
|
151
|
-
部署当前的 Studio(会重新拉取代码并重建),无论当前状态是停止还是运行中均可调用。
|
|
152
|
-
"""
|
|
153
|
-
kwargs["method"] = "POST"
|
|
154
|
-
kwargs["subpath"] = "deploy"
|
|
155
|
-
data = await self.request_openapi_data(**kwargs)
|
|
156
|
-
return StudioRuntimeInfo.from_json(data)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
async def stop(self, **kwargs) -> StudioRuntimeInfo:
|
|
160
|
-
"""
|
|
161
|
-
停止当前的 Studio。
|
|
162
|
-
"""
|
|
163
|
-
kwargs["method"] = "POST"
|
|
164
|
-
kwargs["subpath"] = "stop"
|
|
165
|
-
data = await self.request_openapi_data(**kwargs)
|
|
166
|
-
return StudioRuntimeInfo.from_json(data)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
async def get_logs(
|
|
170
|
-
self,
|
|
171
|
-
log_type: LogsType,
|
|
172
|
-
*,
|
|
173
|
-
page_num: int = 1,
|
|
174
|
-
page_size: int = 100,
|
|
175
|
-
keyword: Optional[str] = None,
|
|
176
|
-
start_timestamp: Optional[int] = None,
|
|
177
|
-
end_timestamp: Optional[int] = None,
|
|
178
|
-
**kwargs
|
|
179
|
-
) -> LogsInfo:
|
|
180
|
-
"""
|
|
181
|
-
获取当前 Studio 的运行日志。
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
log_type: 日志类型:build(构建日志)或 run(运行日志)。
|
|
185
|
-
page_num: 页码,默认 1。
|
|
186
|
-
page_size: 每页数量,默认 100,最大 500。
|
|
187
|
-
keyword: 关键字过滤,可选。
|
|
188
|
-
start_timestamp: 开始时间戳(秒),可选,自动根据 end_timestamp 计算。
|
|
189
|
-
end_timestamp: 结束时间戳(秒),可选,默认当前时间。
|
|
190
|
-
"""
|
|
191
|
-
kwargs["method"] = "GET"
|
|
192
|
-
kwargs["subpath"] = f"logs/{log_type}"
|
|
193
|
-
kwargs.setdefault("params", {}).update({
|
|
194
|
-
"page_num": page_num,
|
|
195
|
-
"page_size": page_size,
|
|
196
|
-
"keyword": keyword,
|
|
197
|
-
"start_timestamp": start_timestamp,
|
|
198
|
-
"end_timestamp": end_timestamp,
|
|
199
|
-
})
|
|
200
|
-
data = await self.request_openapi_data(**kwargs)
|
|
201
|
-
return LogsInfo.from_json(data)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
async def delete(self) -> None:
|
|
205
|
-
"""
|
|
206
|
-
删除当前创空间。
|
|
207
|
-
"""
|
|
208
|
-
await self.super_client.delete_studio(self.id)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
# ==== 访问创空间 API ====
|
|
212
|
-
|
|
213
|
-
@property
|
|
214
|
-
def base_url(self) -> URL:
|
|
215
|
-
"""
|
|
216
|
-
创空间的 API 调用根域名,服务的基础访问入口,不带接口路径。
|
|
217
|
-
"""
|
|
218
|
-
host = (
|
|
219
|
-
"studio-"
|
|
220
|
-
f"{self.owner.lower()}-"
|
|
221
|
-
f"{self.repo_name.lower()}."
|
|
222
|
-
f"{STUDIO_API_DOMAIN}"
|
|
223
|
-
).replace('_', '-')
|
|
224
|
-
return URL.build(scheme="https", host=host)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
async def request_studio_api(self, subpath: Optional[str] = None, **kwargs) -> httpx.Response:
|
|
228
|
-
"""
|
|
229
|
-
调用当前创空间的 API。
|
|
230
|
-
"""
|
|
231
|
-
if subpath is None:
|
|
232
|
-
api_url = self.base_url
|
|
233
|
-
else:
|
|
234
|
-
api_url = self.base_url / subpath
|
|
235
|
-
kwargs["url"] = str(api_url)
|
|
236
|
-
return await self.super_client.modelscope_client.request(**kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/collection/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/magicube/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/__init__.py
RENAMED
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/base_image.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/hardware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/studio/studio.py
RENAMED
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/data_models/user/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/__init__.py
RENAMED
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/collection/collection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/magicube/__init__.py
RENAMED
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/magicube/magicube_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api/models/studio/studio_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelscope_api-0.0.3 → modelscope_api-0.0.4}/src/modelscope_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|