modelscope-hub 0.4.0__tar.gz → 0.4.2__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 (76) hide show
  1. {modelscope_hub-0.4.0/src/modelscope_hub.egg-info → modelscope_hub-0.4.2}/PKG-INFO +1 -1
  2. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_upload.py +36 -0
  3. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/api.py +70 -42
  4. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/compat/hub_api.py +30 -0
  5. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/version.py +1 -1
  6. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2/src/modelscope_hub.egg-info}/PKG-INFO +1 -1
  7. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub.egg-info/SOURCES.txt +1 -0
  8. modelscope_hub-0.4.2/tests/test_compat_delete_files.py +54 -0
  9. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_upload_lfs_gate.py +141 -1
  10. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/LICENSE +0 -0
  11. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/README.md +0 -0
  12. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/pyproject.toml +0 -0
  13. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/setup.cfg +0 -0
  14. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/__init__.py +0 -0
  15. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_cache_manager.py +0 -0
  16. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_download.py +0 -0
  17. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_git.py +0 -0
  18. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_legacy_api.py +0 -0
  19. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_openapi.py +0 -0
  20. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/_repository.py +0 -0
  21. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/agent/__init__.py +0 -0
  22. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/agent/_api.py +0 -0
  23. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/agent_idp.py +0 -0
  24. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/__init__.py +0 -0
  25. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/agent.py +0 -0
  26. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/agent_idp.py +0 -0
  27. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/base.py +0 -0
  28. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/cache.py +0 -0
  29. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/compat.py +0 -0
  30. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/deploy.py +0 -0
  31. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/download.py +0 -0
  32. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/login.py +0 -0
  33. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/main.py +0 -0
  34. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/mcp.py +0 -0
  35. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/repo.py +0 -0
  36. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/secret.py +0 -0
  37. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/studio.py +0 -0
  38. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/cli/upload.py +0 -0
  39. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/compat/__init__.py +0 -0
  40. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/compat/constants.py +0 -0
  41. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/compat/file_download.py +0 -0
  42. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/compat/snapshot_download.py +0 -0
  43. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/config.py +0 -0
  44. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/constants.py +0 -0
  45. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/errors.py +0 -0
  46. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/py.typed +0 -0
  47. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/types.py +0 -0
  48. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/__init__.py +0 -0
  49. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/file_utils.py +0 -0
  50. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/format.py +0 -0
  51. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/logger.py +0 -0
  52. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/media.py +0 -0
  53. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/patterns.py +0 -0
  54. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub/utils/time_utils.py +0 -0
  55. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub.egg-info/dependency_links.txt +0 -0
  56. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub.egg-info/entry_points.txt +0 -0
  57. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub.egg-info/requires.txt +0 -0
  58. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/src/modelscope_hub.egg-info/top_level.txt +0 -0
  59. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_agent_idp.py +0 -0
  60. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_agent_idp_keys.py +0 -0
  61. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_cache_verification.py +0 -0
  62. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_compat_get_model_files.py +0 -0
  63. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_compat_snapshot_download.py +0 -0
  64. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_compat_studio.py +0 -0
  65. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_config_token.py +0 -0
  66. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_credential_lifecycle.py +0 -0
  67. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_download_lock_path.py +0 -0
  68. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_errors_openapi_codes.py +0 -0
  69. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_legacy_cache_detection.py +0 -0
  70. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_login_failure_paths.py +0 -0
  71. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_login_scoped_token.py +0 -0
  72. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_openapi_coverage.py +0 -0
  73. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_repo_files_truncation.py +0 -0
  74. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_upload_config.py +0 -0
  75. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_user_info.py +0 -0
  76. {modelscope_hub-0.4.0 → modelscope_hub-0.4.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelscope-hub
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: The official Python client to connect with ModelScope Hub.
5
5
  License: Apache-2.0
6
6
  Keywords: modelscope,hub,sdk,openapi,machine-learning
@@ -635,6 +635,42 @@ class UploadManager:
635
635
  revision=revision,
636
636
  )
637
637
 
