hfutils 0.2.11__tar.gz → 0.3.0__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.
Files changed (66) hide show
  1. {hfutils-0.2.11/hfutils.egg-info → hfutils-0.3.0}/PKG-INFO +3 -2
  2. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/config/meta.py +1 -1
  3. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/cli.py +4 -0
  4. hfutils-0.3.0/hfutils/entry/clone.py +79 -0
  5. hfutils-0.3.0/hfutils/entry/rollback.py +61 -0
  6. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/operate/upload.py +86 -13
  7. hfutils-0.3.0/hfutils/repository/__init__.py +7 -0
  8. hfutils-0.3.0/hfutils/repository/base.py +81 -0
  9. hfutils-0.3.0/hfutils/repository/clone.py +87 -0
  10. hfutils-0.3.0/hfutils/repository/rollback.py +102 -0
  11. {hfutils-0.2.11 → hfutils-0.3.0/hfutils.egg-info}/PKG-INFO +3 -2
  12. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils.egg-info/SOURCES.txt +6 -0
  13. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils.egg-info/requires.txt +2 -1
  14. {hfutils-0.2.11 → hfutils-0.3.0}/requirements.txt +3 -2
  15. {hfutils-0.2.11 → hfutils-0.3.0}/LICENSE +0 -0
  16. {hfutils-0.2.11 → hfutils-0.3.0}/MANIFEST.in +0 -0
  17. {hfutils-0.2.11 → hfutils-0.3.0}/README.md +0 -0
  18. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/__init__.py +0 -0
  19. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/__main__.py +0 -0
  20. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/archive/__init__.py +0 -0
  21. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/archive/base.py +0 -0
  22. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/archive/rar.py +0 -0
  23. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/archive/sevenz.py +0 -0
  24. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/archive/tar.py +0 -0
  25. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/archive/zip.py +0 -0
  26. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/cache/__init__.py +0 -0
  27. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/cache/delete.py +0 -0
  28. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/config/__init__.py +0 -0
  29. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/__init__.py +0 -0
  30. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/base.py +0 -0
  31. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/dispatch.py +0 -0
  32. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/download.py +0 -0
  33. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/index.py +0 -0
  34. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/ls.py +0 -0
  35. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/ls_repo.py +0 -0
  36. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/upload.py +0 -0
  37. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/entry/whoami.py +0 -0
  38. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/index/__init__.py +0 -0
  39. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/index/fetch.py +0 -0
  40. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/index/hash.py +0 -0
  41. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/index/make.py +0 -0
  42. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/index/validate.py +0 -0
  43. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/operate/__init__.py +0 -0
  44. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/operate/base.py +0 -0
  45. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/operate/download.py +0 -0
  46. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/operate/validate.py +0 -0
  47. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/__init__.py +0 -0
  48. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/binary.py +0 -0
  49. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/download.py +0 -0
  50. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/irregular_repo.json +0 -0
  51. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/logging.py +0 -0
  52. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/path.py +0 -0
  53. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/temp.py +0 -0
  54. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/tqdm_.py +0 -0
  55. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils/utils/walk.py +0 -0
  56. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils.egg-info/dependency_links.txt +0 -0
  57. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils.egg-info/entry_points.txt +0 -0
  58. {hfutils-0.2.11 → hfutils-0.3.0}/hfutils.egg-info/top_level.txt +0 -0
  59. {hfutils-0.2.11 → hfutils-0.3.0}/requirements-7z.txt +0 -0
  60. {hfutils-0.2.11 → hfutils-0.3.0}/requirements-build.txt +0 -0
  61. {hfutils-0.2.11 → hfutils-0.3.0}/requirements-doc.txt +0 -0
  62. {hfutils-0.2.11 → hfutils-0.3.0}/requirements-rar.txt +0 -0
  63. {hfutils-0.2.11 → hfutils-0.3.0}/requirements-test.txt +0 -0
  64. {hfutils-0.2.11 → hfutils-0.3.0}/requirements-transfer.txt +0 -0
  65. {hfutils-0.2.11 → hfutils-0.3.0}/setup.cfg +0 -0
  66. {hfutils-0.2.11 → hfutils-0.3.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hfutils
3
- Version: 0.2.11
3
+ Version: 0.3.0
4
4
  Summary: Useful utilities for huggingface
5
5
  Home-page: https://github.com/deepghs/hfutils
6
6
  Author: narugo1992
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.8
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: hbutils>=0.9.3
23
+ Requires-Dist: hbutils>=0.10.0
24
24
  Requires-Dist: huggingface_hub>=0.23
25
25
  Requires-Dist: tqdm
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: click>=7
28
28
  Requires-Dist: tzlocal
29
29
  Requires-Dist: natsort
30
+ Requires-Dist: urlobject
30
31
  Provides-Extra: doc
31
32
  Requires-Dist: Jinja2>=3.0.0; extra == "doc"
32
33
  Requires-Dist: sphinx>=3.2.0; extra == "doc"
@@ -7,7 +7,7 @@ Overview:
7
7
  __TITLE__ = 'hfutils'
8
8
 
9
9
  #: Version of this project.
10
- __VERSION__ = '0.2.11'
10
+ __VERSION__ = '0.3.0'
11
11
 
12
12
  #: Short description of the project, will be included in ``setup.py``.
13
13
  __DESCRIPTION__ = 'Useful utilities for huggingface'
@@ -1,8 +1,10 @@
1
+ from .clone import _add_clone_subcommand
1
2
  from .dispatch import hfutilcli
2
3
  from .download import _add_download_subcommand
3
4
  from .index import _add_index_subcommand
4
5
  from .ls import _add_ls_subcommand
5
6
  from .ls_repo import _add_ls_repo_subcommand
7
+ from .rollback import _add_rollback_subcommand
6
8
  from .upload import _add_upload_subcommand
7
9
  from .whoami import _add_whoami_subcommand
8
10
 
@@ -13,6 +15,8 @@ _DECORATORS = [
13
15
  _add_whoami_subcommand,
14
16
  _add_ls_repo_subcommand,
15
17
  _add_index_subcommand,
18
+ _add_rollback_subcommand,
19
+ _add_clone_subcommand,
16
20
  ]
17
21
 
18
22
  cli = hfutilcli
@@ -0,0 +1,79 @@
1
+ import logging
2
+ import os.path
3
+ import re
4
+ from typing import Optional
5
+
6
+ import click
7
+
8
+ from .base import CONTEXT_SETTINGS
9
+ from ..operate.base import REPO_TYPES, RepoTypeTyping
10
+ from ..repository import hf_hub_clone
11
+ from ..utils import ColoredFormatter
12
+
13
+
14
+ def _add_clone_subcommand(cli: click.Group) -> click.Group:
15
+ """
16
+ Add a 'clone' subcommand to the given CLI group.
17
+
18
+ This function defines a 'clone' command that allows users to clone repositories
19
+ from the Hugging Face Hub.
20
+
21
+ :param cli: The Click group to which the 'clone' command will be added.
22
+ :type cli: click.Group
23
+ :return: The modified Click group with the 'clone' command added.
24
+ :rtype: click.Group
25
+ """
26
+
27
+ @cli.command('clone', help='Clone remote repository with permission (if possible).\n\n'
28
+ 'Set environment $HF_TOKEN to use your own access token.',
29
+ context_settings=CONTEXT_SETTINGS)
30
+ @click.option('-r', '--repository', 'repo_id', type=str, required=True,
31
+ help='Repository to clone.')
32
+ @click.option('-t', '--type', 'repo_type', type=click.Choice(REPO_TYPES), default='dataset',
33
+ help='Type of the HuggingFace repository.', show_default=True)
34
+ @click.option('-R', '--revision', 'revision', type=str, default='main',
35
+ help='Revision of repository.', show_default=True)
36
+ @click.option('-o', '--output_dir', 'output_dir', type=str, default=None,
37
+ help='Output directory to clone the repository')
38
+ @click.option('--no_lfs', 'no_lfs', type=bool, is_flag=True, default=False,
39
+ help='Do not download LFS files, just download their pointers.')
40
+ @click.option('-d', '--max_depth', 'max_depth', type=int, default=None,
41
+ help='Max commit depth to clone. Useful when this repository has large numbers of commits.')
42
+ def clone(repo_id: str, repo_type: RepoTypeTyping, revision: str, output_dir: Optional[str] = None,
43
+ no_lfs: bool = False, max_depth: Optional[int] = None):
44
+ """
45
+ Clone a repository from the Hugging Face Hub.
46
+
47
+ This function sets up logging and then calls hf_hub_clone to perform the actual cloning.
48
+
49
+ :param repo_id: The ID of the repository to clone.
50
+ :param repo_type: The type of the repository (e.g., 'dataset', 'model').
51
+ :param revision: The revision of the repository to clone.
52
+ :param output_dir: The directory where the repository will be cloned. If None, uses the repo's short name.
53
+ :param no_lfs: If True, don't download LFS files, just their pointers.
54
+ :param max_depth: The maximum depth of commits to clone.
55
+ """
56
+ # Set up logging
57
+ logger = logging.getLogger()
58
+ logger.setLevel(logging.INFO)
59
+ console_handler = logging.StreamHandler()
60
+ console_handler.setFormatter(ColoredFormatter())
61
+ logger.addHandler(console_handler)
62
+
63
+ # Determine the output directory
64
+ short_name = list(filter(bool, re.split(r'[/\\]+', repo_id)))[-1]
65
+ output_dir = os.path.abspath(output_dir or short_name)
66
+ if os.path.dirname(output_dir):
67
+ os.makedirs(os.path.dirname(output_dir), exist_ok=True)
68
+
69
+ # Perform the clone operation
70
+ hf_hub_clone(
71
+ repo_id=repo_id,
72
+ repo_type=repo_type,
73
+ revision=revision,
74
+ dst_dir=output_dir,
75
+ no_lfs=no_lfs,
76
+ max_depth=max_depth,
77
+ )
78
+
79
+ return cli
@@ -0,0 +1,61 @@
1
+ import logging
2
+
3
+ import click
4
+
5
+ from .base import CONTEXT_SETTINGS
6
+ from ..operate.base import REPO_TYPES, RepoTypeTyping
7
+ from ..repository import hf_hub_rollback
8
+ from ..utils import ColoredFormatter
9
+
10
+
11
+ def _add_rollback_subcommand(cli: click.Group) -> click.Group:
12
+ """
13
+ Add a 'rollback' subcommand to the given CLI group.
14
+
15
+ This function defines a 'rollback' command that allows users to rollback
16
+ repositories on the Hugging Face Hub to a specific commit.
17
+
18
+ :param cli: The Click group to which the 'rollback' command will be added.
19
+ :type cli: click.Group
20
+ :return: The modified Click group with the 'rollback' command added.
21
+ :rtype: click.Group
22
+ """
23
+
24
+ @cli.command('rollback', help='Rollback remote repository to a specific commit.\n\n'
25
+ 'Set environment $HF_TOKEN to use your own access token.',
26
+ context_settings=CONTEXT_SETTINGS)
27
+ @click.option('-r', '--repository', 'repo_id', type=str, required=True,
28
+ help='Repository to rollback.')
29
+ @click.option('-t', '--type', 'repo_type', type=click.Choice(REPO_TYPES), default='dataset',
30
+ help='Type of the HuggingFace repository.', show_default=True)
31
+ @click.option('-R', '--revision', 'revision', type=str, default='main',
32
+ help='Revision of repository.', show_default=True)
33
+ @click.option('-c', '--commit_id', 'rollback_to_commit_id', type=str, required=True,
34
+ help='Rollback back to this commit id.')
35
+ def rollback(repo_id: str, repo_type: RepoTypeTyping, revision: str, rollback_to_commit_id: str):
36
+ """
37
+ Rollback a repository on the Hugging Face Hub to a specific commit.
38
+
39
+ This function sets up logging and then calls hf_hub_rollback to perform the actual rollback.
40
+
41
+ :param repo_id: The ID of the repository to rollback.
42
+ :param repo_type: The type of the repository (e.g., 'dataset', 'model').
43
+ :param revision: The revision of the repository to rollback.
44
+ :param rollback_to_commit_id: The commit ID to rollback to.
45
+ """
46
+ # Set up logging
47
+ logger = logging.getLogger()
48
+ logger.setLevel(logging.INFO)
49
+ console_handler = logging.StreamHandler()
50
+ console_handler.setFormatter(ColoredFormatter())
51
+ logger.addHandler(console_handler)
52
+
53
+ # Perform the rollback operation
54
+ hf_hub_rollback(
55
+ repo_id=repo_id,
56
+ repo_type=repo_type,
57
+ revision=revision,
58
+ rollback_to=rollback_to_commit_id,
59
+ )
60
+
61
+ return cli
@@ -1,13 +1,17 @@
1
1
  import datetime
2
+ import logging
3
+ import math
2
4
  import os.path
3
5
  import re
6
+ import time
4
7
  from typing import Optional, List
5
8
 
9
+ from hbutils.string import plural_word
6
10
  from huggingface_hub import CommitOperationAdd, CommitOperationDelete
7
11
 
8
12
  from .base import RepoTypeTyping, get_hf_client, list_files_in_repository, _IGNORE_PATTERN_UNSET
9
13
  from ..archive import get_archive_type, archive_pack
10
- from ..utils import walk_files, TemporaryDirectory
14
+ from ..utils import walk_files, TemporaryDirectory, tqdm
11
15
 
12
16
 
13
17
  def upload_file_to_file(local_file, repo_id: str, file_in_repo: str,
@@ -77,11 +81,14 @@ def upload_directory_as_archive(local_directory, repo_id: str, archive_in_repo:
77
81
  _PATH_SEP = re.compile(r'[/\\]+')
78
82
 
79
83
 
80
- def upload_directory_as_directory(local_directory, repo_id: str, path_in_repo: str,
81
- repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
82
- message: Optional[str] = None, time_suffix: bool = True,
83
- clear: bool = False, ignore_patterns: List[str] = _IGNORE_PATTERN_UNSET,
84
- hf_token: Optional[str] = None):
84
+ def upload_directory_as_directory(
85
+ local_directory, repo_id: str, path_in_repo: str,
86
+ repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
87
+ message: Optional[str] = None, time_suffix: bool = True,
88
+ clear: bool = False, ignore_patterns: List[str] = _IGNORE_PATTERN_UNSET,
89
+ hf_token: Optional[str] = None, operation_chunk_size: Optional[int] = None,
90
+ upload_timespan: float = 5.0,
91
+ ):
85
92
  """
86
93
  Upload a local directory and its files to a specified path in a Hugging Face repository.
87
94
 
@@ -105,6 +112,24 @@ def upload_directory_as_directory(local_directory, repo_id: str, path_in_repo: s
105
112
  :type ignore_patterns: List[str]
106
113
  :param hf_token: Huggingface token for API client, use ``HF_TOKEN`` variable if not assigned.
107
114
  :type hf_token: str, optional
115
+ :param operation_chunk_size: Chunk size of the operations. All the operations will be
116
+ seperated into multiple commits when this is set.
117
+ :type operation_chunk_size: Optional[int]
118
+ :param upload_timespan: Upload minimal time interval when chunked uploading enabled.
119
+ :type upload_timespan: float
120
+
121
+ .. note::
122
+ When `operation_chunk_size` is set, multiple commits will be created. When some commits failed,
123
+ it will roll back to the startup commit, using :func:`hfutils.repository.hf_hub_rollback` function..
124
+
125
+ .. warning::
126
+ When `operation_chunk_size` is set, multiple commits will be created. But HuggingFace's repository
127
+ api cannot guarantee the atomic feature of your data. So **this function is not thread-safe**.
128
+
129
+ .. note::
130
+ The rate limit of HuggingFace repository commit creation is approximately 120 commits / hour.
131
+ So if you really have large number of chunks to create, please set the `upload_timespan` to a value
132
+ no less than `30.0` to make sure your uploading will not be rate-limited.
108
133
  """
109
134
  hf_client = get_hf_client(hf_token)
110
135
  if clear:
@@ -135,10 +160,58 @@ def upload_directory_as_directory(local_directory, repo_id: str, path_in_repo: s
135
160
  if time_suffix:
136
161
  commit_message = f'{commit_message}, on {current_time}'
137
162
 
138
- hf_client.create_commit(
139
- repo_id=repo_id,
140
- repo_type=repo_type,
141
- revision=revision,
142
- operations=operations,
143
- commit_message=commit_message,
144
- )
163
+ if operation_chunk_size:
164
+ initial_commit_id = hf_client.list_repo_commits(
165
+ repo_id=repo_id,
166
+ repo_type=repo_type,
167
+ revision=revision
168
+ )[0].commit_id
169
+
170
+ last_upload_at = None
171
+ try:
172
+ total_chunks = int(math.ceil(len(operations) / operation_chunk_size))
173
+ for chunk_id in tqdm(range(total_chunks), desc='Chunked Commits'):
174
+ operation_chunk = operations[chunk_id * operation_chunk_size:(chunk_id + 1) * operation_chunk_size]
175
+
176
+ # sleep for the given time
177
+ if last_upload_at:
178
+ sleep_time = last_upload_at + upload_timespan - time.time()
179
+ if sleep_time > 0:
180
+ logging.info(f'Sleep for {sleep_time:.1f}s due to the timespan limitation ...')
181
+ time.sleep(sleep_time)
182
+
183
+ last_upload_at = time.time()
184
+ logging.info(f'Uploading chunk #{chunk_id + 1}, '
185
+ f'with {plural_word(len(operation_chunk), "operation")} ...')
186
+ hf_client.create_commit(
187
+ repo_id=repo_id,
188
+ repo_type=repo_type,
189
+ revision=revision,
190
+ operations=operation_chunk,
191
+ commit_message=f'[Chunk #{chunk_id + 1}/{total_chunks}] {commit_message}',
192
+ )
193
+
194
+
195
+ except Exception:
196
+ from ..repository import hf_hub_rollback
197
+
198
+ logging.error(f'Error found when executing chunked uploading, '
199
+ f'revision {revision!r} will rollback to {initial_commit_id!r} ...')
200
+ hf_hub_rollback(
201
+ repo_id=repo_id,
202
+ repo_type=repo_type,
203
+ revision=revision,
204
+ rollback_to=initial_commit_id,
205
+ hf_token=hf_token,
206
+ )
207
+
208
+ raise
209
+
210
+ else:
211
+ hf_client.create_commit(
212
+ repo_id=repo_id,
213
+ repo_type=repo_type,
214
+ revision=revision,
215
+ operations=operations,
216
+ commit_message=commit_message,
217
+ )
@@ -0,0 +1,7 @@
1
+ """
2
+ Overview:
3
+ Some git-based operations.
4
+ """
5
+ from .base import hf_hub_repo_url
6
+ from .clone import hf_hub_clone
7
+ from .rollback import hf_hub_rollback
@@ -0,0 +1,81 @@
1
+ """
2
+ This module provides utility functions for working with Hugging Face Hub repositories and Git.
3
+
4
+ It includes functions to:
5
+
6
+ 1. Generate Hugging Face Hub repository URLs.
7
+ 2. Check Git and Git LFS installations.
8
+
9
+ The module uses the `git_info` function from the `hbutils.system` package and constants from `huggingface_hub`.
10
+ """
11
+
12
+ from typing import Optional
13
+
14
+ from hbutils.system import git_info
15
+ from huggingface_hub.constants import ENDPOINT
16
+
17
+ from ..utils.path import RepoTypeTyping
18
+
19
+
20
+ def hf_hub_repo_url(repo_id: str, repo_type: RepoTypeTyping = 'dataset', endpoint: Optional[str] = None) -> str:
21
+ """
22
+ Generate a Hugging Face Hub repository URL.
23
+
24
+ :param repo_id: The repository ID.
25
+ :type repo_id: str
26
+ :param repo_type: The type of repository. Can be 'model', 'dataset', or 'space'. Defaults to 'dataset'.
27
+ :type repo_type: RepoTypeTyping
28
+ :param endpoint: The Hugging Face Hub endpoint. If None, uses the default ENDPOINT.
29
+ :type endpoint: Optional[str]
30
+
31
+ :return: The full URL for the specified Hugging Face Hub repository.
32
+ :rtype: str
33
+
34
+ :raises ValueError: If an unknown repository type is provided.
35
+
36
+ :usage:
37
+ >>> hf_hub_repo_url('username/repo', 'model')
38
+ 'https://huggingface.co/username/repo'
39
+ >>> hf_hub_repo_url('username/dataset', 'dataset')
40
+ 'https://huggingface.co/datasets/username/dataset'
41
+ """
42
+ endpoint = endpoint or ENDPOINT
43
+ if repo_type == 'model':
44
+ return f'{endpoint}/{repo_id}'
45
+ elif repo_type == 'dataset':
46
+ return f'{endpoint}/datasets/{repo_id}'
47
+ elif repo_type == 'space':
48
+ return f'{endpoint}/spaces/{repo_id}'
49
+ else:
50
+ raise ValueError(f'Unknown repository type - {repo_type!r}.')
51
+
52
+
53
+ def _check_git(requires_lfs: bool = True) -> str:
54
+ """
55
+ Check Git and Git LFS installations.
56
+
57
+ This function checks if Git is installed and, optionally, if Git LFS is installed.
58
+ The results are cached for improved performance.
59
+
60
+ :param requires_lfs: Whether to check for Git LFS installation. Defaults to True.
61
+ :type requires_lfs: bool
62
+
63
+ :return: The path to the Git executable.
64
+ :rtype: str
65
+
66
+ :raises EnvironmentError: If Git is not installed or if Git LFS is required but not installed.
67
+
68
+ :usage:
69
+ >>> _check_git()
70
+ '/usr/bin/git'
71
+ >>> _check_git(requires_lfs=False)
72
+ '/usr/bin/git'
73
+ """
74
+ info = git_info()
75
+ if not info['installed']:
76
+ raise EnvironmentError('Git not installed.')
77
+ else:
78
+ if requires_lfs and not info['lfs']['installed']:
79
+ raise EnvironmentError('Git lfs not installed.')
80
+ else:
81
+ return info['exec']
@@ -0,0 +1,87 @@
1
+ """
2
+ This module provides functionality to clone Hugging Face Hub repositories using Git.
3
+
4
+ It includes a function to clone a repository from Hugging Face Hub to a local directory,
5
+ with options for authentication, repository type, revision, and other Git clone parameters.
6
+ """
7
+
8
+ import copy
9
+ import logging
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ from typing import Optional
14
+
15
+ from huggingface_hub.errors import LocalTokenNotFoundError
16
+ from urlobject import URLObject
17
+
18
+ from .base import hf_hub_repo_url, _check_git
19
+ from ..operate import get_hf_client
20
+ from ..utils.path import RepoTypeTyping
21
+
22
+
23
+ def hf_hub_clone(repo_id: str, dst_dir: str,
24
+ repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
25
+ endpoint: Optional[str] = None, hf_token: Optional[str] = None,
26
+ silent: bool = False, no_lfs: bool = False, max_depth: Optional[int] = None):
27
+ """
28
+ Clone a repository from Hugging Face Hub.
29
+
30
+ :param repo_id: The ID of the repository to clone.
31
+ :type repo_id: str
32
+ :param dst_dir: The local directory to clone the repository into.
33
+ :type dst_dir: str
34
+ :param repo_type: The type of the repository ('dataset', 'model', or 'space'). Defaults to 'dataset'.
35
+ :type repo_type: RepoTypeTyping
36
+ :param revision: The specific revision to clone. Defaults to 'main'.
37
+ :type revision: str
38
+ :param endpoint: The Hugging Face Hub endpoint. If None, uses the default endpoint.
39
+ :type endpoint: Optional[str]
40
+ :param hf_token: The Hugging Face authentication token. If None, attempts to use stored credentials.
41
+ :type hf_token: Optional[str]
42
+ :param silent: If True, suppresses command output. Defaults to False.
43
+ :type silent: bool
44
+ :param no_lfs: If True, skips downloading of LFS files. Defaults to False.
45
+ :type no_lfs: bool
46
+ :param max_depth: The maximum number of commits to fetch. If None, fetches all commits.
47
+ :type max_depth: Optional[int]
48
+
49
+ :raises subprocess.CalledProcessError: If the Git clone command fails.
50
+
51
+ :usage:
52
+ >>> hf_hub_clone('username/repo', '/path/to/local/dir', repo_type='model', revision='v1.0')
53
+ """
54
+ _git = _check_git(requires_lfs=not no_lfs)
55
+ hf_client = get_hf_client(hf_token)
56
+ try:
57
+ username = hf_client.whoami()['name']
58
+ except LocalTokenNotFoundError:
59
+ username = None # anonymous mode
60
+
61
+ clone_url = URLObject(hf_hub_repo_url(
62
+ repo_id=repo_id,
63
+ repo_type=repo_type,
64
+ endpoint=endpoint,
65
+ ))
66
+ if username:
67
+ clone_url = clone_url.with_username(username).with_password(hf_client.token)
68
+ clone_url = str(clone_url)
69
+
70
+ command = [_git, 'clone', '-b', revision]
71
+ if max_depth is not None:
72
+ command.extend(['--depth', str(max_depth)])
73
+ command.extend([clone_url, dst_dir])
74
+ logging.info(f'Cloning repository with command {command!r} ...')
75
+ with open(os.devnull, 'w') as nf:
76
+ envs = copy.deepcopy(os.environ)
77
+ if no_lfs:
78
+ envs['GIT_LFS_SKIP_SMUDGE'] = '1'
79
+ envs['GIT_TERMINAL_PROMPT'] = '0'
80
+ process = subprocess.run(
81
+ args=command,
82
+ stdout=nf if silent else sys.stdout,
83
+ stderr=nf if silent else sys.stderr,
84
+ env=envs,
85
+ bufsize=0 if not silent else -1,
86
+ )
87
+ process.check_returncode()
@@ -0,0 +1,102 @@
1
+ """
2
+ This module provides functionality to rollback a Hugging Face Hub repository to a specific commit.
3
+
4
+ It includes a function to clone a repository, reset it to a specified commit, and force push the changes back to the remote repository.
5
+ """
6
+
7
+ import logging
8
+ import os.path
9
+ import subprocess
10
+ import sys
11
+ from typing import Optional
12
+
13
+ from .base import _check_git
14
+ from .clone import hf_hub_clone
15
+ from ..utils import TemporaryDirectory
16
+ from ..utils.path import RepoTypeTyping
17
+
18
+
19
+ def hf_hub_rollback(repo_id: str, rollback_to: str,
20
+ repo_type: RepoTypeTyping = 'dataset', revision: str = 'main',
21
+ endpoint: Optional[str] = None, hf_token: Optional[str] = None, silent: bool = False):
22
+ """
23
+ Rollback a Hugging Face Hub repository to a specific commit.
24
+
25
+ This function clones the repository, resets it to the specified commit, and force pushes the changes back to the remote.
26
+
27
+ :param repo_id: The ID of the repository to rollback.
28
+ :type repo_id: str
29
+ :param rollback_to: The commit hash or reference to rollback to.
30
+ :type rollback_to: str
31
+ :param repo_type: The type of the repository ('dataset', 'model', or 'space'). Defaults to 'dataset'.
32
+ :type repo_type: RepoTypeTyping
33
+ :param revision: The branch or tag to clone. Defaults to 'main'.
34
+ :type revision: str
35
+ :param endpoint: The Hugging Face Hub endpoint. If None, uses the default endpoint.
36
+ :type endpoint: Optional[str]
37
+ :param hf_token: The Hugging Face authentication token. If None, attempts to use stored credentials.
38
+ :type hf_token: Optional[str]
39
+ :param silent: If True, suppresses command output. Defaults to False.
40
+ :type silent: bool
41
+
42
+ :raises subprocess.CalledProcessError: If any Git command fails during the process.
43
+
44
+ :usage:
45
+ >>> hf_hub_rollback('username/repo', 'abc123', repo_type='model')
46
+
47
+ .. note::
48
+
49
+ This function performs the following steps:
50
+
51
+ 1. Checks for Git installation (without LFS requirement).
52
+ 2. Creates a temporary directory for the operation.
53
+ 3. Clones the specified repository into the temporary directory.
54
+ 4. Resets the repository to the specified commit using 'git reset --hard'.
55
+ 5. Force pushes the changes back to the remote repository.
56
+
57
+ .. warning::
58
+ This operation can overwrite remote history and can not be cancelled. Use with caution.
59
+ """
60
+ _git = _check_git(requires_lfs=False)
61
+ with TemporaryDirectory() as td, open(os.devnull, 'w') as nf:
62
+ envs = {
63
+ **os.environ,
64
+ 'GIT_LFS_SKIP_SMUDGE': '1',
65
+ 'GIT_TERMINAL_PROMPT': '0',
66
+ }
67
+
68
+ repo_dir = os.path.join(td, 'repo')
69
+ hf_hub_clone(
70
+ repo_id=repo_id,
71
+ dst_dir=repo_dir,
72
+ repo_type=repo_type,
73
+ revision=revision,
74
+ endpoint=endpoint,
75
+ hf_token=hf_token,
76
+ silent=silent,
77
+ no_lfs=True,
78
+ )
79
+
80
+ reset_command = [_git, 'reset', '--hard', rollback_to]
81
+ logging.info(f'Git resetting with command {reset_command!r} ...')
82
+ process = subprocess.run(
83
+ args=reset_command,
84
+ stdout=nf if silent else sys.stdout,
85
+ stderr=nf if silent else sys.stderr,
86
+ env=envs,
87
+ bufsize=0 if not silent else -1,
88
+ cwd=repo_dir,
89
+ )
90
+ process.check_returncode()
91
+
92
+ push_command = [_git, 'push', '-f']
93
+ logging.info(f'Git pushing with command {push_command!r} ...')
94
+ process = subprocess.run(
95
+ args=push_command,
96
+ stdout=nf if silent else sys.stdout,
97
+ stderr=nf if silent else sys.stderr,
98
+ env=envs,
99
+ bufsize=0 if not silent else -1,
100
+ cwd=repo_dir,
101
+ )
102
+ process.check_returncode()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hfutils
3
- Version: 0.2.11
3
+ Version: 0.3.0
4
4
  Summary: Useful utilities for huggingface
5
5
  Home-page: https://github.com/deepghs/hfutils
6
6
  Author: narugo1992
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.8
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: hbutils>=0.9.3
23
+ Requires-Dist: hbutils>=0.10.0
24
24
  Requires-Dist: huggingface_hub>=0.23
25
25
  Requires-Dist: tqdm
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: click>=7
28
28
  Requires-Dist: tzlocal
29
29
  Requires-Dist: natsort
30
+ Requires-Dist: urlobject
30
31
  Provides-Extra: doc
31
32
  Requires-Dist: Jinja2>=3.0.0; extra == "doc"
32
33
  Requires-Dist: sphinx>=3.2.0; extra == "doc"
@@ -30,11 +30,13 @@ hfutils/config/meta.py
30
30
  hfutils/entry/__init__.py
31
31
  hfutils/entry/base.py
32
32
  hfutils/entry/cli.py
33
+ hfutils/entry/clone.py
33
34
  hfutils/entry/dispatch.py
34
35
  hfutils/entry/download.py
35
36
  hfutils/entry/index.py
36
37
  hfutils/entry/ls.py
37
38
  hfutils/entry/ls_repo.py
39
+ hfutils/entry/rollback.py
38
40
  hfutils/entry/upload.py
39
41
  hfutils/entry/whoami.py
40
42
  hfutils/index/__init__.py
@@ -47,6 +49,10 @@ hfutils/operate/base.py
47
49
  hfutils/operate/download.py
48
50
  hfutils/operate/upload.py
49
51
  hfutils/operate/validate.py
52
+ hfutils/repository/__init__.py
53
+ hfutils/repository/base.py
54
+ hfutils/repository/clone.py
55
+ hfutils/repository/rollback.py
50
56
  hfutils/utils/__init__.py
51
57
  hfutils/utils/binary.py
52
58
  hfutils/utils/download.py
@@ -1,10 +1,11 @@
1
- hbutils>=0.9.3
1
+ hbutils>=0.10.0
2
2
  huggingface_hub>=0.23
3
3
  tqdm
4
4
  requests
5
5
  click>=7
6
6
  tzlocal
7
7
  natsort
8
+ urlobject
8
9
 
9
10
  [7z]
10
11
  py7zr
@@ -1,7 +1,8 @@
1
- hbutils>=0.9.3
1
+ hbutils>=0.10.0
2
2
  huggingface_hub>=0.23
3
3
  tqdm
4
4
  requests
5
5
  click>=7
6
6
  tzlocal
7
- natsort
7
+ natsort
8
+ urlobject
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