atomgit 1.0.7__tar.gz → 1.0.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomgit
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary: AtomGit模型文件上传下载CLI工具
5
5
  Home-page: https://atomgit.com/gitcode-ai/atomgit_cli
6
6
  Author: AtomGit CLI Team
@@ -8,7 +8,7 @@ AtomGit CLI - 基于Transformers和Hugging Face Hub的模型文件上传下载
8
8
  支持模型和数据集的上传、下载等操作。
9
9
  """
10
10
 
11
- __version__ = '1.0.7'
11
+ __version__ = '1.0.9'
12
12
  __author__ = 'AtomGit CLI Team'
13
13
  __description__ = 'AtomGit模型文件上传下载CLI工具'
14
14
 
@@ -31,6 +31,6 @@ except ImportError:
31
31
 
32
32
  __all__ = [
33
33
  'config', 'api', 'cli',
34
- 'snapshot_download', 'hub_download_url', 'download_file',
34
+ 'snapshot_download', 'hub_download_url', 'download_file',
35
35
  'upload_folder', 'create_repository'
36
- ]
36
+ ]
@@ -16,6 +16,10 @@ os.environ["HF_HOME"] = cache_dir
16
16
 
17
17
 
18
18
  from huggingface_hub import hf_hub_download, upload_folder, create_repo, snapshot_download, constants as hf_constants
19
+ try:
20
+ from .atomgit_hub import snapshot_download as atomgit_snapshot_download
21
+ except ImportError:
22
+ from atomgit_hub import snapshot_download as atomgit_snapshot_download
19
23
  try:
20
24
  from .rate_limiter import install_hf_rate_limiter
21
25
  except ImportError:
@@ -223,7 +227,7 @@ class HuggingFaceAPI:
223
227
  # 首先尝试不使用token下载(适用于公开仓库)
224
228
  credentials = config.get_credentials()
225
229
  try:
226
- snapshot_download(
230
+ atomgit_snapshot_download(
227
231
  repo_id=normalized_repo_id,
228
232
  local_dir=str(local_path),
229
233
  force_download=force_download, # 根据用户选择决定是否强制下载
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomgit
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary: AtomGit模型文件上传下载CLI工具
5
5
  Home-page: https://atomgit.com/gitcode-ai/atomgit_cli
6
6
  Author: AtomGit CLI Team
@@ -9,7 +9,6 @@ config.py
9
9
  rate_limiter.py
10
10
  requirements.txt
11
11
  setup.py
12
- test.py
13
12
  utils.py
14
13
  ./__init__.py
15
14
  ./__main__.py
@@ -18,7 +17,6 @@ utils.py
18
17
  ./cli.py
19
18
  ./config.py
20
19
  ./rate_limiter.py
21
- ./test.py
22
20
  ./utils.py
23
21
  atomgit.egg-info/PKG-INFO
24
22
  atomgit.egg-info/SOURCES.txt
@@ -22,7 +22,9 @@ os.makedirs(cache_dir, exist_ok=True)
22
22
  os.environ["HF_HOME"] = cache_dir
23
23
 
24
24
  from huggingface_hub import snapshot_download as hf_snapshot_download
25
- from huggingface_hub import hf_hub_download, upload_folder as hf_upload_folder, create_repo
25
+ from huggingface_hub import hf_hub_download, upload_file as hf_upload_file, upload_folder as hf_upload_folder, create_repo
26
+ from huggingface_hub import HfApi
27
+ from concurrent.futures import ThreadPoolExecutor
26
28
  try:
27
29
  from .rate_limiter import install_hf_rate_limiter
28
30
  except ImportError:
@@ -86,6 +88,54 @@ def _get_token() -> Optional[str]:
86
88
  return None
87
89
 
88
90
 
91
+ def _snapshot_download_from_tree(
92
+ repo_id: str,
93
+ local_dir: Union[str, Path],
94
+ revision: Optional[str],
95
+ token: Optional[Union[str, bool]],
96
+ force_download: bool,
97
+ allow_patterns: Optional[Union[List[str], str]],
98
+ ignore_patterns: Optional[Union[List[str], str]],
99
+ max_workers: int,
100
+ ) -> str:
101
+ """Download through list_repo_tree when repo_info.siblings is unavailable."""
102
+ from huggingface_hub.utils import filter_repo_objects
103
+ from huggingface_hub.hf_api import RepoFile
104
+
105
+ api = HfApi()
106
+ files = [
107
+ item.path
108
+ for item in api.list_repo_tree(
109
+ repo_id=repo_id, recursive=True, revision=revision, token=token
110
+ )
111
+ if isinstance(item, RepoFile)
112
+ ]
113
+ files = list(filter_repo_objects(
114
+ items=files,
115
+ allow_patterns=allow_patterns,
116
+ ignore_patterns=ignore_patterns,
117
+ ))
118
+ if not files:
119
+ raise FileNotFoundError(f"仓库没有可下载的文件: {repo_id}")
120
+
121
+ destination = Path(local_dir)
122
+ destination.mkdir(parents=True, exist_ok=True)
123
+
124
+ def download_one(filename: str) -> None:
125
+ hf_hub_download(
126
+ repo_id=repo_id,
127
+ filename=filename,
128
+ revision=revision,
129
+ token=token,
130
+ local_dir=str(destination),
131
+ force_download=force_download,
132
+ )
133
+
134
+ with ThreadPoolExecutor(max_workers=max(1, max_workers)) as executor:
135
+ list(executor.map(download_one, files))
136
+ return str(destination.resolve())
137
+
138
+
89
139
  def snapshot_download(
90
140
  repo_id: str,
91
141
  revision: Optional[str] = None,
@@ -181,6 +231,24 @@ def snapshot_download(
181
231
  return result
182
232
  except Exception as e:
183
233
  error_msg = str(e)
234
+ if local_dir is not None and (
235
+ "min() iterable argument is empty" in error_msg
236
+ or "min() arg is an empty sequence" in error_msg
237
+ or "404 Not Found" in error_msg
238
+ ):
239
+ try:
240
+ return _snapshot_download_from_tree(
241
+ repo_id=normalized_repo_id,
242
+ local_dir=local_dir,
243
+ revision=revision,
244
+ token=token,
245
+ force_download=force_download,
246
+ allow_patterns=allow_patterns,
247
+ ignore_patterns=ignore_patterns,
248
+ max_workers=max_workers,
249
+ )
250
+ except Exception as fallback_error:
251
+ error_msg = str(fallback_error)
184
252
  if "401" in error_msg or "403" in error_msg:
185
253
  raise Exception(f"认证失败:{error_msg}。请检查token是否正确,或使用 'atomgit login' 重新登录。")
186
254
  elif "404" in error_msg:
@@ -291,18 +359,21 @@ def upload_folder(
291
359
  upload_timeout: float = 300.0,
292
360
  ) -> str:
293
361
  """
