intellif-aihub 0.1.3__py3-none-any.whl → 0.1.5__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.
- aihub/__init__.py +1 -1
- aihub/client.py +95 -91
- aihub/exceptions.py +18 -18
- aihub/models/artifact.py +137 -137
- aihub/models/common.py +13 -13
- aihub/models/dataset_management.py +99 -99
- aihub/models/document_center.py +28 -28
- aihub/models/eval.py +17 -0
- aihub/models/labelfree.py +53 -31
- aihub/models/model_training_platform.py +234 -230
- aihub/models/quota_schedule_management.py +239 -0
- aihub/models/tag_resource_management.py +50 -50
- aihub/models/task_center.py +117 -117
- aihub/models/user_system.py +262 -262
- aihub/services/artifact.py +353 -353
- aihub/services/dataset_management.py +240 -240
- aihub/services/document_center.py +43 -43
- aihub/services/eval.py +68 -0
- aihub/services/labelfree.py +44 -44
- aihub/services/model_training_platform.py +209 -135
- aihub/services/quota_schedule_management.py +182 -18
- aihub/services/reporter.py +20 -20
- aihub/services/tag_resource_management.py +55 -55
- aihub/services/task_center.py +190 -190
- aihub/services/user_system.py +339 -339
- aihub/utils/download.py +69 -69
- aihub/utils/http.py +13 -13
- aihub/utils/s3.py +77 -77
- {intellif_aihub-0.1.3.dist-info → intellif_aihub-0.1.5.dist-info}/METADATA +1 -1
- intellif_aihub-0.1.5.dist-info/RECORD +36 -0
- {intellif_aihub-0.1.3.dist-info → intellif_aihub-0.1.5.dist-info}/licenses/LICENSE +200 -200
- intellif_aihub-0.1.3.dist-info/RECORD +0 -34
- {intellif_aihub-0.1.3.dist-info → intellif_aihub-0.1.5.dist-info}/WHEEL +0 -0
- {intellif_aihub-0.1.3.dist-info → intellif_aihub-0.1.5.dist-info}/top_level.txt +0 -0
aihub/utils/download.py
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import concurrent.futures
|
|
4
|
-
import os
|
|
5
|
-
import tempfile
|
|
6
|
-
from typing import List, TypedDict
|
|
7
|
-
|
|
8
|
-
import pyarrow.parquet as pq
|
|
9
|
-
from tqdm import tqdm
|
|
10
|
-
|
|
11
|
-
from .http import http_download_file
|
|
12
|
-
from .s3 import s3_to_url
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class DatasetParquetMeta(TypedDict):
|
|
16
|
-
parent_dir: str
|
|
17
|
-
name: str
|
|
18
|
-
s3path: str
|
|
19
|
-
type: int # 0=file, 1=dir
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
_ENUM_FILE = 0
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _read_parquet_index(file_path: str) -> List[DatasetParquetMeta]:
|
|
26
|
-
table = pq.read_table(file_path)
|
|
27
|
-
return table.to_pylist() # 每行转 dict
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _safe_rel(part: str) -> str:
|
|
31
|
-
if not part:
|
|
32
|
-
return ""
|
|
33
|
-
drive, tail = os.path.splitdrive(part)
|
|
34
|
-
return tail.lstrip(r"\/")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def dataset_download(index_url: str, local_dir: str, worker: int = 4) -> None:
|
|
38
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
39
|
-
tmp_file = os.path.join(tmpdir, "index.parquet")
|
|
40
|
-
http_download_file(index_url, tmp_file)
|
|
41
|
-
rows = _read_parquet_index(tmp_file)
|
|
42
|
-
|
|
43
|
-
host = (index_url.split("//", 1)[-1]).split("/", 1)[0]
|
|
44
|
-
|
|
45
|
-
files = [
|
|
46
|
-
(
|
|
47
|
-
os.path.join(
|
|
48
|
-
local_dir,
|
|
49
|
-
_safe_rel(row["parent_dir"]),
|
|
50
|
-
_safe_rel(row["name"]),
|
|
51
|
-
),
|
|
52
|
-
s3_to_url(row["s3path"], host),
|
|
53
|
-
)
|
|
54
|
-
for row in rows if row["type"] == _ENUM_FILE
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
if worker < 1:
|
|
58
|
-
worker = 1
|
|
59
|
-
|
|
60
|
-
with tqdm(total=len(files), desc="Downloading dataset") as bar, \
|
|
61
|
-
concurrent.futures.ThreadPoolExecutor(max_workers=worker) as pool:
|
|
62
|
-
|
|
63
|
-
def _one(flocal: str, furl: str):
|
|
64
|
-
http_download_file(furl, flocal)
|
|
65
|
-
bar.update()
|
|
66
|
-
|
|
67
|
-
futures = [pool.submit(_one, p, u) for p, u in files]
|
|
68
|
-
for fut in concurrent.futures.as_completed(futures):
|
|
69
|
-
fut.result()
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import concurrent.futures
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from typing import List, TypedDict
|
|
7
|
+
|
|
8
|
+
import pyarrow.parquet as pq
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
|
|
11
|
+
from .http import http_download_file
|
|
12
|
+
from .s3 import s3_to_url
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatasetParquetMeta(TypedDict):
|
|
16
|
+
parent_dir: str
|
|
17
|
+
name: str
|
|
18
|
+
s3path: str
|
|
19
|
+
type: int # 0=file, 1=dir
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_ENUM_FILE = 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _read_parquet_index(file_path: str) -> List[DatasetParquetMeta]:
|
|
26
|
+
table = pq.read_table(file_path)
|
|
27
|
+
return table.to_pylist() # 每行转 dict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _safe_rel(part: str) -> str:
|
|
31
|
+
if not part:
|
|
32
|
+
return ""
|
|
33
|
+
drive, tail = os.path.splitdrive(part)
|
|
34
|
+
return tail.lstrip(r"\/")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def dataset_download(index_url: str, local_dir: str, worker: int = 4) -> None:
|
|
38
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
39
|
+
tmp_file = os.path.join(tmpdir, "index.parquet")
|
|
40
|
+
http_download_file(index_url, tmp_file)
|
|
41
|
+
rows = _read_parquet_index(tmp_file)
|
|
42
|
+
|
|
43
|
+
host = (index_url.split("//", 1)[-1]).split("/", 1)[0]
|
|
44
|
+
|
|
45
|
+
files = [
|
|
46
|
+
(
|
|
47
|
+
os.path.join(
|
|
48
|
+
local_dir,
|
|
49
|
+
_safe_rel(row["parent_dir"]),
|
|
50
|
+
_safe_rel(row["name"]),
|
|
51
|
+
),
|
|
52
|
+
s3_to_url(row["s3path"], host),
|
|
53
|
+
)
|
|
54
|
+
for row in rows if row["type"] == _ENUM_FILE
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
if worker < 1:
|
|
58
|
+
worker = 1
|
|
59
|
+
|
|
60
|
+
with tqdm(total=len(files), desc="Downloading dataset") as bar, \
|
|
61
|
+
concurrent.futures.ThreadPoolExecutor(max_workers=worker) as pool:
|
|
62
|
+
|
|
63
|
+
def _one(flocal: str, furl: str):
|
|
64
|
+
http_download_file(furl, flocal)
|
|
65
|
+
bar.update()
|
|
66
|
+
|
|
67
|
+
futures = [pool.submit(_one, p, u) for p, u in files]
|
|
68
|
+
for fut in concurrent.futures.as_completed(futures):
|
|
69
|
+
fut.result()
|
aihub/utils/http.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import httpx
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def http_download_file(url: str, dst_path: str, chunk: int = 1 << 16) -> None:
|
|
8
|
-
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
|
9
|
-
with httpx.stream("GET", url, follow_redirects=True, timeout=None) as r:
|
|
10
|
-
r.raise_for_status()
|
|
11
|
-
with open(dst_path, "wb") as f:
|
|
12
|
-
for block in r.iter_bytes(chunk):
|
|
13
|
-
f.write(block)
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def http_download_file(url: str, dst_path: str, chunk: int = 1 << 16) -> None:
|
|
8
|
+
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
|
9
|
+
with httpx.stream("GET", url, follow_redirects=True, timeout=None) as r:
|
|
10
|
+
r.raise_for_status()
|
|
11
|
+
with open(dst_path, "wb") as f:
|
|
12
|
+
for block in r.iter_bytes(chunk):
|
|
13
|
+
f.write(block)
|
aihub/utils/s3.py
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import re
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from loguru import logger
|
|
9
|
-
from minio import Minio
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def s3_to_url(s3_path: str, host: str) -> str:
|
|
13
|
-
key = s3_path.replace("s3://", "").lstrip("/")
|
|
14
|
-
return f"http://{host.rstrip('/')}/{key}"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def S3_path_to_info(s3_path) -> tuple[str | Any, str | Any] | None:
|
|
18
|
-
if not s3_path.startswith("s3://"):
|
|
19
|
-
return None
|
|
20
|
-
|
|
21
|
-
pattern = r"s3://(?P<bucket>\w+)/(?P<objectname>.+)"
|
|
22
|
-
|
|
23
|
-
match = re.match(pattern, s3_path)
|
|
24
|
-
|
|
25
|
-
if match:
|
|
26
|
-
bucket = match.group("bucket")
|
|
27
|
-
objectname = match.group("objectname")
|
|
28
|
-
return bucket, objectname
|
|
29
|
-
return None
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def local_path_to_s3_key(work_dir: str, local_path: str) -> str:
|
|
33
|
-
work_dir = Path(work_dir)
|
|
34
|
-
local_path = Path(local_path)
|
|
35
|
-
s3_key = str(local_path.relative_to(work_dir))
|
|
36
|
-
return s3_key
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def upload_dir_to_s3(
|
|
40
|
-
s3_client: Minio, local_dir: str, bucket: str, object_prefix: str
|
|
41
|
-
) -> None:
|
|
42
|
-
logger.info(
|
|
43
|
-
f"Uploading directory {local_dir} to S3 bucket {bucket} with prefix {object_prefix}"
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
for root, dirs, files in os.walk(local_dir):
|
|
47
|
-
for file in files:
|
|
48
|
-
local_path = Path(root) / file
|
|
49
|
-
s3_key = local_path_to_s3_key(local_dir, str(local_path))
|
|
50
|
-
s3_client.fput_object(
|
|
51
|
-
bucket, os.path.join(object_prefix, s3_key), str(local_path)
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
logger.info(
|
|
55
|
-
f"Uploaded directory {local_dir} to S3 bucket {bucket} with prefix {object_prefix}"
|
|
56
|
-
)
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def download_dir_from_s3(
|
|
61
|
-
s3_client: Minio, bucket: str, object_prefix: str, local_dir: str
|
|
62
|
-
) -> None:
|
|
63
|
-
logger.info(
|
|
64
|
-
f"Downloading directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}"
|
|
65
|
-
)
|
|
66
|
-
objs = s3_client.list_objects(bucket, object_prefix, recursive=True)
|
|
67
|
-
|
|
68
|
-
for obj in objs:
|
|
69
|
-
file_name = Path(obj.object_name).relative_to(object_prefix)
|
|
70
|
-
s3_client.fget_object(
|
|
71
|
-
bucket, obj.object_name, os.path.join(local_dir, file_name)
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
logger.info(
|
|
75
|
-
f"Downloaded directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}"
|
|
76
|
-
)
|
|
77
|
-
return
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from loguru import logger
|
|
9
|
+
from minio import Minio
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def s3_to_url(s3_path: str, host: str) -> str:
|
|
13
|
+
key = s3_path.replace("s3://", "").lstrip("/")
|
|
14
|
+
return f"http://{host.rstrip('/')}/{key}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def S3_path_to_info(s3_path) -> tuple[str | Any, str | Any] | None:
|
|
18
|
+
if not s3_path.startswith("s3://"):
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
pattern = r"s3://(?P<bucket>\w+)/(?P<objectname>.+)"
|
|
22
|
+
|
|
23
|
+
match = re.match(pattern, s3_path)
|
|
24
|
+
|
|
25
|
+
if match:
|
|
26
|
+
bucket = match.group("bucket")
|
|
27
|
+
objectname = match.group("objectname")
|
|
28
|
+
return bucket, objectname
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def local_path_to_s3_key(work_dir: str, local_path: str) -> str:
|
|
33
|
+
work_dir = Path(work_dir)
|
|
34
|
+
local_path = Path(local_path)
|
|
35
|
+
s3_key = str(local_path.relative_to(work_dir))
|
|
36
|
+
return s3_key
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def upload_dir_to_s3(
|
|
40
|
+
s3_client: Minio, local_dir: str, bucket: str, object_prefix: str
|
|
41
|
+
) -> None:
|
|
42
|
+
logger.info(
|
|
43
|
+
f"Uploading directory {local_dir} to S3 bucket {bucket} with prefix {object_prefix}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
for root, dirs, files in os.walk(local_dir):
|
|
47
|
+
for file in files:
|
|
48
|
+
local_path = Path(root) / file
|
|
49
|
+
s3_key = local_path_to_s3_key(local_dir, str(local_path))
|
|
50
|
+
s3_client.fput_object(
|
|
51
|
+
bucket, os.path.join(object_prefix, s3_key), str(local_path)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
logger.info(
|
|
55
|
+
f"Uploaded directory {local_dir} to S3 bucket {bucket} with prefix {object_prefix}"
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def download_dir_from_s3(
|
|
61
|
+
s3_client: Minio, bucket: str, object_prefix: str, local_dir: str
|
|
62
|
+
) -> None:
|
|
63
|
+
logger.info(
|
|
64
|
+
f"Downloading directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}"
|
|
65
|
+
)
|
|
66
|
+
objs = s3_client.list_objects(bucket, object_prefix, recursive=True)
|
|
67
|
+
|
|
68
|
+
for obj in objs:
|
|
69
|
+
file_name = Path(obj.object_name).relative_to(object_prefix)
|
|
70
|
+
s3_client.fget_object(
|
|
71
|
+
bucket, obj.object_name, os.path.join(local_dir, file_name)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
logger.info(
|
|
75
|
+
f"Downloaded directory from S3 bucket {bucket} with prefix {object_prefix} to {local_dir}"
|
|
76
|
+
)
|
|
77
|
+
return
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
aihub/__init__.py,sha256=rPSfWgIeq2YWVPyESOAwCBt8vftsTpIkuLAGDEzyRQc,22
|
|
2
|
+
aihub/client.py,sha256=Eu0evMLB_aWyqL0VOFTfNh0XYQb9Ohob4dnaLzVrfNI,3779
|
|
3
|
+
aihub/exceptions.py,sha256=l2cMAvipTqQOio3o11fXsCCSCevbuK4PTsxofkobFjk,500
|
|
4
|
+
aihub/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
aihub/models/artifact.py,sha256=V1rPNn9pUwSP0M7FvLIqR9Q7429JYbTU2tB-hUEJ04w,3533
|
|
6
|
+
aihub/models/common.py,sha256=qmabc2LkAdQJXIcpT1P35zxd0Lc8yDYdD4ame1iF4Bs,241
|
|
7
|
+
aihub/models/dataset_management.py,sha256=lP92aOsZJihg4SEhf1jeITcXp-N8l_YzHYf1l9Zq7-g,3381
|
|
8
|
+
aihub/models/document_center.py,sha256=xmAk_JIY3GjuVDZurMUonmSz3Siy3TAxhj3ewIJ6dUQ,489
|
|
9
|
+
aihub/models/eval.py,sha256=5h25jR4-JeZ-Sbq0ifp6fd1IVrq0I3w-S8YV_BFUDao,326
|
|
10
|
+
aihub/models/labelfree.py,sha256=RgSCWPQ-_XHbCjalaTDQcgGAQXCKObVs38h2kTpRm88,1755
|
|
11
|
+
aihub/models/model_training_platform.py,sha256=IWgGZ60A9qjLxj1srC3dUc6a-i8gLe8D2yASJ3HeNRY,6521
|
|
12
|
+
aihub/models/quota_schedule_management.py,sha256=E_q6x5KaJOyrOPlYqBumwDgKB1uRpfOHSsYgMxqI6MA,6801
|
|
13
|
+
aihub/models/tag_resource_management.py,sha256=httqeAjCypqykkmhJXSjJfL4Qlfqz21y3w2qXEYtT6g,1104
|
|
14
|
+
aihub/models/task_center.py,sha256=0Sbxsi7mwKUsotO5_4EpQ1dE8goYcI9jnPWLJ5YzfpA,4030
|
|
15
|
+
aihub/models/user_system.py,sha256=4hhOw6DJVjqoJKLkhPbRZnzMOITH2s-t40id1d409zE,7433
|
|
16
|
+
aihub/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
aihub/services/artifact.py,sha256=Ye2T78KZFwDLFK7ngvqw712HxSfoNwoNdJ71-wjd4YI,11201
|
|
18
|
+
aihub/services/dataset_management.py,sha256=fqGJATO1kisPfjk7GxKlzh9fU2w42FeeWEAh-WRDJto,8669
|
|
19
|
+
aihub/services/document_center.py,sha256=DClItxWXMMFPnKMR7kyNGohhXP3wowImj-Lm8vzBgNo,1339
|
|
20
|
+
aihub/services/eval.py,sha256=X6NBV6PTsczoeulV2y1ySrr9GB9R5EBaXgjEmiiUiCE,1963
|
|
21
|
+
aihub/services/labelfree.py,sha256=UUuq519us1fma0ERXM-sXfLPWRHB1f6BqE2XvCq-3Pw,1215
|
|
22
|
+
aihub/services/model_training_platform.py,sha256=fsCO4BEuja_1ePl0hwt32kpnN99AArjzBv_e459p0X4,7560
|
|
23
|
+
aihub/services/quota_schedule_management.py,sha256=4MdtEP2Ziz6hh3tXmd6nbLXxtQ46Z_eT2VtjE3Kr-0I,6803
|
|
24
|
+
aihub/services/reporter.py,sha256=ot93SmhxgwDJOzlHSCwlxDOuSydTWUEUQ-Ctp97wJBQ,669
|
|
25
|
+
aihub/services/tag_resource_management.py,sha256=GcuVMlwGlfZkvUKvLUTKtteHNjurlLr2FUy2Eg8cfyE,1811
|
|
26
|
+
aihub/services/task_center.py,sha256=xhyMdmhFjeL9QZ5YZ0eGz6WIgclECitG8Yp_dXO7OEY,6211
|
|
27
|
+
aihub/services/user_system.py,sha256=vEcwvSX28lMp7YBUm1AWRDujEa73NqOtwMQi-t7xPSM,13654
|
|
28
|
+
aihub/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
aihub/utils/download.py,sha256=Rh1m3VpMlw8-Kl36sowJ7M0dpB68u-9V4Vo3GQChq1I,1758
|
|
30
|
+
aihub/utils/http.py,sha256=rSNh4uNP7E3YGm3H1indRHctxC5Wu5xNBPvDrb9UHt4,421
|
|
31
|
+
aihub/utils/s3.py,sha256=ISIBP-XdBPkURpXnN56ZnIWokOOg2SRUh_qvxJk-G1Q,2187
|
|
32
|
+
intellif_aihub-0.1.5.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
33
|
+
intellif_aihub-0.1.5.dist-info/METADATA,sha256=2TgCnrq8SWHiEnohwrD2ZaktHoHDIu2W7Q2Zoy_9yWA,2922
|
|
34
|
+
intellif_aihub-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
intellif_aihub-0.1.5.dist-info/top_level.txt,sha256=vIvTtSIN73xv46BpYM-ctVGnyOiUQ9EWP_6ngvdIlvw,6
|
|
36
|
+
intellif_aihub-0.1.5.dist-info/RECORD,,
|