hfutils 0.1.2__tar.gz → 0.2.1__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.
- {hfutils-0.1.2/hfutils.egg-info → hfutils-0.2.1}/PKG-INFO +11 -2
- {hfutils-0.1.2 → hfutils-0.2.1}/README.md +8 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/config/meta.py +1 -1
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/entry/cli.py +2 -0
- hfutils-0.2.1/hfutils/entry/ls.py +189 -0
- hfutils-0.2.1/hfutils/index/__init__.py +4 -0
- hfutils-0.2.1/hfutils/index/fetch.py +284 -0
- hfutils-0.2.1/hfutils/index/hash.py +22 -0
- hfutils-0.2.1/hfutils/index/make.py +215 -0
- hfutils-0.2.1/hfutils/index/validate.py +111 -0
- {hfutils-0.1.2 → hfutils-0.2.1/hfutils.egg-info}/PKG-INFO +11 -2
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils.egg-info/SOURCES.txt +6 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils.egg-info/requires.txt +2 -1
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements.txt +2 -1
- {hfutils-0.1.2 → hfutils-0.2.1}/LICENSE +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/MANIFEST.in +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/__init__.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/archive/__init__.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/archive/base.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/archive/rar.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/archive/sevenz.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/archive/tar.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/archive/zip.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/config/__init__.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/entry/__init__.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/entry/base.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/entry/dispatch.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/entry/download.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/entry/upload.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/operate/__init__.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/operate/base.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/operate/download.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/operate/upload.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/operate/validate.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/utils/__init__.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/utils/binary.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/utils/download.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/utils/temp.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/utils/tqdm_.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils/utils/walk.py +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils.egg-info/dependency_links.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils.egg-info/entry_points.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/hfutils.egg-info/top_level.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements-7z.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements-build.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements-doc.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements-rar.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements-test.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/requirements-transfer.txt +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/setup.cfg +0 -0
- {hfutils-0.1.2 → hfutils-0.2.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: hfutils
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Useful utilities for huggingface
|
|
5
5
|
Home-page: https://github.com/deepghs/hfutils
|
|
6
6
|
Author: narugo1992
|
|
@@ -21,10 +21,11 @@ Requires-Python: >=3.8
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: hbutils>=0.9.0
|
|
24
|
-
Requires-Dist: huggingface_hub>=0.
|
|
24
|
+
Requires-Dist: huggingface_hub>=0.22
|
|
25
25
|
Requires-Dist: tqdm
|
|
26
26
|
Requires-Dist: requests
|
|
27
27
|
Requires-Dist: click>=7
|
|
28
|
+
Requires-Dist: tzlocal
|
|
28
29
|
Provides-Extra: rar
|
|
29
30
|
Requires-Dist: rarfile; extra == "rar"
|
|
30
31
|
Provides-Extra: test
|
|
@@ -229,6 +230,14 @@ hfutils download -h
|
|
|
229
230
|
|
|
230
231
|
```
|
|
231
232
|
|
|
233
|
+
## List Files in Repository
|
|
234
|
+
|
|
235
|
+
List files in repositories
|
|
236
|
+
|
|
237
|
+
```shell
|
|
238
|
+
hfutils ls -r your/repository -o /your/local/file -d subdir/in/repo
|
|
239
|
+
```
|
|
240
|
+
|
|
232
241
|
## Supported Formats
|
|
233
242
|
|
|
234
243
|
By default, we support the `zip` and `tar` formats, including `.zip`, `.tar`, `.tar.gz`, `.tar.bz2`, and `.tar.xz`.
|
|
@@ -156,6 +156,14 @@ hfutils download -h
|
|
|
156
156
|
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
## List Files in Repository
|
|
160
|
+
|
|
161
|
+
List files in repositories
|
|
162
|
+
|
|
163
|
+
```shell
|
|
164
|
+
hfutils ls -r your/repository -o /your/local/file -d subdir/in/repo
|
|
165
|
+
```
|
|
166
|
+
|
|
159
167
|
## Supported Formats
|
|
160
168
|
|
|
161
169
|
By default, we support the `zip` and `tar` formats, including `.zip`, `.tar`, `.tar.gz`, `.tar.bz2`, and `.tar.xz`.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from .dispatch import hfutilcli
|
|
2
2
|
from .download import _add_download_subcommand
|
|
3
|
+
from .ls import _add_ls_subcommand
|
|
3
4
|
from .upload import _add_upload_subcommand
|
|
4
5
|
|
|
5
6
|
_DECORATORS = [
|
|
6
7
|
_add_download_subcommand,
|
|
7
8
|
_add_upload_subcommand,
|
|
9
|
+
_add_ls_subcommand,
|
|
8
10
|
]
|
|
9
11
|
|
|
10
12
|
cli = hfutilcli
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import mimetypes
|
|
2
|
+
import os.path
|
|
3
|
+
from enum import Enum, unique
|
|
4
|
+
from typing import Union, List
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import tzlocal
|
|
8
|
+
from huggingface_hub.hf_api import RepoFolder, RepoFile
|
|
9
|
+
|
|
10
|
+
from .base import CONTEXT_SETTINGS
|
|
11
|
+
from ..operate.base import REPO_TYPES, get_hf_client
|
|
12
|
+
|
|
13
|
+
mimetypes.add_type('image/webp', '.webp')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@unique
|
|
17
|
+
class ListItemType(Enum):
|
|
18
|
+
"""
|
|
19
|
+
Enum class representing different types of list items.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
FILE = 0x1
|
|
23
|
+
FOLDER = 0x2
|
|
24
|
+
IMAGE = 0x3
|
|
25
|
+
ARCHIVE = 0x4
|
|
26
|
+
MODEL = 0x5
|
|
27
|
+
DATA = 0x6
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def render_color(self):
|
|
31
|
+
"""
|
|
32
|
+
Get the render color based on the item type.
|
|
33
|
+
|
|
34
|
+
:return: The render color for the item type.
|
|
35
|
+
:rtype: str
|
|
36
|
+
"""
|
|
37
|
+
if self == self.FILE:
|
|
38
|
+
return None
|
|
39
|
+
elif self == self.FOLDER:
|
|
40
|
+
return 'blue'
|
|
41
|
+
elif self == self.IMAGE:
|
|
42
|
+
return 'magenta'
|
|
43
|
+
elif self == self.ARCHIVE:
|
|
44
|
+
return 'red'
|
|
45
|
+
elif self == self.MODEL:
|
|
46
|
+
return 'green'
|
|
47
|
+
elif self == self.DATA:
|
|
48
|
+
return 'yellow'
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f'Unknown type - {self!r}') # pragma: no cover
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ListItem:
|
|
54
|
+
"""
|
|
55
|
+
Class representing a list item.
|
|
56
|
+
|
|
57
|
+
:param item: The item object.
|
|
58
|
+
:type item: Union[RepoFolder, RepoFile]
|
|
59
|
+
:param base_dir: The base directory.
|
|
60
|
+
:type base_dir: str
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, item: Union[RepoFolder, RepoFile], base_dir: str):
|
|
64
|
+
self.item = item
|
|
65
|
+
self.base_dir = base_dir
|
|
66
|
+
if isinstance(item, RepoFolder):
|
|
67
|
+
self.type = ListItemType.FOLDER
|
|
68
|
+
else:
|
|
69
|
+
mimetype, _ = mimetypes.guess_type(item.path)
|
|
70
|
+
_, ext = os.path.splitext(item.path)
|
|
71
|
+
self.type = ListItemType.FILE
|
|
72
|
+
if ext in {'.ckpt', '.pt', '.safetensors', '.onnx', '.model', '.h5', '.mlmodel',
|
|
73
|
+
'.ftz', '.pb', '.pth', '.tflite'}:
|
|
74
|
+
self.type = ListItemType.MODEL
|
|
75
|
+
elif ext in {'.json', '.csv', '.tsv', '.arrow', '.bin', '.msgpack', '.npy', '.npz',
|
|
76
|
+
'.parquet', '.pickle', '.pkl', '.wasm'}:
|
|
77
|
+
self.type = ListItemType.DATA
|
|
78
|
+
elif mimetype:
|
|
79
|
+
if 'image' in mimetype:
|
|
80
|
+
self.type = ListItemType.IMAGE
|
|
81
|
+
elif mimetype.split('/', maxsplit=1)[-1] in {'zip', 'rar', 'x-tar', 'x-7z-compressed'}:
|
|
82
|
+
self.type = ListItemType.ARCHIVE
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _add_ls_subcommand(cli: click.Group) -> click.Group:
|
|
86
|
+
"""
|
|
87
|
+
Add the 'ls' subcommand to the CLI.
|
|
88
|
+
|
|
89
|
+
:param cli: The Click CLI application.
|
|
90
|
+
:type cli: click.Group
|
|
91
|
+
:return: The modified Click CLI application.
|
|
92
|
+
:rtype: click.Group
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
@cli.command('ls', help='List files from HuggingFace repository.\n\n'
|
|
96
|
+
'Set environment $HF_TOKEN to use your own access token.',
|
|
97
|
+
context_settings=CONTEXT_SETTINGS)
|
|
98
|
+
@click.option('-r', '--repository', 'repo_id', type=str, required=True,
|
|
99
|
+
help='Repository to download from.')
|
|
100
|
+
@click.option('-t', '--type', 'repo_type', type=click.Choice(REPO_TYPES), default='dataset',
|
|
101
|
+
help='Type of the HuggingFace repository.', show_default=True)
|
|
102
|
+
@click.option('-d', '--directory', 'dir_in_repo', type=str, default=None,
|
|
103
|
+
help='Directory in repository to download the full directory tree.')
|
|
104
|
+
@click.option('-R', '--revision', 'revision', type=str, default='main',
|
|
105
|
+
help='Revision of repository.', show_default=True)
|
|
106
|
+
@click.option('-a', '--all', 'show_all', is_flag=True, type=bool, default=False,
|
|
107
|
+
help='Show all files, including hidden files.', show_default=True)
|
|
108
|
+
@click.option('-l', '--list', 'show_detailed', is_flag=True, type=bool, default=False,
|
|
109
|
+
help='Show detailed file information.', show_default=True)
|
|
110
|
+
def ls(repo_id: str, repo_type: str, dir_in_repo, revision: str, show_all: bool, show_detailed: bool):
|
|
111
|
+
"""
|
|
112
|
+
List files from a HuggingFace repository.
|
|
113
|
+
|
|
114
|
+
:param repo_id: The identifier of the repository.
|
|
115
|
+
:type repo_id: str
|
|
116
|
+
:param repo_type: The type of the HuggingFace repository.
|
|
117
|
+
:type repo_type: str
|
|
118
|
+
:param dir_in_repo: The directory in the repository to list files from.
|
|
119
|
+
:type dir_in_repo: str
|
|
120
|
+
:param revision: The revision of the repository.
|
|
121
|
+
:type revision: str
|
|
122
|
+
:param show_all: Flag to indicate whether to show all files, including hidden files.
|
|
123
|
+
:type show_all: bool
|
|
124
|
+
:param show_detailed: Flag to indicate whether to show detailed file information.
|
|
125
|
+
:type show_detailed: bool
|
|
126
|
+
"""
|
|
127
|
+
hf_client = get_hf_client()
|
|
128
|
+
items: List[ListItem] = []
|
|
129
|
+
for item in hf_client.list_repo_tree(
|
|
130
|
+
repo_id=repo_id,
|
|
131
|
+
repo_type=repo_type,
|
|
132
|
+
path_in_repo=dir_in_repo,
|
|
133
|
+
revision=revision,
|
|
134
|
+
recursive=False,
|
|
135
|
+
expand=show_detailed,
|
|
136
|
+
):
|
|
137
|
+
relpath = os.path.relpath(item.path, dir_in_repo)
|
|
138
|
+
if not show_all and relpath.startswith('.'):
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
item = ListItem(item, dir_in_repo)
|
|
142
|
+
items.append(item)
|
|
143
|
+
if not show_detailed:
|
|
144
|
+
print(click.style(
|
|
145
|
+
os.path.relpath(item.item.path, item.base_dir),
|
|
146
|
+
fg=item.type.render_color
|
|
147
|
+
))
|
|
148
|
+
|
|
149
|
+
if show_detailed:
|
|
150
|
+
max_size_length = 0
|
|
151
|
+
max_commit_info_length = 0
|
|
152
|
+
for item in items:
|
|
153
|
+
if item.type == ListItemType.FOLDER:
|
|
154
|
+
size_text = '-'
|
|
155
|
+
else:
|
|
156
|
+
size_text = str(item.item.size)
|
|
157
|
+
max_size_length = max(max_size_length, len(size_text))
|
|
158
|
+
|
|
159
|
+
commit_text = (item.item.last_commit.title or '').splitlines(keepends=False)[0]
|
|
160
|
+
max_commit_info_length = max(max_commit_info_length, len(commit_text))
|
|
161
|
+
|
|
162
|
+
for item in items:
|
|
163
|
+
print('d' if item.type == ListItemType.FOLDER else '-', end='')
|
|
164
|
+
print('L' if item.type != ListItemType.FOLDER and item.item.lfs else '-', end='')
|
|
165
|
+
|
|
166
|
+
commit_text = (item.item.last_commit.title or '').splitlines(keepends=False)[0]
|
|
167
|
+
print(
|
|
168
|
+
' ' + ' ' * (max_commit_info_length - len(commit_text)) +
|
|
169
|
+
click.style(commit_text, underline=True),
|
|
170
|
+
end=''
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if item.type == ListItemType.FOLDER:
|
|
174
|
+
size_text = '-'
|
|
175
|
+
else:
|
|
176
|
+
size_text = str(item.item.size)
|
|
177
|
+
size_text = ' ' * (max_size_length - len(size_text)) + size_text
|
|
178
|
+
print(' ' + size_text, end='')
|
|
179
|
+
|
|
180
|
+
print(' ' + item.item.last_commit.oid[:8], end='')
|
|
181
|
+
commit_time = item.item.last_commit.date.astimezone(tzlocal.get_localzone())
|
|
182
|
+
print(' ' + commit_time.strftime('%Y-%m-%d %H:%M %Z'), end='')
|
|
183
|
+
|
|
184
|
+
print(' ' + click.style(
|
|
185
|
+
os.path.relpath(item.item.path, item.base_dir),
|
|
186
|
+
fg=item.type.render_color
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
return cli
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from .fetch import hf_tar_list_files, hf_tar_file_download, hf_tar_get_index, hf_tar_file_exists, \
|
|
2
|
+
ArchiveStandaloneFileIncompleteDownload, ArchiveStandaloneFileHashNotMatch
|
|
3
|
+
from .make import tar_create_index, hf_tar_create_index, tar_get_index_info, hf_tar_create_from_directory
|
|
4
|
+
from .validate import hf_tar_item_validate, hf_tar_validate
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os.path
|
|
3
|
+
from typing import Optional, Dict, Union
|
|
4
|
+
|
|
5
|
+
from huggingface_hub.file_download import http_get, hf_hub_url
|
|
6
|
+
from huggingface_hub.utils import build_hf_headers
|
|
7
|
+
|
|
8
|
+
from .hash import _f_sha256
|
|
9
|
+
from ..operate.base import RepoTypeTyping, get_hf_client
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ArchiveStandaloneFileIncompleteDownload(Exception):
|
|
13
|
+
"""
|
|
14
|
+
Exception raised when a standalone file in an archive is incompletely downloaded.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ArchiveStandaloneFileHashNotMatch(Exception):
|
|
19
|
+
"""
|
|
20
|
+
Exception raised when the hash of a standalone file in an archive does not match.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def hf_tar_get_index(repo_id: str, archive_in_repo: str,
|
|
25
|
+
repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
26
|
+
idx_repo_id: Optional[str] = None, idx_file_in_repo: Optional[str] = None,
|
|
27
|
+
idx_repo_type: Optional[RepoTypeTyping] = None, idx_revision: Optional[str] = None,
|
|
28
|
+
hf_token: Optional[str] = None):
|
|
29
|
+
"""
|
|
30
|
+
Get the index of a tar archive file in a Hugging Face repository.
|
|
31
|
+
|
|
32
|
+
:param repo_id: The identifier of the repository.
|
|
33
|
+
:type repo_id: str
|
|
34
|
+
:param archive_in_repo: The path to the archive file in the repository.
|
|
35
|
+
:type archive_in_repo: str
|
|
36
|
+
:param repo_type: The type of the Hugging Face repository.
|
|
37
|
+
:type repo_type: RepoTypeTyping, optional
|
|
38
|
+
:param revision: The revision of the repository.
|
|
39
|
+
:type revision: str, optional
|
|
40
|
+
:param idx_repo_id: The identifier of the index repository.
|
|
41
|
+
:type idx_repo_id: str, optional
|
|
42
|
+
:param idx_file_in_repo: The path to the index file in the index repository.
|
|
43
|
+
:type idx_file_in_repo: str, optional
|
|
44
|
+
:param idx_repo_type: The type of the index repository.
|
|
45
|
+
:type idx_repo_type: RepoTypeTyping, optional
|
|
46
|
+
:param idx_revision: The revision of the index repository.
|
|
47
|
+
:type idx_revision: str, optional
|
|
48
|
+
:param hf_token: The Hugging Face access token.
|
|
49
|
+
:type hf_token: str, optional
|
|
50
|
+
:return: The index of the tar archive file.
|
|
51
|
+
:rtype: Dict
|
|
52
|
+
"""
|
|
53
|
+
hf_client = get_hf_client(hf_token)
|
|
54
|
+
body, _ = os.path.splitext(archive_in_repo)
|
|
55
|
+
default_index_file = f'{body}.json'
|
|
56
|
+
with open(hf_client.hf_hub_download(
|
|
57
|
+
repo_id=idx_repo_id or repo_id,
|
|
58
|
+
repo_type=idx_repo_type or repo_type,
|
|
59
|
+
filename=idx_file_in_repo or default_index_file,
|
|
60
|
+
revision=idx_revision or revision,
|
|
61
|
+
), 'r') as f:
|
|
62
|
+
return json.load(f)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def hf_tar_list_files(repo_id: str, archive_in_repo: str,
|
|
66
|
+
repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
67
|
+
idx_repo_id: Optional[str] = None, idx_file_in_repo: Optional[str] = None,
|
|
68
|
+
idx_repo_type: Optional[RepoTypeTyping] = None, idx_revision: Optional[str] = None,
|
|
69
|
+
hf_token: Optional[str] = None):
|
|
70
|
+
"""
|
|
71
|
+
List files inside a tar archive file in a Hugging Face repository.
|
|
72
|
+
|
|
73
|
+
:param repo_id: The identifier of the repository.
|
|
74
|
+
:type repo_id: str
|
|
75
|
+
:param archive_in_repo: The path to the archive file in the repository.
|
|
76
|
+
:type archive_in_repo: str
|
|
77
|
+
:param repo_type: The type of the Hugging Face repository.
|
|
78
|
+
:type repo_type: RepoTypeTyping, optional
|
|
79
|
+
:param revision: The revision of the repository.
|
|
80
|
+
:type revision: str, optional
|
|
81
|
+
:param idx_repo_id: The identifier of the index repository.
|
|
82
|
+
:type idx_repo_id: str, optional
|
|
83
|
+
:param idx_file_in_repo: The path to the index file in the index repository.
|
|
84
|
+
:type idx_file_in_repo: str, optional
|
|
85
|
+
:param idx_repo_type: The type of the index repository.
|
|
86
|
+
:type idx_repo_type: RepoTypeTyping, optional
|
|
87
|
+
:param idx_revision: The revision of the index repository.
|
|
88
|
+
:type idx_revision: str, optional
|
|
89
|
+
:param hf_token: The Hugging Face access token.
|
|
90
|
+
:type hf_token: str, optional
|
|
91
|
+
:return: The list of files inside the tar archive.
|
|
92
|
+
:rtype: List[str]
|
|
93
|
+
"""
|
|
94
|
+
index_data = hf_tar_get_index(
|
|
95
|
+
repo_id=repo_id,
|
|
96
|
+
archive_in_repo=archive_in_repo,
|
|
97
|
+
repo_type=repo_type,
|
|
98
|
+
revision=revision,
|
|
99
|
+
|
|
100
|
+
idx_repo_id=idx_repo_id,
|
|
101
|
+
idx_file_in_repo=idx_file_in_repo,
|
|
102
|
+
idx_repo_type=idx_repo_type,
|
|
103
|
+
idx_revision=idx_revision,
|
|
104
|
+
|
|
105
|
+
hf_token=hf_token,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return list(index_data['files'].keys())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def hf_tar_file_exists(repo_id: str, archive_in_repo: str, file_in_archive: str,
|
|
112
|
+
repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
113
|
+
idx_repo_id: Optional[str] = None, idx_file_in_repo: Optional[str] = None,
|
|
114
|
+
idx_repo_type: Optional[RepoTypeTyping] = None, idx_revision: Optional[str] = None,
|
|
115
|
+
hf_token: Optional[str] = None):
|
|
116
|
+
"""
|
|
117
|
+
Check if a file exists inside a tar archive file in a Hugging Face repository.
|
|
118
|
+
|
|
119
|
+
:param repo_id: The identifier of the repository.
|
|
120
|
+
:type repo_id: str
|
|
121
|
+
:param archive_in_repo: The path to the archive file in the repository.
|
|
122
|
+
:type archive_in_repo: str
|
|
123
|
+
:param file_in_archive: The path to the file inside the archive.
|
|
124
|
+
:type file_in_archive: str
|
|
125
|
+
:param repo_type: The type of the Hugging Face repository.
|
|
126
|
+
:type repo_type: RepoTypeTyping, optional
|
|
127
|
+
:param revision: The revision of the repository.
|
|
128
|
+
:type revision: str, optional
|
|
129
|
+
:param idx_repo_id: The identifier of the index repository.
|
|
130
|
+
:type idx_repo_id: str, optional
|
|
131
|
+
:param idx_file_in_repo: The path to the index file in the index repository.
|
|
132
|
+
:type idx_file_in_repo: str, optional
|
|
133
|
+
:param idx_repo_type: The type of the index repository.
|
|
134
|
+
:type idx_repo_type: RepoTypeTyping, optional
|
|
135
|
+
:param idx_revision: The revision of the index repository.
|
|
136
|
+
:type idx_revision: str, optional
|
|
137
|
+
:param hf_token: The Hugging Face access token.
|
|
138
|
+
:type hf_token: str, optional
|
|
139
|
+
:return: True if the file exists, False otherwise.
|
|
140
|
+
:rtype: bool
|
|
141
|
+
"""
|
|
142
|
+
index = hf_tar_get_index(
|
|
143
|
+
repo_id=repo_id,
|
|
144
|
+
archive_in_repo=archive_in_repo,
|
|
145
|
+
repo_type=repo_type,
|
|
146
|
+
revision=revision,
|
|
147
|
+
|
|
148
|
+
idx_repo_id=idx_repo_id,
|
|
149
|
+
idx_file_in_repo=idx_file_in_repo,
|
|
150
|
+
idx_repo_type=idx_repo_type,
|
|
151
|
+
idx_revision=idx_revision,
|
|
152
|
+
|
|
153
|
+
hf_token=hf_token,
|
|
154
|
+
)
|
|
155
|
+
files = _hf_files_process(index['files'])
|
|
156
|
+
return _n_path(file_in_archive) in files
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _n_path(path):
|
|
160
|
+
"""
|
|
161
|
+
Normalize a file path.
|
|
162
|
+
|
|
163
|
+
:param path: The file path to normalize.
|
|
164
|
+
:type path: str
|
|
165
|
+
:return: The normalized file path.
|
|
166
|
+
:rtype: str
|
|
167
|
+
"""
|
|
168
|
+
return os.path.normpath(os.path.join('/', path))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _hf_files_process(files: Dict[str, dict]):
|
|
172
|
+
"""
|
|
173
|
+
Normalize file paths in a dictionary of files.
|
|
174
|
+
|
|
175
|
+
:param files: The dictionary of files.
|
|
176
|
+
:type files: Dict[str, dict]
|
|
177
|
+
:return: The dictionary of files with normalized paths.
|
|
178
|
+
:rtype: Dict[str, dict]
|
|
179
|
+
"""
|
|
180
|
+
return {_n_path(key): value for key, value in files.items()}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def hf_tar_file_download(repo_id: str, archive_in_repo: str, file_in_archive: str, local_file: str,
|
|
184
|
+
repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
185
|
+
idx_repo_id: Optional[str] = None, idx_file_in_repo: Optional[str] = None,
|
|
186
|
+
idx_repo_type: Optional[RepoTypeTyping] = None, idx_revision: Optional[str] = None,
|
|
187
|
+
proxies: Optional[Dict] = None, user_agent: Union[Dict, str, None] = None,
|
|
188
|
+
headers: Optional[Dict[str, str]] = None, endpoint: Optional[str] = None,
|
|
189
|
+
hf_token: Optional[str] = None):
|
|
190
|
+
"""
|
|
191
|
+
Download a file from a tar archive file in a Hugging Face repository.
|
|
192
|
+
|
|
193
|
+
:param repo_id: The identifier of the repository.
|
|
194
|
+
:type repo_id: str
|
|
195
|
+
:param archive_in_repo: The path to the archive file in the repository.
|
|
196
|
+
:type archive_in_repo: str
|
|
197
|
+
:param file_in_archive: The path to the file inside the archive.
|
|
198
|
+
:type file_in_archive: str
|
|
199
|
+
:param local_file: The path to save the downloaded file locally.
|
|
200
|
+
:type local_file: str
|
|
201
|
+
:param repo_type: The type of the Hugging Face repository.
|
|
202
|
+
:type repo_type: RepoTypeTyping, optional
|
|
203
|
+
:param revision: The revision of the repository.
|
|
204
|
+
:type revision: str, optional
|
|
205
|
+
:param idx_repo_id: The identifier of the index repository.
|
|
206
|
+
:type idx_repo_id: str, optional
|
|
207
|
+
:param idx_file_in_repo: The path to the index file in the index repository.
|
|
208
|
+
:type idx_file_in_repo: str, optional
|
|
209
|
+
:param idx_repo_type: The type of the index repository.
|
|
210
|
+
:type idx_repo_type: RepoTypeTyping, optional
|
|
211
|
+
:param idx_revision: The revision of the index repository.
|
|
212
|
+
:type idx_revision: str, optional
|
|
213
|
+
:param proxies: The proxies to be used for the HTTP request.
|
|
214
|
+
:type proxies: Dict, optional
|
|
215
|
+
:param user_agent: The user agent for the HTTP request.
|
|
216
|
+
:type user_agent: Union[Dict, str, None], optional
|
|
217
|
+
:param headers: The additional headers for the HTTP request.
|
|
218
|
+
:type headers: Dict[str, str], optional
|
|
219
|
+
:param endpoint: The Hugging Face API endpoint.
|
|
220
|
+
:type endpoint: str, optional
|
|
221
|
+
:param hf_token: The Hugging Face access token.
|
|
222
|
+
:type hf_token: str, optional
|
|
223
|
+
"""
|
|
224
|
+
index = hf_tar_get_index(
|
|
225
|
+
repo_id=repo_id,
|
|
226
|
+
archive_in_repo=archive_in_repo,
|
|
227
|
+
repo_type=repo_type,
|
|
228
|
+
revision=revision,
|
|
229
|
+
|
|
230
|
+
idx_repo_id=idx_repo_id,
|
|
231
|
+
idx_file_in_repo=idx_file_in_repo,
|
|
232
|
+
idx_repo_type=idx_repo_type,
|
|
233
|
+
idx_revision=idx_revision,
|
|
234
|
+
|
|
235
|
+
hf_token=hf_token,
|
|
236
|
+
)
|
|
237
|
+
files = _hf_files_process(index['files'])
|
|
238
|
+
if _n_path(file_in_archive) not in files:
|
|
239
|
+
raise FileNotFoundError(f'File {file_in_archive!r} not found '
|
|
240
|
+
f'in {repo_type}s/{repo_id}@{revision}/{archive_in_repo}.')
|
|
241
|
+
|
|
242
|
+
info = files[_n_path(file_in_archive)]
|
|
243
|
+
url_to_download = hf_hub_url(repo_id, archive_in_repo, repo_type=repo_type, revision=revision, endpoint=endpoint)
|
|
244
|
+
headers = build_hf_headers(
|
|
245
|
+
token=hf_token,
|
|
246
|
+
library_name=None,
|
|
247
|
+
library_version=None,
|
|
248
|
+
user_agent=user_agent,
|
|
249
|
+
headers=headers,
|
|
250
|
+
)
|
|
251
|
+
start_bytes = info['offset']
|
|
252
|
+
end_bytes = info['offset'] + info['size'] - 1
|
|
253
|
+
headers['Range'] = f'bytes={start_bytes}-{end_bytes}'
|
|
254
|
+
|
|
255
|
+
if os.path.dirname(local_file):
|
|
256
|
+
os.makedirs(os.path.dirname(local_file), exist_ok=True)
|
|
257
|
+
try:
|
|
258
|
+
with open(local_file, 'wb') as f:
|
|
259
|
+
http_get(
|
|
260
|
+
url_to_download,
|
|
261
|
+
f,
|
|
262
|
+
proxies=proxies,
|
|
263
|
+
resume_size=0,
|
|
264
|
+
headers=headers,
|
|
265
|
+
expected_size=info['size'],
|
|
266
|
+
displayed_filename=file_in_archive,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if os.path.getsize(local_file) != info['size']:
|
|
270
|
+
raise ArchiveStandaloneFileIncompleteDownload(
|
|
271
|
+
f'Expected size is {info["size"]}, but actually {os.path.getsize(local_file)} downloaded.'
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if info.get('sha256'):
|
|
275
|
+
_sha256 = _f_sha256(local_file)
|
|
276
|
+
if _sha256 != info['sha256']:
|
|
277
|
+
raise ArchiveStandaloneFileHashNotMatch(
|
|
278
|
+
f'Expected hash is {info["sha256"]!r}, but actually {_sha256!r} found.'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
except Exception:
|
|
282
|
+
if os.path.exists(local_file):
|
|
283
|
+
os.remove(local_file)
|
|
284
|
+
raise
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from hashlib import sha256
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _f_sha256(file, chunk_for_hash: int = 1 << 20):
|
|
5
|
+
"""
|
|
6
|
+
Calculate the SHA-256 hash of a file.
|
|
7
|
+
|
|
8
|
+
:param file: The path to the file.
|
|
9
|
+
:type file: str
|
|
10
|
+
:param chunk_for_hash: The chunk size for hashing, defaults to 1 << 20 (1 MB).
|
|
11
|
+
:type chunk_for_hash: int, optional
|
|
12
|
+
:return: The SHA-256 hash of the file.
|
|
13
|
+
:rtype: str
|
|
14
|
+
"""
|
|
15
|
+
file_sha = sha256()
|
|
16
|
+
with open(file, 'rb') as f:
|
|
17
|
+
while True:
|
|
18
|
+
data = f.read(chunk_for_hash)
|
|
19
|
+
if not data:
|
|
20
|
+
break
|
|
21
|
+
file_sha.update(data)
|
|
22
|
+
return file_sha.hexdigest()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import tarfile
|
|
5
|
+
from hashlib import sha256, sha1
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .hash import _f_sha256
|
|
9
|
+
from ..archive import archive_pack
|
|
10
|
+
from ..operate import download_file_to_file, upload_file_to_file, upload_directory_as_directory
|
|
11
|
+
from ..operate.base import RepoTypeTyping
|
|
12
|
+
from ..utils import TemporaryDirectory, tqdm
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def tar_get_index_info(src_tar_file, chunk_for_hash: int = 1 << 20, with_hash: bool = True, silent: bool = False):
|
|
16
|
+
"""
|
|
17
|
+
Get the index information of a tar archive file.
|
|
18
|
+
|
|
19
|
+
:param src_tar_file: The path to the source tar archive file.
|
|
20
|
+
:type src_tar_file: str
|
|
21
|
+
:param chunk_for_hash: The chunk size for hashing, defaults to 1 << 20 (1 MB).
|
|
22
|
+
:type chunk_for_hash: int, optional
|
|
23
|
+
:param with_hash: Whether to include file hashes in the index, defaults to True.
|
|
24
|
+
:type with_hash: bool, optional
|
|
25
|
+
:param silent: Whether to suppress progress bars and logging messages, defaults to False.
|
|
26
|
+
:type silent: bool, optional
|
|
27
|
+
:return: The index information of the tar archive file.
|
|
28
|
+
:rtype: dict
|
|
29
|
+
"""
|
|
30
|
+
filesize = os.path.getsize(src_tar_file)
|
|
31
|
+
sha_common = sha1()
|
|
32
|
+
sha_common.update(f'blob {filesize}\0'.encode('utf-8'))
|
|
33
|
+
sha_lfs = sha256()
|
|
34
|
+
logging.info(f'Calculating hash of {src_tar_file!r} ...')
|
|
35
|
+
with open(src_tar_file, 'rb') as f:
|
|
36
|
+
# make sure the big files will not cause OOM
|
|
37
|
+
while True:
|
|
38
|
+
data = f.read(chunk_for_hash)
|
|
39
|
+
if not data:
|
|
40
|
+
break
|
|
41
|
+
sha_common.update(data)
|
|
42
|
+
sha_lfs.update(data)
|
|
43
|
+
|
|
44
|
+
logging.info(f'Indexing tar file {src_tar_file!r} ...')
|
|
45
|
+
files = {}
|
|
46
|
+
with tarfile.open(src_tar_file, mode='r|') as tar:
|
|
47
|
+
for tarinfo in tqdm(tar, desc='Indexing tar file ...', silent=silent):
|
|
48
|
+
tarinfo: tarfile.TarInfo
|
|
49
|
+
if tarinfo.isreg():
|
|
50
|
+
info = {
|
|
51
|
+
'offset': tarinfo.offset_data,
|
|
52
|
+
'size': tarinfo.size,
|
|
53
|
+
}
|
|
54
|
+
if with_hash:
|
|
55
|
+
with TemporaryDirectory() as td:
|
|
56
|
+
tar.extract(tarinfo, td)
|
|
57
|
+
dst_file = os.path.join(td, tarinfo.name)
|
|
58
|
+
info['sha256'] = _f_sha256(dst_file)
|
|
59
|
+
|
|
60
|
+
files[tarinfo.name] = info
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
'filesize': filesize,
|
|
64
|
+
'hash': sha_common.hexdigest(),
|
|
65
|
+
'hash_lfs': sha_lfs.hexdigest(),
|
|
66
|
+
'files': files,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def tar_create_index(src_tar_file, dst_index_file: Optional[str] = None,
|
|
71
|
+
chunk_for_hash: int = 1 << 20, with_hash: bool = True, silent: bool = False):
|
|
72
|
+
"""
|
|
73
|
+
Create an index file for a tar archive file.
|
|
74
|
+
|
|
75
|
+
:param src_tar_file: The path to the source tar archive file.
|
|
76
|
+
:type src_tar_file: str
|
|
77
|
+
:param dst_index_file: The path to save the index file, defaults to None.
|
|
78
|
+
:type dst_index_file: str, optional
|
|
79
|
+
:param chunk_for_hash: The chunk size for hashing, defaults to 1 << 20 (1 MB).
|
|
80
|
+
:type chunk_for_hash: int, optional
|
|
81
|
+
:param with_hash: Whether to include file hashes in the index, defaults to True.
|
|
82
|
+
:type with_hash: bool, optional
|
|
83
|
+
:param silent: Whether to suppress progress bars and logging messages, defaults to False.
|
|
84
|
+
:type silent: bool, optional
|
|
85
|
+
:return: The path to the created index file.
|
|
86
|
+
:rtype: str
|
|
87
|
+
"""
|
|
88
|
+
body, _ = os.path.splitext(src_tar_file)
|
|
89
|
+
dst_index_file = dst_index_file or f'{body}.json'
|
|
90
|
+
with open(dst_index_file, 'w') as f:
|
|
91
|
+
json.dump(tar_get_index_info(src_tar_file, chunk_for_hash, with_hash, silent), f)
|
|
92
|
+
return dst_index_file
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def hf_tar_create_index(repo_id: str, archive_in_repo: str,
|
|
96
|
+
repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
97
|
+
idx_repo_id: Optional[str] = None, idx_file_in_repo: Optional[str] = None,
|
|
98
|
+
idx_repo_type: Optional[RepoTypeTyping] = None, idx_revision: Optional[str] = None,
|
|
99
|
+
chunk_for_hash: int = 1 << 20, with_hash: bool = True, skip_when_synced: bool = True,
|
|
100
|
+
hf_token: Optional[str] = None, ):
|
|
101
|
+
"""
|
|
102
|
+
Create an index file for a tar archive file in a Hugging Face repository.
|
|
103
|
+
|
|
104
|
+
:param repo_id: The identifier of the repository.
|
|
105
|
+
:type repo_id: str
|
|
106
|
+
:param archive_in_repo: The path to the tar archive file.
|
|
107
|
+
:type archive_in_repo: str
|
|
108
|
+
:param repo_type: The type of the Hugging Face repository, defaults to 'dataset'.
|
|
109
|
+
:type repo_type: RepoTypeTyping, optional
|
|
110
|
+
:param revision: The revision of the repository, defaults to 'main'.
|
|
111
|
+
:type revision: str, optional
|
|
112
|
+
:param idx_repo_id: The identifier of the index repository, defaults to None.
|
|
113
|
+
:type idx_repo_id: str, optional
|
|
114
|
+
:param idx_file_in_repo: The path to save the index file in the index repository, defaults to None.
|
|
115
|
+
:type idx_file_in_repo: str, optional
|
|
116
|
+
:param idx_repo_type: The type of the index repository, defaults to None.
|
|
117
|
+
:type idx_repo_type: RepoTypeTyping, optional
|
|
118
|
+
:param idx_revision: The revision of the index repository, defaults to None.
|
|
119
|
+
:type idx_revision: str, optional
|
|
120
|
+
:param chunk_for_hash: The chunk size for hashing, defaults to 1 << 20 (1 MB).
|
|
121
|
+
:type chunk_for_hash: int, optional
|
|
122
|
+
:param with_hash: Whether to include file hashes in the index, defaults to True.
|
|
123
|
+
:type with_hash: bool, optional
|
|
124
|
+
:param skip_when_synced: Skip syncing when index is ready, defaults to True.
|
|
125
|
+
:type skip_when_synced: bool
|
|
126
|
+
:param hf_token: The Hugging Face access token, defaults to None.
|
|
127
|
+
:type hf_token: str, optional
|
|
128
|
+
"""
|
|
129
|
+
body, _ = os.path.splitext(archive_in_repo)
|
|
130
|
+
default_index_filename = f'{body}.json'
|
|
131
|
+
|
|
132
|
+
from .validate import hf_tar_validate
|
|
133
|
+
if skip_when_synced and hf_tar_validate(
|
|
134
|
+
repo_id=repo_id,
|
|
135
|
+
repo_type=repo_type,
|
|
136
|
+
archive_in_repo=archive_in_repo,
|
|
137
|
+
revision=revision,
|
|
138
|
+
|
|
139
|
+
idx_repo_id=idx_repo_id or repo_id,
|
|
140
|
+
idx_repo_type=idx_repo_type or repo_type,
|
|
141
|
+
idx_file_in_repo=idx_file_in_repo or default_index_filename,
|
|
142
|
+
idx_revision=idx_revision or revision,
|
|
143
|
+
|
|
144
|
+
hf_token=hf_token,
|
|
145
|
+
):
|
|
146
|
+
logging.info(f'Entry {repo_type}s/{repo_id}/{archive_in_repo} already indexed, skipped.')
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
with TemporaryDirectory() as td:
|
|
150
|
+
local_tar_file = os.path.join(td, os.path.basename(archive_in_repo))
|
|
151
|
+
download_file_to_file(
|
|
152
|
+
repo_id=repo_id,
|
|
153
|
+
repo_type=repo_type,
|
|
154
|
+
file_in_repo=archive_in_repo,
|
|
155
|
+
local_file=local_tar_file,
|
|
156
|
+
revision=revision,
|
|
157
|
+
hf_token=hf_token,
|
|
158
|
+
)
|
|
159
|
+
dst_index_file = tar_create_index(local_tar_file, chunk_for_hash=chunk_for_hash, with_hash=with_hash)
|
|
160
|
+
|
|
161
|
+
upload_file_to_file(
|
|
162
|
+
repo_id=idx_repo_id or repo_id,
|
|
163
|
+
repo_type=idx_repo_type or repo_type,
|
|
164
|
+
file_in_repo=idx_file_in_repo or default_index_filename,
|
|
165
|
+
local_file=dst_index_file,
|
|
166
|
+
revision=idx_revision or revision,
|
|
167
|
+
hf_token=hf_token,
|
|
168
|
+
message=f'Create index for {repo_type}s/{repo_id}@{revision}/{archive_in_repo}',
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def hf_tar_create_from_directory(
|
|
173
|
+
repo_id: str, archive_in_repo: str, local_directory: str,
|
|
174
|
+
repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
175
|
+
chunk_for_hash: int = 1 << 20, with_hash: bool = True,
|
|
176
|
+
silent: bool = False, hf_token: Optional[str] = None):
|
|
177
|
+
"""
|
|
178
|
+
Create a tar archive file from a local directory and upload it to a Hugging Face repository.
|
|
179
|
+
|
|
180
|
+
:param repo_id: The identifier of the repository.
|
|
181
|
+
:type repo_id: str
|
|
182
|
+
:param archive_in_repo: The path to save the tar archive file in the repository.
|
|
183
|
+
:type archive_in_repo: str
|
|
184
|
+
:param local_directory: The path to the local directory to be archived.
|
|
185
|
+
:type local_directory: str
|
|
186
|
+
:param repo_type: The type of the Hugging Face repository, defaults to 'dataset'.
|
|
187
|
+
:type repo_type: RepoTypeTyping, optional
|
|
188
|
+
:param revision: The revision of the repository, defaults to 'main'.
|
|
189
|
+
:type revision: str, optional
|
|
190
|
+
:param chunk_for_hash: The chunk size for hashing, defaults to 1 << 20 (1 MB).
|
|
191
|
+
:type chunk_for_hash: int, optional
|
|
192
|
+
:param with_hash: Whether to include file hashes in the index, defaults to True.
|
|
193
|
+
:type with_hash: bool, optional
|
|
194
|
+
:param silent: Whether to suppress progress bars and logging messages, defaults to False.
|
|
195
|
+
:type silent: bool, optional
|
|
196
|
+
:param hf_token: The Hugging Face access token, defaults to None.
|
|
197
|
+
:type hf_token: str, optional
|
|
198
|
+
"""
|
|
199
|
+
_, ext = os.path.splitext(archive_in_repo)
|
|
200
|
+
with TemporaryDirectory() as td:
|
|
201
|
+
local_tar_file = os.path.join(td, archive_in_repo)
|
|
202
|
+
if os.path.dirname(local_tar_file):
|
|
203
|
+
os.makedirs(os.path.dirname(local_tar_file), exist_ok=True)
|
|
204
|
+
archive_pack('tar', local_directory, local_tar_file, silent=silent)
|
|
205
|
+
tar_create_index(local_tar_file, chunk_for_hash=chunk_for_hash, with_hash=with_hash, silent=silent)
|
|
206
|
+
|
|
207
|
+
upload_directory_as_directory(
|
|
208
|
+
repo_id=repo_id,
|
|
209
|
+
repo_type=repo_type,
|
|
210
|
+
path_in_repo='.',
|
|
211
|
+
local_directory=td,
|
|
212
|
+
revision=revision,
|
|
213
|
+
hf_token=hf_token,
|
|
214
|
+
message=f'Create indexed tar {repo_type}s/{repo_id}@{revision}/{archive_in_repo}'
|
|
215
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from huggingface_hub.hf_api import RepoFile
|
|
4
|
+
from huggingface_hub.utils import EntryNotFoundError, RepositoryNotFoundError
|
|
5
|
+
|
|
6
|
+
from .fetch import hf_tar_get_index
|
|
7
|
+
from ..operate.base import RepoTypeTyping, get_hf_client
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def hf_tar_item_validate(file_item: RepoFile, size: int, hash_: Optional[str] = None, hash_lfs: Optional[str] = None):
|
|
11
|
+
"""
|
|
12
|
+
Validate a file item in a tar archive.
|
|
13
|
+
|
|
14
|
+
This function checks if the file item matches the expected size and hash.
|
|
15
|
+
|
|
16
|
+
:param file_item: The file item from the Hugging Face repository.
|
|
17
|
+
:type file_item: RepoFile
|
|
18
|
+
:param size: The expected size of the file.
|
|
19
|
+
:type size: int
|
|
20
|
+
:param hash_: The expected SHA-1 hash of the file.
|
|
21
|
+
:type hash_: str, optional
|
|
22
|
+
:param hash_lfs: The expected SHA-256 hash of the file if stored in LFS.
|
|
23
|
+
:type hash_lfs: str, optional
|
|
24
|
+
:return: True if the file item is valid, False otherwise.
|
|
25
|
+
:rtype: bool
|
|
26
|
+
"""
|
|
27
|
+
# size not match
|
|
28
|
+
if (file_item.lfs and size != file_item.lfs.size) or \
|
|
29
|
+
(not file_item.lfs and size != file_item.size):
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
# compare tar file hash
|
|
33
|
+
item_hashes = [file_item.blob_id]
|
|
34
|
+
if file_item.lfs:
|
|
35
|
+
item_hashes.append(file_item.lfs.sha256)
|
|
36
|
+
item_hashes = set(filter(bool, item_hashes))
|
|
37
|
+
cmp_hashes = [hash_, hash_lfs]
|
|
38
|
+
cmp_hashes = set(filter(bool, cmp_hashes))
|
|
39
|
+
return bool(cmp_hashes & item_hashes)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def hf_tar_validate(repo_id: str, archive_in_repo: str, repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
|
|
43
|
+
idx_repo_id: Optional[str] = None, idx_file_in_repo: Optional[str] = None,
|
|
44
|
+
idx_repo_type: Optional[RepoTypeTyping] = None, idx_revision: Optional[str] = None,
|
|
45
|
+
hf_token: Optional[str] = None):
|
|
46
|
+
"""
|
|
47
|
+
Validate a tar archive in a Hugging Face repository.
|
|
48
|
+
|
|
49
|
+
This function validates if the tar archive in the Hugging Face repository matches the expected size and hash.
|
|
50
|
+
|
|
51
|
+
:param repo_id: The ID of the Hugging Face repository.
|
|
52
|
+
:type repo_id: str
|
|
53
|
+
:param archive_in_repo: The path to the tar archive in the repository.
|
|
54
|
+
:type archive_in_repo: str
|
|
55
|
+
:param repo_type: The type of the Hugging Face repository, defaults to 'dataset'.
|
|
56
|
+
:type repo_type: RepoTypeTyping, optional
|
|
57
|
+
:param revision: The revision of the repository, defaults to 'main'.
|
|
58
|
+
:type revision: str, optional
|
|
59
|
+
:param idx_repo_id: The ID of the repository where the index file is stored.
|
|
60
|
+
:type idx_repo_id: Optional[str], optional
|
|
61
|
+
:param idx_file_in_repo: The path to the index file in the repository.
|
|
62
|
+
:type idx_file_in_repo: Optional[str], optional
|
|
63
|
+
:param idx_repo_type: The type of the repository where the index file is stored.
|
|
64
|
+
:type idx_repo_type: Optional[RepoTypeTyping], optional
|
|
65
|
+
:param idx_revision: The revision of the repository where the index file is stored.
|
|
66
|
+
:type idx_revision: Optional[str], optional
|
|
67
|
+
:param hf_token: The Hugging Face token for authentication, defaults to None.
|
|
68
|
+
:type hf_token: Optional[str], optional
|
|
69
|
+
:raises EntryNotFoundError: If the specified entry is not found in the repository.
|
|
70
|
+
:raises IsADirectoryError: If the specified entry is a directory.
|
|
71
|
+
:return: True if the tar archive is valid, False otherwise.
|
|
72
|
+
:rtype: bool
|
|
73
|
+
"""
|
|
74
|
+
hf_client = get_hf_client(hf_token)
|
|
75
|
+
|
|
76
|
+
items = list(hf_client.get_paths_info(
|
|
77
|
+
repo_id=repo_id,
|
|
78
|
+
repo_type=repo_type,
|
|
79
|
+
paths=[archive_in_repo],
|
|
80
|
+
revision=revision,
|
|
81
|
+
))
|
|
82
|
+
if len(items) == 0:
|
|
83
|
+
raise EntryNotFoundError(f'Entry {repo_type}s/{repo_id}/{archive_in_repo} not found.')
|
|
84
|
+
elif not isinstance(items[0], RepoFile):
|
|
85
|
+
raise IsADirectoryError(f'Entry {repo_type}s/{repo_id}/{archive_in_repo} is a directory, not a file.')
|
|
86
|
+
else:
|
|
87
|
+
item = items[0]
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
index = hf_tar_get_index(
|
|
91
|
+
repo_id=repo_id,
|
|
92
|
+
archive_in_repo=archive_in_repo,
|
|
93
|
+
repo_type=repo_type,
|
|
94
|
+
revision=revision,
|
|
95
|
+
|
|
96
|
+
idx_repo_id=idx_repo_id,
|
|
97
|
+
idx_file_in_repo=idx_file_in_repo,
|
|
98
|
+
idx_repo_type=idx_repo_type,
|
|
99
|
+
idx_revision=idx_revision,
|
|
100
|
+
|
|
101
|
+
hf_token=hf_token,
|
|
102
|
+
)
|
|
103
|
+
except (EntryNotFoundError, RepositoryNotFoundError):
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
return hf_tar_item_validate(
|
|
107
|
+
file_item=item,
|
|
108
|
+
size=index['filesize'],
|
|
109
|
+
hash_=index.get('hash'),
|
|
110
|
+
hash_lfs=index.get('hash_lfs'),
|
|
111
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: hfutils
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Useful utilities for huggingface
|
|
5
5
|
Home-page: https://github.com/deepghs/hfutils
|
|
6
6
|
Author: narugo1992
|
|
@@ -21,10 +21,11 @@ Requires-Python: >=3.8
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: hbutils>=0.9.0
|
|
24
|
-
Requires-Dist: huggingface_hub>=0.
|
|
24
|
+
Requires-Dist: huggingface_hub>=0.22
|
|
25
25
|
Requires-Dist: tqdm
|
|
26
26
|
Requires-Dist: requests
|
|
27
27
|
Requires-Dist: click>=7
|
|
28
|
+
Requires-Dist: tzlocal
|
|
28
29
|
Provides-Extra: rar
|
|
29
30
|
Requires-Dist: rarfile; extra == "rar"
|
|
30
31
|
Provides-Extra: test
|
|
@@ -229,6 +230,14 @@ hfutils download -h
|
|
|
229
230
|
|
|
230
231
|
```
|
|
231
232
|
|
|
233
|
+
## List Files in Repository
|
|
234
|
+
|
|
235
|
+
List files in repositories
|
|
236
|
+
|
|
237
|
+
```shell
|
|
238
|
+
hfutils ls -r your/repository -o /your/local/file -d subdir/in/repo
|
|
239
|
+
```
|
|
240
|
+
|
|
232
241
|
## Supported Formats
|
|
233
242
|
|
|
234
243
|
By default, we support the `zip` and `tar` formats, including `.zip`, `.tar`, `.tar.gz`, `.tar.bz2`, and `.tar.xz`.
|
|
@@ -29,7 +29,13 @@ hfutils/entry/base.py
|
|
|
29
29
|
hfutils/entry/cli.py
|
|
30
30
|
hfutils/entry/dispatch.py
|
|
31
31
|
hfutils/entry/download.py
|
|
32
|
+
hfutils/entry/ls.py
|
|
32
33
|
hfutils/entry/upload.py
|
|
34
|
+
hfutils/index/__init__.py
|
|
35
|
+
hfutils/index/fetch.py
|
|
36
|
+
hfutils/index/hash.py
|
|
37
|
+
hfutils/index/make.py
|
|
38
|
+
hfutils/index/validate.py
|
|
33
39
|
hfutils/operate/__init__.py
|
|
34
40
|
hfutils/operate/base.py
|
|
35
41
|
hfutils/operate/download.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|