638
+ # ------------------------------------------------------------------
639
+ # Public: delete_files
640
+ # ------------------------------------------------------------------
641
+ def delete_files(
642
+ self,
643
+ repo_id: str,
644
+ repo_type: str,
645
+ file_paths: list[str],
646
+ *,
647
+ commit_message: str = "Delete files",
648
+ revision: str = "master",
649
+ ) -> dict:
650
+ """Delete repository files through a commit operation.
651
+
652
+ The direct repository DELETE endpoints reject API-token authentication.
653
+ Commit ``delete`` actions use the same supported write path as uploads
654
+ and are applied atomically in a single commit.
655
+ """
656
+ paths = list(dict.fromkeys(path for path in file_paths if path))
657
+ if not paths:
658
+ raise InvalidParameter(
659
+ "file_paths must contain at least one non-empty path.")
660
+
661
+ self._commit_with_retry(
662
+ repo_id=repo_id,
663
+ repo_type=repo_type,
664
+ operations=self._build_delete_operations(paths),
665
+ commit_message=commit_message,
666
+ revision=revision,
667
+ )
668
+ return {
669
+ "deleted_files": paths,
670
+ "failed_files": [],
671
+ "total_files": len(paths),
672
+ }
673
+
638
674
  # ------------------------------------------------------------------
639
675
  # Public: upload_folder
640
676
  # ------------------------------------------------------------------
@@ -23,6 +23,7 @@ Design principles
23
23
 
24
24
  from __future__ import annotations
25
25
 
26
+ import fnmatch
26
27
  import time
27
28
  from collections.abc import Iterable, Mapping
28
29
  from pathlib import Path
@@ -46,7 +47,6 @@ from .errors import (
46
47
  AuthenticationError,
47
48
  HubError,
48
49
  InvalidParameter,
49
- NetworkError,
50
50
  NotExistError,
51
51
  NotSupportedError,
52
52
  PermissionDeniedError,
@@ -1670,17 +1670,18 @@ class HubApi:
1670
1670
  self,
1671
1671
  repo_id: str,
1672
1672
  repo_type: RepoTypeLike,
1673
- file_paths: Iterable[str],
1673
+ file_paths: Iterable[str] | str | None = None,
1674
1674
  *,
1675
+ delete_patterns: Iterable[str] | str | None = None,
1675
1676
  commit_message: str | None = None,
1676
1677
  revision: str | None = None,
1677
1678
  ) -> dict:
1678
- """Delete one or more files from a repository.
1679
+ """Delete repository files selected by paths or glob patterns.
1679
1680
 
1680
- .. note::
1681
- File deletion is restricted by the server to cookie-based session
1682
- auth (interactive login). API tokens (``ms-...``) may receive a 401
1683
- "token no longer supports deletion operations" error.
1681
+ The direct repository DELETE endpoints reject API-token authentication.
1682
+ This method resolves optional glob patterns against the remote file list,
1683
+ then sends commit ``delete`` actions through the same supported write
1684
+ path as :meth:`upload_file` and :meth:`upload_folder`.
1684
1685
 
1685
1686
  Parameters
1686
1687
  ----------
@@ -1688,53 +1689,80 @@ class HubApi:
1688
1689
  Canonical ``owner/name`` identifier.
1689
1690
  repo_type : str or RepoType
1690
1691
  Repository type.
1691
- file_paths : iterable of str
1692
- Paths of files to remove. Empty entries are ignored.
1692
+ file_paths : iterable of str or str, optional
1693
+ Explicit repository-relative paths to remove.
1694
+ delete_patterns : iterable of str or str, optional
1695
+ Glob patterns matched against remote file paths. For example,
1696
+ ``"*.json"`` matches JSON files at any repository depth.
1693
1697
  commit_message : str, optional
1694
- Unused (kept for API compatibility).
1698
+ Message for the delete commit. Defaults to ``"Delete files"``.
1695
1699
  revision : str, optional
1696
1700
  Branch to delete from. Defaults to ``"master"``.
1697
1701
 
1698
1702
  Returns
1699
1703
  -------
1700
1704
  dict