294
- 上传文件夹到AtomGit Hub
295
-
362
+ 上传文件或文件夹到AtomGit Hub
363
+
364
+ 自动判断传入路径是文件还是文件夹,两者都支持。
365
+
296
366
  参数:
297
- folder_path (str 或 Path): 本地文件夹路径
367
+ folder_path (str 或 Path): 本地文件或文件夹路径
298
368
  repo_id (str): 仓库ID
299
369
  token (str, 可选): 认证token
300
370
  repo_type (str, 可选): 仓库类型
301
371
  revision (str, 可选): 分支名
302
372
  commit_message (str, 可选): 提交消息
303
373
  commit_description (str, 可选): 提交描述
304
- path_in_repo (str, 可选): 在仓库中的路径,默认为根目录
305
- ignore_patterns (List[str], 可选): 要忽略的文件模式
374
+ path_in_repo (str, 可选): 在仓库中的路径,默认为根目录。
375
+ 若传入的是文件,文件名会自动拼接到该路径末尾。
376
+ ignore_patterns (List[str], 可选): 要忽略的文件模式(仅文件夹上传生效)
306
377
  upload_timeout (float, 可选): 上传超时时间(秒),默认60秒(1分钟)。
307
378
  对于大文件,服务器处理响应可能需要较长时间。
308
379
 
