ap-client 0.5.3.dev2__tar.gz → 0.5.3.dev4__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.dev2 → ap_client-0.5.3.dev4}/PKG-INFO +1 -1
  2. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/ossutil_commands.py +67 -16
  3. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/ossutil_io.py +13 -13
  4. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/pyproject.toml +1 -1
  5. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/__init__.py +0 -0
  6. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/akpool_commands.py +0 -0
  7. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/api.py +0 -0
  8. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/cli.py +0 -0
  9. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/config.py +0 -0
  10. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/dataset_commands.py +0 -0
  11. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/dataset_register.py +0 -0
  12. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/exporter.py +0 -0
  13. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/fs_commands.py +0 -0
  14. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/instance_commands.py +0 -0
  15. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/irepo_sdk.py +0 -0
  16. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/managed_ak.py +0 -0
  17. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/manifest_view.py +0 -0
  18. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/profile_commands.py +0 -0
  19. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/repo_read_guard.py +0 -0
  20. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/share_api.py +0 -0
  21. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/split_publish.py +0 -0
  22. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/ap_client/tbb.py +0 -0
  23. {ap_client-0.5.3.dev2 → ap_client-0.5.3.dev4}/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.dev2
3
+ Version: 0.5.3.dev4
4
4
  Summary: Agent Platform API Client & CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: irepo-artifact==0.2.1
@@ -8,7 +8,7 @@ from typing import Optional
8
8
 
9
9
  import typer
10
10
 
11
- from .ossutil_io import OssBinding, OssFiles, OssUsageError, safe_error
11
+ from .ossutil_io import OssBinding, OssFiles, OssUsageError, direction_for, 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
@@ -42,16 +42,62 @@ def _format(value: Optional[str]) -> str:
42
42
  return _normalize_output_format(value, keep_table=True)
43
43
 
44
44
 
45
- def _emit(value: dict, output_format: str, *, short: bool = False) -> None:
45
+ def _tree(entries: list[dict], root: str) -> None:
46
+ from rich.console import Console
47
+ from rich.text import Text
48
+ from rich.tree import Tree
49
+
50
+ base = root.rstrip("/") + "/" if root else ""
51
+ if entries and not all(row["path"].startswith(base) for row in entries):
52
+ base = root.rsplit("/", 1)[0] + "/" if "/" in root else ""
53
+ tree = Tree(Text(base or "./", style="bold cyan"), guide_style="dim")
54
+ nodes: dict = {}
55
+ for row in entries:
56
+ relative = row["path"][len(base) :] if row["path"].startswith(base) else row["path"]
57
+ if not relative.rstrip("/"):
58
+ continue
59
+ parts = relative.rstrip("/").split("/")
60
+ children = nodes
61
+ for index, part in enumerate(parts):
62
+ directory = (
63
+ index < len(parts) - 1 or row["type"] == "directory" or row["path"].endswith("/")
64
+ )
65
+ key = (part, directory)
66
+ node = children.setdefault(key, {"children": {}})
67
+ if index == len(parts) - 1:
68
+ node["size"] = row.get("size")
69
+ children = node["children"]
70
+
71
+ def add(parent, children):
72
+ for (name, directory), node in sorted(
73
+ children.items(), key=lambda item: (not item[0][1], item[0][0].casefold(), item[0][0])
74
+ ):
75
+ label = Text(name + ("/" if directory else ""), style="bold blue" if directory else "")
76
+ if not directory and node.get("size") is not None:
77
+ size = float(node["size"])
78
+ unit = "B"
79
+ for unit in ("B", "KiB", "MiB", "GiB", "TiB"):
80
+ if size < 1024 or unit == "TiB":
81
+ break
82
+ size /= 1024
83
+ label.append(f" {int(size) if unit == 'B' else f'{size:.1f}'} {unit}", style="dim")
84
+ add(parent.add(label), node["children"])
85
+
86
+ add(tree, nodes)
87
+ Console().print(tree)
88
+
89
+
90
+ def _emit(value: dict, output_format: str, *, short: bool = False, root: str = "") -> None:
46
91
  if output_format in ("json", "yaml"):
47
92
  from .cli import _print_formatted
48
93
 
49
94
  _print_formatted(value, output_format)
50
95
  elif "entries" in value:
51
- for row in value["entries"]:
52
- typer.echo(
53
- row["path"] if short else f"{row['path']}\t{row.get('size', '-')}\t{row['type']}"
54
- )
96
+ if short:
97
+ for row in value["entries"]:
98
+ typer.echo(row["path"])
99
+ else:
100
+ _tree(value["entries"], root)
55
101
  if value["is_truncated"]:
