modelscope-hub 0.4.3__tar.gz → 0.4.4__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 (81) hide show
  1. {modelscope_hub-0.4.3/src/modelscope_hub.egg-info → modelscope_hub-0.4.4}/PKG-INFO +1 -1
  2. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_legacy_api.py +86 -6
  3. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_openapi.py +19 -1
  4. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_upload.py +769 -88
  5. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/agent/_api.py +1 -1
  6. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/api.py +17 -1
  7. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/constants.py +307 -10
  8. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/version.py +1 -1
  9. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4/src/modelscope_hub.egg-info}/PKG-INFO +1 -1
  10. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub.egg-info/SOURCES.txt +4 -0
  11. modelscope_hub-0.4.4/tests/test_commit_action_limit.py +110 -0
  12. modelscope_hub-0.4.4/tests/test_repo_tree_page_retry.py +143 -0
  13. modelscope_hub-0.4.4/tests/test_upload_batching.py +885 -0
  14. modelscope_hub-0.4.4/tests/test_upload_blob_dedup.py +256 -0
  15. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_upload_config.py +140 -0
  16. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_upload_lfs_gate.py +7 -12
  17. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/LICENSE +0 -0
  18. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/README.md +0 -0
  19. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/pyproject.toml +0 -0
  20. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/setup.cfg +0 -0
  21. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/__init__.py +0 -0
  22. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_cache_manager.py +0 -0
  23. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_download.py +0 -0
  24. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_git.py +0 -0
  25. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/_repository.py +0 -0
  26. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/agent/__init__.py +0 -0
  27. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/agent_idp.py +0 -0
  28. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/__init__.py +0 -0
  29. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/agent.py +0 -0
  30. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/agent_idp.py +0 -0
  31. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/base.py +0 -0
  32. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/cache.py +0 -0
  33. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/compat.py +0 -0
  34. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/deploy.py +0 -0
  35. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/download.py +0 -0
  36. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/login.py +0 -0
  37. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/main.py +0 -0
  38. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/mcp.py +0 -0
  39. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/repo.py +0 -0
  40. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/secret.py +0 -0
  41. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/studio.py +0 -0
  42. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/cli/upload.py +0 -0
  43. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/compat/__init__.py +0 -0
  44. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/compat/constants.py +0 -0
  45. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/compat/file_download.py +0 -0
  46. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/compat/hub_api.py +0 -0
  47. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/compat/snapshot_download.py +0 -0
  48. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/config.py +0 -0
  49. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/errors.py +0 -0
  50. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/py.typed +0 -0
  51. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/types.py +0 -0
  52. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/__init__.py +0 -0
  53. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/file_utils.py +0 -0
  54. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/format.py +0 -0
  55. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/logger.py +0 -0
  56. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/media.py +0 -0
  57. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/patterns.py +0 -0
  58. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub/utils/time_utils.py +0 -0
  59. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub.egg-info/dependency_links.txt +0 -0
  60. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub.egg-info/entry_points.txt +0 -0
  61. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub.egg-info/requires.txt +0 -0
  62. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/src/modelscope_hub.egg-info/top_level.txt +0 -0
  63. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_agent_idp.py +0 -0
  64. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_agent_idp_keys.py +0 -0
  65. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_cache_verification.py +0 -0
  66. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_compat_constants_completeness.py +0 -0
  67. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_compat_delete_files.py +0 -0
  68. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_compat_get_model_files.py +0 -0
  69. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_compat_snapshot_download.py +0 -0
  70. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_compat_studio.py +0 -0
  71. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_config_token.py +0 -0
  72. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_credential_lifecycle.py +0 -0
  73. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_download_lock_path.py +0 -0
  74. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_errors_openapi_codes.py +0 -0
  75. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_legacy_cache_detection.py +0 -0
  76. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_login_failure_paths.py +0 -0
  77. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_login_scoped_token.py +0 -0
  78. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_openapi_coverage.py +0 -0
  79. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_repo_files_truncation.py +0 -0
  80. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/tests/test_user_info.py +0 -0
  81. {modelscope_hub-0.4.3 → modelscope_hub-0.4.4}/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.3
3
+ Version: 0.4.4
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
@@ -14,6 +14,7 @@ needed for API calls.
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import time
17
18
  import uuid
18
19
  from concurrent.futures import ThreadPoolExecutor, as_completed
19
20
  from typing import IO, Any, BinaryIO
@@ -24,18 +25,28 @@ from requests.adapters import HTTPAdapter, Retry
24
25
 