@@ -313,6 +384,7 @@ def upload_folder(
313
384
  >>> upload_folder("./my-model/", "username/repo-name")
314
385
  >>> upload_folder("./data/", "username/repo", path_in_repo="datasets/")
315
386
  >>> upload_folder("./big-files/", "username/repo", upload_timeout=1200.0) # 20分钟超时
387
+ >>> upload_folder("./weights.pth", "username/repo", path_in_repo="checkpoints/") # 上传单个文件
316
388
  """
317
389
  # 标准化仓库ID
318
390
  normalized_repo_id = _normalize_repo_id(repo_id)
@@ -321,10 +393,10 @@ def upload_folder(
321
393
  folder_path = Path(folder_path)
322
394
 
323
395
  if not folder_path.exists():
324
- raise FileNotFoundError(f"文件夹不存在: {folder_path}")
325
-
326
- if not folder_path.is_dir():
327
- raise NotADirectoryError(f"路径不是目录: {folder_path}")
396
+ raise FileNotFoundError(f"路径不存在: {folder_path}")
397
+
398
+ if not folder_path.is_file() and not folder_path.is_dir():
399
+ raise ValueError(f"路径既不是文件也不是目录: {folder_path}")
328
400
 
329
401
  # 如果没有提供token,尝试使用保存的token
330
402
  if token is None:
@@ -332,42 +404,62 @@ def upload_folder(
332
404
  if token is None:
333
405
  raise Exception("上传需要认证token,请先使用 'atomgit login' 登录,或提供token参数。")
334
406
 
335
- # 直接使用原始目录,或者创建临时目录来重新组织结构
407
+ # 使用 Monkey Patch 方式临时修改 huggingface_hub 的默认超时配置
408
+ from huggingface_hub import constants as hf_constants
336
409
  import tempfile
337
410
  import shutil
338
411
 
339
- if path_in_repo == "./" or path_in_repo == "." or path_in_repo == "":
340
- # 如果要上传到根目录,直接使用源文件夹
341
- upload_path = str(folder_path)
342
- else:
343
- # 如果要上传到特定路径,需要重新组织目录结构。
344
- # 注意:不能提前退出 with TemporaryDirectory(),否则上传时临时目录已被删除。
345
- temp_dir = tempfile.mkdtemp(prefix="atomgit_upload_")
346
- try:
347
- temp_path = Path(temp_dir)
348
-
349
- # 创建目标路径
350
- target_path = temp_path / path_in_repo.strip('./')
351
- target_path.parent.mkdir(parents=True, exist_ok=True)
352
-
353
- # 复制整个目录树
354
- shutil.copytree(folder_path, target_path, dirs_exist_ok=True)
355
-
356
- except Exception:
357
- shutil.rmtree(temp_dir, ignore_errors=True)
358
- raise
359
-
360
- upload_path = str(temp_path)
361
- # 使用 Monkey Patch 方式临时修改 huggingface_hub 的默认超时配置
362
- from huggingface_hub import constants as hf_constants
363
-
364
412
  # 保存原始超时配置
365
413
  original_timeout = hf_constants.DEFAULT_REQUEST_TIMEOUT
366
-
414
+
367
415
  # 临时修改超时配置
368
416
  hf_constants.DEFAULT_REQUEST_TIMEOUT = upload_timeout
369
-
417
+
418
+ temp_dir = None
370
419
  try:
420
+ # 上传单个文件
421
+ if folder_path.is_file():
422
+ # path_in_repo 按目录语义处理(与文件夹分支一致,去掉首尾 ./ 和 /),
423
+ # 文件名自动拼接到末尾
424
+ clean_path = path_in_repo.strip().strip("./")
425
+ if not clean_path:
426
+ target_path = folder_path.name
427
+ else:
428
+ target_path = clean_path + "/" + folder_path.name
429
+ commit_msg = commit_message or f"Upload file {folder_path.name}"
430
+ result = hf_upload_file(
431
+ repo_id=normalized_repo_id,
432
+ path_or_fileobj=str(folder_path),
433
+ path_in_repo=target_path,
434
+ token=token,
435
+ commit_message=commit_msg
436
+ )
437
+ return result
438
+
439
+ # 上传文件夹:直接使用原始目录,或者创建临时目录来重新组织结构
440
+ if path_in_repo == "./" or path_in_repo == "." or path_in_repo == "":
441
+ # 如果要上传到根目录,直接使用源文件夹
442
+ upload_path = str(folder_path)
443
+ else:
444
+ # 如果要上传到特定路径,需要重新组织目录结构。
445
+ # 注意:不能提前退出 with TemporaryDirectory(),否则上传时临时目录已被删除。
446
+ temp_dir = tempfile.mkdtemp(prefix="atomgit_upload_")
447
+ try:
448
+ temp_path = Path(temp_dir)
449
+
450
+ # 创建目标路径
451
+ target_path = temp_path / path_in_repo.strip('./')
452
+ target_path.parent.mkdir(parents=True, exist_ok=True)
453
+
454
+ # 复制整个目录树
455
+ shutil.copytree(folder_path, target_path, dirs_exist_ok=True)
456
+
457
+ except Exception:
458
+ shutil.rmtree(temp_dir, ignore_errors=True)
459
+ raise
460
+
461
+ upload_path = str(temp_path)
462
+
371
463
  # 使用huggingface_hub的upload_folder上传
372
464
  commit_msg = commit_message or f"Upload folder {folder_path.name}"
373
465
  result = hf_upload_folder(
@@ -376,7 +468,7 @@ def upload_folder(
376
468
  token=token,
377
469
  commit_message=commit_msg
378
470
  )
379
-
471
+
380
472
  return result
381
473
 
382
474
  except Exception as e:
@@ -388,8 +480,10 @@ def upload_folder(
388
480
  else:
389
481
  raise Exception(f"上传失败:{error_msg}")
390
482
  finally:
483
+ # 恢复原始超时配置
484
+ hf_constants.DEFAULT_REQUEST_TIMEOUT = original_timeout
391
485
  # 清理临时目录(仅重新组织目录结构时创建)
392
- if path_in_repo not in ("./", ".", ""):
486
+ if temp_dir is not None:
393
487
  shutil.rmtree(temp_dir, ignore_errors=True)
394
488
 
395
489
 
@@ -39,7 +39,7 @@ except ImportError:
39
39
 
40
40
 
41
41
  @click.group()
42
- @click.version_option(version='1.0.7')
42
+ @click.version_option(version='1.0.9')
43
43
  def cli():
44
44
  """AtomGit CLI - 基于Transformers和Hugging Face Hub的AtomGit平台模型文件上传下载工具"""
45
45
  pass
@@ -292,4 +292,4 @@ def config_show():
292
292
 
293
293
 
294
294
  if __name__ == '__main__':
295
- cli()
295
+ cli()
@@ -31,7 +31,7 @@ def read_requirements():
31
31
 
32
32
  setup(
33
33
  name='atomgit',
34
- version='1.0.7',
34
+ version='1.0.9',
35
35
  author='AtomGit CLI Team',
36
36
  author_email='sa@atomgit.com',
37
37
  description='AtomGit模型文件上传下载CLI工具',
@@ -69,4 +69,4 @@ setup(
69
69
  'Bug Reports': 'https://atomgit.com/gitcode-ai/atomgit_cli/issues',
70
70
  'Source': 'https://atomgit.com/gitcode-ai/atomgit_cli',
71
71
  },
72
- )
72
+ )
atomgit-1.0.7/test.py DELETED
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- AtomGit Hub 功能测试
5
- """
6
-
7
- from atomgit_hub import (
8
- create_repository,
9
- upload_folder,
10
- snapshot_download,
11
- hub_download_url,
12
- download_file,
13
- load_dataset
14
- )
15
- from pathlib import Path
16
-
17
-
18
- def test_create_model(repo_id: str, repo_type: str):
19
- print("\n=== 测试 create_repository ===")
20
- try:
21
- result = create_repository(
22
- repo_id=repo_id,
23
- token=None, # 使用保存的token
24
- private=True,
25
- repo_type=repo_type,
26
- exist_ok=True,
27
- space_sdk=None,
28
- space_hardware=None,
29
- space_storage=None,
30
- space_sleep_time=None,
31
- space_secrets=None,
32
- space_variables=None
33
- )
34
- print(f"✅ 创建仓库成功: {result}")
35
- except Exception as e:
36
- print(f"❌ 创建仓库失败: {e}")
37
-
38
- def test_upload_folder(repo_id: str, folder_path: str):
39
- print("\n=== 测试 upload_folder ===")
40
-
41
- try:
42
- result = upload_folder(
43
- folder_path=folder_path,
44
- repo_id=repo_id,
45
- token=None, # 使用保存的token
46
- commit_message="Test upload with all parameters",
47
- commit_description="Full parameter test for upload_folder function",
48
- upload_timeout=100
49
- )
50
- print(f"✅ 上传文件夹成功: {result}")
51
- except Exception as e:
52
- print(f"❌ 上传文件夹失败: {e}")
53
-
54
- def test_snapshot_download(repo_id: str, local_dir: str):
55
- print("\n=== 测试 snapshot_download ===")
56
- try:
57
- local_path = snapshot_download(
58
- repo_id=repo_id,
59
- local_dir=local_dir,
60
- proxies=None,
61
- etag_timeout=10,
62
- resume_download=False,
63
- force_download=True,
64
- token=None, # 使用保存的token
65
- local_files_only=False,
66
- max_workers=8,
67
- tqdm_class=None
68
- )
69
- print(f"✅ 下载快照成功: {local_path}")
70
- except Exception as e:
71
- print(f"❌ 下载快照失败: {e}")
72
-
73
- def test_download_file(repo_id: str, filename: str, local_dir: str):
74
- print("\n=== 测试 download_file ===")
75
- try:
76
- file_path = download_file(
77
- repo_id=repo_id,
78
- filename=filename,
79
- local_dir=local_dir,
80
- token=None, # 使用保存的token
81
- force_download=True
82
- )
83
- print(f"✅ 下载文件成功: {file_path}")
84
- except Exception as e:
85
- print(f"❌ 下载文件失败: {e}")
86
-
87
-
88
- def test_hub_download_url(repo_id: str, filename: str, repo_type: str):
89
- print("\n=== 测试 hub_download_url ===")
90
- try:
91
- url = hub_download_url(
92
- repo_id=repo_id,
93
- filename=filename,
94
- repo_type=repo_type
95
- )
96
- print(f"✅ 获取URL成功: {url}")
97
- except Exception as e:
98
- print(f"❌ 获取URL失败: {e}")
99
-
100
- def test_load_dataset(repo_id: str, local_dir: str):
101
- print("\n=== 测试 load_dataset ===")
102
- try:
103
- dataset = load_dataset(
104
- path=repo_id,
105
- cache_dir=local_dir,
106
- token=None
107
- )
108
- print(f"✅ 加载数据集成功: {dataset}")
109
- except Exception as e:
110
- print(f"❌ 加载数据集失败: {e}")
111
-
112
-
113
- def run_all_tests():
114
- """运行所有测试"""
115
- print("=" * 60)
116
- print("开始运行 AtomGit Hub 完整参数测试")
117
- print("=" * 60)
118
- model = "ai-test/atomgit-cli-test-model-full"
119
- dataset = "ai-test/atomgit-cli-test-dataset-full"
120
-
121
- model_folder_path="/Users/yanlp/csdn/aipython/gitcode_cli/site/yanlp-model-1"
122
- dataset_folder_path="/Users/yanlp/csdn/aipython/gitcode_cli/site/yanlp-model-1"
123
-
124
- test_dataset = "yanlp/glaive_toolcall_zh"
125
- # 注意:按照依赖顺序执行测试
126
- # 1. 先创建仓库
127
- # test_create_model(repo_id=model, repo_type="model")
128
- # test_create_model(repo_id=dataset, repo_type="dataset")
129
-
130
- # 2. 测试upload
131
- # test_upload_folder(repo_id=model, folder_path=model_folder_path)
132
- # test_upload_folder(repo_id=dataset, folder_path=dataset_folder_path)
133
- test_upload_folder(repo_id="yanlp/dataset-t2", folder_path="/Users/yanlp/csdn/IdeaProjects/gitcode/gitcode-hf-registry/dataset-t11")
134
-
135
- # 3. 测试下载
136
- # test_snapshot_download(repo_id=model, local_dir="./test_downloads/model/snapshot_full")
137
- # test_snapshot_download(repo_id="hf_mirrors/Qwen/Qwen3-0.6B", local_dir="./test_downloads/dataset/snapshot_full2")
138
-
139
- # 4. 测试下载文件
140
- # test_download_file(repo_id=model, filename="README.md", local_dir="./test_downloads/model/single_file")
141
-
142
- # 5. 测试URL获取
143
- # test_hub_download_url(repo_id=model, filename="model.safetensors", repo_type="model")
144
-
145
- # 6. 测试数据集加载
146
- # test_load_dataset(repo_id=test_dataset, local_dir="./test_downloads/dataset/load_dataset")
147
-
148
- print("\n" + "=" * 60)
149
- print("所有测试完成")
150
- print("=" * 60)
151
-
152
-
153
- if __name__ == "__main__":
154
- run_all_tests()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes