ap-client 0.5.3.dev5__tar.gz → 0.5.3.dev6__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 (23) hide show
  1. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/PKG-INFO +1 -1
  2. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/irepo_sdk.py +2 -2
  3. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/ossutil_commands.py +173 -44
  4. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/ossutil_io.py +7 -1
  5. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/pyproject.toml +1 -1
  6. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/__init__.py +0 -0
  7. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/akpool_commands.py +0 -0
  8. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/api.py +0 -0
  9. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/cli.py +0 -0
  10. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/config.py +0 -0
  11. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/dataset_commands.py +0 -0
  12. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/dataset_register.py +0 -0
  13. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/exporter.py +0 -0
  14. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/fs_commands.py +0 -0
  15. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/instance_commands.py +0 -0
  16. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/managed_ak.py +0 -0
  17. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/manifest_view.py +0 -0
  18. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/profile_commands.py +0 -0
  19. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/repo_read_guard.py +0 -0
  20. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/share_api.py +0 -0
  21. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/split_publish.py +0 -0
  22. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/tbb.py +0 -0
  23. {ap_client-0.5.3.dev5 → ap_client-0.5.3.dev6}/ap_client/waiter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ap-client
3
- Version: 0.5.3.dev5
3
+ Version: 0.5.3.dev6
4
4
  Summary: Agent Platform API Client & CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: irepo-artifact==0.2.1
@@ -1001,9 +1001,9 @@ def load_ossutil_repo() -> tuple[Any, dict]:
1001
1001
  cfg = get_config(verbose=_api._verbose_override, cluster=_api._cluster_override)
1002
1002
  cluster = _cluster_from_headers(cfg.headers)
1003
1003
  if not cluster:
1004
- raise ConfigurationError("Set --cluster / AP_CLUSTER before using ap ossutil")
1004
+ raise ConfigurationError("Set --cluster / AP_CLUSTER before using ap oss")
1005
1005
  if not cfg.token_key:
1006
- raise ConfigurationError("Set AP_API_KEY before using ap ossutil")
1006
+ raise ConfigurationError("Set AP_API_KEY before using ap oss")
1007
1007
  transport = Transport(cfg.base_url, cfg.token_key, env=_env_header(cfg.headers))
1008
1008
  if cfg.verbose:
1009
1009
  # STS material must stay redacted even when general HTTP redaction is disabled.
@@ -4,11 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import threading
6
6
  from pathlib import Path
7
- from typing import Optional
7
+ from typing import Annotated, Optional
8
8
 
9
9
  import typer
10
10
 
11
- from .ossutil_io import OssBinding, OssFiles, OssUsageError, direction_for, safe_error
11
+ from .ossutil_io import OssBinding, OssFiles, OssUsageError, safe_error
12
12
 
