ap-client 0.6.0.dev1__tar.gz → 0.6.0.dev2__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 (21) hide show
  1. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/PKG-INFO +1 -1
  2. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/api.py +38 -1
  3. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/cli.py +78 -4
  4. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/managed_ak.py +16 -5
  5. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/pyproject.toml +1 -1
  6. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/__init__.py +0 -0
  7. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/akpool_commands.py +0 -0
  8. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/config.py +0 -0
  9. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/dataset_commands.py +0 -0
  10. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/dataset_register.py +0 -0
  11. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/exporter.py +0 -0
  12. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/fs_commands.py +0 -0
  13. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/instance_commands.py +0 -0
  14. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/irepo_sdk.py +0 -0
  15. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/manifest_view.py +0 -0
  16. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/profile_commands.py +0 -0
  17. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/repo_read_guard.py +0 -0
  18. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/share_api.py +0 -0
  19. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/split_publish.py +0 -0
  20. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/ap_client/tbb.py +0 -0
  21. {ap_client-0.6.0.dev1 → ap_client-0.6.0.dev2}/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.0.dev1
3
+ Version: 0.6.0.dev2
4
4
  Summary: Agent Platform API Client & CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: irepo-artifact==0.2.1
@@ -1125,8 +1125,15 @@ class APIClient:
1125
1125
  benchmark: Optional[str] = None,
1126
1126
  experiment: Optional[str] = None,
1127
1127
  ak_ids: Optional[list[str]] = None,
1128
+ priority: Optional[str] = None,
1129
+ priority_weight: Optional[int] = None,
1128
1130
  ) -> dict:
1129
- """Create a group."""
1131
+ """Create a group with optional scheduling defaults for its Jobs.
1132
+
1133
+ Omitted priority fields use the server defaults: medium and weight 0.
1134
+ """
1135
+ if priority is not None and priority not in {"high", "medium"}:
1136
+ raise ValueError("priority must be one of high/medium")
1130
1137
  if ak_ids is not None and not isinstance(ak_ids, list):
1131
1138
  raise ValueError("ak_ids must be a list")
1132
1139
  references = list(ak_ids or [])
@@ -1164,6 +1171,10 @@ class APIClient:
1164
1171
  body["queue_id"] = effective_queue_id
1165
1172
  if max_concurrency is not None:
1166
1173
  body["max_concurrency"] = max_concurrency
1174
+ if priority is not None:
1175
+ body["priority"] = priority
1176
+ if priority_weight is not None:
1177
+ body["priority_weight"] = priority_weight
1167
1178
  if eval_config is not None:
1168
1179
  body["eval_config"] = eval_config
1169
1180
  if model_base_url_collection is not None:
@@ -1881,9 +1892,15 @@ class APIClient:
1881
1892
  meta_job_type: Optional[str] = None,
1882
1893
  meta_teacher_direction: Optional[str] = None,
1883
1894
  meta_train_stage: Optional[str] = None,
1895
+ benchmark: Optional[str] = None,
1896
+ priority: Optional[str] = None,
1897
+ priority_weight: Optional[int] = None,
1898
+ priority_update_mode: Optional[str] = None,
1884
1899
  ) -> dict:
1885
1900
  """Update group properties.
1886
1901
 
1902
+ ``benchmark`` is omitted when None; pass an empty string to clear it.
1903
+
1887
1904
  ``tags``(`key:value` 数组)在客户端先与 Group 现有 tags 合并,再以
1888
1905
  全量替换语义提交(服务端 PATCH 是整体替换);同名 key 覆盖,未提及
1889
1906
  的 key 保留。服务端会从 tags 同步 meta_* 等派生字段。
@@ -1891,7 +1908,19 @@ class APIClient:
1891
1908
  ``meta_*`` 独立属性为部分更新(传了才改,不支持清除);新服务端
1892
1909
  优先读属性,老服务端忽略属性——因此过渡期内同时也会将属性合并进
1893
1910
  tags 轨(双写)。
1911
+
1912
+ Priority changes update the Group defaults. The default existing mode
1913
+ also updates all current Jobs, including PostProcess; incremental mode
1914
+ changes only the defaults for future Jobs. Updating priority alone uses
1915
+ weight 0; updating only weight preserves the current priority tier.
1894
1916
  """
