huggingface-hub 1.0.0rc0__py3-none-any.whl → 1.0.0rc1__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 huggingface-hub might be problematic. Click here for more details.

@@ -49,174 +49,112 @@ Usage:
49
49
  import os
50
50
  import time
51
51
  import warnings
52
- from argparse import Namespace, _SubParsersAction
53
- from typing import Optional
52
+ from typing import Annotated, Optional
53
+
54
+ import typer
54
55
 
55
56
  from huggingface_hub import logging
56
57
  from huggingface_hub._commit_scheduler import CommitScheduler
57
- from huggingface_hub.commands import BaseHuggingfaceCLICommand
58
58
  from huggingface_hub.constants import HF_HUB_ENABLE_HF_TRANSFER
59
59
  from huggingface_hub.errors import RevisionNotFoundError
60
- from huggingface_hub.hf_api import HfApi
61
60
  from huggingface_hub.utils import disable_progress_bars, enable_progress_bars
62
61
  from huggingface_hub.utils._runtime import is_xet_available
63
62
 
63
+ from ._cli_utils import PrivateOpt, RepoIdArg, RepoType, RepoTypeOpt, RevisionOpt, TokenOpt, get_hf_api
64
+
64
65
 
65
66
  logger = logging.get_logger(__name__)
66
67
 
67
68
 