13
13
  ossutil_app = typer.Typer(
14
14
  help="OSS file operations within the server-configured storage prefix", no_args_is_help=True
@@ -16,7 +16,7 @@ ossutil_app = typer.Typer(
16
16
 
17
17
 
18
18
  def register(app: typer.Typer) -> None:
19
- app.add_typer(ossutil_app, name="ossutil")
19
+ app.add_typer(ossutil_app, name="oss")
20
20
 
21
21
 
22
22
  def _files() -> OssFiles:
@@ -218,38 +218,67 @@ def oss_cat(
218
218
  _fail(exc)
219
219
 
220
220
 
221
- @ossutil_app.command("cp")
222
- def oss_cp(
223
- src: str = typer.Argument(..., help="Local path or full oss:// URI"),
224
- dst: str = typer.Argument(..., help="Local path or full oss:// URI"),
225
- recursive: bool = typer.Option(False, "--recursive", "-r"),
226
- force: bool = typer.Option(False, "--force", "-f", help="Overwrite without confirmation"),
227
- update: bool = typer.Option(False, "--update", "-u", help="Copy only missing or older targets"),
228
- jobs: int = typer.Option(3, "--jobs", "--job", "-j", min=1, max=10000),
229
- parallel: int = typer.Option(4, "--parallel", min=1, max=10000),
230
- bigfile_threshold: int = typer.Option(100 * 1024 * 1024, "--bigfile-threshold", min=0),
231
- part_size: Optional[int] = typer.Option(None, "--part-size", min=1),
232
- checkpoint_dir: Optional[Path] = typer.Option(None, "--checkpoint-dir"),
233
- include: Optional[list[str]] = typer.Option(None, "--include"),
234
- exclude: Optional[list[str]] = typer.Option(None, "--exclude"),
235
- only_current_dir: bool = typer.Option(False, "--only-current-dir"),
236
- disable_ignore_error: bool = typer.Option(False, "--disable-ignore-error"),
237
- disable_crc64: bool = typer.Option(False, "--disable-crc64"),
238
- disable_all_symlink: bool = typer.Option(False, "--disable-all-symlink"),
239
- byte_range: Optional[str] = typer.Option(
240
- None, "--range", help="Download bytes: START-END, START-, or -END"
241
- ),
242
- meta: Optional[str] = typer.Option(
243
- None, "--meta", help="Object metadata: header:value#header:value"
244
- ),
245
- start_time: Optional[int] = typer.Option(None, "--start-time", min=0),
246
- end_time: Optional[int] = typer.Option(None, "--end-time", min=0),
247
- encoding_type: Optional[str] = typer.Option(None, "--encoding-type"),
248
- payer: Optional[str] = typer.Option(None, "--payer"),
249
- output_format: Optional[str] = typer.Option(None, "--output-format", "--format"),
250
- ):
251
- """Upload, download or copy objects. One operand must be a full oss:// URI.
252
- Downloads require private-network access."""
221
+ Recursive = Annotated[bool, typer.Option("--recursive", "-r")]
222
+ Force = Annotated[bool, typer.Option("--force", "-f", help="Overwrite without confirmation")]
223
+ Update = Annotated[bool, typer.Option("--update", "-u", help="Copy only missing or older targets")]
224
+ Jobs = Annotated[int, typer.Option("--jobs", "--job", "-j", min=1, max=10000)]
225
+ Parallel = Annotated[int, typer.Option("--parallel", min=1, max=10000)]
226
+ BigFileThreshold = Annotated[int, typer.Option("--bigfile-threshold", min=0)]
227
+ PartSize = Annotated[Optional[int], typer.Option("--part-size", min=1)]
228
+ CheckpointDir = Annotated[Optional[Path], typer.Option("--checkpoint-dir")]
229
+ Include = Annotated[Optional[list[str]], typer.Option("--include")]
230
+ Exclude = Annotated[Optional[list[str]], typer.Option("--exclude")]
231
+ OnlyCurrentDir = Annotated[bool, typer.Option("--only-current-dir")]
232
+ DisableIgnoreError = Annotated[bool, typer.Option("--disable-ignore-error")]
233
+ DisableCrc64 = Annotated[bool, typer.Option("--disable-crc64")]
234
+ DisableAllSymlink = Annotated[bool, typer.Option("--disable-all-symlink")]
235
+ ByteRange = Annotated[
236
+ Optional[str], typer.Option("--range", help="Download bytes: START-END, START-, or -END")
237
+ ]
238
+ Meta = Annotated[
239
+ Optional[str], typer.Option("--meta", help="Object metadata: header:value#header:value")
240
+ ]
241
+ StartTime = Annotated[Optional[int], typer.Option("--start-time", min=0)]
242
+ EndTime = Annotated[Optional[int], typer.Option("--end-time", min=0)]
243
+ EncodingType = Annotated[Optional[str], typer.Option("--encoding-type")]
244
+ Payer = Annotated[Optional[str], typer.Option("--payer")]
245
+ OutputFormat = Annotated[Optional[str], typer.Option("--output-format", "--format")]
246
+
247
+ #: Operand forms that are always local paths. Everything else (including a bare
248
+ #: relative path) addresses the repo-config prefix.
249
+ _LOCAL_PREFIXES = ("./", "../", "/", "~")
250
+
251
+
252
+ def _looks_local(value: str) -> bool:
253
+ return value in (".", "..", "-") or value.startswith(_LOCAL_PREFIXES)
254
+
255
+
256
+ def _require_remote(value: str, argument: str) -> None:
257
+ if not value.startswith("oss://") and _looks_local(value):
258
+ raise OssUsageError(
259
+ f"{argument} must be an oss:// URI or a repo-relative path; "
260
+ "use get or put for local files"
261
+ )
262
+
263
+
264
+ def _require_local(value: str, argument: str) -> None:
265
+ if value.startswith("oss://"):
266
+ raise OssUsageError(f"{argument} must be a local path; use cp for OSS-to-OSS copies")
267
+
268
+
269
+ def _transfer(direction: str, src: str, dst: str, ctx: typer.Context) -> None:
270
+ options = ctx.params
271
+ recursive = bool(options.get("recursive"))
272
+ force = bool(options.get("force"))
273
+ update = bool(options.get("update"))
274
+ only_current_dir = bool(options.get("only_current_dir"))
275
+ disable_ignore_error = bool(options.get("disable_ignore_error"))
276
+ disable_crc64 = bool(options.get("disable_crc64"))
277
+ disable_all_symlink = bool(options.get("disable_all_symlink"))
278
+ byte_range = options.get("byte_range")
279
+ meta = options.get("meta")
280
+ start_time = options.get("start_time")
281
+ end_time = options.get("end_time")
253
282
  lock = threading.Lock()
254
283
 
255
284
  def progress(path, done, total):
@@ -275,12 +304,20 @@ def oss_cp(
275
304
  typer.echo("Enter y or n", err=True)
276
305
 
277
306
  try:
278
- fmt = _format(output_format)
307
+ fmt = _format(options.get("output_format"))
279
308
  if start_time is not None and end_time is not None and start_time > end_time:
280
309
  raise OssUsageError("--start-time cannot be later than --end-time")
281
- direction_for(src, dst)
310
+ if direction == "download":
311
+ _require_remote(src, "REMOTE")
312
+ _require_local(dst, "LOCAL")
313
+ elif direction == "upload":
314
+ _require_local(src, "LOCAL")
315
+ _require_remote(dst, "REMOTE")
316
+ else:
317
+ _require_remote(src, "SRC")
318
+ _require_remote(dst, "DST")
282
319
  files = _files()
283
- _configure(files, encoding_type, payer)
320
+ _configure(files, options.get("encoding_type"), options.get("payer"))
284
321
  files.disable_crc64 = disable_crc64
285
322
  files.disable_all_symlink = disable_all_symlink
286
323
  files.byte_range = None
@@ -309,20 +346,24 @@ def oss_cp(
309
346
  "--meta requires standard content headers or x-oss-meta-* headers"
310
347
  )
311
348
  files.write_headers[key] = value
312
- files.multipart_threshold, files.part_size = bigfile_threshold, part_size
349
+ files.multipart_threshold = options.get("bigfile_threshold", 100 * 1024 * 1024)
350
+ files.part_size = options.get("part_size")
351
+ checkpoint_dir = options.get("checkpoint_dir")
313
352
  if checkpoint_dir is not None:
314
- files.state_dir = checkpoint_dir
353
+ # ctx.params carries the raw value; Typer only converts to Path in the callback.
354
+ files.state_dir = Path(checkpoint_dir)
315
355
  result = files.cp(
316
356
  src,
317
357
  dst,
358
+ direction=direction,
318
359
  recursive=recursive,
319
360
  overwrite=force,
320
- jobs=jobs,
321
- parallel=parallel,
361
+ jobs=options.get("jobs", 3),
362
+ parallel=options.get("parallel", 4),
322
363
  progress=progress,
323
364
  update=update,
324
- include=tuple(include or ()),
325
- exclude=tuple(exclude or ()),
365
+ include=tuple(options.get("include") or ()),
366
+ exclude=tuple(options.get("exclude") or ()),
326
367
  only_current_dir=only_current_dir,
327
368
  stop_on_error=disable_ignore_error,
328
369
  confirm=None if force or update else confirm,
@@ -335,3 +376,91 @@ def oss_cp(
335
376
  _emit(result, fmt)
336
377
  if result["failed_count"]:
337
378
  raise typer.Exit(1)
379
+
380
+
381
+ @ossutil_app.command("get")
382
+ def oss_get(
383
+ ctx: typer.Context,
384
+ remote: str = typer.Argument(..., help="Remote oss:// URI or repo-relative path"),
385
+ local: str = typer.Argument(".", help="Local file or directory to write"),
386
+ recursive: Recursive = False,
387
+ force: Force = False,
388
+ update: Update = False,
389
+ jobs: Jobs = 3,
390
+ parallel: Parallel = 4,
391
+ bigfile_threshold: BigFileThreshold = 100 * 1024 * 1024,
392
+ part_size: PartSize = None,
393
+ checkpoint_dir: CheckpointDir = None,
394
+ include: Include = None,
395
+ exclude: Exclude = None,
396
+ only_current_dir: OnlyCurrentDir = False,
397
+ disable_ignore_error: DisableIgnoreError = False,
398
+ disable_crc64: DisableCrc64 = False,
399
+ byte_range: ByteRange = None,
400
+ start_time: StartTime = None,
401
+ end_time: EndTime = None,
402
+ encoding_type: EncodingType = None,
403
+ payer: Payer = None,
404
+ output_format: OutputFormat = None,
405
+ ):
406
+ """Download objects to the local filesystem. Requires private-network access."""
407
+ _transfer("download", remote, local, ctx)
408
+
409
+
410
+ @ossutil_app.command("put")
411
+ def oss_put(
412
+ ctx: typer.Context,
413
+ local: str = typer.Argument(..., help="Local file or directory to upload"),
414
+ remote: str = typer.Argument(..., help="Remote oss:// URI or repo-relative path"),
415
+ recursive: Recursive = False,
416
+ force: Force = False,
417
+ update: Update = False,
418
+ jobs: Jobs = 3,
419
+ parallel: Parallel = 4,
420
+ bigfile_threshold: BigFileThreshold = 100 * 1024 * 1024,
421
+ part_size: PartSize = None,
422
+ checkpoint_dir: CheckpointDir = None,
423
+ include: Include = None,
424
+ exclude: Exclude = None,
425
+ only_current_dir: OnlyCurrentDir = False,
426
+ disable_ignore_error: DisableIgnoreError = False,
427
+ disable_crc64: DisableCrc64 = False,
428
+ disable_all_symlink: DisableAllSymlink = False,
429
+ meta: Meta = None,
430
+ start_time: StartTime = None,
431
+ end_time: EndTime = None,
432
+ encoding_type: EncodingType = None,
433
+ payer: Payer = None,
434
+ output_format: OutputFormat = None,
435
+ ):
436
+ """Upload a local file or directory to OSS."""
437
+ _transfer("upload", local, remote, ctx)
438
+
439
+
440
+ @ossutil_app.command("cp")
441
+ def oss_cp(
442
+ ctx: typer.Context,
443
+ src: str = typer.Argument(..., help="Remote oss:// URI or repo-relative path"),
444
+ dst: str = typer.Argument(..., help="Remote oss:// URI or repo-relative path"),
445
+ recursive: Recursive = False,
446
+ force: Force = False,
447
+ update: Update = False,
448
+ jobs: Jobs = 3,
449
+ parallel: Parallel = 4,
450
+ bigfile_threshold: BigFileThreshold = 100 * 1024 * 1024,
451
+ part_size: PartSize = None,
452
+ checkpoint_dir: CheckpointDir = None,
453
+ include: Include = None,
454
+ exclude: Exclude = None,
455
+ only_current_dir: OnlyCurrentDir = False,
456
+ disable_ignore_error: DisableIgnoreError = False,
457
+ disable_crc64: DisableCrc64 = False,
458
+ meta: Meta = None,
459
+ start_time: StartTime = None,
460
+ end_time: EndTime = None,
461
+ encoding_type: EncodingType = None,
462
+ payer: Payer = None,
463
+ output_format: OutputFormat = None,
464
+ ):
465
+ """Copy objects within OSS. Use get/put for local transfers."""
466
+ _transfer("copy", src, dst, ctx)
@@ -791,6 +791,7 @@ class OssFiles:
791
791
  src: str,
792
792
  dst: str,
793
793
  *,
794
+ direction: str | None = None,
794
795
  recursive: bool,
795
796
  overwrite: bool,
796
797
  jobs: int,
@@ -805,7 +806,12 @@ class OssFiles:
805
806
  start_time: int | None = None,
806
807
  end_time: int | None = None,
807
808
  ) -> dict:
808
- direction = direction_for(src, dst)
809
+ if direction is None:
810
+ # Library convenience for direct callers; the CLI commands pass the
811
+ # direction explicitly so relative remote paths stay unambiguous.
812
+ direction = direction_for(src, dst)
813
+ elif direction not in ("upload", "download", "copy"):
814
+ raise OssUsageError("direction must be upload, download or copy")
809
815
  remote_src = self.resolve(src, directory=recursive) if direction != "upload" else None
810
816
  remote_dst = None
811
817
  if direction != "download":
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ap-client"
7
- version = "0.5.3.dev5"
7
+ version = "0.5.3.dev6"
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"