1917
+ if priority is not None and priority not in {"high", "medium"}:
1918
+ raise ValueError("priority must be one of high/medium")
1919
+ if priority_update_mode is not None:
1920
+ if priority_update_mode not in {"existing", "incremental"}:
1921
+ raise ValueError("priority_update_mode must be one of existing/incremental")
1922
+ if priority is None and priority_weight is None:
1923
+ raise ValueError("priority_update_mode requires priority or priority_weight")
1895
1924
  body: dict = {}
1896
1925
 
1897
1926
  if max_concurrency is not None:
@@ -1906,6 +1935,14 @@ class APIClient:
1906
1935
  body["model"] = model
1907
1936
  if scaffold is not None:
1908
1937
  body["scaffold"] = scaffold
1938
+ if benchmark is not None:
1939
+ body["benchmark"] = benchmark
1940
+ if priority is not None:
1941
+ body["priority"] = priority
1942
+ if priority_weight is not None:
1943
+ body["priority_weight"] = priority_weight
1944
+ if priority_update_mode is not None:
1945
+ body["priority_update_mode"] = priority_update_mode
1909
1946
  meta_attr_tags = _merge_meta_tags(
1910
1947
  None,
1911
1948
  meta_model=meta_model,
@@ -642,7 +642,18 @@ def _print_formatted(data: dict, output_format: str) -> None:
642
642
  import yaml
643
643
  except ImportError as exc:
644
644
  raise typer.BadParameter("--format yaml requires PyYAML to be installed") from exc
645
- typer.echo(yaml.safe_dump(data, allow_unicode=True, sort_keys=False))
645
+
646
+ class LosslessDumper(yaml.SafeDumper):
647
+ pass
648
+
649
+ def represent_string(dumper, value):
650
+ # YAML normalizes literal NEL/line separators in unquoted scalars.
651
+ # Double quotes make the emitter escape them while keeping Chinese readable.
652
+ style = '"' if any(character in value for character in "\x85\u2028\u2029") else None
653
+ return dumper.represent_scalar("tag:yaml.org,2002:str", value, style=style)
654
+
655
+ LosslessDumper.add_representer(str, represent_string)
656
+ typer.echo(yaml.dump(data, Dumper=LosslessDumper, allow_unicode=True, sort_keys=False))
646
657
  return
647
658
  _print_json(data)
648
659
 
@@ -3219,12 +3230,12 @@ def job_create(
3219
3230
  priority: Optional[str] = typer.Option(
3220
3231
  None,
3221
3232
  "--priority",
3222
- help="Scheduling priority tier: high or medium (default: medium)",
3233
+ help="Scheduling tier: high or medium. Omit both priority flags to inherit Group defaults; otherwise an omitted tier is medium",
3223
3234
  ),
3224
3235
  priority_weight: Optional[int] = typer.Option(
3225
3236
  None,
3226
3237
  "--priority-weight",
3227
- help="Fine-grained priority weight within tier, 0-10 (default: 0)",
3238
+ help="Priority weight, 0-10. Omit both priority flags to inherit Group defaults; otherwise an omitted weight is 0",
3228
3239
  ),
3229
3240
  resource_mode: Optional[str] = typer.Option(
3230
3241
  None,
@@ -5457,6 +5468,18 @@ def group_create(
5457
5468
  min=1,
5458
5469
  help="Maximum concurrent jobs. Server default: 50.",
5459
5470
  ),
5471
+ priority: Optional[str] = typer.Option(
5472
+ None,
5473
+ "--priority",
5474
+ help="Default priority inherited by Jobs: high or medium (server default: medium)",
5475
+ ),
5476
+ priority_weight: Optional[int] = typer.Option(
5477
+ None,
5478
+ "--priority-weight",
5479
+ min=0,
5480
+ max=10,
5481
+ help="Default priority weight inherited by Jobs, 0-10 (server default: 0)",
5482
+ ),
5460
5483
  eval_config: Optional[str] = typer.Option(
5461
5484
  None, "--eval-config", help="Evaluation config (JSON)"
5462
5485
  ),
@@ -5569,6 +5592,8 @@ def group_create(
5569
5592
  ):
5570
5593
  """Create a Group."""
5571
5594
  output_format = _normalize_output_format(output_format)
5595
+ if priority is not None and priority not in {"high", "medium"}:
5596
+ raise typer.BadParameter("--priority must be one of high/medium")
5572
5597
  try:
5573
5598
  for reference in ak_id or []:
5574
5599
  validate_ak_id(reference)
@@ -5650,6 +5675,8 @@ def group_create(
5650
5675
  checkpoint=checkpoint_dict,
5651
5676
  benchmark=benchmark,
5652
5677
  experiment=experiment,
5678
+ **({"priority": priority} if priority is not None else {}),
5679
+ **({"priority_weight": priority_weight} if priority_weight is not None else {}),
5653
5680
  )
5654
5681
  except ManagedAKProtocolError as exc:
5655
5682
  _emit_error(str(exc))
@@ -5662,6 +5689,10 @@ def group_create(
5662
5689
  print(f" ak_ids: {response_ak_ids(result)}")
5663
5690
  if result.get("queue_id") is not None:
5664
5691
  print(f" queue_id: {result.get('queue_id')}")
5692
+ if result.get("priority") is not None:
5693
+ print(f" priority: {result.get('priority')}")
5694
+ if result.get("priority_weight") is not None:
5695
+ print(f" priority_weight: {result.get('priority_weight')}")
5665
5696
  if result.get("template") is not None:
5666
5697
  print(f" template: {result.get('template')}")
5667
5698
  if result.get("agenthub_revision") is not None:
@@ -6066,6 +6097,9 @@ def group_update(
6066
6097
  scaffold: Optional[str] = typer.Option(
6067
6098
  None, "--scaffold", "-s", help="Scaffold/agent name (benchmark perspective)"
6068
6099
  ),
6100
+ benchmark: Optional[str] = typer.Option(
6101
+ None, "--benchmark", help="Benchmark name; pass an empty string to clear"
6102
+ ),
6069
6103
  tags: Optional[str] = typer.Option(
6070
6104
  None,
6071
6105
  "--tags",
@@ -6085,14 +6119,40 @@ def group_update(
6085
6119
  "--meta-train-stage",
6086
6120
  help="Set meta_train_stage tag (options: ap meta-train-stage list)",
6087
6121
  ),
6122
+ priority: Optional[str] = typer.Option(
6123
+ None,
6124
+ "--priority",
6125
+ help="Group priority default: high or medium; --priority alone sets weight to 0",
6126
+ ),
6127
+ priority_weight: Optional[int] = typer.Option(
6128
+ None,
6129
+ "--priority-weight",
6130
+ min=0,
6131
+ max=10,
6132
+ help="Group priority weight default, 0-10; weight alone preserves the current tier",
6133
+ ),
6134
+ priority_update_mode: Optional[str] = typer.Option(
6135
+ None,
6136
+ "--priority-update-mode",
6137
+ help="existing updates defaults and all current Jobs (including PostProcess); incremental updates only defaults (server default: existing)",
6138
+ ),
6088
6139
  output_format: str = typer.Option(
6089
6140
  None,
6090
6141
  "--format",
6091
6142
  help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
6092
6143
  ),
6093
6144
  ):
6094
- """Update Group properties."""
6145
+ """Update Group properties and scheduling defaults inherited by Jobs."""
6095
6146
  output_format = _normalize_output_format(output_format)
6147
+ if priority is not None and priority not in {"high", "medium"}:
6148
+ raise typer.BadParameter("--priority must be one of high/medium")
6149
+ if priority_update_mode is not None:
6150
+ if priority_update_mode not in {"existing", "incremental"}:
6151
+ raise typer.BadParameter("--priority-update-mode must be one of existing/incremental")
6152
+ if priority is None and priority_weight is None:
6153
+ raise typer.BadParameter(
6154
+ "--priority-update-mode requires --priority or --priority-weight"
6155
+ )
6096
6156
  eval_config_dict = _parse_json_object(eval_config, "--eval-config")
6097
6157
  model_base_url_collection_list = _parse_model_base_url_collection_option(
6098
6158
  model_base_url_collection, model_base_url_file
@@ -6127,7 +6187,10 @@ def group_update(
6127
6187
  dataset,
6128
6188
  model_name,
6129
6189
  scaffold,
6190
+ benchmark,
6130
6191
  tags_list,
6192
+ priority,
6193
+ priority_weight,
6131
6194
  )
6132
6195
  ):
6133
6196
  raise typer.BadParameter("At least one field must be provided to update")
@@ -6141,7 +6204,15 @@ def group_update(
6141
6204
  dataset=dataset,
6142
6205
  model=model_name,
6143
6206
  scaffold=scaffold,
6207
+ **({"benchmark": benchmark} if benchmark is not None else {}),
6144
6208
  **({"tags": tags_list} if tags_list is not None else {}),
6209
+ **({"priority": priority} if priority is not None else {}),
6210
+ **({"priority_weight": priority_weight} if priority_weight is not None else {}),
6211
+ **(
6212
+ {"priority_update_mode": priority_update_mode}
6213
+ if priority_update_mode is not None
6214
+ else {}
6215
+ ),
6145
6216
  )
6146
6217
  if output_format == "plain":
6147
6218
  _print_group_action_plain(
@@ -6155,6 +6226,9 @@ def group_update(
6155
6226
  "dataset",
6156
6227
  "model",
6157
6228
  "scaffold",
6229
+ "benchmark",
6230
+ "priority",
6231
+ "priority_weight",
6158
6232
  "updated_at",
6159
6233
  ],
6160
6234
  )
@@ -138,20 +138,31 @@ def _safe_identifier(value: Any) -> str | None:
138
138
 
139
139
 
140
140
  class ManagedAKProtocolError(RuntimeError):
141
- """A response cannot confirm that the requested AK reference was honored."""
141
+ """An unconfirmed AK reference, with only safe receipt identities retained."""
142
142
 
143
143
  def __init__(self, ak_id: str, response: Any):
144
144
  self.ak_id = ak_id
145
- self.response = response
145
+ self.response: dict[str, Any] = {}
146
146
  self.job_ids: list[str] = []
147
147
  self.group_id = None
148
148
  if isinstance(response, dict):
149
149
  self.group_id = _safe_identifier(response.get("group_id"))
150
- candidates = [response.get("job_id")]
150
+ if self.group_id:
151
+ self.response["group_id"] = self.group_id
152
+ job_id = _safe_identifier(response.get("job_id"))
153
+ if job_id:
154
+ self.response["job_id"] = job_id
155
+ self.job_ids.append(job_id)
151
156
  jobs = response.get("jobs")
152
157
  if isinstance(jobs, list):
153
- candidates.extend(job.get("job_id") for job in jobs if isinstance(job, dict))
154
- self.job_ids = [value for value in map(_safe_identifier, candidates) if value]
158
+ job_ids = [
159
+ value
160
+ for job in jobs
161
+ if isinstance(job, dict)
162
+ if (value := _safe_identifier(job.get("job_id")))
163
+ ]
164
+ self.job_ids.extend(job_ids)
165
+ self.response["jobs"] = [{"job_id": value} for value in job_ids]
155
166
  super().__init__(
156
167
  f"Managed AK protocol mismatch: server did not acknowledge ak_id={ak_id}. "
157
168
  f"Returned Job IDs: {self.job_ids}. Verify these jobs before resubmitting; "
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ap-client"
7
- version = "0.6.0.dev1"
7
+ version = "0.6.0.dev2"
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"