intellif-aihub 0.1.22__py3-none-any.whl → 0.1.23__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.

Potentially problematic release.


This version of intellif-aihub might be problematic. Click here for more details.

@@ -2,40 +2,47 @@
2
2
  # -*- coding:utf-8 -*-
3
3
  """模型中心服务模块
4
4
 
5
- 封装与 **Model‑Center** 后端交互的常用能力,主要涉及模型的增、删、改、查,以及模型元数据(类型 / 部署平台 / 量化等级)的查询功能:
6
-
7
- - **分页查询模型列表**
8
- - **获取单个模型详情**
9
- - **新建模型**
10
- - **编辑模型**
11
- - **删除模型**
12
- - **查询模型类型下拉**
13
- - **查询部署平台下拉**
14
- - **查询量化等级下拉**
5
+ 当前封装的能力:
6
+ - 分页查询模型列表
7
+ - 获取单个模型详情
8
+ - 新建模型
9
+ - 编辑模型
10
+ - 删除模型
11
+ - 上传模型
12
+ - 下载模型
13
+ - 查询模型 DB 信息
15
14
  """
16
15
 
17
16
  from __future__ import annotations
18
17
 
18
+ import io
19
+ import os
20
+ import time
21
+
19
22
  import httpx
23
+ import minio
24
+ from loguru import logger
20
25
 
21
26
  from ..exceptions import APIError
27
+ from ..models.artifact import StsResp
22
28
  from ..models.common import APIWrapper
23
29
  from ..models.model_center import (
24
30
  ListModelsRequest,
25
31
  ListModelsResponse,
26
- ListModelTypesRequest,
27
- ListModelTypesResponse,
28
- ListDeployPlatformsRequest,
29
- ListDeployPlatformsResponse,
30
- ListQuantLevelsRequest,
31
- ListQuantLevelsResponse,
32
+ ModelCardDetail,
32
33
  CreateModelRequest,
33
34
  CreateModelResponse,
34
35
  EditModelRequest,
35
- Model,
36
+ ModelDb,
36
37
  )
38
+ from ..utils.di import SimpleS3Client, DataUploader
39
+ from ..utils.s3 import parse_s3_path_strict, download_dir_from_s3_to_local
37
40
 
38
41
  _BASE = "/model-center/api/v1"
42
+ MODEL_STATUS_INIT = "init"
43
+ MODEL_STATUS_UPLOADING = "uploading"
44
+ MODEL_STATUS_READY = "ready"
45
+ MODEL_STATUS_FAILED = "failed"
39
46
 
40
47
 
41
48
  class ModelCenterService:
@@ -55,14 +62,14 @@ class ModelCenterService:
55
62
  """
56
63
  return self._model.list(payload)
57
64
 
58
- def get_model(self, model_id: int) -> Model:
65
+ def get_model(self, model_id: int) -> ModelCardDetail:
59
66
  """获取模型详情
60
67
 
61
68
  Args:
62
69
  model_id: 模型 ID
63
70
 
64
71
  Returns:
65
- Model: 模型完整信息
72
+ ModelCardDetail: 模型详情(含 README、模型树、基模型等)
66
73
  """
67
74
  return self._model.get(model_id)
68
75
 
@@ -77,13 +84,14 @@ class ModelCenterService:
77
84
  """
78
85
  return self._model.create(payload)
79
86
 
80
- def edit_model(self, payload: EditModelRequest) -> None:
87
+ def edit_model(self, model_id: int, payload: EditModelRequest) -> None:
81
88
  """编辑模型信息
82
89
 
83
90
  Args:
84
- payload: 编辑模型所需字段(需包含 id)
91
+ model_id: 模型ID
92
+ payload: 编辑模型信息
85
93
  """
86
- self._model.edit(payload)
94
+ self._model.edit(model_id, payload)
87
95
 
88
96
  def delete_model(self, model_id: int) -> None:
89
97
  """删除模型
@@ -93,29 +101,50 @@ class ModelCenterService:
93
101
  """
94
102
  self._model.delete(model_id)
95
103
 
96
- def list_model_types(self) -> ListModelTypesResponse:
97
- """查询模型类型列表
104
+ def get_model_db(self, *, id: int | None = None, name: str | None = None) -> ModelDb:
105
+ """通过 id 或 name 查询模型 DB 信息
106
+
107
+ Args:
108
+ id: 模型id
109
+ name: 模型名称
98
110
 
99
111
  Returns:
100
- ListModelTypesResponse: 模型类型集合
112
+ ModelDb: 模型在DB中的信息
101
113
  """
102
- return self._model.list_types(ListModelTypesRequest())
114
+ return self._model.get_model_db(id=id, name=name)
115
+
116
+ def upload(
117
+ self,
118
+ *,
119
+ local_dir: str,
120
+ model_id: int | None = None,
121
+ model_name: str | None = None,
122
+ timeout_seconds: int = 3600,
123
+ ) -> None:
124
+ """上传模型
103
125
 
104
- def list_deploy_platforms(self) -> ListDeployPlatformsResponse:
105
- """查询可用部署平台列表
106
-
107
- Returns:
108
- ListDeployPlatformsResponse: 部署平台集合
126
+ Args:
127
+ local_dir: 本地模型目录
128
+ model_id: 模型 id
129
+ model_name: 模型名称
130
+ timeout_seconds: 超时时间
109
131
  """
110
- return self._model.list_platforms(ListDeployPlatformsRequest())
132
+ return self._model.upload(
133
+ local_dir=local_dir,
134
+ model_id=model_id,
135
+ model_name=model_name,
136
+ timeout_seconds=timeout_seconds,
137
+ )
111
138
 
112
- def list_quant_levels(self) -> ListQuantLevelsResponse:
113
- """查询量化等级列表
139
+ def download(self, *, local_dir: str, model_id: int | None = None, model_name: str | None = None) -> None:
140
+ """下载模型
114
141
 