68
- class UploadCommand(BaseHuggingfaceCLICommand):
69
- @staticmethod
70
- def register_subcommand(parser: _SubParsersAction):
71
- upload_parser = parser.add_parser(
72
- "upload", help="Upload a file or a folder to the Hub. Recommended for single-commit uploads."
73
- )
74
- upload_parser.add_argument(
75
- "repo_id", type=str, help="The ID of the repo to upload to (e.g. `username/repo-name`)."
76
- )
77
- upload_parser.add_argument(
78
- "local_path",
79
- nargs="?",
69
+ def upload(
70
+ repo_id: RepoIdArg,
71
+ local_path: Annotated[
72
+ Optional[str],
73
+ typer.Argument(
80
74
  help="Local path to the file or folder to upload. Wildcard patterns are supported. Defaults to current directory.",
81
- )
82
- upload_parser.add_argument(
83
- "path_in_repo",
84
- nargs="?",
75
+ ),
76
+ ] = None,
77
+ path_in_repo: Annotated[
78
+ Optional[str],
79
+ typer.Argument(
85
80
  help="Path of the file or folder in the repo. Defaults to the relative path of the file or folder.",
86
- )
87
- upload_parser.add_argument(
88
- "--repo-type",
89
- choices=["model", "dataset", "space"],
90
- default="model",
91
- help="Type of the repo to upload to (e.g. `dataset`).",
92
- )
93
- upload_parser.add_argument(
94
- "--revision",
95
- type=str,
96
- help=(
97
- "An optional Git revision to push to. It can be a branch name or a PR reference. If revision does not"
98
- " exist and `--create-pr` is not set, a branch will be automatically created."
99
- ),
100
- )
101
- upload_parser.add_argument(
102
- "--private",
103
- action="store_true",
104
- help=(
105
- "Whether to create a private repo if repo doesn't exist on the Hub. Ignored if the repo already"
106
- " exists."
107
- ),
108
- )
109
- upload_parser.add_argument("--include", nargs="*", type=str, help="Glob patterns to match files to upload.")
110
- upload_parser.add_argument(
111
- "--exclude", nargs="*", type=str, help="Glob patterns to exclude from files to upload."
112
- )
113
- upload_parser.add_argument(
114
- "--delete",
115
- nargs="*",
116
- type=str,
81
+ ),
82
+ ] = None,
83
+ repo_type: RepoTypeOpt = RepoType.model,
84
+ revision: RevisionOpt = None,
85
+ private: PrivateOpt = False,
86
+ include: Annotated[
87
+ Optional[list[str]],
88
+ typer.Option(
89
+ help="Glob patterns to match files to upload.",
90
+ ),
91
+ ] = None,
92
+ exclude: Annotated[
93
+ Optional[list[str]],
94
+ typer.Option(
95
+ help="Glob patterns to exclude from files to upload.",
96
+ ),
97
+ ] = None,
98
+ delete: Annotated[
99
+ Optional[list[str]],
100
+ typer.Option(
117
101
  help="Glob patterns for file to be deleted from the repo while committing.",
118
- )
119
- upload_parser.add_argument(
120
- "--commit-message", type=str, help="The summary / title / first line of the generated commit."
121
- )
122
- upload_parser.add_argument("--commit-description", type=str, help="The description of the generated commit.")
123
- upload_parser.add_argument(
124
- "--create-pr", action="store_true", help="Whether to upload content as a new Pull Request."
125
- )
126
- upload_parser.add_argument(
127
- "--every",
128
- type=float,
129
- help="If set, a background job is scheduled to create commits every `every` minutes.",
130
- )
131
- upload_parser.add_argument(
132
- "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
133
- )
134
- upload_parser.add_argument(
135
- "--quiet",
136
- action="store_true",
137
- help="If True, progress bars are disabled and only the path to the uploaded files is printed.",
138
- )
139
- upload_parser.set_defaults(func=UploadCommand)
140
-
141
- def __init__(self, args: Namespace) -> None:
142
- self.repo_id: str = args.repo_id
143
- self.repo_type: Optional[str] = args.repo_type
144
- self.revision: Optional[str] = args.revision
145
- self.private: bool = args.private
146
-
147
- self.include: Optional[list[str]] = args.include
148
- self.exclude: Optional[list[str]] = args.exclude
149
- self.delete: Optional[list[str]] = args.delete
150
-
151
- self.commit_message: Optional[str] = args.commit_message
152
- self.commit_description: Optional[str] = args.commit_description
153
- self.create_pr: bool = args.create_pr
154
- self.api: HfApi = HfApi(token=args.token, library_name="hf")
155
- self.quiet: bool = args.quiet # disable warnings and progress bars
156
-
157
- # Check `--every` is valid
158
- if args.every is not None and args.every <= 0:
159
- raise ValueError(f"`every` must be a positive value (got '{args.every}')")
160
- self.every: Optional[float] = args.every
161
-
162
- # Resolve `local_path` and `path_in_repo`
163
- repo_name: str = args.repo_id.split("/")[-1] # e.g. "Wauplin/my-cool-model" => "my-cool-model"
164
- self.local_path: str
165
- self.path_in_repo: str
166
-
167
- if args.local_path is not None and any(c in args.local_path for c in ["*", "?", "["]):
168
- if args.include is not None:
169
- raise ValueError("Cannot set `--include` when passing a `local_path` containing a wildcard.")
170
- if args.path_in_repo is not None and args.path_in_repo != ".":
171
- raise ValueError("Cannot set `path_in_repo` when passing a `local_path` containing a wildcard.")
172
- self.local_path = "."
173
- self.include = args.local_path
174
- self.path_in_repo = "."
175
- elif args.local_path is None and os.path.isfile(repo_name):
176
- # Implicit case 1: user provided only a repo_id which happen to be a local file as well => upload it with same name
177
- self.local_path = repo_name
178
- self.path_in_repo = repo_name
179
- elif args.local_path is None and os.path.isdir(repo_name):
180
- # Implicit case 2: user provided only a repo_id which happen to be a local folder as well => upload it at root
181
- self.local_path = repo_name
182
- self.path_in_repo = "."
183
- elif args.local_path is None:
184
- # Implicit case 3: user provided only a repo_id that does not match a local file or folder
185
- # => the user must explicitly provide a local_path => raise exception
186
- raise ValueError(f"'{repo_name}' is not a local file or folder. Please set `local_path` explicitly.")
187
- elif args.path_in_repo is None and os.path.isfile(args.local_path):
188
- # Explicit local path to file, no path in repo => upload it at root with same name
189
- self.local_path = args.local_path
190
- self.path_in_repo = os.path.basename(args.local_path)
191
- elif args.path_in_repo is None:
192
- # Explicit local path to folder, no path in repo => upload at root
193
- self.local_path = args.local_path
194
- self.path_in_repo = "."
195
- else:
196
- # Finally, if both paths are explicit
197
- self.local_path = args.local_path
198
- self.path_in_repo = args.path_in_repo
199
-
200
- def run(self) -> None:
201
- if self.quiet:
202
- disable_progress_bars()
203
- with warnings.catch_warnings():
204
- warnings.simplefilter("ignore")
205
- print(self._upload())
206
- enable_progress_bars()
207
- else:
208
- logging.set_verbosity_info()
209
- print(self._upload())
210
- logging.set_verbosity_warning()
211
-
212
- def _upload(self) -> str:
213
- if os.path.isfile(self.local_path):
214
- if self.include is not None and len(self.include) > 0:
215
- warnings.warn("Ignoring `--include` since a single file is uploaded.")
216
- if self.exclude is not None and len(self.exclude) > 0:
217
- warnings.warn("Ignoring `--exclude` since a single file is uploaded.")
218
- if self.delete is not None and len(self.delete) > 0:
219
- warnings.warn("Ignoring `--delete` since a single file is uploaded.")
102
+ ),
103
+ ] = None,
104
+ commit_message: Annotated[
105
+ Optional[str],
106
+ typer.Option(
107
+ help="The summary / title / first line of the generated commit.",
108
+ ),
109
+ ] = None,
110
+ commit_description: Annotated[
111
+ Optional[str],
112
+ typer.Option(
113
+ help="The description of the generated commit.",
114
+ ),
115
+ ] = None,
116
+ create_pr: Annotated[
117
+ bool,
118
+ typer.Option(
119
+ help="Whether to upload content as a new Pull Request.",
120
+ ),
121
+ ] = False,
122
+ every: Annotated[
123
+ Optional[float],
124
+ typer.Option(
125
+ help="f set, a background job is scheduled to create commits every `every` minutes.",
126
+ ),
127
+ ] = None,
128
+ token: TokenOpt = None,
129
+ quiet: Annotated[
130
+ bool,
131
+ typer.Option(
132
+ help="Disable progress bars and warnings; print only the returned path.",
133
+ ),
134
+ ] = False,
135
+ ) -> None:
136
+ """Upload a file or a folder to the Hub. Recommended for single-commit uploads."""
137
+
138
+ if every is not None and every <= 0:
139
+ raise typer.BadParameter("--every must be a positive value", param_hint="every")
140
+
141
+ repo_type_str = repo_type.value
142
+
143
+ api = get_hf_api(token=token)
144
+
145
+ # Resolve local_path and path_in_repo based on implicit/explicit rules
146
+ resolved_local_path, resolved_path_in_repo, resolved_include = _resolve_upload_paths(
147
+ repo_id=repo_id, local_path=local_path, path_in_repo=path_in_repo, include=include
148
+ )
149
+
150
+ def run_upload() -> str:
151
+ if os.path.isfile(resolved_local_path):
152
+ if resolved_include is not None and len(resolved_include) > 0 and isinstance(resolved_include, list):
153
+ warnings.warn("Ignoring --include since a single file is uploaded.")
154
+ if exclude is not None and len(exclude) > 0:
155
+ warnings.warn("Ignoring --exclude since a single file is uploaded.")
156
+ if delete is not None and len(delete) > 0:
157
+ warnings.warn("Ignoring --delete since a single file is uploaded.")
220
158
 