1701
- Summary with ``deleted_files`` and ``failed_files`` lists.
1702
-
1703
- Raises
1704
- ------
1705
- InvalidParameter
1706
- When ``file_paths`` resolves to an empty list.
1707
-
1708
- Examples
1709
- --------
1710
- >>> api.delete_files(
1711
- ... "alice/llama-7b",
1712
- ... "model",
1713
- ... ["old_weights.bin", "deprecated/config.json"],
1714
- ... )
1705
+ Summary with ``deleted_files``, ``failed_files``, and ``total_files``.
1706
+ A pattern with no remote matches returns an empty successful summary.
1715
1707
  """
1716
1708
  rt = self._normalize_repo_type(repo_type)
1717
- paths = [p for p in file_paths if p]
1709
+ paths = self._normalize_delete_values(file_paths, "file_paths")
1710
+ patterns = self._normalize_delete_values(
1711
+ delete_patterns, "delete_patterns")
1712
+ if not paths and not patterns:
1713
+ raise InvalidParameter(
1714
+ "Provide at least one file path or delete pattern.")
1715
+
1716
+ resolved_revision = revision or "master"
1717
+ if patterns:
1718
+ remote_paths = [
1719
+ file.path
1720
+ for file in self.list_repo_files(
1721
+ repo_id,
1722
+ rt,
1723
+ revision=resolved_revision,
1724
+ recursive=True,
1725
+ )
1726
+ if file.path and file.type != "tree"
1727
+ ]
1728
+ paths.extend(
1729
+ path for path in remote_paths
1730
+ if any(fnmatch.fnmatchcase(path, pattern)
1731
+ for pattern in patterns))
1732
+
1733
+ paths = list(dict.fromkeys(paths))
1718
1734
  if not paths:
1719
- raise InvalidParameter("file_paths must contain at least one non-empty path.")
1735
+ return {
1736
+ "deleted_files": [],
1737
+ "failed_files": [],
1738
+ "total_files": 0,
1739
+ }
1720
1740
 
1721
- deleted, failed = [], []
1722
- for p in paths:
1723
- try:
1724
- self.legacy.delete_file(
1725
- repo_id=repo_id,
1726
- repo_type=str(rt),
1727
- file_path=p,
1728
- revision=revision or "master",
1729
- )
1730
- deleted.append(p)
1731
- except (AuthenticationError, NetworkError):
1732
- failed.append(p)
1733
- raise
1734
- except Exception:
1735
- failed.append(p)
1741
+ return self.uploader.delete_files(
1742
+ repo_id=repo_id,
1743
+ repo_type=str(rt),
1744
+ file_paths=paths,
1745
+ commit_message=commit_message or "Delete files",
1746
+ revision=resolved_revision,
1747
+ )
1736
1748
 
1737
- return {"deleted_files": deleted, "failed_files": failed, "total_files": len(paths)}
1749
+ @staticmethod
1750
+ def _normalize_delete_values(
1751
+ values: Iterable[str] | str | None,
1752
+ parameter_name: str,
1753
+ ) -> list[str]:
1754
+ """Normalize a delete path or glob-pattern argument."""
1755
+ if values is None:
1756
+ return []
1757
+ raw_values = [values] if isinstance(values, str) else list(values)
1758
+ normalized: list[str] = []
1759
+ for value in raw_values:
1760
+ if not isinstance(value, str):
1761
+ raise InvalidParameter(
1762
+ f"{parameter_name} must contain only strings.")
1763
+ if value:
1764
+ normalized.append(value)
1765
+ return normalized
1738
1766
 
1739
1767
  # ==================================================================
1740
1768
  # Versioning
@@ -195,6 +195,36 @@ class LegacyHubApi:
195
195
  result = [f for f in result if f["Path"] == prefix or str(f["Path"]).startswith(prefix + "/")]
196
196
  return result
197
197
 
198
+ def delete_files(
199
+ self,
200
+ repo_id: str,
201
+ repo_type: str | RepoType = RepoType.MODEL,
202
+ delete_patterns: str | list[str] | None = None,
203
+ *,
204
+ file_paths: str | list[str] | None = None,
205
+ revision: str | None = DEFAULT_DATASET_REVISION,
206
+ commit_message: str | None = None,
207
+ endpoint: str | None = None,
208
+ token: str | None = None,
209
+ ) -> dict:
210
+ """Delete files selected by legacy glob patterns or explicit paths.
211
+
212
+ ``delete_patterns`` preserves the historical ``modelscope.hub.api``
213
+ contract. Patterns are resolved against the remote repository by the
214
+ modelscope-hub facade before it commits atomic delete actions.
215
+ """
216
+ api = self._api
217
+ if token or endpoint:
218
+ api = HubApi(endpoint=endpoint or self._endpoint, token=token)
219
+ return api.delete_files(
220
+ repo_id,
221
+ repo_type,
222
+ file_paths=file_paths,
223
+ delete_patterns=delete_patterns,
224
+ commit_message=commit_message,
225
+ revision=revision,
226
+ )
227
+
198
228
  def create_repo(
199
229
  self,
200
230
  repo_id: str,
@@ -1,3 +1,3 @@
1
1
  """Version information for modelscope_hub."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelscope-hub
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: The official Python client to connect with ModelScope Hub.
5
5
  License: Apache-2.0
