Skip to content

Commit b92a5d7

Browse files
devesh-2002hanouticelinaWauplin
authored
Added support for Wildcards in huggingface-cli upload (#2868)
* Added support for Wildcards in huggingface-cli upload * Made some changes in line endings * some changes in test cli * some changes in test cli * Made changes in upload.py based on suggestions * Update handling of wildcard arguments * fix wildcard support and improve test * fix * Update src/huggingface_hub/commands/upload.py Co-authored-by: Lucain <[email protected]> * Update src/huggingface_hub/commands/upload.py Co-authored-by: Lucain <[email protected]> --------- Co-authored-by: Celina Hanouti <[email protected]> Co-authored-by: Lucain <[email protected]>
1 parent 7aa250f commit b92a5d7

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

src/huggingface_hub/commands/upload.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
# Upload filtered directory (example: tensorboard logs except for the last run)
3131
huggingface-cli upload my-cool-model ./model/training /logs --include "*.tfevents.*" --exclude "*20230905*"
3232
33+
# Upload with wildcard
34+
huggingface-cli upload my-cool-model "./model/training/*.safetensors"
35+
3336
# Upload private dataset
3437
huggingface-cli upload Wauplin/my-cool-dataset ./data . --repo-type=dataset --private
3538
@@ -69,7 +72,9 @@ def register_subcommand(parser: _SubParsersAction):
6972
"repo_id", type=str, help="The ID of the repo to upload to (e.g. `username/repo-name`)."
7073
)
7174
upload_parser.add_argument(
72-
"local_path", nargs="?", help="Local path to the file or folder to upload. Defaults to current directory."
75+
"local_path",
76+
nargs="?",
77+
help="Local path to the file or folder to upload. Wildcard patterns are supported. Defaults to current directory.",
7378
)
7479
upload_parser.add_argument(
7580
"path_in_repo",
@@ -155,7 +160,16 @@ def __init__(self, args: Namespace) -> None:
155160
repo_name: str = args.repo_id.split("/")[-1] # e.g. "Wauplin/my-cool-model" => "my-cool-model"
156161
self.local_path: str
157162
self.path_in_repo: str
158-
if args.local_path is None and os.path.isfile(repo_name):
163+
164+
if args.local_path is not None and any(c in args.local_path for c in ["*", "?", "["]):
165+
if args.include is not None:
166+
raise ValueError("Cannot set `--include` when passing a `local_path` containing a wildcard.")
167+
if args.path_in_repo is not None and args.path_in_repo != ".":
168+
raise ValueError("Cannot set `path_in_repo` when passing a `local_path` containing a wildcard.")
169+
self.local_path = "."
170+
self.include = args.local_path
171+
self.path_in_repo = "."
172+
elif args.local_path is None and os.path.isfile(repo_name):
159173
# Implicit case 1: user provided only a repo_id which happen to be a local file as well => upload it with same name
160174
self.local_path = repo_name
161175
self.path_in_repo = repo_name

tests/test_cli.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,42 @@ def test_upload_basic(self) -> None:
9191
self.assertEqual(cmd.api.token, None)
9292
self.assertEqual(cmd.quiet, False)
9393

94+
def test_upload_with_wildcard(self) -> None:
95+
"""Test uploading files using wildcard patterns."""
96+
with tmp_current_directory() as cache_dir:
97+
# Create test files
98+
(Path(cache_dir) / "model1.safetensors").touch()
99+
(Path(cache_dir) / "model2.safetensors").touch()
100+
(Path(cache_dir) / "model.bin").touch()
101+
(Path(cache_dir) / "config.json").touch()
102+
103+
# Test basic wildcard pattern
104+
cmd = UploadCommand(self.parser.parse_args(["upload", DUMMY_MODEL_ID, "*.safetensors"]))
105+
self.assertEqual(cmd.local_path, ".")
106+
self.assertEqual(cmd.include, "*.safetensors")
107+
self.assertEqual(cmd.path_in_repo, ".")
108+
self.assertEqual(cmd.repo_id, DUMMY_MODEL_ID)
109+
110+
# Test wildcard pattern with specific directory
111+
subdir = Path(cache_dir) / "subdir"
112+
subdir.mkdir()
113+
(subdir / "special.safetensors").touch()
114+
115+
cmd = UploadCommand(self.parser.parse_args(["upload", DUMMY_MODEL_ID, "subdir/*.safetensors"]))
116+
self.assertEqual(cmd.local_path, ".")
117+
self.assertEqual(cmd.include, "subdir/*.safetensors")
118+
self.assertEqual(cmd.path_in_repo, ".")
119+
120+
# Test error when using wildcard with --include
121+
with self.assertRaises(ValueError):
122+
UploadCommand(
123+
self.parser.parse_args(["upload", DUMMY_MODEL_ID, "*.safetensors", "--include", "*.json"])
124+
)
125+
126+
# Test error when using wildcard with explicit path_in_repo
127+
with self.assertRaises(ValueError):
128+
UploadCommand(self.parser.parse_args(["upload", DUMMY_MODEL_ID, "*.safetensors", "models/"]))
129+
94130
def test_upload_with_all_options(self) -> None:
95131
"""Test `huggingface-cli upload my-file to dummy-repo with all options selected`."""
96132
cmd = UploadCommand(

0 commit comments

Comments
 (0)