xiaoshiai-hub 0.1.0__py3-none-any.whl → 0.1.2__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 +45 -3
- xiaoshiai_hub/client.py +119 -18
- xiaoshiai_hub/download.py +190 -56
- xiaoshiai_hub/encryption.py +777 -0
- xiaoshiai_hub/exceptions.py +6 -1
- xiaoshiai_hub/types.py +21 -4
- xiaoshiai_hub/upload.py +831 -0
- xiaoshiai_hub-0.1.2.dist-info/METADATA +560 -0
- xiaoshiai_hub-0.1.2.dist-info/RECORD +12 -0
- {xiaoshiai_hub-0.1.0.dist-info → xiaoshiai_hub-0.1.2.dist-info}/top_level.txt +0 -1
- tests/__init__.py +0 -4
- tests/test_download.py +0 -142
- xiaoshiai_hub-0.1.0.dist-info/METADATA +0 -321
- xiaoshiai_hub-0.1.0.dist-info/RECORD +0 -12
- {xiaoshiai_hub-0.1.0.dist-info → xiaoshiai_hub-0.1.2.dist-info}/WHEEL +0 -0
- {xiaoshiai_hub-0.1.0.dist-info → xiaoshiai_hub-0.1.2.dist-info}/licenses/LICENSE +0 -0
xiaoshiai_hub/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ A Python library for interacting with XiaoShi AI Hub repositories.
|
|
|
6
6
|
|
|
7
7
|
from .client import HubClient, DEFAULT_BASE_URL
|
|
8
8
|
from .download import (
|
|
9
|
-
|
|
9
|
+
moha_hub_download,
|
|
10
10
|
snapshot_download,
|
|
11
11
|
)
|
|
12
12
|
from .exceptions import (
|
|
@@ -14,6 +14,7 @@ from .exceptions import (
|
|
|
14
14
|
RepositoryNotFoundError,
|
|
15
15
|
FileNotFoundError,
|
|
16
16
|
AuthenticationError,
|
|
17
|
+
EncryptionError,
|
|
17
18
|
)
|
|
18
19
|
from .types import (
|
|
19
20
|
Repository,
|
|
@@ -22,20 +23,61 @@ from .types import (
|
|
|
22
23
|
Commit,
|
|
23
24
|
)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
# Upload functionality (requires GitPython)
|
|
27
|
+
try:
|
|
28
|
+
from .upload import (
|
|
29
|
+
upload_file,
|
|
30
|
+
upload_folder,
|
|
31
|
+
UploadError,
|
|
32
|
+
)
|
|
33
|
+
_upload_available = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
_upload_available = False
|
|
36
|
+
upload_file = None
|
|
37
|
+
upload_folder = None
|
|
38
|
+
UploadError = None
|
|
39
|
+
|
|
40
|
+
# Encryption functionality
|
|
41
|
+
try:
|
|
42
|
+
from .encryption import (
|
|
43
|
+
EncryptionAlgorithm,
|
|
44
|
+
encrypt_file,
|
|
45
|
+
decrypt_file,
|
|
46
|
+
)
|
|
47
|
+
_encryption_available = True
|
|
48
|
+
except ImportError:
|
|
49
|
+
_encryption_available = False
|
|
50
|
+
EncryptionAlgorithm = None
|
|
51
|
+
encrypt_file = None
|
|
52
|
+
decrypt_file = None
|
|
53
|
+
|
|
54
|
+
__version__ = "0.1.0"
|
|
26
55
|
|
|
27
56
|
__all__ = [
|
|
57
|
+
# Client
|
|
28
58
|
"HubClient",
|
|
29
59
|
"DEFAULT_BASE_URL",
|
|
30
|
-
|
|
60
|
+
# Download
|
|
61
|
+
"moha_hub_download",
|
|
31
62
|
"snapshot_download",
|
|
63
|
+
# Upload
|
|
64
|
+
"upload_file",
|
|
65
|
+
"upload_folder",
|
|
66
|
+
# Exceptions
|
|
32
67
|
"HubException",
|
|
33
68
|
"RepositoryNotFoundError",
|
|
34
69
|
"FileNotFoundError",
|
|
35
70
|
"AuthenticationError",
|
|
71
|
+
"EncryptionError",
|
|
72
|
+
"UploadError",
|
|
73
|
+
# Types
|
|
36
74
|
"Repository",
|
|
37
75
|
"Ref",
|
|
38
76
|
"GitContent",
|
|
39
77
|
"Commit",
|
|
78
|
+
# Encryption
|
|
79
|
+
"EncryptionAlgorithm",
|
|
80
|
+
"encrypt_file",
|
|
81
|
+
"decrypt_file",
|
|
40
82
|
]
|
|
41
83
|
|
xiaoshiai_hub/client.py
CHANGED
|
@@ -3,10 +3,9 @@ XiaoShi AI Hub Client
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
-
import json
|
|
7
6
|
import os
|
|
8
7
|
from typing import List, Optional
|
|
9
|
-
|
|
8
|
+
|
|
10
9
|
|
|
11
10
|
import requests
|
|
12
11
|
|
|
@@ -20,13 +19,13 @@ from .exceptions import (
|
|
|
20
19
|
HTTPError,
|
|
21
20
|
RepositoryNotFoundError,
|
|
22
21
|
)
|
|
23
|
-
from .types import Repository, Ref, GitContent
|
|
22
|
+
from .types import EncryptionMetadata, Repository, Ref, GitContent
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
# 默认基础 URL,可通过环境变量 MOHA_ENDPOINT 覆盖
|
|
27
26
|
DEFAULT_BASE_URL = os.environ.get(
|
|
28
27
|
"MOHA_ENDPOINT",
|
|
29
|
-
"https://rune.develop.xiaoshiai.cn/
|
|
28
|
+
"https://rune-api.develop.xiaoshiai.cn/moha"
|
|
30
29
|
)
|
|
31
30
|
|
|
32
31
|
|
|
@@ -86,6 +85,60 @@ class HubClient:
|
|
|
86
85
|
return response
|
|
87
86
|
except requests.RequestException as e:
|
|
88
87
|
raise HTTPError(f"Request failed: {str(e)}")
|
|
88
|
+
|
|
89
|
+
def get_moha_encryption(
|
|
90
|
+
self,
|
|
91
|
+
organization: str,
|
|
92
|
+
repo_type: str,
|
|
93
|
+
repo_name: str,
|
|
94
|
+
reference: str,
|
|
95
|
+
) -> Optional[EncryptionMetadata]:
|
|
96
|
+
"""
|
|
97
|
+
Get .moha_encryption file content from the repository.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
organization: Organization name
|
|
101
|
+
repo_type: Repository type ("models" or "datasets")
|
|
102
|
+
repo_name: Repository name
|
|
103
|
+
reference: Branch/tag/commit reference
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Encryption metadata if the repository has encrypted files, None otherwise
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
AuthenticationError: If authentication fails
|
|
110
|
+
RepositoryNotFoundError: If the repository is not found
|
|
111
|
+
HTTPError: If the request fails
|
|
112
|
+
"""
|
|
113
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}/encryption/{reference}"
|
|
114
|
+
response = self._make_request("GET", url)
|
|
115
|
+
if response.status_code == 204 or not response.content:
|
|
116
|
+
return None
|
|
117
|
+
try:
|
|
118
|
+
data = response.json()
|
|
119
|
+
except ValueError:
|
|
120
|
+
return None
|
|
121
|
+
if not data:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
from datetime import datetime
|
|
125
|
+
files = None
|
|
126
|
+
if 'files' in data and data['files']:
|
|
127
|
+
from .types import FileEncryptionMetadata
|
|
128
|
+
files = [
|
|
129
|
+
FileEncryptionMetadata(
|
|
130
|
+
path=f.get('path', ''),
|
|
131
|
+
algorithm=f.get('algorithm', ''),
|
|
132
|
+
encryptedHash=f.get('encryptedHash', ''),
|
|
133
|
+
encryptedSize=f.get('encryptedSize', 0),
|
|
134
|
+
)
|
|
135
|
+
for f in data['files']
|
|
136
|
+
]
|
|
137
|
+
return EncryptionMetadata(
|
|
138
|
+
version=data.get('version', '1.0'),
|
|
139
|
+
createAt=datetime.fromisoformat(data['createAt'].replace('Z', '+00:00')) if 'createAt' in data else datetime.now(),
|
|
140
|
+
files=files,
|
|
141
|
+
)
|
|
89
142
|
|
|
90
143
|
def get_repository_info(
|
|
91
144
|
self,
|
|
@@ -95,25 +148,36 @@ class HubClient:
|
|
|
95
148
|
) -> Repository:
|
|
96
149
|
"""
|
|
97
150
|
Get repository information.
|
|
98
|
-
|
|
151
|
+
|
|
99
152
|
Args:
|
|
100
153
|
organization: Organization name
|
|
101
154
|
repo_type: Repository type ("models" or "datasets")
|
|
102
155
|
repo_name: Repository name
|
|
103
|
-
|
|
156
|
+
|
|
104
157
|
Returns:
|
|
105
158
|
Repository information
|
|
106
159
|
"""
|
|
107
|
-
url = f"{self.base_url}/organizations/{organization}/{repo_type}/{repo_name}"
|
|
160
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}"
|
|
108
161
|
response = self._make_request("GET", url)
|
|
109
162
|
data = response.json()
|
|
110
|
-
|
|
163
|
+
|
|
164
|
+
# Parse annotations if present
|
|
165
|
+
annotations = {}
|
|
166
|
+
if 'annotations' in data and isinstance(data['annotations'], dict):
|
|
167
|
+
annotations = data['annotations']
|
|
168
|
+
|
|
169
|
+
# Parse metadata if present
|
|
170
|
+
metadata = {}
|
|
171
|
+
if 'metadata' in data and isinstance(data['metadata'], dict):
|
|
172
|
+
metadata = data['metadata']
|
|
173
|
+
|
|
111
174
|
return Repository(
|
|
112
175
|
name=data.get('name', repo_name),
|
|
113
176
|
organization=organization,
|
|
114
177
|
type=repo_type,
|
|
115
|
-
default_branch=data.get('defaultBranch'),
|
|
116
178
|
description=data.get('description'),
|
|
179
|
+
metadata=metadata,
|
|
180
|
+
annotations=annotations,
|
|
117
181
|
)
|
|
118
182
|
|
|
119
183
|
def get_repository_refs(
|
|
@@ -124,31 +188,53 @@ class HubClient:
|
|
|
124
188
|
) -> List[Ref]:
|
|
125
189
|
"""
|
|
126
190
|
Get repository references (branches and tags).
|
|
127
|
-
|
|
191
|
+
|
|
128
192
|
Args:
|
|
129
193
|
organization: Organization name
|
|
130
194
|
repo_type: Repository type ("models" or "datasets")
|
|
131
195
|
repo_name: Repository name
|
|
132
|
-
|
|
196
|
+
|
|
133
197
|
Returns:
|
|
134
198
|
List of references
|
|
135
199
|
"""
|
|
136
|
-
url = f"{self.base_url}/organizations/{organization}/{repo_type}/{repo_name}/refs"
|
|
200
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}/refs"
|
|
137
201
|
response = self._make_request("GET", url)
|
|
138
202
|
data = response.json()
|
|
139
|
-
|
|
203
|
+
|
|
140
204
|
refs = []
|
|
141
205
|
for ref_data in data:
|
|
142
206
|
refs.append(Ref(
|
|
143
207
|
name=ref_data.get('name', ''),
|
|
144
208
|
ref=ref_data.get('ref', ''),
|
|
145
|
-
fully_name=ref_data.get('fullyName', ''),
|
|
146
209
|
type=ref_data.get('type', ''),
|
|
147
210
|
hash=ref_data.get('hash', ''),
|
|
148
211
|
is_default=ref_data.get('isDefault', False),
|
|
149
212
|
))
|
|
150
|
-
|
|
213
|
+
|
|
151
214
|
return refs
|
|
215
|
+
|
|
216
|
+
def get_default_branch(
|
|
217
|
+
self,
|
|
218
|
+
organization: str,
|
|
219
|
+
repo_type: str,
|
|
220
|
+
repo_name: str,
|
|
221
|
+
) -> str:
|
|
222
|
+
"""
|
|
223
|
+
Get the default branch name for a repository.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
organization: Organization name
|
|
227
|
+
repo_type: Repository type ("models" or "datasets")
|
|
228
|
+
repo_name: Repository name
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Default branch name (defaults to "main" if not found)
|
|
232
|
+
"""
|
|
233
|
+
refs = self.get_repository_refs(organization, repo_type, repo_name)
|
|
234
|
+
for ref in refs:
|
|
235
|
+
if ref.is_default and ref.type == "branch":
|
|
236
|
+
return ref.name
|
|
237
|
+
return "main"
|
|
152
238
|
|
|
153
239
|
def get_repository_content(
|
|
154
240
|
self,
|
|
@@ -172,9 +258,9 @@ class HubClient:
|
|
|
172
258
|
Git content information
|
|
173
259
|
"""
|
|
174
260
|
if path:
|
|
175
|
-
url = f"{self.base_url}/organizations/{organization}/{repo_type}/{repo_name}/contents/{branch}/{path}"
|
|
261
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}/contents/{branch}/{path}"
|
|
176
262
|
else:
|
|
177
|
-
url = f"{self.base_url}/organizations/{organization}/{repo_type}/{repo_name}/contents/{branch}"
|
|
263
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}/contents/{branch}"
|
|
178
264
|
|
|
179
265
|
response = self._make_request("GET", url)
|
|
180
266
|
data = response.json()
|
|
@@ -199,6 +285,21 @@ class HubClient:
|
|
|
199
285
|
entries=entries,
|
|
200
286
|
)
|
|
201
287
|
|
|
288
|
+
def add_download_count(self, organization: str, repo_type: str, repo_name: str) -> None:
|
|
289
|
+
"""
|
|
290
|
+
Add download count for a repository.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
organization: Organization name
|
|
294
|
+
repo_type: Repository type ("models" or "datasets")
|
|
295
|
+
repo_name: Repository name
|
|
296
|
+
"""
|
|
297
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}/downloads"
|
|
298
|
+
response = self._make_request("POST", url)
|
|
299
|
+
# 检查http code
|
|
300
|
+
if response.status_code != 200:
|
|
301
|
+
raise HTTPError(f"Failed to add download count: {response.text}")
|
|
302
|
+
|
|
202
303
|
def download_file(
|
|
203
304
|
self,
|
|
204
305
|
organization: str,
|
|
@@ -221,7 +322,7 @@ class HubClient:
|
|
|
221
322
|
local_path: Local path to save the file
|
|
222
323
|
show_progress: Whether to show download progress bar
|
|
223
324
|
"""
|
|
224
|
-
url = f"{self.base_url}/organizations/{organization}/{repo_type}/{repo_name}/resolve/{branch}/{file_path}"
|
|
325
|
+
url = f"{self.base_url}/v1/organizations/{organization}/{repo_type}/{repo_name}/resolve/{branch}/{file_path}"
|
|
225
326
|
response = self._make_request("GET", url, stream=True)
|
|
226
327
|
|
|
227
328
|
# Get file size from headers
|