6
6
  Keywords: modelscope,hub,sdk,openapi,machine-learning
@@ -55,6 +55,7 @@ src/modelscope_hub/utils/time_utils.py
55
55
  tests/test_agent_idp.py
56
56
  tests/test_agent_idp_keys.py
57
57
  tests/test_cache_verification.py
58
+ tests/test_compat_delete_files.py
58
59
  tests/test_compat_get_model_files.py
59
60
  tests/test_compat_snapshot_download.py
60
61
  tests/test_compat_studio.py
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest import mock
4
+
5
+ from modelscope_hub.compat import LegacyHubApi
6
+ from modelscope_hub.constants import RepoType
7
+
8
+
9
+ def test_legacy_hub_api_preserves_delete_patterns_contract() -> None:
10
+ api = LegacyHubApi(endpoint="https://modelscope.cn", token="test-token")
11
+ expected = {
12
+ "deleted_files": ["config.json"],
13
+ "failed_files": [],
14
+ "total_files": 1,
15
+ }
16
+
17
+ with mock.patch.object(api._api, "delete_files", return_value=expected) as delete_files:
18
+ result = api.delete_files(
19
+ repo_id="owner/repo",
20
+ repo_type=RepoType.MODEL,
21
+ delete_patterns="*.json",
22
+ revision="main",
23
+ commit_message="Delete JSON files",
24
+ )
25
+
26
+ delete_files.assert_called_once_with(
27
+ "owner/repo",
28
+ RepoType.MODEL,
29
+ file_paths=None,
30
+ delete_patterns="*.json",
31
+ commit_message="Delete JSON files",
32
+ revision="main",
33
+ )
34
+ assert result == expected
35
+
36
+
37
+ def test_legacy_hub_api_delete_files_supports_explicit_paths() -> None:
38
+ api = LegacyHubApi(token="test-token")
39
+
40
+ with mock.patch.object(api._api, "delete_files", return_value={}) as delete_files:
41
+ api.delete_files(
42
+ "owner/repo",
43
+ repo_type="dataset",
44
+ file_paths=["data/old.json"],
45
+ )
46
+
47
+ delete_files.assert_called_once_with(
48
+ "owner/repo",
49
+ "dataset",
50
+ file_paths=["data/old.json"],
51
+ delete_patterns=None,
52
+ commit_message=None,
53
+ revision="master",
54
+ )
@@ -4,11 +4,15 @@ import base64
4
4
  import hashlib
5
5
  import json
6
6
  from pathlib import Path
7
+ from types import SimpleNamespace
7
8
  from unittest.mock import MagicMock
8
9
 
10
+ import pytest
11
+
9
12
  import modelscope_hub._upload as upload_module
13
+ from modelscope_hub import HubApi
10
14
  from modelscope_hub._upload import UploadManager
11
- from modelscope_hub.errors import NetworkError
15
+ from modelscope_hub.errors import InvalidParameter, NetworkError
12
16
 
13
17
 
14
18
  def _make_manager() -> tuple[UploadManager, MagicMock]:
@@ -27,6 +31,142 @@ def _make_manager() -> tuple[UploadManager, MagicMock]:
27
31
  return UploadManager(client, MagicMock()), client
28
32
 
29
33
 