56
102
  typer.echo(
57
103
  "Listing truncated; continue with --marker " + value["next_marker"], err=True
@@ -88,6 +134,7 @@ def _fail(exc: Exception) -> None:
88
134
  def oss_ls(
89
135
  path: str = typer.Argument(..., help="Full oss:// URI or prefix relative to repo-config"),
90
136
  directory: bool = typer.Option(False, "--directory", "-d", help="List only the current level"),
137
+ recursive: bool = typer.Option(False, "--recursive", "-r", help="Expand all directory levels"),
91
138
  short: bool = typer.Option(False, "--short-format", "-s", help="Print only paths"),
92
139
  limited_num: Optional[int] = typer.Option(None, "--limited-num", min=1),
93
140
  marker: str = typer.Option("", "--marker", help="Resume after this OSS URI or relative path"),
@@ -97,15 +144,20 @@ def oss_ls(
97
144
  payer: Optional[str] = typer.Option(None, "--payer"),
98
145
  output_format: Optional[str] = typer.Option(None, "--output-format", "--format"),
99
146
  ):
100
- """List matching objects recursively; use -d for one directory level."""
147
+ """Show one directory level as a tree; use -r to expand all levels."""
101
148
  try:
102
149
  fmt = _format(output_format)
150
+ if recursive and directory:
151
+ raise OssUsageError("Use either --recursive or --directory")
103
152
  files = _files()
104
153
  _configure(files, encoding_type, payer)
154
+ remote = (
155
+ files.resolve(path, prefix=True) if recursive else files.resolve(path, directory=True)
156
+ )
105
157
  _emit(
106
158
  files.list(
107
- files.resolve(path, prefix=True),
108
- recursive=not directory,
159
+ remote,
160
+ recursive=recursive,
109
161
  limit=limited_num,
110
162
  marker=marker,
111
163
  include=tuple(include or ()),
@@ -113,6 +165,7 @@ def oss_ls(
113
165
  ),
114
166
  fmt,
115
167
  short=short,
168
+ root=remote.path,
116
169
  )
117
170
  except Exception as exc:
118
171
  _fail(exc)
@@ -167,11 +220,8 @@ def oss_cat(
167
220
 
168
221
  @ossutil_app.command("cp")
169
222
  def oss_cp(
170
- src: str = typer.Argument(..., help="Local path, full OSS URI or remote relative path"),
171
- dst: str = typer.Argument(..., help="Local path, full OSS URI or remote relative path"),
172
- direction: Optional[str] = typer.Option(
173
- None, "--direction", help="AP extension for two relative paths: upload/download/copy"
174
- ),
223
+ src: str = typer.Argument(..., help="Local path or full oss:// URI"),
224
+ dst: str = typer.Argument(..., help="Local path or full oss:// URI"),
175
225
  recursive: bool = typer.Option(False, "--recursive", "-r"),
176
226
  force: bool = typer.Option(False, "--force", "-f", help="Overwrite without confirmation"),
177
227
  update: bool = typer.Option(False, "--update", "-u", help="Copy only missing or older targets"),
@@ -198,7 +248,8 @@ def oss_cp(
198
248
  payer: Optional[str] = typer.Option(None, "--payer"),
199
249
  output_format: Optional[str] = typer.Option(None, "--output-format", "--format"),
200
250
  ):
201
- """Upload, download or copy objects. Downloads require private-network access."""
251
+ """Upload, download or copy objects. One operand must be a full oss:// URI.
252
+ Downloads require private-network access."""
202
253
  lock = threading.Lock()
203
254
 
204
255
  def progress(path, done, total):
@@ -227,6 +278,7 @@ def oss_cp(
227
278
  fmt = _format(output_format)
228
279
  if start_time is not None and end_time is not None and start_time > end_time:
229
280
  raise OssUsageError("--start-time cannot be later than --end-time")
281
+ direction_for(src, dst)
230
282
  files = _files()
231
283
  _configure(files, encoding_type, payer)
232
284
  files.disable_crc64 = disable_crc64
@@ -263,7 +315,6 @@ def oss_cp(
263
315
  result = files.cp(
264
316
  src,
265
317
  dst,
266
- direction=direction,
267
318
  recursive=recursive,
268
319
  overwrite=force,
269
320
  jobs=jobs,
@@ -140,17 +140,18 @@ class RemotePath:
140
140
  path: str
141
141
 
142
142
 
143
- def direction_for(src: str, dst: str, direction: str | None) -> str:
143
+ def direction_for(src: str, dst: str) -> str:
144
+ """Infer cp direction the way ossutil does: at least one operand is an OSS URI."""
144
145
  remote_src, remote_dst = src.startswith("oss://"), dst.startswith("oss://")
145
- if direction is None:
146
- if not remote_src and not remote_dst:
147
- raise OssUsageError("Relative cp paths require --direction upload, download or copy")
148
- direction = "copy" if remote_src and remote_dst else "download" if remote_src else "upload"
149
- if direction not in ("upload", "download", "copy"):
150
- raise OssUsageError("--direction must be upload, download or copy")
151
- if direction == "upload" and remote_src or direction == "download" and remote_dst:
152
- raise OssUsageError("The local cp operand cannot be an OSS URI")
153
- return direction
146
+ if remote_src and remote_dst:
147
+ return "copy"
148
+ if remote_src:
149
+ return "download"
150
+ if remote_dst:
151
+ return "upload"
152
+ raise OssUsageError(
153
+ "cp requires at least one operand starting with oss://; paths without it are local"
154
+ )
154
155
 
155
156
 
156
157
  def matches(path: str, include: tuple[str, ...], exclude: tuple[str, ...]) -> bool:
@@ -415,7 +416,7 @@ class OssFiles:
415
416
  self,
416
417
  remote: RemotePath,
417
418
  *,
418
- recursive: bool = True,
419
+ recursive: bool = False,
419
420
  limit: int | None = None,
420
421
  marker: str = "",
421
422
  include: tuple[str, ...] = (),
@@ -796,7 +797,6 @@ class OssFiles:
796
797
  src: str,
797
798
  dst: str,
798
799
  *,
799
- direction: str | None,
800
800
  recursive: bool,
801
801
  overwrite: bool,
802
802
  jobs: int,
@@ -811,7 +811,7 @@ class OssFiles:
811
811
  start_time: int | None = None,
812
812
  end_time: int | None = None,
813
813
  ) -> dict:
814
- direction = direction_for(src, dst, direction)
814
+ direction = direction_for(src, dst)
815
815
  remote_src = self.resolve(src, directory=recursive) if direction != "upload" else None
816
816
  remote_dst = None
817
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.dev2"
7
+ version = "0.5.3.dev4"
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"