25
26
  from .constants import (
26
27
  API_CONNECT_TIMEOUT,
28
+ API_CONNECTION_POOL_MAXSIZE,
27
29
  API_MAX_RETRIES,
28
30
  API_TIMEOUT,
29
31
  LEGACY_API_PREFIX,
30
32
  REPO_FILES_TRUNCATION_LIMIT,
31
33
  REPO_TREE_MAX_REQUESTS,
34
+ REPO_TREE_PAGE_MAX_ATTEMPTS,
35
+ REPO_TREE_PAGE_RETRY_MAX_DELAY_SECONDS,
32
36
  REPO_TREE_WALK_WORKERS,
33
37
  UPLOAD_BLOB_CONNECT_TIMEOUT_SECONDS,
34
38
  UPLOAD_BLOB_READ_TIMEOUT_SECONDS,
35
39
  UPLOAD_HTTP_RETRY_ALLOWED_METHODS,
36
40
  RepoType,
37
41
  )
38
- from .errors import InvalidParameter, NetworkError, RequestTimeoutError, ServerError, raise_for_status
42
+ from .errors import (
43
+ InvalidParameter,
44
+ NetworkError,
45
+ PermissionDeniedError,
46
+ RequestTimeoutError,
47
+ ServerError,
48
+ raise_for_status,
49
+ )
39
50
  from .utils.logger import get_logger
40
51
 
41
52
  logger = get_logger("legacy_api")
@@ -100,6 +111,10 @@ class LegacyClient:
100
111
  self._endpoint = endpoint.rstrip("/")
101
112
  self._timeout: int | tuple[int, int] = (API_CONNECT_TIMEOUT, timeout)
102
113
  self._session_authenticated = False
114
+ # Set once any file-tree read succeeds. From then on a 403 on a tree
115
+ # request cannot be an authorization result, so it is retried instead of
116
+ # aborting an enumeration that spans hundreds of requests.
117
+ self._tree_reads_ok = False
103
118
 
104
119
  self._session = requests.Session()
105
120
  if user_agent:
@@ -110,7 +125,11 @@ class LegacyClient:
110
125
  status_forcelist=[429, 500, 502, 503, 504],
111
126
  allowed_methods=UPLOAD_HTTP_RETRY_ALLOWED_METHODS,
112
127
  )
113
- adapter = HTTPAdapter(max_retries=retry)
128
+ adapter = HTTPAdapter(
129
+ max_retries=retry,
130
+ pool_connections=API_CONNECTION_POOL_MAXSIZE,
131
+ pool_maxsize=API_CONNECTION_POOL_MAXSIZE,
132
+ )
114
133
  self._session.mount("https://", adapter)
115
134
  self._session.mount("http://", adapter)
116
135
 
@@ -394,7 +413,12 @@ class LegacyClient:
394
413
  params["Root"] = root
395
414
 
396
415
  suffix = "repo/tree" if _is_dataset(repo_type) else "repo/files"
397
- resp = self._request("GET", f"{segment}/{repo_id}/{suffix}", params=params)
416
+ resp = self._request_repo_tree(
417
+ f"{segment}/{repo_id}/{suffix}",
418
+ params,
419
+ repo_id=repo_id,
420
+ authorized=self._tree_reads_ok,
421
+ )
398
422
  data = self._json_data(resp)
399
423
  if isinstance(data, list):
400
424
  return data
@@ -567,6 +591,13 @@ class LegacyClient:
567
591
  Datasets can have millions of files, so this method pages through
568
592
  ``GET /api/v1/datasets/{repo_id}/repo/tree`` with
569
593
  ``PageNumber``/``PageSize`` params.