34
+ @pytest.mark.parametrize("repo_type", ["model", "dataset"])
35
+ def test_delete_files_commits_atomic_delete_actions(repo_type: str) -> None:
36
+ manager, client = _make_manager()
37
+
38
+ result = manager.delete_files(
39
+ repo_id="owner/repo",
40
+ repo_type=repo_type,
41
+ file_paths=["old.bin", "nested/deprecated.json", "old.bin"],
42
+ commit_message="Remove obsolete files",
43
+ revision="main",
44
+ )
45
+
46
+ client.create_commit.assert_called_once_with(
47
+ repo_id="owner/repo",
48
+ repo_type=repo_type,
49
+ operations=[
50
+ {
51
+ "action": "delete",
52
+ "path": "old.bin",
53
+ "type": "normal",
54
+ "size": 0,
55
+ "sha256": "",
56
+ "content": "",
57
+ "encoding": "",
58
+ },
59
+ {
60
+ "action": "delete",
61
+ "path": "nested/deprecated.json",
62
+ "type": "normal",
63
+ "size": 0,
64
+ "sha256": "",
65
+ "content": "",
66
+ "encoding": "",
67
+ },
68
+ ],
69
+ commit_message="Remove obsolete files",
70
+ revision="main",
71
+ )
72
+ assert result == {
73
+ "deleted_files": ["old.bin", "nested/deprecated.json"],
74
+ "failed_files": [],
75
+ "total_files": 2,
76
+ }
77
+
78
+
79
+ def test_delete_files_rejects_empty_paths() -> None:
80
+ manager, client = _make_manager()
81
+
82
+ with pytest.raises(InvalidParameter, match="at least one"):
83
+ manager.delete_files(
84
+ repo_id="owner/repo", repo_type="model", file_paths=["", ""])
85
+
86
+ client.create_commit.assert_not_called()
87
+
88
+
89
+ @pytest.mark.parametrize("repo_type", ["model", "dataset"])
90
+ @pytest.mark.parametrize("file_paths", [["", "old.bin"], "old.bin"])
91
+ def test_hub_api_delete_files_delegates_to_upload_manager(
92
+ repo_type: str, file_paths: list[str] | str
93
+ ) -> None:
94
+ api = HubApi(token="test-token")
95
+ api._uploader = MagicMock()
96
+ api._uploader.delete_files.return_value = {"deleted_files": ["old.bin"]}
97
+
98
+ result = api.delete_files(
99
+ "owner/repo",
100
+ repo_type,
101
+ file_paths,
102
+ commit_message="Remove obsolete files",
103
+ revision="main",
104
+ )
105
+
106
+ api._uploader.delete_files.assert_called_once_with(
107
+ repo_id="owner/repo",
108
+ repo_type=repo_type,
109
+ file_paths=["old.bin"],
110
+ commit_message="Remove obsolete files",
111
+ revision="main",
112
+ )
113
+ assert result == {"deleted_files": ["old.bin"]}
114
+
115
+
116
+ @pytest.mark.parametrize("repo_type", ["model", "dataset"])
117
+ def test_hub_api_delete_patterns_resolve_remote_paths(repo_type: str) -> None:
118
+ api = HubApi(token="test-token")
119
+ api._uploader = MagicMock()
120
+ api._uploader.delete_files.return_value = {
121
+ "deleted_files": ["config.json", "nested/metadata.json"],
122
+ "failed_files": [],
123
+ "total_files": 2,
124
+ }
125
+ api.list_repo_files = MagicMock(
126
+ return_value=[
127
+ SimpleNamespace(path="config.json", type="blob"),
128
+ SimpleNamespace(path="nested/metadata.json", type="blob"),
129
+ SimpleNamespace(path="weights.bin", type="blob"),
130
+ SimpleNamespace(path="nested", type="tree"),
131
+ ])
132
+
133
+ result = api.delete_files(
134
+ "owner/repo",
135
+ repo_type,
136
+ delete_patterns="*.json",
137
+ commit_message="Remove JSON files",
138
+ revision="main",
139
+ )
140
+
141
+ api.list_repo_files.assert_called_once_with(
142
+ "owner/repo", repo_type, revision="main", recursive=True)
143
+ api._uploader.delete_files.assert_called_once_with(
144
+ repo_id="owner/repo",
145
+ repo_type=repo_type,
146
+ file_paths=["config.json", "nested/metadata.json"],
147
+ commit_message="Remove JSON files",
148
+ revision="main",
149
+ )
150
+ assert result["total_files"] == 2
151
+
152
+
153
+ def test_hub_api_delete_patterns_with_no_match_is_noop() -> None:
154
+ api = HubApi(token="test-token")
155
+ api._uploader = MagicMock()
156
+ api.list_repo_files = MagicMock(
157
+ return_value=[SimpleNamespace(path="weights.bin", type="blob")])
158
+
159
+ result = api.delete_files(
160
+ "owner/repo", "model", delete_patterns="*.json")
161
+
162
+ api._uploader.delete_files.assert_not_called()
163
+ assert result == {
164
+ "deleted_files": [],
165
+ "failed_files": [],
166
+ "total_files": 0,
167
+ }
168
+
169
+
30
170
  def test_upload_file_normal_commits_inline_without_blob_api() -> None:
31
171
  manager, client = _make_manager()
32
172
 
File without changes
File without changes
File without changes