ap-client 0.6.1.dev11__tar.gz → 0.6.1.dev12__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 (27) hide show
  1. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/PKG-INFO +1 -1
  2. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/fs_transfer.py +44 -2
  3. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/pyproject.toml +1 -1
  4. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/__init__.py +0 -0
  5. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/akpool_commands.py +0 -0
  6. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/api.py +0 -0
  7. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/cli.py +0 -0
  8. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/config.py +0 -0
  9. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/dataset_commands.py +0 -0
  10. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/dataset_name.py +0 -0
  11. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/dataset_register.py +0 -0
  12. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/dfs_commands.py +0 -0
  13. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/exporter.py +0 -0
  14. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/fs_commands.py +0 -0
  15. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/instance_commands.py +0 -0
  16. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/instance_options.py +0 -0
  17. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/irepo_sdk.py +0 -0
  18. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/managed_ak.py +0 -0
  19. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/manifest_view.py +0 -0
  20. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/ossutil_hash.py +0 -0
  21. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/ossutil_io.py +0 -0
  22. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/profile_commands.py +0 -0
  23. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/repo_read_guard.py +0 -0
  24. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/share_api.py +0 -0
  25. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/split_publish.py +0 -0
  26. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/tbb.py +0 -0
  27. {ap_client-0.6.1.dev11 → ap_client-0.6.1.dev12}/ap_client/waiter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ap-client
3
- Version: 0.6.1.dev11
3
+ Version: 0.6.1.dev12
4
4
  Summary: Agent Platform API Client & CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: irepo-artifact==0.2.2
@@ -24,6 +24,20 @@ from .ossutil_io import OssOperationError, _WriteBucket
24
24
  PART_SIZE = 8 * 1024 * 1024
25
25
 
26
26
 
27
+ def _scope_denied(exc: Exception) -> bool:
28
+ """Only explicit scope refusals allow a more specific authorization request."""
29
+ seen = set()
30
+ cause = exc
31
+ while cause is not None and id(cause) not in seen:
32
+ seen.add(id(cause))
33
+ if getattr(cause, "biz_code", None) == 94023: # user-data has no grant
34
+ return True
35
+ if getattr(cause, "status", None) == 403 and getattr(cause, "code", None) == "AccessDenied":
36
+ return True
37
+ cause = cause.__cause__
38
+ return False
39
+
40
+
27
41
  class TransferProgress:
28
42
  def __init__(self, description: str, enabled: bool | None):
29
43
  console = Console(stderr=True, markup=False, highlight=False)
@@ -125,6 +139,7 @@ class UserDataTransfer:
125
139
  from instance_repo._routing import _OperationIntent
126
140
 
127
141
  source = Path(source)
142
+ remote = self.repo._clean_user_data_subpath(remote)
128
143
  base = self._key(uid, remote)
129
144
  directory = source.is_dir()
130
145
  files = []
@@ -145,6 +160,7 @@ class UserDataTransfer:
145
160
  workers = min(self.concurrency, max(1, len(files)))
146
161
  part_threads = max(1, self.concurrency // workers)
147
162
  report = TransferProgress("Uploading", self.progress).start(sizes)
163
+ directory_scope = base.rstrip("/") + "/" if directory else None
148
164
 
149
165
  def transfer(item):
150
166
  local, key = item
@@ -170,14 +186,40 @@ class UserDataTransfer:
170
186
  )
171
187
  return {"key": key, "skipped": False}
172
188
 
173
- # Exact object scope retains subdirectory/file ACL behavior.
174
- result = self.content._execute_oss(_OperationIntent.NON_DOWNLOAD, key, None, upload)
189
+ scope = directory_scope or key
190
+ try:
191
+ result = self.content._execute_oss(
192
+ _OperationIntent.NON_DOWNLOAD, scope, None, upload
193
+ )
194
+ except Exception as exc:
195
+ if scope == key or not _scope_denied(exc):
196
+ raise
197
+ # A directory token may deny a reader subtree that contains a
198
+ # more specific writer grant. Re-authorize only this object;
199
+ # never ignore a Deny or request a broader ancestor token.
200
+ result = self.content._execute_oss(_OperationIntent.NON_DOWNLOAD, key, None, upload)
175
201
  report.finish(key, skipped=result["skipped"])
176
202
  return result
177
203
 
178
204
  with report:
179
205
  if not files:
180
206
  return []
207
+ if directory_scope is not None:
208
+ try:
209
+ # Prime the SDK's thread-safe, expiry-aware credential cache.
210
+ # The server includes descendant reader Deny rules in this
211
+ # directory policy. Never reconstruct that policy locally.
212
+ credentials = self.content._credentials(directory_scope, None)
213
+ except Exception as exc:
214
+ if not _scope_denied(exc):
215
+ raise
216
+ # File-only grants remain usable without a parent grant.
217
+ directory_scope = None
218
+ else:
219
+ if credentials.get("prefix") != directory_scope:
220
+ raise OssOperationError(
221
+ "Directory credential scope differs from the requested directory"
222
+ )
181
223
  if workers == 1:
182
224
  outcomes = [transfer(item) for item in files]
183
225
  else:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ap-client"
7
- version = "0.6.1.dev11"
7
+ version = "0.6.1.dev12"
8
8
  description = "Agent Platform API Client & CLI"
9
9
  readme = { text = "A lightweight Python SDK and command line interface for Agent Platform. It provides helpers for configuring API access and managing templates, datasets, jobs, and groups.", content-type = "text/markdown" }
10
10
  requires-python = ">=3.10"