115
- Returns:
116
- ListQuantLevelsResponse: 量化等级集合
142
+ Args:
143
+ local_dir: 要下载到的本地目录
144
+ model_id: 模型id
145
+ model_name: 模型名称
117
146
  """
118
- return self._model.list_quant_levels(ListQuantLevelsRequest())
147
+ return self._model.download(local_dir=local_dir, model_id=model_id, model_name=model_name)
119
148
 
120
149
  @property
121
150
  def model(self) -> _Model:
@@ -134,23 +163,39 @@ class _Model:
134
163
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
135
164
  return wrapper.data
136
165
 
137
- def get(self, model_id: int) -> Model:
166
+ def get(self, model_id: int) -> ModelCardDetail:
138
167
  resp = self._http.get(f"{_BASE}/models/{model_id}")
139
- wrapper = APIWrapper[Model].model_validate(resp.json())
168
+ wrapper = APIWrapper[ModelCardDetail].model_validate(resp.json())
140
169
  if wrapper.code != 0:
141
170
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
142
171
  return wrapper.data
143
172
 
144
- def create(self, payload: CreateModelRequest) -> int:
145
- resp = self._http.post(f"{_BASE}/models", json=payload.model_dump(by_alias=True, exclude_none=True))
173
+ def create(self, form: CreateModelRequest) -> int:
174
+ files = {
175
+ "name": (None, form.name),
176
+ "is_public": (None, "true" if bool(form.is_public) else "false"),
177
+ }
178
+ if form.description is not None:
179
+ files["description"] = (None, form.description)
180
+ if form.tags is not None:
181
+ files["tags"] = (None, form.tags)
182
+
183
+ if form.readme_content:
184
+ md_text = (
185
+ form.readme_content
186
+ if (form.readme_content and form.readme_content.strip())
187
+ else f"# {form.name}\n\nAutogenerated at {int(time.time())}\n"
188
+ )
189
+ files["readme_file"] = ("README.md", io.BytesIO(md_text.encode("utf-8")), "text/markdown")
190
+
191
+ resp = self._http.post(f"{_BASE}/models", files=files)
146
192
  wrapper = APIWrapper[CreateModelResponse].model_validate(resp.json())
147
193
  if wrapper.code != 0:
148
194
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
149
195
  return wrapper.data.id
150
196
 
151
- def edit(self, payload: EditModelRequest) -> None:
152
- resp = self._http.put(f"{_BASE}/models/{payload.id}",
153
- json=payload.model_dump(by_alias=True, exclude_none=True))
197
+ def edit(self, model_id: int, payload: EditModelRequest) -> None:
198
+ resp = self._http.put(f"{_BASE}/models/{model_id}", json=payload.model_dump(by_alias=True, exclude_none=True))
154
199
  wrapper = APIWrapper[dict].model_validate(resp.json())
155
200
  if wrapper.code != 0:
156
201
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
@@ -161,23 +206,126 @@ class _Model:
161
206
  if wrapper.code != 0:
162
207
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
163
208
 
164
- def list_types(self, payload: ListModelTypesRequest) -> ListModelTypesResponse:
165
- resp = self._http.get(f"{_BASE}/model-types", params=payload.model_dump(by_alias=True))
166
- wrapper = APIWrapper[ListModelTypesResponse].model_validate(resp.json())
209
+ def get_model_db(self, *, id: int | None = None, name: str | None = None) -> ModelDb:
210
+ if id is None and (name is None or name == ""):
211
+ raise ValueError("id or name is required")
212
+
213
+ params = {"id": id} if id is not None else {"name": name}
214
+ resp = self._http.get(f"{_BASE}/models/db", params=params)
215
+ wrapper = APIWrapper[ModelDb].model_validate(resp.json())
167
216
  if wrapper.code != 0:
168
217
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
169
218
  return wrapper.data
170
219
 
171
- def list_platforms(self, payload: ListDeployPlatformsRequest) -> ListDeployPlatformsResponse:
172
- resp = self._http.get(f"{_BASE}/deploy-platforms", params=payload.model_dump(by_alias=True))
173
- wrapper = APIWrapper[ListDeployPlatformsResponse].model_validate(resp.json())
220
+ def _get_sts(self) -> StsResp:
221
+ resp = self._http.get(f"{_BASE}/models/get-sts")
222
+ wrapper = APIWrapper[StsResp].model_validate(resp.json())
174
223
  if wrapper.code != 0:
175
224
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
176
225
  return wrapper.data
177
226
 
178
- def list_quant_levels(self, payload: ListQuantLevelsRequest) -> ListQuantLevelsResponse:
179
- resp = self._http.get(f"{_BASE}/quant-levels", params=payload.model_dump(by_alias=True))
180
- wrapper = APIWrapper[ListQuantLevelsResponse].model_validate(resp.json())
227
+ def upload(
228
+ self,
229
+ *,
230
+ local_dir: str,
231
+ model_id: int | None = None,
232
+ model_name: str | None = None,
233
+ timeout_seconds: int = 3600,
234
+ ) -> None:
235
+ if (model_id is None) and (not model_name):
236
+ raise ValueError("id or name is required")
237
+ if not local_dir or not os.path.exists(local_dir):
238
+ raise ValueError(f"local_dir not exists: {local_dir}")
239
+
240
+ # 1. get db info
241
+ db_info = self.get_model_db(id=model_id, name=model_name)
242
+ resolved_id = db_info.id
243
+ s3_target = db_info.object_storage_path
244
+ s3_csv_path = db_info.csv_file_path
245
+ s3_status_path = db_info.task_status_s3_path
246
+
247
+ # 2. upload file to s3
248
+ sts = self._get_sts()
249
+ s3_client = SimpleS3Client(
250
+ sts.endpoint, sts.access_key_id, sts.secret_access_key, session_token=sts.session_token
251
+ )
252
+ uploader = DataUploader(
253
+ task_id=resolved_id,
254
+ local_path=local_dir,
255
+ s3_target=s3_target,
256
+ csv_path=s3_csv_path,
257
+ status_path=s3_status_path,
258
+ num_workers=40,
259
+ )
260
+ stats = uploader.run(s3_client)
261
+
262
+ # 3. invoke model center upload interface
263
+ payload = {
264
+ "model_id": resolved_id,
265
+ "object_cnt": stats.uploaded_count,
266
+ "data_size": stats.uploaded_size,
267
+ }
268
+ resp = self._http.post(f"{_BASE}/models/upload", json=payload)
269
+ wrapper = APIWrapper[dict].model_validate(resp.json())
181
270
  if wrapper.code != 0:
182
271
  raise APIError(f"backend code {wrapper.code}: {wrapper.msg}")
183
- return wrapper.data
272
+
273
+ logger.info(f"模型上传本地处理完成,model_id={resolved_id},等待服务端处理,3s 后开始轮询状态…")
274
+ time.sleep(3)
275
+
276
+ start_ts = time.time()
277
+ poll_interval = 10
278
+ while True:
279
+ db_cur = self.get_model_db(id=resolved_id)
280
+ status = (db_cur.status or "").lower()
281
+
282
+ if status == MODEL_STATUS_READY:
283
+ logger.info(f"模型处理成功:model_id={resolved_id}, status=ready")
284
+ return
285
+
286
+ if status == MODEL_STATUS_FAILED:
287
+ logger.error(f"模型处理失败:model_id={resolved_id}, status=failed")
288
+ raise APIError(f"模型处理失败:model_id={resolved_id}, status=failed")
289
+
290
+ elapsed = time.time() - start_ts
291
+ if elapsed > timeout_seconds:
292
+ logger.error(f"等待模型就绪超时:model_id={resolved_id}, waited={int(elapsed)}s")
293
+ raise TimeoutError(f"等待模型就绪超时:model_id={resolved_id}, waited={int(elapsed)}s")
294
+
295
+ logger.info(f"[Model Upload] id={resolved_id} 已等待 {int(elapsed)}s,当前 status={status},继续轮询…")
296
+ time.sleep(poll_interval)
297
+
298
+ def download(self, *, local_dir: str, model_id: int | None = None, model_name: str | None = None) -> None:
299
+ if (model_id is None) and (not model_name):
300
+ raise ValueError("id or name is required")
301
+ if not local_dir:
302
+ raise ValueError("local_dir is required")
303
+
304
+ db_info = self.get_model_db(id=model_id, name=model_name)
305
+
306
+ # 判断是否允许下载
307
+ status = (db_info.status or "").lower()
308
+ if status != "ready":
309
+ raise APIError(f"model is not ready for download (current status: {db_info.status})")
310
+ if not (db_info.parquet_index_path or "").strip():
311
+ raise APIError("parquet index path is required and cannot be empty")
312
+
313
+ s3_dir_path = db_info.object_storage_path
314
+ if not s3_dir_path or not s3_dir_path.startswith("s3://"):
315
+ raise APIError(f"invalid object_storage_path: {s3_dir_path}")
316
+
317
+ sts = self._get_sts()
318
+ s3_client = minio.Minio(
319
+ sts.endpoint,
320
+ access_key=sts.access_key_id,
321
+ secret_key=sts.secret_access_key,
322
+ session_token=sts.session_token,
323
+ secure=False,
324
+ )
325
+
326
+ bucket, object_name = parse_s3_path_strict(s3_dir_path)
327
+ if not bucket or not object_name:
328
+ raise APIError(f"invalid s3 path: {s3_dir_path}")
329
+
330
+ os.makedirs(local_dir, exist_ok=True)
331
+ download_dir_from_s3_to_local(s3_client, bucket, object_name, local_dir)
aihub/utils/s3.py CHANGED
@@ -36,9 +36,7 @@ def local_path_to_s3_key(work_dir: str, local_path: str) -> str:
36
36
  return s3_key
37
37
 
38
38
 
39
- def upload_dir_to_s3(
40
- s3_client: Minio, local_dir: str, bucket: str, object_prefix: str
41
- ) -> None:
39
+ def upload_dir_to_s3(s3_client: Minio, local_dir: str, bucket: str, object_prefix: str) -> None:
42
40
  logger.info(
43
41
  f"Uploading directory {local_dir} to S3 bucket {bucket} with prefix {object_prefix}"
44
42
  )
@@ -57,9 +55,7 @@ def upload_dir_to_s3(
57
55
  return
58
56
 
59
57
 
60
- def download_dir_from_s3(
61
- s3_client: Minio, bucket: str, object_prefix: str, local_dir: str
62
- ) -> None:
58
+ def download_dir_from_s3(s3_client: Minio, bucket: str, object_prefix: str, local_dir: str) -> None:
63
59
  logger.info(
64
60
  f"Downloading directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}"
65
61
  )
@@ -75,3 +71,50 @@ def download_dir_from_s3(
75
71
  f"Downloaded directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}"
76
72
  )
77
73
  return
74
+
75
+
76
+ def parse_s3_path_strict(s3_path: str) -> tuple[str, str]:
77
+ if not isinstance(s3_path, str) or not s3_path.startswith("s3://"):
78
+ raise ValueError(f"invalid s3 path: {s3_path}")
79
+ without_scheme = s3_path[5:]
80
+ parts = without_scheme.split("/", 1)
81
+ bucket = parts[0].strip()
82
+ if not bucket:
83
+ raise ValueError(f"invalid s3 path (empty bucket): {s3_path}")
84
+ key = parts[1] if len(parts) > 1 else ""
85
+ return bucket, key
86
+
87
+
88
+ def download_dir_from_s3_to_local(s3_client: Minio, bucket: str, object_prefix: str, local_dir: str) -> None:
89
+ logger.info(f"Downloading directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}")
90
+
91
+ norm_prefix = object_prefix.replace("\\", "/").lstrip("/")
92
+ if norm_prefix and not norm_prefix.endswith("/"):
93
+ norm_prefix = norm_prefix + "/"
94
+
95
+ prefixes = [object_prefix]
96
+ norm_for_list = object_prefix.replace("\\", "/")
97
+ if norm_for_list != object_prefix:
98
+ prefixes.append(norm_for_list)
99
+
100
+ seen = set()
101
+ for pfx in prefixes:
102
+ for obj in s3_client.list_objects(bucket, pfx, recursive=True):
103
+ key = obj.object_name
104
+ if key in seen:
105
+ continue
106
+ seen.add(key)
107
+
108
+ norm_key = key.replace("\\", "/")
109
+ if norm_key.endswith("/"):
110
+ continue
111
+
112
+ if norm_prefix and not norm_key.startswith(norm_prefix):
113
+ continue
114
+
115
+ rel = norm_key[len(norm_prefix):] if norm_prefix else norm_key
116
+ dest_path = os.path.join(local_dir, *rel.split("/"))
117
+ os.makedirs(os.path.dirname(dest_path), exist_ok=True)
118
+ s3_client.fget_object(bucket, key, dest_path)
119
+
120
+ logger.info(f"Downloaded directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: intellif-aihub
3
- Version: 0.1.22
3
+ Version: 0.1.23
4
4
  Summary: Intellif AI-hub SDK.
5
5
  Author-email: Platform Team <aihub@example.com>
6
6
  License-Expression: Apache-2.0
@@ -18,6 +18,7 @@ Requires-Dist: tqdm<5.0,>=4.66
18
18
  Requires-Dist: loguru>=0.7.3
19
19
  Requires-Dist: minio>=7.2.7
20
20
  Requires-Dist: requests>=2.32.4
21
+ Requires-Dist: typer>=0.12.0
21
22
  Dynamic: license-file
22
23
 
23
24
  # Intellif AI-Hub SDK
@@ -1,6 +1,11 @@
1
- aihub/__init__.py,sha256=zmP2TRnzKPjZJ1eiBcT-cRInsji6FW-OVD3FafQFCc4,23
1
+ aihub/__init__.py,sha256=0byemO6n6WCv41u9vBG2AIsOkVbxLvok7puvwy8EhfU,23
2
2
  aihub/client.py,sha256=Fu3jlEy21T4nJDV5EXTDujy1_B3Pf6CSTyPwkj3PPuE,5574
3
3
  aihub/exceptions.py,sha256=l2cMAvipTqQOio3o11fXsCCSCevbuK4PTsxofkobFjk,500
4
+ aihub/cli/__init__.py,sha256=I6NwAccz4OA13yBkQNVGqdY4by3a9S4Nwc_Nb9myXqM,27
5
+ aihub/cli/__main__.py,sha256=0O3_NGUh4DfaofAxxjmJnyTNSLljwWTclJHQTXrnCco,139
6
+ aihub/cli/config.py,sha256=GRn_SaQFHPK8BuIuyf3PHWmxvr-ZC4vLf8RUAZM5gqQ,3564
7
+ aihub/cli/main.py,sha256=ujEZZuwG2UsQRO5kS2A41EpfaA8G-B41wfUvtcN94ts,9924
8
+ aihub/cli/model_center.py,sha256=CbYP_vkIGIZ_K5k63S_VmSAoSKLyeF1vrKbEg5sXgnA,11974
4
9
  aihub/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
10
  aihub/models/artifact.py,sha256=F-r7DJY9A09yIQJqWol6gLRu6y7NGjRa6-BxkMEluxU,4655
6
11
  aihub/models/common.py,sha256=qmabc2LkAdQJXIcpT1P35zxd0Lc8yDYdD4ame1iF4Bs,241
@@ -9,7 +14,7 @@ aihub/models/dataset_management.py,sha256=4DuQ0zM7jv73SJiqvieHLtn2Y-T6FIFV9r7bgz
9
14
  aihub/models/document_center.py,sha256=od9bzx6krAS6ktIA-ChxeqGcch0v2wsS1flY2vuHXBc,1340
10
15
  aihub/models/eval.py,sha256=eebUv31GJ_gf7-vCYCLknN6utsPZw9G3t6ikZb9gK0I,4865
11
16
  aihub/models/labelfree.py,sha256=YUnUv0tjYSFAFzYtmbnLOha8rnDe32sb50HkPOclAzU,2016
12
- aihub/models/model_center.py,sha256=DNMchrN0pYDcTMHApWNNVMrARF_i9Ng5xlAwHX5isYw,5935
17
+ aihub/models/model_center.py,sha256=q-ga1Khnb-WbA_gbLHbhwqGPuQ2qjpC4ailaaNpoccU,5491
13
18
  aihub/models/model_training_platform.py,sha256=2zir5i-XvuxKKVYr4wuNYUC7nwMzetdtCRoysZ1W_Tc,11725
14
19
  aihub/models/quota_schedule_management.py,sha256=LdXwKkpJd0jUFSHtTHUlFLlH-NUSmgywWtxwFg57CNk,12368
15
20
  aihub/models/tag_resource_management.py,sha256=-FgiKyDIG7bZagzVRf-8rXWuqH9GyciDadxz5W2f3I8,2195
@@ -23,7 +28,7 @@ aihub/services/dataset_management.py,sha256=R7mFsJ1dNOI_p5yNj_rQdLolRC0UKEN4WejE
23
28
  aihub/services/document_center.py,sha256=dG67Ji-DOnzL2t-4x4gVfMt9fbSj_IjVHCLw5R-VTkQ,1813
24
29
  aihub/services/eval.py,sha256=n1ytGzPJz-bwRyHoXaAugh7-u7Dak4aMYWklX3X_Pso,7287
25
30
  aihub/services/labelfree.py,sha256=xua62UWhVXTxJjHRyy86waaormnJjmpQwepcARBy_h0,1450
26
- aihub/services/model_center.py,sha256=QZXlldPZrbC6YkVG3eGw5_53qvFAim2QlXg3DflByTA,6215
31
+ aihub/services/model_center.py,sha256=x42_Xo66IxE6ni8mAMLPF_QBRxBvEU-lXrcF8Er2o-I,11404
27
32
  aihub/services/model_training_platform.py,sha256=38o6HJnyi3htFzpX7qj6UhzdqTchcXLRTYU0nM7ffJg,10176
28
33
  aihub/services/quota_schedule_management.py,sha256=UYOMwjXxJTgkpN6Rv5GzlcejtpZfu23PXlSKr0WihTY,9586
29
34
  aihub/services/reporter.py,sha256=ot93SmhxgwDJOzlHSCwlxDOuSydTWUEUQ-Ctp97wJBQ,669
@@ -35,9 +40,10 @@ aihub/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
40
  aihub/utils/di.py,sha256=vFUzno5WbRKu6-pj8Hnz9IqT7xb9UDZQ4qpOFH1YAtM,11812
36
41
  aihub/utils/download.py,sha256=ZZVbcC-PnN3PumV7ZiJ_-srkt4HPPovu2F6Faa2RrPE,1830
37
42
  aihub/utils/http.py,sha256=AmfHHNjptuuSFx2T1twWCnerR_hLN_gd0lUs8z36ERA,547
38
- aihub/utils/s3.py,sha256=ISIBP-XdBPkURpXnN56ZnIWokOOg2SRUh_qvxJk-G1Q,2187
39
- intellif_aihub-0.1.22.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
40
- intellif_aihub-0.1.22.dist-info/METADATA,sha256=DuDrCBkMMCiEHFQtkQwE1AdvA2JfVJmwWwvUMEMp6HM,2949
41
- intellif_aihub-0.1.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
- intellif_aihub-0.1.22.dist-info/top_level.txt,sha256=vIvTtSIN73xv46BpYM-ctVGnyOiUQ9EWP_6ngvdIlvw,6
43
- intellif_aihub-0.1.22.dist-info/RECORD,,
43
+ aihub/utils/s3.py,sha256=_HFL5QJQqOF8WuEX8RWGPFKYtad_lGn-jsNzTIfXjHM,3977
44
+ intellif_aihub-0.1.23.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
45
+ intellif_aihub-0.1.23.dist-info/METADATA,sha256=p921kZhhTqFeDE6VsN8HpSDer_emgD5-vswwgppOqwo,2978
46
+ intellif_aihub-0.1.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ intellif_aihub-0.1.23.dist-info/entry_points.txt,sha256=PfgnpEJlG76kFmrCdTvfRIRNsZO1Xu1pTEH2S5DSO1M,45
48
+ intellif_aihub-0.1.23.dist-info/top_level.txt,sha256=vIvTtSIN73xv46BpYM-ctVGnyOiUQ9EWP_6ngvdIlvw,6
49
+ intellif_aihub-0.1.23.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aihub = aihub.cli.main:app