ap-client 0.5.1.dev3__tar.gz → 0.5.1.dev5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ap-client
3
- Version: 0.5.1.dev3
3
+ Version: 0.5.1.dev5
4
4
  Summary: Agent Platform API Client & CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: pyyaml>=6.0
@@ -9,9 +9,9 @@ Requires-Dist: rich>=13.0.0
9
9
  Requires-Dist: typer>=0.9.0
10
10
  Requires-Dist: websockets>=13.0
11
11
  Provides-Extra: all
12
- Requires-Dist: instance-repo[oss]==1.3.2.dev3; extra == 'all'
12
+ Requires-Dist: instance-repo[oss]==1.3.2.dev5; extra == 'all'
13
13
  Provides-Extra: dataset
14
- Requires-Dist: instance-repo[oss]==1.3.2.dev3; extra == 'dataset'
14
+ Requires-Dist: instance-repo[oss]==1.3.2.dev5; extra == 'dataset'
15
15
  Description-Content-Type: text/markdown
16
16
 
17
17
  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.
@@ -493,7 +493,17 @@ def instance_get(
493
493
  f"Failed to query instance {instance}: not found",
494
494
  f"Check the instance id: ap instance list --dataset {dataset} --split {split}",
495
495
  )
496
- _emit_detail(_instance_record(record), output_format)
496
+ payload = _instance_record(record)
497
+ manifest = payload.get("manifest")
498
+ if output_format in ("json", "yaml") or not isinstance(manifest, list):
499
+ _emit_detail(payload, output_format)
500
+ return
501
+ from .manifest_view import manifest_lines
502
+
503
+ _emit_detail({key: value for key, value in payload.items() if key != "manifest"}, output_format)
504
+ typer.echo()
505
+ for line in manifest_lines(manifest):
506
+ typer.echo(line)
497
507
 
498
508
 
499
509
  def _check_version_autocreate(repo, dataset: str, version: str, *, environment: str) -> bool:
@@ -0,0 +1,103 @@
1
+ """Human-readable manifest paths; never resolve or fetch the referenced files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+
11
+ @dataclass
12
+ class _Directory:
13
+ directories: dict[str, _Directory] = field(default_factory=dict)
14
+ files: list[str] = field(default_factory=list)
15
+
16
+
17
+ def _visible(value: str) -> str:
18
+ """Escape control characters without interpreting terminal or Rich markup."""
19
+ return "".join(
20
+ json.dumps(char)[1:-1] if ord(char) < 32 or ord(char) == 127 else char for char in value
21
+ )
22
+
23
+
24
+ def _path(entry: Any) -> tuple[str, list[str]] | None:
25
+ if not isinstance(entry, dict):
26
+ return None
27
+ path = entry.get("file_path") or entry.get("object_path")
28
+ if not isinstance(path, str) or not path:
29
+ return None
30
+ uri = re.match(r"^([a-zA-Z][a-zA-Z0-9+.-]*://[^/]+/)(.*)$", path, re.DOTALL)
31
+ if uri:
32
+ root, path = uri.groups()
33
+ elif "://" in path:
34
+ return None
35
+ elif path.startswith("/"):
36
+ root, path = "/", path[1:]
37
+ else:
38
+ root = ""
39
+ parts = path.split("/")
40
+ # Unusual paths remain verbatim instead of being silently normalized.
41
+ if any(part in ("", ".", "..") for part in parts):
42
+ return None
43
+ return root, parts
44
+
45
+
46
+ def _tree(directory: _Directory) -> list[str]:
47
+ lines: list[str] = []
48
+ pending: list[tuple[_Directory | str, str]] = [(directory, "")]
49
+ # Iterative traversal also supports paths deeper than Python's recursion limit.
50
+ while pending:
51
+ node, prefix = pending.pop()
52
+ if isinstance(node, str):
53
+ lines.append(node)
54
+ continue
55
+ children = [(name + "/", child) for name, child in sorted(node.directories.items())]
56
+ children.extend((name, None) for name in sorted(node.files))
57
+ for index in range(len(children) - 1, -1, -1):
58
+ name, child = children[index]
59
+ last = index == len(children) - 1
60
+ if child is not None:
61
+ pending.append((child, prefix + (" " if last else "│ ")))
62
+ pending.append((prefix + ("└── " if last else "├── ") + _visible(name), ""))
63
+ return lines
64
+
65
+
66
+ def manifest_lines(manifest: list[Any]) -> list[str]:
67
+ """Group by storage root and show a lossless hierarchy of file paths.
68
+
69
+ Entries without usable paths retain their original values in a separate block.
70
+ Full metadata remains available through the command's JSON/YAML output.
71
+ """
72
+ groups: dict[str, list[list[str]]] = {}
73
+ other: list[Any] = []
74
+ for entry in manifest:
75
+ path = _path(entry)
76
+ if path is None:
77
+ other.append(entry)
78
+ else:
79
+ root, parts = path
80
+ groups.setdefault(root, []).append(parts)
81
+
82
+ count = sum(len(paths) for paths in groups.values())
83
+ lines = [f"manifest: {count} {'file' if count == 1 else 'files'}"]
84
+ for root, paths in sorted(groups.items()):
85
+ common: list[str] = []
86
+ for parts in zip(*(path[:-1] for path in paths)):
87
+ if len(set(parts)) != 1:
88
+ break
89
+ common.append(parts[0])
90
+ shared_root = root + "/".join(common) + ("/" if common else "")
91
+ lines.append(f"root: {_visible(shared_root or './')}")
92
+ directory = _Directory()
93
+ for parts in paths:
94
+ node = directory
95
+ for part in parts[len(common) : -1]:
96
+ node = node.directories.setdefault(part, _Directory())
97
+ node.files.append(parts[-1])
98
+ lines.extend(_tree(directory))
99
+ if other:
100
+ lines.append(f"other entries: {len(other)}")
101
+ for entry in other:
102
+ lines.append(" " + json.dumps(entry, ensure_ascii=False, default=str))
103
+ return lines
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ap-client"
7
- version = "0.5.1.dev3"
7
+ version = "0.5.1.dev5"
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"
@@ -18,10 +18,10 @@ dependencies = [
18
18
 
19
19
  [project.optional-dependencies]
20
20
  dataset = [
21
- "instance-repo[oss]==1.3.2.dev3",
21
+ "instance-repo[oss]==1.3.2.dev5",
22
22
  ]
23
23
  all = [
24
- "instance-repo[oss]==1.3.2.dev3",
24
+ "instance-repo[oss]==1.3.2.dev5",
25
25
  ]
26
26
 
27
27
  [project.scripts]