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.
- tangle_cli/__init__.py +19 -0
- tangle_cli/api_cli.py +787 -0
- tangle_cli/api_schema.py +633 -0
- tangle_cli/api_transport.py +461 -0
- tangle_cli/args_container.py +244 -0
- tangle_cli/artifacts.py +293 -0
- tangle_cli/artifacts_cli.py +108 -0
- tangle_cli/cli.py +57 -0
- tangle_cli/cli_helpers.py +116 -0
- tangle_cli/cli_options.py +52 -0
- tangle_cli/client.py +677 -0
- tangle_cli/component_from_func.py +1856 -0
- tangle_cli/component_generator.py +298 -0
- tangle_cli/component_inspector.py +494 -0
- tangle_cli/component_publisher.py +921 -0
- tangle_cli/components_cli.py +269 -0
- tangle_cli/dynamic_discovery_client.py +296 -0
- tangle_cli/generated_model_extensions.py +405 -0
- tangle_cli/generated_runtime.py +43 -0
- tangle_cli/handler.py +96 -0
- tangle_cli/hydration_trust.py +222 -0
- tangle_cli/logger.py +166 -0
- tangle_cli/models.py +407 -0
- tangle_cli/module_bundler.py +662 -0
- tangle_cli/openapi/__init__.py +0 -0
- tangle_cli/openapi/codegen.py +1090 -0
- tangle_cli/openapi/parser.py +77 -0
- tangle_cli/pipeline_dehydrator.py +720 -0
- tangle_cli/pipeline_hydrator.py +1785 -0
- tangle_cli/pipeline_run_annotations.py +41 -0
- tangle_cli/pipeline_run_details.py +203 -0
- tangle_cli/pipeline_run_manager.py +1994 -0
- tangle_cli/pipeline_run_search.py +712 -0
- tangle_cli/pipeline_runner.py +620 -0
- tangle_cli/pipeline_runs_cli.py +584 -0
- tangle_cli/pipelines.py +581 -0
- tangle_cli/pipelines_cli.py +271 -0
- tangle_cli/published_components_cli.py +373 -0
- tangle_cli/py.typed +0 -0
- tangle_cli/quickstart.py +110 -0
- tangle_cli/secrets.py +156 -0
- tangle_cli/secrets_cli.py +269 -0
- tangle_cli/utils.py +942 -0
- tangle_cli/version_manager.py +470 -0
- tangle_cli-0.0.1a1.dist-info/METADATA +561 -0
- tangle_cli-0.0.1a1.dist-info/RECORD +48 -0
- tangle_cli-0.0.1a1.dist-info/WHEEL +4 -0
- 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
|
+
)
|