tangle-cli 0.0.1a1__py3-none-any.whl

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 (48) hide show
  1. tangle_cli/__init__.py +19 -0
  2. tangle_cli/api_cli.py +787 -0
  3. tangle_cli/api_schema.py +633 -0
  4. tangle_cli/api_transport.py +461 -0
  5. tangle_cli/args_container.py +244 -0
  6. tangle_cli/artifacts.py +293 -0
  7. tangle_cli/artifacts_cli.py +108 -0
  8. tangle_cli/cli.py +57 -0
  9. tangle_cli/cli_helpers.py +116 -0
  10. tangle_cli/cli_options.py +52 -0
  11. tangle_cli/client.py +677 -0
  12. tangle_cli/component_from_func.py +1856 -0
  13. tangle_cli/component_generator.py +298 -0
  14. tangle_cli/component_inspector.py +494 -0
  15. tangle_cli/component_publisher.py +921 -0
  16. tangle_cli/components_cli.py +269 -0
  17. tangle_cli/dynamic_discovery_client.py +296 -0
  18. tangle_cli/generated_model_extensions.py +405 -0
  19. tangle_cli/generated_runtime.py +43 -0
  20. tangle_cli/handler.py +96 -0
  21. tangle_cli/hydration_trust.py +222 -0
  22. tangle_cli/logger.py +166 -0
  23. tangle_cli/models.py +407 -0
  24. tangle_cli/module_bundler.py +662 -0
  25. tangle_cli/openapi/__init__.py +0 -0
  26. tangle_cli/openapi/codegen.py +1090 -0
  27. tangle_cli/openapi/parser.py +77 -0
  28. tangle_cli/pipeline_dehydrator.py +720 -0
  29. tangle_cli/pipeline_hydrator.py +1785 -0
  30. tangle_cli/pipeline_run_annotations.py +41 -0
  31. tangle_cli/pipeline_run_details.py +203 -0
  32. tangle_cli/pipeline_run_manager.py +1994 -0
  33. tangle_cli/pipeline_run_search.py +712 -0
  34. tangle_cli/pipeline_runner.py +620 -0
  35. tangle_cli/pipeline_runs_cli.py +584 -0
  36. tangle_cli/pipelines.py +581 -0
  37. tangle_cli/pipelines_cli.py +271 -0
  38. tangle_cli/published_components_cli.py +373 -0
  39. tangle_cli/py.typed +0 -0
  40. tangle_cli/quickstart.py +110 -0
  41. tangle_cli/secrets.py +156 -0
  42. tangle_cli/secrets_cli.py +269 -0
  43. tangle_cli/utils.py +942 -0
  44. tangle_cli/version_manager.py +470 -0
  45. tangle_cli-0.0.1a1.dist-info/METADATA +561 -0
  46. tangle_cli-0.0.1a1.dist-info/RECORD +48 -0
  47. tangle_cli-0.0.1a1.dist-info/WHEEL +4 -0
  48. tangle_cli-0.0.1a1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,584 @@
