xiaoshiai-hub 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xiaoshiai_hub/__init__.py +18 -3
- xiaoshiai_hub/auth.py +163 -0
- xiaoshiai_hub/cli.py +496 -0
- xiaoshiai_hub/client.py +57 -10
- xiaoshiai_hub/download.py +21 -3
- xiaoshiai_hub/envelope_crypto.py +237 -0
- xiaoshiai_hub/upload.py +47 -63
- {xiaoshiai_hub-1.0.0.dist-info → xiaoshiai_hub-1.1.0.dist-info}/METADATA +177 -50
- xiaoshiai_hub-1.1.0.dist-info/RECORD +15 -0
- xiaoshiai_hub-1.1.0.dist-info/entry_points.txt +2 -0
- xiaoshiai_hub-1.0.0.dist-info/RECORD +0 -11
- {xiaoshiai_hub-1.0.0.dist-info → xiaoshiai_hub-1.1.0.dist-info}/WHEEL +0 -0
- {xiaoshiai_hub-1.0.0.dist-info → xiaoshiai_hub-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {xiaoshiai_hub-1.0.0.dist-info → xiaoshiai_hub-1.1.0.dist-info}/top_level.txt +0 -0
xiaoshiai_hub/__init__.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
XiaoShi AI Hub Python SDK
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
小时 AI Hub Python SDK,用于与小时 AI Hub 仓库进行交互。
|
|
5
|
+
支持模型和数据集的上传、下载,以及大型模型文件的透明加密。
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .client import HubClient, DEFAULT_BASE_URL
|
|
@@ -22,6 +22,12 @@ from .types import (
|
|
|
22
22
|
GitContent,
|
|
23
23
|
Commit,
|
|
24
24
|
)
|
|
25
|
+
from .auth import (
|
|
26
|
+
login,
|
|
27
|
+
save_token,
|
|
28
|
+
load_token,
|
|
29
|
+
delete_token,
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
# Upload functionality (requires GitPython)
|
|
27
33
|
try:
|
|
@@ -35,21 +41,30 @@ except ImportError:
|
|
|
35
41
|
upload_folder = None
|
|
36
42
|
UploadError = None
|
|
37
43
|
|
|
38
|
-
__version__ = "
|
|
44
|
+
__version__ = "1.1.0"
|
|
39
45
|
|
|
40
46
|
__all__ = [
|
|
41
47
|
# Client
|
|
42
48
|
"HubClient",
|
|
43
49
|
"DEFAULT_BASE_URL",
|
|
50
|
+
# Download
|
|
44
51
|
"moha_hub_download",
|
|
45
52
|
"snapshot_download",
|
|
53
|
+
# Upload
|
|
46
54
|
"upload_file",
|
|
47
55
|
"upload_folder",
|
|
56
|
+
# Auth
|
|
57
|
+
"login",
|
|
58
|
+
"save_token",
|
|
59
|
+
"load_token",
|
|
60
|
+
"delete_token",
|
|
61
|
+
# Exceptions
|
|
48
62
|
"HubException",
|
|
49
63
|
"RepositoryNotFoundError",
|
|
50
64
|
"FileNotFoundError",
|
|
51
65
|
"AuthenticationError",
|
|
52
66
|
"UploadError",
|
|
67
|
+
# Types
|
|
53
68
|
"Repository",
|
|
54
69
|
"Ref",
|
|
55
70
|
"GitContent",
|
xiaoshiai_hub/auth.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
认证管理模块 - Token 的保存和读取
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from .client import DEFAULT_BASE_URL
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# 默认 token 存储目录
|
|
16
|
+
DEFAULT_TOKEN_DIR = Path.home() / ".moha"
|
|
17
|
+
DEFAULT_TOKEN_FILE = "token.json"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_token_path(token_dir: Optional[str] = None) -> Path:
|
|
21
|
+
"""获取 token 文件路径"""
|
|
22
|
+
if token_dir:
|
|
23
|
+
return Path(token_dir) / DEFAULT_TOKEN_FILE
|
|
24
|
+
return DEFAULT_TOKEN_DIR / DEFAULT_TOKEN_FILE
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def save_token(
|
|
28
|
+
token: str,
|
|
29
|
+
base_url: Optional[str] = None,
|
|
30
|
+
username: Optional[str] = None,
|
|
31
|
+
token_dir: Optional[str] = None,
|
|
32
|
+
) -> Path:
|
|
33
|
+
"""
|
|
34
|
+
保存 token 到文件
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
token: 认证令牌
|
|
38
|
+
base_url: API 地址(可选,用于记录)
|
|
39
|
+
username: 用户名(可选,用于记录)
|
|
40
|
+
token_dir: token 存储目录(默认: ~/.moha)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
token 文件路径
|
|
44
|
+
"""
|
|
45
|
+
token_path = get_token_path(token_dir)
|
|
46
|
+
token_path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
data = {
|
|
49
|
+
"token": token,
|
|
50
|
+
}
|
|
51
|
+
if base_url:
|
|
52
|
+
data["base_url"] = base_url
|
|
53
|
+
if username:
|
|
54
|
+
data["username"] = username
|
|
55
|
+
|
|
56
|
+
with open(token_path, "w", encoding="utf-8") as f:
|
|
57
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
58
|
+
|
|
59
|
+
# 设置文件权限为仅用户可读写
|
|
60
|
+
os.chmod(token_path, 0o600)
|
|
61
|
+
|
|
62
|
+
return token_path
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_token(token_dir: Optional[str] = None) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
|
66
|
+
"""
|
|
67
|
+
从文件加载 token
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
token_dir: token 存储目录(默认: ~/.moha)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
(token, base_url, username) 元组,如果文件不存在则返回 (None, None, None)
|
|
74
|
+
"""
|
|
75
|
+
token_path = get_token_path(token_dir)
|
|
76
|
+
|
|
77
|
+
if not token_path.exists():
|
|
78
|
+
return None, None, None
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
with open(token_path, "r", encoding="utf-8") as f:
|
|
82
|
+
data = json.load(f)
|
|
83
|
+
return data.get("token"), data.get("base_url"), data.get("username")
|
|
84
|
+
except (json.JSONDecodeError, IOError):
|
|
85
|
+
return None, None, None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def delete_token(token_dir: Optional[str] = None) -> bool:
|
|
89
|
+
"""
|
|
90
|
+
删除 token 文件
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
token_dir: token 存储目录(默认: ~/.moha)
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
是否成功删除
|
|
97
|
+
"""
|
|
98
|
+
token_path = get_token_path(token_dir)
|
|
99
|
+
|
|
100
|
+
if token_path.exists():
|
|
101
|
+
token_path.unlink()
|
|
102
|
+
return True
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def login(
|
|
107
|
+
username: str,
|
|
108
|
+
password: str,
|
|
109
|
+
base_url: Optional[str] = None,
|
|
110
|
+
token_dir: Optional[str] = None,
|
|
111
|
+
) -> Tuple[str, Path]:
|
|
112
|
+
"""
|
|
113
|
+
登录并保存 token
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
username: 用户名
|
|
117
|
+
password: 密码
|
|
118
|
+
base_url: API 地址
|
|
119
|
+
token_dir: token 存储目录
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
(token, token_path) 元组
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
AuthenticationError: 认证失败
|
|
126
|
+
requests.RequestException: 请求失败
|
|
127
|
+
"""
|
|
128
|
+
from .exceptions import AuthenticationError
|
|
129
|
+
|
|
130
|
+
base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
131
|
+
url = f"{base_url}/api/iam/login"
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
response = requests.post(
|
|
135
|
+
url,
|
|
136
|
+
json={
|
|
137
|
+
"username": username,
|
|
138
|
+
"type": "Password",
|
|
139
|
+
"remeberMe": True,
|
|
140
|
+
"password": {
|
|
141
|
+
"algorithm": "PlainText",
|
|
142
|
+
"value": password,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
timeout=30,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if response.status_code == 401:
|
|
149
|
+
raise AuthenticationError("用户名或密码错误")
|
|
150
|
+
|
|
151
|
+
response.raise_for_status()
|
|
152
|
+
data = response.json()
|
|
153
|
+
token = data.get("token") or data.get("access_token")
|
|
154
|
+
|
|
155
|
+
if not token:
|
|
156
|
+
raise AuthenticationError("服务器未返回 token")
|
|
157
|
+
|
|
158
|
+
token_path = save_token(token, base_url, username, token_dir)
|
|
159
|
+
return token, token_path
|
|
160
|
+
|
|
161
|
+
except requests.RequestException as e:
|
|
162
|
+
raise AuthenticationError(f"登录请求失败: {e}")
|
|
163
|
+
|
xiaoshiai_hub/cli.py
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""
|
|
2
|
+
小时 AI Hub SDK 命令行工具
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import getpass
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_auth_from_env():
|
|
12
|
+
"""从环境变量获取认证信息"""
|
|
13
|
+
token = os.environ.get("MOHA_TOKEN")
|
|
14
|
+
username = os.environ.get("MOHA_USERNAME")
|
|
15
|
+
password = os.environ.get("MOHA_PASSWORD")
|
|
16
|
+
return token, username, password
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_auth_from_file():
|
|
20
|
+
"""从文件获取已保存的 token"""
|
|
21
|
+
from xiaoshiai_hub.auth import load_token
|
|
22
|
+
token, base_url, _ = load_token()
|
|
23
|
+
return token, base_url
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_auth(args):
|
|
27
|
+
"""从命令行参数、环境变量和文件获取认证信息"""
|
|
28
|
+
token, username, password = get_auth_from_env()
|
|
29
|
+
|
|
30
|
+
# 命令行参数优先级最高
|
|
31
|
+
if args.token:
|
|
32
|
+
token = args.token
|
|
33
|
+
if args.username:
|
|
34
|
+
username = args.username
|
|
35
|
+
if args.password:
|
|
36
|
+
password = args.password
|
|
37
|
+
|
|
38
|
+
# 如果没有通过环境变量或命令行提供 token,尝试从文件读取
|
|
39
|
+
if not token and not (username and password):
|
|
40
|
+
saved_token, _ = get_auth_from_file()
|
|
41
|
+
if saved_token:
|
|
42
|
+
token = saved_token
|
|
43
|
+
|
|
44
|
+
return token, username, password
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def cmd_upload_folder(args):
|
|
48
|
+
"""处理 upload-folder 命令"""
|
|
49
|
+
from xiaoshiai_hub.upload import upload_folder
|
|
50
|
+
|
|
51
|
+
token, username, password = get_auth(args)
|
|
52
|
+
|
|
53
|
+
# 解析忽略模式
|
|
54
|
+
ignore_patterns = args.ignore if args.ignore else None
|
|
55
|
+
|
|
56
|
+
# 从环境变量或参数获取加密密码
|
|
57
|
+
encryption_password = None
|
|
58
|
+
if args.encrypt:
|
|
59
|
+
encryption_password = os.environ.get("MOHA_ENCRYPTION_PASSWORD") or args.encryption_password
|
|
60
|
+
if not encryption_password:
|
|
61
|
+
print("错误: --encrypt 需要加密密码。"
|
|
62
|
+
"请设置 MOHA_ENCRYPTION_PASSWORD 环境变量或使用 --encryption-password 参数", file=sys.stderr)
|
|
63
|
+
return 1
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
upload_folder(
|
|
67
|
+
folder_path=args.folder,
|
|
68
|
+
repo_id=args.repo_id,
|
|
69
|
+
repo_type=args.repo_type,
|
|
70
|
+
revision=args.revision,
|
|
71
|
+
commit_message=args.message,
|
|
72
|
+
base_url=args.base_url,
|
|
73
|
+
username=username,
|
|
74
|
+
password=password,
|
|
75
|
+
token=token,
|
|
76
|
+
encryption_password=encryption_password,
|
|
77
|
+
ignore_patterns=ignore_patterns,
|
|
78
|
+
temp_dir=args.temp_dir,
|
|
79
|
+
)
|
|
80
|
+
print("上传成功!")
|
|
81
|
+
return 0
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def cmd_upload_file(args):
|
|
88
|
+
"""处理 upload-file 命令"""
|
|
89
|
+
from xiaoshiai_hub.upload import upload_file
|
|
90
|
+
|
|
91
|
+
token, username, password = get_auth(args)
|
|
92
|
+
|
|
93
|
+
# 从环境变量或参数获取加密密码
|
|
94
|
+
encryption_password = None
|
|
95
|
+
if args.encrypt:
|
|
96
|
+
encryption_password = os.environ.get("MOHA_ENCRYPTION_PASSWORD") or args.encryption_password
|
|
97
|
+
if not encryption_password:
|
|
98
|
+
print("错误: --encrypt 需要加密密码。"
|
|
99
|
+
"请设置 MOHA_ENCRYPTION_PASSWORD 环境变量或使用 --encryption-password 参数", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
# 确定仓库中的路径
|
|
103
|
+
path_in_repo = args.path_in_repo if args.path_in_repo else os.path.basename(args.file)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
upload_file(
|
|
107
|
+
path_file=args.file,
|
|
108
|
+
path_in_repo=path_in_repo,
|
|
109
|
+
repo_id=args.repo_id,
|
|
110
|
+
repo_type=args.repo_type,
|
|
111
|
+
revision=args.revision,
|
|
112
|
+
commit_message=args.message,
|
|
113
|
+
base_url=args.base_url,
|
|
114
|
+
username=username,
|
|
115
|
+
password=password,
|
|
116
|
+
token=token,
|
|
117
|
+
encryption_password=encryption_password,
|
|
118
|
+
)
|
|
119
|
+
print(f"上传成功: {path_in_repo}")
|
|
120
|
+
return 0
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
123
|
+
return 1
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def cmd_download_repo(args):
|
|
127
|
+
"""处理 download 命令"""
|
|
128
|
+
from xiaoshiai_hub.download import snapshot_download
|
|
129
|
+
|
|
130
|
+
token, username, password = get_auth(args)
|
|
131
|
+
|
|
132
|
+
# 解析模式
|
|
133
|
+
allow_patterns = args.include if args.include else None
|
|
134
|
+
ignore_patterns = args.ignore if args.ignore else None
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
local_path = snapshot_download(
|
|
138
|
+
repo_id=args.repo_id,
|
|
139
|
+
repo_type=args.repo_type,
|
|
140
|
+
revision=args.revision,
|
|
141
|
+
local_dir=args.local_dir,
|
|
142
|
+
allow_patterns=allow_patterns,
|
|
143
|
+
ignore_patterns=ignore_patterns,
|
|
144
|
+
base_url=args.base_url,
|
|
145
|
+
username=username,
|
|
146
|
+
password=password,
|
|
147
|
+
token=token,
|
|
148
|
+
show_progress=not args.quiet,
|
|
149
|
+
)
|
|
150
|
+
print(f"下载完成: {local_path}")
|
|
151
|
+
return 0
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def cmd_download_file(args):
|
|
158
|
+
"""处理 download-file 命令"""
|
|
159
|
+
from xiaoshiai_hub.download import moha_hub_download
|
|
160
|
+
|
|
161
|
+
token, username, password = get_auth(args)
|
|
162
|
+
|
|
163
|
+
# 确定本地目录
|
|
164
|
+
local_dir = args.local_dir if args.local_dir else "."
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
local_path = moha_hub_download(
|
|
168
|
+
repo_id=args.repo_id,
|
|
169
|
+
filename=args.filename,
|
|
170
|
+
repo_type=args.repo_type,
|
|
171
|
+
revision=args.revision,
|
|
172
|
+
local_dir=local_dir,
|
|
173
|
+
base_url=args.base_url,
|
|
174
|
+
username=username,
|
|
175
|
+
password=password,
|
|
176
|
+
token=token,
|
|
177
|
+
show_progress=not args.quiet,
|
|
178
|
+
)
|
|
179
|
+
print(f"下载完成: {local_path}")
|
|
180
|
+
return 0
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
183
|
+
return 1
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def cmd_login(args):
|
|
187
|
+
"""处理 login 命令"""
|
|
188
|
+
from xiaoshiai_hub.auth import login
|
|
189
|
+
from xiaoshiai_hub.exceptions import AuthenticationError
|
|
190
|
+
|
|
191
|
+
# 获取用户名
|
|
192
|
+
username = args.username
|
|
193
|
+
if not username:
|
|
194
|
+
username = input("用户名: ")
|
|
195
|
+
|
|
196
|
+
# 获取密码
|
|
197
|
+
password = args.password
|
|
198
|
+
if not password:
|
|
199
|
+
password = getpass.getpass("密码: ")
|
|
200
|
+
|
|
201
|
+
if not username or not password:
|
|
202
|
+
print("错误: 用户名和密码不能为空", file=sys.stderr)
|
|
203
|
+
return 1
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
_, token_path = login(
|
|
207
|
+
username=username,
|
|
208
|
+
password=password,
|
|
209
|
+
base_url=args.base_url,
|
|
210
|
+
token_dir=args.token_dir,
|
|
211
|
+
)
|
|
212
|
+
print("登录成功!")
|
|
213
|
+
print(f"Token 已保存到: {token_path}")
|
|
214
|
+
return 0
|
|
215
|
+
except AuthenticationError as e:
|
|
216
|
+
print(f"登录失败: {e}", file=sys.stderr)
|
|
217
|
+
return 1
|
|
218
|
+
except Exception as e:
|
|
219
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
220
|
+
return 1
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def cmd_logout(args):
|
|
224
|
+
"""处理 logout 命令"""
|
|
225
|
+
from xiaoshiai_hub.auth import delete_token, get_token_path
|
|
226
|
+
|
|
227
|
+
token_path = get_token_path(args.token_dir)
|
|
228
|
+
|
|
229
|
+
if delete_token(args.token_dir):
|
|
230
|
+
print(f"已退出登录,Token 文件已删除: {token_path}")
|
|
231
|
+
return 0
|
|
232
|
+
else:
|
|
233
|
+
print(f"未找到 Token 文件: {token_path}")
|
|
234
|
+
return 0
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def cmd_whoami(args):
|
|
238
|
+
"""处理 whoami 命令"""
|
|
239
|
+
from xiaoshiai_hub.auth import load_token, get_token_path
|
|
240
|
+
|
|
241
|
+
token, base_url, username = load_token(args.token_dir)
|
|
242
|
+
token_path = get_token_path(args.token_dir)
|
|
243
|
+
|
|
244
|
+
if token:
|
|
245
|
+
print(f"已登录")
|
|
246
|
+
if username:
|
|
247
|
+
print(f"用户名: {username}")
|
|
248
|
+
if base_url:
|
|
249
|
+
print(f"API 地址: {base_url}")
|
|
250
|
+
print(f"Token 文件: {token_path}")
|
|
251
|
+
return 0
|
|
252
|
+
else:
|
|
253
|
+
print("未登录")
|
|
254
|
+
print(f"请使用 'moha login' 命令登录")
|
|
255
|
+
return 1
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _add_common_args(parser):
|
|
259
|
+
"""添加通用参数"""
|
|
260
|
+
parser.add_argument(
|
|
261
|
+
"--base-url",
|
|
262
|
+
help="Hub API 地址(默认从 MOHA_ENDPOINT 环境变量获取)",
|
|
263
|
+
)
|
|
264
|
+
parser.add_argument(
|
|
265
|
+
"--token",
|
|
266
|
+
help="认证令牌(或设置 MOHA_TOKEN 环境变量)",
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument(
|
|
269
|
+
"--username",
|
|
270
|
+
help="用户名(或设置 MOHA_USERNAME 环境变量)",
|
|
271
|
+
)
|
|
272
|
+
parser.add_argument(
|
|
273
|
+
"--password",
|
|
274
|
+
help="密码(或设置 MOHA_PASSWORD 环境变量)",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _add_encryption_args(parser):
|
|
279
|
+
"""添加加密相关参数"""
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"--encrypt", "-e",
|
|
282
|
+
action="store_true",
|
|
283
|
+
help="启用模型文件加密",
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"--encryption-password",
|
|
287
|
+
help="加密密码(或设置 MOHA_ENCRYPTION_PASSWORD 环境变量)",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def create_parser():
|
|
292
|
+
"""创建命令行参数解析器"""
|
|
293
|
+
parser = argparse.ArgumentParser(
|
|
294
|
+
prog="moha",
|
|
295
|
+
description="小时 AI Hub 命令行工具 - 上传和下载模型及数据集",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
299
|
+
|
|
300
|
+
# ========== 上传文件夹命令 ==========
|
|
301
|
+
upload_folder_parser = subparsers.add_parser(
|
|
302
|
+
"upload-folder",
|
|
303
|
+
aliases=["upload"],
|
|
304
|
+
help="上传文件夹到仓库",
|
|
305
|
+
)
|
|
306
|
+
upload_folder_parser.add_argument("folder", help="要上传的文件夹路径")
|
|
307
|
+
upload_folder_parser.add_argument("repo_id", help="仓库 ID(格式: 组织/仓库名)")
|
|
308
|
+
upload_folder_parser.add_argument(
|
|
309
|
+
"--repo-type", "-t",
|
|
310
|
+
default="models",
|
|
311
|
+
choices=["models", "datasets"],
|
|
312
|
+
help="仓库类型(默认: models)",
|
|
313
|
+
)
|
|
314
|
+
upload_folder_parser.add_argument(
|
|
315
|
+
"--revision", "-r",
|
|
316
|
+
default="main",
|
|
317
|
+
help="上传到的分支(默认: main)",
|
|
318
|
+
)
|
|
319
|
+
upload_folder_parser.add_argument(
|
|
320
|
+
"--message", "-m",
|
|
321
|
+
help="提交信息",
|
|
322
|
+
)
|
|
323
|
+
upload_folder_parser.add_argument(
|
|
324
|
+
"--ignore", "-i",
|
|
325
|
+
action="append",
|
|
326
|
+
help="忽略的文件模式(可多次指定)",
|
|
327
|
+
)
|
|
328
|
+
upload_folder_parser.add_argument(
|
|
329
|
+
"--temp-dir",
|
|
330
|
+
help="加密临时目录",
|
|
331
|
+
)
|
|
332
|
+
_add_common_args(upload_folder_parser)
|
|
333
|
+
_add_encryption_args(upload_folder_parser)
|
|
334
|
+
upload_folder_parser.set_defaults(func=cmd_upload_folder)
|
|
335
|
+
|
|
336
|
+
# ========== 上传文件命令 ==========
|
|
337
|
+
upload_file_parser = subparsers.add_parser(
|
|
338
|
+
"upload-file",
|
|
339
|
+
help="上传单个文件到仓库",
|
|
340
|
+
)
|
|
341
|
+
upload_file_parser.add_argument("file", help="要上传的文件路径")
|
|
342
|
+
upload_file_parser.add_argument("repo_id", help="仓库 ID(格式: 组织/仓库名)")
|
|
343
|
+
upload_file_parser.add_argument(
|
|
344
|
+
"--path-in-repo", "-p",
|
|
345
|
+
help="文件在仓库中的路径(默认: 文件名)",
|
|
346
|
+
)
|
|
347
|
+
upload_file_parser.add_argument(
|
|
348
|
+
"--repo-type", "-t",
|
|
349
|
+
default="models",
|
|
350
|
+
choices=["models", "datasets"],
|
|
351
|
+
help="仓库类型(默认: models)",
|
|
352
|
+
)
|
|
353
|
+
upload_file_parser.add_argument(
|
|
354
|
+
"--revision", "-r",
|
|
355
|
+
default="main",
|
|
356
|
+
help="上传到的分支(默认: main)",
|
|
357
|
+
)
|
|
358
|
+
upload_file_parser.add_argument(
|
|
359
|
+
"--message", "-m",
|
|
360
|
+
help="提交信息",
|
|
361
|
+
)
|
|
362
|
+
_add_common_args(upload_file_parser)
|
|
363
|
+
_add_encryption_args(upload_file_parser)
|
|
364
|
+
upload_file_parser.set_defaults(func=cmd_upload_file)
|
|
365
|
+
|
|
366
|
+
# ========== 下载仓库命令 ==========
|
|
367
|
+
download_repo_parser = subparsers.add_parser(
|
|
368
|
+
"download",
|
|
369
|
+
aliases=["download-repo"],
|
|
370
|
+
help="下载整个仓库",
|
|
371
|
+
)
|
|
372
|
+
download_repo_parser.add_argument("repo_id", help="仓库 ID(格式: 组织/仓库名)")
|
|
373
|
+
download_repo_parser.add_argument(
|
|
374
|
+
"--local-dir", "-o",
|
|
375
|
+
help="保存文件的目录",
|
|
376
|
+
)
|
|
377
|
+
download_repo_parser.add_argument(
|
|
378
|
+
"--repo-type", "-t",
|
|
379
|
+
default="models",
|
|
380
|
+
choices=["models", "datasets"],
|
|
381
|
+
help="仓库类型(默认: models)",
|
|
382
|
+
)
|
|
383
|
+
download_repo_parser.add_argument(
|
|
384
|
+
"--revision", "-r",
|
|
385
|
+
help="要下载的分支/标签/提交(默认: main)",
|
|
386
|
+
)
|
|
387
|
+
download_repo_parser.add_argument(
|
|
388
|
+
"--include",
|
|
389
|
+
action="append",
|
|
390
|
+
help="包含的文件模式(可多次指定)",
|
|
391
|
+
)
|
|
392
|
+
download_repo_parser.add_argument(
|
|
393
|
+
"--ignore", "-i",
|
|
394
|
+
action="append",
|
|
395
|
+
help="忽略的文件模式(可多次指定)",
|
|
396
|
+
)
|
|
397
|
+
download_repo_parser.add_argument(
|
|
398
|
+
"--quiet", "-q",
|
|
399
|
+
action="store_true",
|
|
400
|
+
help="禁用进度条",
|
|
401
|
+
)
|
|
402
|
+
_add_common_args(download_repo_parser)
|
|
403
|
+
download_repo_parser.set_defaults(func=cmd_download_repo)
|
|
404
|
+
|
|
405
|
+
# ========== 下载文件命令 ==========
|
|
406
|
+
download_file_parser = subparsers.add_parser(
|
|
407
|
+
"download-file",
|
|
408
|
+
help="从仓库下载单个文件",
|
|
409
|
+
)
|
|
410
|
+
download_file_parser.add_argument("repo_id", help="仓库 ID(格式: 组织/仓库名)")
|
|
411
|
+
download_file_parser.add_argument("filename", help="仓库中的文件路径")
|
|
412
|
+
download_file_parser.add_argument(
|
|
413
|
+
"--local-dir", "-o",
|
|
414
|
+
help="保存文件的目录(默认: 当前目录)",
|
|
415
|
+
)
|
|
416
|
+
download_file_parser.add_argument(
|
|
417
|
+
"--repo-type", "-t",
|
|
418
|
+
default="models",
|
|
419
|
+
choices=["models", "datasets"],
|
|
420
|
+
help="仓库类型(默认: models)",
|
|
421
|
+
)
|
|
422
|
+
download_file_parser.add_argument(
|
|
423
|
+
"--revision", "-r",
|
|
424
|
+
help="要下载的分支/标签/提交(默认: main)",
|
|
425
|
+
)
|
|
426
|
+
download_file_parser.add_argument(
|
|
427
|
+
"--quiet", "-q",
|
|
428
|
+
action="store_true",
|
|
429
|
+
help="禁用进度条",
|
|
430
|
+
)
|
|
431
|
+
_add_common_args(download_file_parser)
|
|
432
|
+
download_file_parser.set_defaults(func=cmd_download_file)
|
|
433
|
+
|
|
434
|
+
# ========== 登录命令 ==========
|
|
435
|
+
login_parser = subparsers.add_parser(
|
|
436
|
+
"login",
|
|
437
|
+
help="登录并保存 Token",
|
|
438
|
+
)
|
|
439
|
+
login_parser.add_argument(
|
|
440
|
+
"--username", "-u",
|
|
441
|
+
help="用户名(不指定则交互式输入)",
|
|
442
|
+
)
|
|
443
|
+
login_parser.add_argument(
|
|
444
|
+
"--password", "-p",
|
|
445
|
+
help="密码(不指定则交互式输入)",
|
|
446
|
+
)
|
|
447
|
+
login_parser.add_argument(
|
|
448
|
+
"--base-url",
|
|
449
|
+
help="Hub API 地址(默认从 MOHA_ENDPOINT 环境变量获取)",
|
|
450
|
+
)
|
|
451
|
+
login_parser.add_argument(
|
|
452
|
+
"--token-dir",
|
|
453
|
+
help="Token 存储目录(默认: ~/.moha)",
|
|
454
|
+
)
|
|
455
|
+
login_parser.set_defaults(func=cmd_login)
|
|
456
|
+
|
|
457
|
+
# ========== 退出登录命令 ==========
|
|
458
|
+
logout_parser = subparsers.add_parser(
|
|
459
|
+
"logout",
|
|
460
|
+
help="退出登录并删除 Token",
|
|
461
|
+
)
|
|
462
|
+
logout_parser.add_argument(
|
|
463
|
+
"--token-dir",
|
|
464
|
+
help="Token 存储目录(默认: ~/.moha)",
|
|
465
|
+
)
|
|
466
|
+
logout_parser.set_defaults(func=cmd_logout)
|
|
467
|
+
|
|
468
|
+
# ========== 查看当前登录状态命令 ==========
|
|
469
|
+
whoami_parser = subparsers.add_parser(
|
|
470
|
+
"whoami",
|
|
471
|
+
help="查看当前登录状态",
|
|
472
|
+
)
|
|
473
|
+
whoami_parser.add_argument(
|
|
474
|
+
"--token-dir",
|
|
475
|
+
help="Token 存储目录(默认: ~/.moha)",
|
|
476
|
+
)
|
|
477
|
+
whoami_parser.set_defaults(func=cmd_whoami)
|
|
478
|
+
|
|
479
|
+
return parser
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def main():
|
|
483
|
+
"""主入口"""
|
|
484
|
+
parser = create_parser()
|
|
485
|
+
args = parser.parse_args()
|
|
486
|
+
|
|
487
|
+
if args.command is None:
|
|
488
|
+
parser.print_help()
|
|
489
|
+
return 1
|
|
490
|
+
|
|
491
|
+
return args.func(args)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
if __name__ == "__main__":
|
|
495
|
+
sys.exit(main())
|
|
496
|
+
|