221
159
  if not is_xet_available() and not HF_HUB_ENABLE_HF_TRANSFER:
222
160
  logger.info(
@@ -225,39 +163,45 @@ class UploadCommand(BaseHuggingfaceCLICommand):
225
163
  )
226
164
 
227
165
  # Schedule commits if `every` is set
228
- if self.every is not None:
229
- if os.path.isfile(self.local_path):
166
+ if every is not None:
167
+ if os.path.isfile(resolved_local_path):
230
168
  # If file => watch entire folder + use allow_patterns
231
- folder_path = os.path.dirname(self.local_path)
232
- path_in_repo = (
233
- self.path_in_repo[: -len(self.local_path)] # remove filename from path_in_repo
234
- if self.path_in_repo.endswith(self.local_path)
235
- else self.path_in_repo
169
+ folder_path = os.path.dirname(resolved_local_path)
170
+ pi = (
171
+ resolved_path_in_repo[: -len(resolved_local_path)]
172
+ if resolved_path_in_repo.endswith(resolved_local_path)
173
+ else resolved_path_in_repo
236
174
  )
237
- allow_patterns = [self.local_path]
238
- ignore_patterns = []
175
+ allow_patterns = [resolved_local_path]
176
+ ignore_patterns: Optional[list[str]] = []
239
177
  else:
240
- folder_path = self.local_path
241
- path_in_repo = self.path_in_repo
242
- allow_patterns = self.include or []
243
- ignore_patterns = self.exclude or []
244
- if self.delete is not None and len(self.delete) > 0:
245
- warnings.warn("Ignoring `--delete` when uploading with scheduled commits.")
178
+ folder_path = resolved_local_path
179
+ pi = resolved_path_in_repo
180
+ allow_patterns = (
181
+ resolved_include or []
182
+ if isinstance(resolved_include, list)
183
+ else [resolved_include]
184
+ if isinstance(resolved_include, str)
185
+ else []
186
+ )
187
+ ignore_patterns = exclude or []
188
+ if delete is not None and len(delete) > 0:
189
+ warnings.warn("Ignoring --delete when uploading with scheduled commits.")
246
190
 
247
191
  scheduler = CommitScheduler(
248
192
  folder_path=folder_path,
249
- repo_id=self.repo_id,
250
- repo_type=self.repo_type,
251
- revision=self.revision,
193
+ repo_id=repo_id,
194
+ repo_type=repo_type_str,
195
+ revision=revision,
252
196
  allow_patterns=allow_patterns,
253
197
  ignore_patterns=ignore_patterns,
254
- path_in_repo=path_in_repo,
255
- private=self.private,
256
- every=self.every,
257
- hf_api=self.api,
198
+ path_in_repo=pi,
199
+ private=private,
200
+ every=every,
201
+ hf_api=api,
258
202
  )
259
- print(f"Scheduling commits every {self.every} minutes to {scheduler.repo_id}.")
260
- try: # Block main thread until KeyboardInterrupt
203
+ print(f"Scheduling commits every {every} minutes to {scheduler.repo_id}.")
204
+ try:
261
205
  while True:
262
206
  time.sleep(100)
263
207
  except KeyboardInterrupt:
@@ -265,52 +209,94 @@ class UploadCommand(BaseHuggingfaceCLICommand):
265
209
  return "Stopped scheduled commits."
266
210
 
267
211
  # Otherwise, create repo and proceed with the upload
268
- if not os.path.isfile(self.local_path) and not os.path.isdir(self.local_path):
269
- raise FileNotFoundError(f"No such file or directory: '{self.local_path}'.")
270
- repo_id = self.api.create_repo(
271
- repo_id=self.repo_id,
272
- repo_type=self.repo_type,
212
+ if not os.path.isfile(resolved_local_path) and not os.path.isdir(resolved_local_path):
213
+ raise FileNotFoundError(f"No such file or directory: '{resolved_local_path}'.")
214
+ created = api.create_repo(
215
+ repo_id=repo_id,
216
+ repo_type=repo_type_str,
273
217
  exist_ok=True,
274
- private=self.private,
275
- space_sdk="gradio" if self.repo_type == "space" else None,
218
+ private=private,
219
+ space_sdk="gradio" if repo_type_str == "space" else None,
276
220
  # ^ We don't want it to fail when uploading to a Space => let's set Gradio by default.
277
221
  # ^ I'd rather not add CLI args to set it explicitly as we already have `hf repo create` for that.
278
222
  ).repo_id
279
223
 
280
224
  # Check if branch already exists and if not, create it
281
- if self.revision is not None and not self.create_pr:
225
+ if revision is not None and not create_pr:
282
226
  try:
283
- self.api.repo_info(repo_id=repo_id, repo_type=self.repo_type, revision=self.revision)
227
+ api.repo_info(repo_id=created, repo_type=repo_type_str, revision=revision)
284
228
  except RevisionNotFoundError:
285
- logger.info(f"Branch '{self.revision}' not found. Creating it...")
286
- self.api.create_branch(repo_id=repo_id, repo_type=self.repo_type, branch=self.revision, exist_ok=True)
229
+ logger.info(f"Branch '{revision}' not found. Creating it...")
230
+ api.create_branch(repo_id=created, repo_type=repo_type_str, branch=revision, exist_ok=True)
287
231
  # ^ `exist_ok=True` to avoid race concurrency issues
288
232
 
289
233
  # File-based upload
290
- if os.path.isfile(self.local_path):
291
- return self.api.upload_file(
292
- path_or_fileobj=self.local_path,
293
- path_in_repo=self.path_in_repo,
294
- repo_id=repo_id,
295
- repo_type=self.repo_type,
296
- revision=self.revision,
297
- commit_message=self.commit_message,
298
- commit_description=self.commit_description,
299
- create_pr=self.create_pr,
234
+ if os.path.isfile(resolved_local_path):
235
+ return api.upload_file(
236
+ path_or_fileobj=resolved_local_path,
237
+ path_in_repo=resolved_path_in_repo,
238
+ repo_id=created,
239
+ repo_type=repo_type_str,
240
+ revision=revision,
241
+ commit_message=commit_message,
242
+ commit_description=commit_description,
243
+ create_pr=create_pr,
300
244
  )
301
245
 
302
246
  # Folder-based upload
303
- else:
304
- return self.api.upload_folder(
305
- folder_path=self.local_path,
306
- path_in_repo=self.path_in_repo,
307
- repo_id=repo_id,
308
- repo_type=self.repo_type,
309
- revision=self.revision,
310
- commit_message=self.commit_message,
311
- commit_description=self.commit_description,
312
- create_pr=self.create_pr,
313
- allow_patterns=self.include,
314
- ignore_patterns=self.exclude,
315
- delete_patterns=self.delete,
316
- )
247
+ return api.upload_folder(
248
+ folder_path=resolved_local_path,
249
+ path_in_repo=resolved_path_in_repo,
250
+ repo_id=created,
251
+ repo_type=repo_type_str,
252
+ revision=revision,
253
+ commit_message=commit_message,
254
+ commit_description=commit_description,
255
+ create_pr=create_pr,
256
+ allow_patterns=(
257
+ resolved_include
258
+ if isinstance(resolved_include, list)
259
+ else [resolved_include]
260
+ if isinstance(resolved_include, str)
261
+ else None
262
+ ),
263
+ ignore_patterns=exclude,
264
+ delete_patterns=delete,
265
+ )
266
+
267
+ if quiet:
268
+ disable_progress_bars()
269
+ with warnings.catch_warnings():
270
+ warnings.simplefilter("ignore")
271
+ print(run_upload())
272
+ enable_progress_bars()
273
+ else:
274
+ print(run_upload())
275
+ logging.set_verbosity_warning()
276
+
277
+
278
+ def _resolve_upload_paths(
279
+ *, repo_id: str, local_path: Optional[str], path_in_repo: Optional[str], include: Optional[list[str]]
280
+ ) -> tuple[str, str, Optional[list[str]]]:
281
+ repo_name = repo_id.split("/")[-1]
282
+ resolved_include = include
283
+
284
+ if local_path is not None and any(c in local_path for c in ["*", "?", "["]):
285
+ if include is not None:
286
+ raise ValueError("Cannot set --include when local_path contains a wildcard.")
287
+ if path_in_repo is not None and path_in_repo != ".":
288
+ raise ValueError("Cannot set path_in_repo when local_path contains a wildcard.")
289
+ return ".", local_path, ["."] # will be adjusted below; placeholder for type
290
+
291
+ if local_path is None and os.path.isfile(repo_name):
292
+ return repo_name, repo_name, resolved_include
293
+ if local_path is None and os.path.isdir(repo_name):
294
+ return repo_name, ".", resolved_include
295
+ if local_path is None:
296
+ raise ValueError(f"'{repo_name}' is not a local file or folder. Please set local_path explicitly.")
297
+
298
+ if path_in_repo is None and os.path.isfile(local_path):
299
+ return local_path, os.path.basename(local_path), resolved_include
300
+ if path_in_repo is None:
301
+ return local_path, ".", resolved_include
302
+ return local_path, path_in_repo, resolved_include