1
+ """`tangle sdk pipeline-runs` command implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import pathlib
7
+ from typing import Annotated, Any
8
+
9
+ from cyclopts import App, Parameter
10
+
11
+ from .args_container import ArgsContainer
12
+ from .cli_helpers import (
13
+ LazyTangleApiClient,
14
+ api_arg_specs,
15
+ include_env_credentials_for_args,
16
+ load_args_or_exit,
17
+ optional_path,
18
+ print_json,
19
+ )
20
+ from .cli_options import (
21
+ AuthHeaderOption,
22
+ BaseUrlOption,
23
+ ConfigOption,
24
+ HeaderOption,
25
+ LogTypeOption,
26
+ TokenOption,
27
+ )
28
+ from .logger import Logger, logger_for_log_type
29
+ from .pipeline_run_annotations import AnnotationManager
30
+ from .pipeline_run_manager import (
31
+ PipelineRunError,
32
+ PipelineRunHooks,
33
+ PipelineRunManager,
34
+ parse_json_or_key_values,
35
+ parse_key_value_entries,
36
+ )
37
+ from .pipeline_run_search import normalize_query_input, parse_annotation
38
+
39
+ app = App(name="pipeline-runs", help="Submit and inspect Tangle pipeline runs.")
40
+ annotations_app = App(name="annotations", help="Work with pipeline-run annotations.")
41
+ app.command(annotations_app)
42
+
43
+
44
+ def _trusted_hydration_config(args: ArgsContainer) -> dict[str, Any]:
45
+ config = getattr(args, "_config", {}).get("trusted_hydration", {})
46
+ return config if isinstance(config, dict) else {}
47
+
48
+
49
+ def _trusted_sources_for_args(args: ArgsContainer) -> list[str]:
50
+ sources: list[str] = []
51
+ config_sources = _trusted_hydration_config(args).get("trusted_python_sources", [])
52
+ if isinstance(config_sources, str):
53
+ sources.append(config_sources)
54
+ elif isinstance(config_sources, list):
55
+ sources.extend(str(source) for source in config_sources)
56
+ cli_sources = getattr(args, "trusted_source", None)
57
+ if isinstance(cli_sources, str):
58
+ sources.append(cli_sources)
59
+ elif isinstance(cli_sources, list):
60
+ sources.extend(str(source) for source in cli_sources)
61
+ return [source for source in sources if source]
62
+
63
+
64
+ def _allow_all_hydration_for_args(args: ArgsContainer) -> bool:
65
+ if bool(getattr(args, "trusted_hydration_cli", False)):
66
+ return True
67
+ config = _trusted_hydration_config(args)
68
+ return bool(config.get("allow_all", False))
69
+
70
+
71
+ def _api_client(args: ArgsContainer, *, cli_base_url: str | None, command_name: str) -> LazyTangleApiClient:
72
+ return LazyTangleApiClient(
73
+ base_url=args.base_url,
74
+ token=args.token,
75
+ auth_header=args.auth_header,
76
+ header=args.header,
77
+ include_env_credentials=include_env_credentials_for_args(args, cli_base_url),
78
+ command_name=command_name,
79
+ )
80
+
81
+
82
+ def _manager(args: ArgsContainer, *, cli_base_url: str | None, logger: Logger) -> PipelineRunManager:
83
+ return PipelineRunManager(
84
+ client=_api_client(args, cli_base_url=cli_base_url, command_name="pipeline-run commands"),
85
+ hooks=PipelineRunHooks(
86
+ logger=logger,
87
+ trusted_python_sources=_trusted_sources_for_args(args),
88
+ allow_all_hydration=_allow_all_hydration_for_args(args),
89
+ ),
90
+ logger=logger,
91
+ )
92
+
93
+
94
+ def _run_manager_action(config: str | None, cli_base_url: str | None, specs: dict[str, tuple[Any, ...]], fn):
95
+ for args in load_args_or_exit(config, **specs):
96
+ try:
97
+ logger, finalize_logs = logger_for_log_type(getattr(args, "log_type", "console"))
98
+ except ValueError as exc:
99
+ raise SystemExit(str(exc)) from exc
100
+ try:
101
+ try:
102
+ result = fn(_manager(args, cli_base_url=cli_base_url, logger=logger), args)
103
+ except PipelineRunError as exc:
104
+ raise SystemExit(str(exc)) from exc
105
+ if result is not None:
106
+ print_json(result)
107
+ finally:
108
+ finalize_logs()
109
+
110
+
111
+ def _run_annotation_action(config: str | None, cli_base_url: str | None, specs: dict[str, tuple[Any, ...]], fn):
112
+ for args in load_args_or_exit(config, **specs):
113
+ try:
114
+ logger, finalize_logs = logger_for_log_type(getattr(args, "log_type", "console"))
115
+ except ValueError as exc:
116
+ raise SystemExit(str(exc)) from exc
117
+ try:
118
+ manager = AnnotationManager(
119
+ client=_api_client(args, cli_base_url=cli_base_url, command_name="pipeline-run annotation commands"),
120
+ logger=logger,
121
+ )
122
+ print_json(fn(manager, args))
123
+ finally:
124
+ finalize_logs()
125
+
126
+
127
+ @app.command(name="submit")
128
+ def pipeline_runs_submit(
129
+ pipeline_path: pathlib.Path | None = None,
130
+ *,
131
+ arg: Annotated[
132
+ list[str] | None,
133
+ Parameter(help="Pipeline argument as KEY=VALUE. Repeat for multiple.", negative_iterable=()),
134
+ ] = None,
135
+ args_json: Annotated[str | None, Parameter(help="Pipeline arguments as a JSON object.")] = None,
136
+ annotation: Annotated[
137
+ list[str] | None,
138
+ Parameter(help="Run annotation as KEY=VALUE. Repeat for multiple.", negative_iterable=()),
139
+ ] = None,
140
+ hydrate: Annotated[bool | None, Parameter(help="Hydrate refs before submit.")] = True,
141
+ dry_run: Annotated[
142
+ bool | None,
143
+ Parameter(help="Hydrate and print the submit payload without creating a run."),
144
+ ] = None,
145
+ run_as: Annotated[
146
+ str | None,
147
+ Parameter(help="Downstream extension point; unsupported by the OSS default hooks."),
148
+ ] = None,
149
+ trusted_source: Annotated[
150
+ list[str] | None,
151
+ Parameter(
152
+ name="--trusted-source",
153
+ help="Trusted local_from_python source root or glob. Repeat for multiple.",
154
+ negative_iterable=(),
155
+ ),
156
+ ] = None,
157
+ trusted_hydration: Annotated[
158
+ bool | None,
159
+ Parameter(
160
+ name="--trusted-hydration",
161
+ help="Allow all local_from_python execution during hydration for trusted inputs.",
162
+ ),
163
+ ] = None,
164
+ base_url: BaseUrlOption = None,
165
+ token: TokenOption = None,
166
+ auth_header: AuthHeaderOption = None,
167
+ header: HeaderOption = None,
168
+ config: ConfigOption = None,
169
+ log_type: LogTypeOption = "console",
170
+ ) -> None:
171
+ """Hydrate and submit a local pipeline YAML file as a run."""
172
+
173
+ specs = {
174
+ "pipeline_path": ("pipeline_path", pipeline_path, None, False, True, optional_path),
175
+ "arg": (arg, None),
176
+ "args_json": (args_json, None),
177
+ "args_config": ("args", None, None, True),
178
+ "annotation": (annotation, None),
179
+ "hydrate": (hydrate, True),
180
+ "dry_run": (dry_run, None),
181
+ "run_as": (run_as, None),
182
+ "trusted_source": (trusted_source, None),
183
+ "trusted_hydration_cli": ("trusted_hydration_cli", trusted_hydration, None, False),
184
+ "log_type": (log_type, "console"),
185
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
186
+ }
187
+
188
+ def action(manager: PipelineRunManager, args: ArgsContainer) -> dict[str, Any]:
189
+ kwargs = {
190
+ "run_args": parse_json_or_key_values(args.args_json or args.args_config, args.arg),
191
+ "annotations": parse_key_value_entries(args.annotation),
192
+ "hydrate": bool(args.hydrate),
193
+ "run_as": args.run_as,
194
+ }
195
+ if args.dry_run:
196
+ return manager.build_submit_body(args.pipeline_path, **kwargs)
197
+ return manager.submit_pipeline(args.pipeline_path, **kwargs)
198
+
199
+ _run_manager_action(config, base_url, specs, action)
200
+
201
+
202
+ @app.command(name="details")
203
+ def pipeline_runs_details(
204
+ run_id: str | None = None,
205
+ *,
206
+ execution_id: str | None = None,
207
+ include_implementations: bool | None = None,
208
+ include_annotations: bool | None = None,
209
+ include_execution_state: bool | None = None,
210
+ base_url: BaseUrlOption = None,
211
+ token: TokenOption = None,
212
+ auth_header: AuthHeaderOption = None,
213
+ header: HeaderOption = None,
214
+ config: ConfigOption = None,
215
+ log_type: LogTypeOption = "console",
216
+ ) -> None:
217
+ """Print run details, including root execution details."""
218
+ specs = {
219
+ "run_id": (run_id,),
220
+ "execution_id": (execution_id, None),
221
+ "include_implementations": (include_implementations, None),
222
+ "include_annotations": (include_annotations, None),
223
+ "include_execution_state": (include_execution_state, None),
224
+ "log_type": (log_type, "console"),
225
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
226
+ }
227
+ _run_manager_action(
228
+ config,
229
+ base_url,
230
+ specs,
231
+ lambda manager, args: manager.get_run_details(
232
+ args.run_id,
233
+ include_annotations=bool(args.include_annotations),
234
+ include_execution_state=bool(args.include_execution_state),
235
+ include_implementations=bool(args.include_implementations),
236
+ execution_id=args.execution_id,
237
+ ),
238
+ )
239
+
240
+
241
+ @app.command(name="status")
242
+ def pipeline_runs_status(
243
+ run_id: str | None = None,
244
+ *,
245
+ base_url: BaseUrlOption = None,
246
+ token: TokenOption = None,
247
+ auth_header: AuthHeaderOption = None,
248
+ header: HeaderOption = None,
249
+ config: ConfigOption = None,
250
+ log_type: LogTypeOption = "console",
251
+ ) -> None:
252
+ """Print a pipeline run and derived status summary."""
253
+ specs = {
254
+ "run_id": (run_id,),
255
+ "log_type": (log_type, "console"),
256
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
257
+ }
258
+
259
+ def action(manager: PipelineRunManager, args: ArgsContainer) -> dict[str, Any]:
260
+ run = manager.get_run(args.run_id, include_execution_stats=True)
261
+ return {"run": run, "status": manager.status_from_run(run) or "UNKNOWN"}
262
+
263
+ _run_manager_action(config, base_url, specs, action)
264
+
265
+
266
+ @app.command(name="graph-state")
267
+ def pipeline_runs_graph_state(
268
+ execution_id: str | None = None,
269
+ *,
270
+ base_url: BaseUrlOption = None,
271
+ token: TokenOption = None,
272
+ auth_header: AuthHeaderOption = None,
273
+ header: HeaderOption = None,
274
+ config: ConfigOption = None,
275
+ log_type: LogTypeOption = "console",
276
+ ) -> None:
277
+ """Print graph execution state for an execution id."""
278
+ specs = {
279
+ "execution_id": (execution_id,),
280
+ "log_type": (log_type, "console"),
281
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
282
+ }
283
+ _run_manager_action(config, base_url, specs, lambda manager, args: manager.graph_state(args.execution_id))
284
+
285
+
286
+ @app.command(name="cancel")
287
+ def pipeline_runs_cancel(
288
+ run_id: str | None = None,
289
+ *,
290
+ base_url: BaseUrlOption = None,
291
+ token: TokenOption = None,
292
+ auth_header: AuthHeaderOption = None,
293
+ header: HeaderOption = None,
294
+ config: ConfigOption = None,
295
+ log_type: LogTypeOption = "console",
296
+ ) -> None:
297
+ """Cancel a pipeline run."""
298
+ specs = {
299
+ "run_id": (run_id,),
300
+ "log_type": (log_type, "console"),
301
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
302
+ }
303
+ _run_manager_action(config, base_url, specs, lambda manager, args: manager.cancel_run(args.run_id))
304
+
305
+
306
+ @app.command(name="wait")
307
+ def pipeline_runs_wait(
308
+ run_id: str | None = None,
309
+ *,
310
+ max_wait: float = 600.0,
311
+ poll_interval: float = 10.0,
312
+ exit_on_first_failure: bool = False,
313
+ base_url: BaseUrlOption = None,
314
+ token: TokenOption = None,
315
+ auth_header: AuthHeaderOption = None,
316
+ header: HeaderOption = None,
317
+ config: ConfigOption = None,
318
+ log_type: LogTypeOption = "console",
319
+ ) -> None:
320
+ """Poll a run until terminal state or bounded timeout."""
321
+ specs = {
322
+ "run_id": (run_id,),
323
+ "max_wait": (max_wait, 600.0),
324
+ "poll_interval": (poll_interval, 10.0),
325
+ "exit_on_first_failure": (exit_on_first_failure, False),
326
+ "log_type": (log_type, "console"),
327
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
328
+ }
329
+ _run_manager_action(
330
+ config,
331
+ base_url,
332
+ specs,
333
+ lambda manager, args: manager.wait_for_completion(
334
+ args.run_id,
335
+ max_wait=float(args.max_wait),
336
+ poll_interval=float(args.poll_interval),
337
+ exit_on_first_failure=bool(args.exit_on_first_failure),
338
+ ),
339
+ )
340
+
341
+
342
+ @app.command(name="logs")
343
+ def pipeline_runs_logs(
344
+ execution_id: str | None = None,
345
+ *,
346
+ base_url: BaseUrlOption = None,
347
+ token: TokenOption = None,
348
+ auth_header: AuthHeaderOption = None,
349
+ header: HeaderOption = None,
350
+ config: ConfigOption = None,
351
+ log_type: LogTypeOption = "console",
352
+ ) -> None:
353
+ """Print Tangle API container logs for an execution id."""
354
+ specs = {
355
+ "execution_id": (execution_id,),
356
+ "log_type": (log_type, "console"),
357
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
358
+ }
359
+
360
+ def action(manager: PipelineRunManager, args: ArgsContainer) -> object:
361
+ result = manager.logs(args.execution_id)
362
+ if isinstance(result, dict) and isinstance(result.get("log_text"), str):
363
+ print(result["log_text"], end="" if result["log_text"].endswith("\n") else "\n")
364
+ return None
365
+ return result
366
+
367
+ _run_manager_action(config, base_url, specs, action)
368
+
369
+
370
+ @app.command(name="search")
371
+ def pipeline_runs_search(
372
+ query: str | None = None,
373
+ *,
374
+ filter_query: str | None = None,
375
+ name: str | None = None,
376
+ created_by: str | None = None,
377
+ annotation: Annotated[
378
+ list[str] | None,
379
+ Parameter(help="Annotation filter as key or key=value. Repeat for multiple.", negative_iterable=()),
380
+ ] = None,
381
+ annotations_json: str | None = None,
382
+ start_date: str | None = None,
383
+ end_date: str | None = None,
384
+ local_time: bool | None = None,
385
+ raw_query: Annotated[
386
+ str | None,
387
+ Parameter(name="--query", help="Raw filter_query JSON, plain or URL-encoded."),
388
+ ] = None,
389
+ limit: int | None = None,
390
+ page_token: str | None = None,
391
+ include_pipeline_names: bool | None = None,
392
+ include_execution_stats: bool | None = None,
393
+ output: str | None = None,
394
+ base_url: BaseUrlOption = None,
395
+ token: TokenOption = None,
396
+ auth_header: AuthHeaderOption = None,
397
+ header: HeaderOption = None,
398
+ config: ConfigOption = None,
399
+ log_type: LogTypeOption = "console",
400
+ ) -> None:
401
+ """Search/list pipeline runs using simple or rich Tangle API filters."""
402
+ specs = {
403
+ "query": ("filter", query, None, False),
404
+ "filter_query": (filter_query, None),
405
+ "name": (name, None),
406
+ "created_by": (created_by, None),
407
+ "annotation": (annotation, None),
408
+ "annotations_json": (annotations_json, None),
409
+ "start_date": (start_date, None),
410
+ "end_date": (end_date, None),
411
+ "local_time": (local_time, None),
412
+ "raw_query": (raw_query, None),
413
+ "limit": (limit, None),
414
+ "page_token": (page_token, None),
415
+ "include_pipeline_names": (include_pipeline_names, None),
416
+ "include_execution_stats": (include_execution_stats, None),
417
+ "output": (output, "json"),
418
+ "log_type": (log_type, "console"),
419
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
420
+ }
421
+
422
+ def action(manager: PipelineRunManager, args: ArgsContainer) -> object:
423
+ rich_search = any(
424
+ getattr(args, attr)
425
+ for attr in (
426
+ "name",
427
+ "created_by",
428
+ "annotation",
429
+ "annotations_json",
430
+ "start_date",
431
+ "end_date",
432
+ "raw_query",
433
+ "limit",
434
+ )
435
+ )
436
+ if not rich_search:
437
+ return manager.search_runs(
438
+ filter=args.query,
439
+ filter_query=args.filter_query,
440
+ page_token=args.page_token,
441
+ include_pipeline_names=args.include_pipeline_names,
442
+ include_execution_stats=args.include_execution_stats,
443
+ )
444
+
445
+ annotations: dict[str, str | None] | None = None
446
+ if args.annotation:
447
+ annotations = {}
448
+ for item in args.annotation:
449
+ key, value = parse_annotation(str(item))
450
+ annotations[key] = value
451
+ if args.annotations_json:
452
+ loaded = json.loads(args.annotations_json)
453
+ if not isinstance(loaded, dict):
454
+ raise PipelineRunError("--annotations-json must be a JSON object")
455
+ annotations = annotations or {}
456
+ annotations.update({str(key): value for key, value in loaded.items()})
457
+ parsed_query = normalize_query_input(args.raw_query) if args.raw_query else None
458
+ result = manager.search_pipeline_runs(
459
+ name=args.name,
460
+ created_by=args.created_by,
461
+ annotations=annotations,
462
+ start_date=args.start_date,
463
+ end_date=args.end_date,
464
+ local_time=bool(args.local_time),
465
+ query=parsed_query,
466
+ limit=int(args.limit or 10),
467
+ page_token=args.page_token,
468
+ )
469
+ if "error" in result:
470
+ raise PipelineRunError(str(result["error"]))
471
+ if str(args.output or "json").lower() == "table":
472
+ print(result.get("cli_table", ""))
473
+ return None
474
+ return result
475
+
476
+ _run_manager_action(config, base_url, specs, action)
477
+
478
+
479
+ @app.command(name="export")
480
+ def pipeline_runs_export(
481
+ run_id: str | None = None,
482
+ *,
483
+ output: pathlib.Path | None = None,
484
+ dehydrate: Annotated[
485
+ bool | None,
486
+ Parameter(help="Dehydrate exported pipeline specs into portable component refs."),
487
+ ] = None,
488
+ base_url: BaseUrlOption = None,
489
+ token: TokenOption = None,
490
+ auth_header: AuthHeaderOption = None,
491
+ header: HeaderOption = None,
492
+ config: ConfigOption = None,
493
+ log_type: LogTypeOption = "console",
494
+ ) -> None:
495
+ """Export a run's root pipeline spec to YAML."""
496
+ specs = {
497
+ "run_id": (run_id,),
498
+ "output": (output, None, optional_path),
499
+ "dehydrate": (dehydrate, None),
500
+ "log_type": (log_type, "console"),
501
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
502
+ }
503
+
504
+ def action(manager: PipelineRunManager, args: ArgsContainer) -> object:
505
+ result = manager.export_run(args.run_id, args.output, dehydrate=bool(args.dehydrate))
506
+ if args.output is None and "yaml" in result:
507
+ print(result["yaml"], end="" if result["yaml"].endswith("\n") else "\n")
508
+ return None
509
+ return result
510
+
511
+ _run_manager_action(config, base_url, specs, action)
512
+
513
+
514
+ @annotations_app.command(name="list")
515
+ def pipeline_runs_annotations_list(
516
+ run_id: str | None = None,
517
+ *,
518
+ base_url: BaseUrlOption = None,
519
+ token: TokenOption = None,
520
+ auth_header: AuthHeaderOption = None,
521
+ header: HeaderOption = None,
522
+ config: ConfigOption = None,
523
+ log_type: LogTypeOption = "console",
524
+ ) -> None:
525
+ specs = {
526
+ "run_id": (run_id,),
527
+ "log_type": (log_type, "console"),
528
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
529
+ }
530
+ _run_annotation_action(config, base_url, specs, lambda manager, args: manager.list_annotations(args.run_id))
531
+
532
+
533
+ @annotations_app.command(name="set")
534
+ def pipeline_runs_annotations_set(
535
+ run_id: str | None = None,
536
+ key: str | None = None,
537
+ value: str | None = None,
538
+ *,
539
+ base_url: BaseUrlOption = None,
540
+ token: TokenOption = None,
541
+ auth_header: AuthHeaderOption = None,
542
+ header: HeaderOption = None,
543
+ config: ConfigOption = None,
544
+ log_type: LogTypeOption = "console",
545
+ ) -> None:
546
+ specs = {
547
+ "run_id": (run_id,),
548
+ "key": (key,),
549
+ "value": (value, None),
550
+ "log_type": (log_type, "console"),
551
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
552
+ }
553
+ _run_annotation_action(
554
+ config,
555
+ base_url,
556
+ specs,
557
+ lambda manager, args: manager.set_annotation(args.run_id, args.key, args.value),
558
+ )
559
+
560
+
561
+ @annotations_app.command(name="delete")
562
+ def pipeline_runs_annotations_delete(
563
+ run_id: str | None = None,
564
+ key: str | None = None,
565
+ *,
566
+ base_url: BaseUrlOption = None,
567
+ token: TokenOption = None,
568
+ auth_header: AuthHeaderOption = None,
569
+ header: HeaderOption = None,
570
+ config: ConfigOption = None,
571
+ log_type: LogTypeOption = "console",
572
+ ) -> None:
573
+ specs = {
574
+ "run_id": (run_id,),
575
+ "key": (key,),
576
+ "log_type": (log_type, "console"),
577
+ **api_arg_specs(base_url=base_url, token=token, auth_header=auth_header, header=header),
578
+ }
579
+ _run_annotation_action(
580
+ config,
581
+ base_url,
582
+ specs,
583
+ lambda manager, args: manager.delete_annotation(args.run_id, args.key),
584
+ )