594
+
595
+ A page occasionally answers ``403 无权访问该数据集`` on a repository the
596
+ caller demonstrably can read. Once any page has succeeded the credential
597
+ is proven, so a later-page denial is a server-side hiccup rather than an
598
+ authorization result, and it is retried instead of discarding every page
599
+ collected so far -- a large listing spans hundreds of pages, which makes
600
+ hitting it near-certain.
570
601
  """
571
602
  all_files: list[dict] = []
572
603
  page_number = 1
@@ -579,10 +610,11 @@ class LegacyClient:
579
610
  }
580
611
  if root_path and root_path != "/":
581
612
  params["Root"] = root_path
582
- resp = self._request(
583
- "GET",
613
+ resp = self._request_repo_tree(
584
614
  f"datasets/{repo_id}/repo/tree",
585
- params=params,
615
+ params,
616
+ repo_id=repo_id,
617
+ authorized=self._tree_reads_ok,
586
618
  )
587
619
  data = self._json_data(resp)
588
620
  if isinstance(data, list):
@@ -598,6 +630,54 @@ class LegacyClient:
598
630
  page_number += 1
599
631
  return all_files
600
632
 
633
+ def _request_repo_tree(
634
+ self,
635
+ path: str,
636
+ params: dict[str, Any],
637
+ *,
638
+ repo_id: str,
639
+ authorized: bool,
640
+ ) -> Any:
641
+ """Fetch one file-tree listing, retrying a spurious denial.
642
+
643
+ The server intermittently answers a tree request with ``403 无权访问该数据
644
+ 集`` on a repository the caller has just read successfully -- observed on
645
+ both ``PageNumber``-paginated and ``Root``-scoped listings. Enumerating a
646
+ large repository takes hundreds of such requests, so at that scale a
647
+ single-request failure rate is effectively a guaranteed whole-listing
648
+ failure, and it discards every entry gathered so far.
649
+
650
+ ``authorized`` means some tree read already succeeded on this client, so
651
+ the credential is proven and a denial cannot be an authorization result.
652
+ Until then a 403 is taken at face value, keeping a real permission error
653
+ fast and honest.
654
+ """
655
+ last_error: PermissionDeniedError | None = None
656
+ for attempt in range(REPO_TREE_PAGE_MAX_ATTEMPTS):
657
+ try:
658
+ resp = self._request("GET", path, params=params)
659
+ except PermissionDeniedError as error:
660
+ if not authorized:
661
+ raise
662
+ last_error = error
663
+ if attempt < REPO_TREE_PAGE_MAX_ATTEMPTS - 1:
664
+ wait = min(2**attempt, REPO_TREE_PAGE_RETRY_MAX_DELAY_SECONDS)
665
+ logger.warning(
666
+ "Repo %s: tree listing (%s) denied on an already-authorized repo, retrying in %ds ...",
667
+ repo_id,
668
+ params.get("Root") or params.get("PageNumber") or "/",
669
+ wait,
670
+ )
671
+ time.sleep(wait)
672
+ continue
673
+ self._tree_reads_ok = True
674
+ return resp
675
+ raise NetworkError(
676
+ f"Repo {repo_id}: tree listing ({params.get('Root') or params.get('PageNumber') or '/'}) kept "
677
+ f"returning a denial after {REPO_TREE_PAGE_MAX_ATTEMPTS} attempts on an already-authorized "
678
+ f"repo: {last_error}"
679
+ ) from last_error
680
+
601
681
  # ------------------------------------------------------------------
602
682
  # Revisions
603
683
  # ------------------------------------------------------------------
@@ -30,9 +30,17 @@ from typing import Any, BinaryIO
30
30
  from urllib.parse import urljoin, urlsplit
31
31
 
32
32
  import requests
33
+ from requests.adapters import HTTPAdapter
33
34
 
34
35
  from .config import HubConfig, get_default_config
35
- from .constants import API_CONNECT_TIMEOUT, API_MAX_RETRIES, API_TIMEOUT, OPENAPI_PREFIX, TokenScope
36
+ from .constants import (
37
+ API_CONNECT_TIMEOUT,
38
+ API_CONNECTION_POOL_MAXSIZE,
39
+ API_MAX_RETRIES,
40
+ API_TIMEOUT,
41
+ OPENAPI_PREFIX,
42
+ TokenScope,
43
+ )
36
44
  from .errors import (
37
45
  APIError,
38
46
  AuthenticationError,
@@ -200,6 +208,16 @@ class OpenAPIClient:
200
208
  ) -> None:
201
209
  self._config = config or get_default_config()
202
210
  self._session = session or requests.Session()
211
+ if session is None:
212
+ # A bare Session caps the pool at urllib3's default of 10, which is
213
+ # below the concurrency bulk transfers use; the excess connections
214
+ # are discarded and pay for a new TLS handshake on next use.
215
+ adapter = HTTPAdapter(
216
+ pool_connections=API_CONNECTION_POOL_MAXSIZE,
217
+ pool_maxsize=API_CONNECTION_POOL_MAXSIZE,
218
+ )
219
+ self._session.mount("https://", adapter)
220
+ self._session.mount("http://", adapter)
203
221
  self._timeout: float | tuple[float, float] = (
204
222
  float(timeout) if timeout is not None else (float(API_CONNECT_TIMEOUT), float(API_TIMEOUT))
205
223
  )