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,41 @@
|
|
|
1
|
+
"""Pipeline-run annotation helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .handler import TangleCliHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnnotationManager(TangleCliHandler):
|
|
11
|
+
"""Manage annotations on Tangle pipeline runs."""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def to_plain(value: Any) -> Any:
|
|
15
|
+
if hasattr(value, "to_dict"):
|
|
16
|
+
return value.to_dict()
|
|
17
|
+
if hasattr(value, "model_dump"):
|
|
18
|
+
return value.model_dump(by_alias=True)
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
def list_annotations(self, run_id: str) -> dict[str, Any]:
|
|
22
|
+
annotations = self.to_plain(self._require_client().pipeline_runs_annotations(run_id)) or {}
|
|
23
|
+
if not isinstance(annotations, dict):
|
|
24
|
+
annotations = dict(annotations)
|
|
25
|
+
return {
|
|
26
|
+
"status": "success",
|
|
27
|
+
"run_id": run_id,
|
|
28
|
+
"count": len(annotations),
|
|
29
|
+
"annotations": annotations,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def set_annotation(self, run_id: str, key: str, value: Any = None) -> dict[str, Any]:
|
|
33
|
+
self._require_client().pipeline_runs_put_annotations(run_id, key, value=value)
|
|
34
|
+
return {"status": "success", "run_id": run_id, "key": key, "value": value}
|
|
35
|
+
|
|
36
|
+
def delete_annotation(self, run_id: str, key: str) -> dict[str, Any]:
|
|
37
|
+
self._require_client().pipeline_runs_delete_annotations(run_id, key)
|
|
38
|
+
return {"status": "success", "run_id": run_id, "key": key}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["AnnotationManager"]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Pipeline-run details and graph-state serialization helpers.
|
|
2
|
+
|
|
3
|
+
These helpers are native-free and keep provider-specific log enrichment out of
|
|
4
|
+
OSS. Downstreams can call them with their authenticated API client and layer
|
|
5
|
+
provider-specific log output through ``PipelineRunHooks.fetch_logs`` or wrappers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
11
|
+
from concurrent.futures import TimeoutError as FutureTimeoutError
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .handler import TangleCliHandler
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PipelineRunDetails(TangleCliHandler):
|
|
18
|
+
"""Resource manager for pipeline run details and graph-state output.
|
|
19
|
+
|
|
20
|
+
Downstream packages can subclass this class or inject a lazy
|
|
21
|
+
``client_factory`` to supply authenticated clients without OSS importing
|
|
22
|
+
provider-specific auth or SDK code.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
client: Any = None,
|
|
28
|
+
*,
|
|
29
|
+
client_factory: Any | None = None,
|
|
30
|
+
logger: Any | None = None,
|
|
31
|
+
**kwargs: Any,
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(client=client, client_factory=client_factory, logger=logger, **kwargs)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def to_plain(value: Any) -> Any:
|
|
37
|
+
"""Convert generated/native objects into JSON-serializable values."""
|
|
38
|
+
|
|
39
|
+
return _to_plain(value)
|
|
40
|
+
|
|
41
|
+
def serialize_execution(self, execution: Any) -> dict[str, Any]:
|
|
42
|
+
"""Serialize an execution details object into a concise dict."""
|
|
43
|
+
|
|
44
|
+
out: dict[str, Any] = {"id": _value(execution, "id", "")}
|
|
45
|
+
task_spec = _value(execution, "task_spec")
|
|
46
|
+
component_spec = _value(task_spec, "component_spec")
|
|
47
|
+
if component_spec:
|
|
48
|
+
out["component"] = _value(component_spec, "name", "unknown") or "unknown"
|
|
49
|
+
try:
|
|
50
|
+
from .component_inspector import ComponentInspector
|
|
51
|
+
|
|
52
|
+
transparent, reason = ComponentInspector.transparency_check(component_spec)
|
|
53
|
+
out["transparent"] = transparent
|
|
54
|
+
out["transparency_reason"] = reason
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
description = _value(component_spec, "description")
|
|
58
|
+
if description:
|
|
59
|
+
out["description"] = description
|
|
60
|
+
implementation = _value(component_spec, "implementation")
|
|
61
|
+
if implementation:
|
|
62
|
+
out["implementation"] = self.to_plain(implementation)
|
|
63
|
+
arguments = _value(task_spec, "arguments")
|
|
64
|
+
if arguments:
|
|
65
|
+
out["arguments"] = self.to_plain(arguments)
|
|
66
|
+
raw = _value(execution, "raw", {}) or {}
|
|
67
|
+
for key in ("state", "created_at", "finished_at"):
|
|
68
|
+
raw_value = _value(raw, key)
|
|
69
|
+
if raw_value:
|
|
70
|
+
out[key] = raw_value
|
|
71
|
+
input_artifacts = _value(execution, "input_artifacts")
|
|
72
|
+
if input_artifacts:
|
|
73
|
+
out["input_artifacts"] = self.to_plain(input_artifacts)
|
|
74
|
+
output_artifacts = _value(execution, "output_artifacts")
|
|
75
|
+
if output_artifacts:
|
|
76
|
+
out["output_artifacts"] = self.to_plain(output_artifacts)
|
|
77
|
+
return out
|
|
78
|
+
|
|
79
|
+
def serialize_run_details(self, details: Any) -> dict[str, Any]:
|
|
80
|
+
"""Convert ``RunDetails`` into a JSON-serializable dict."""
|
|
81
|
+
|
|
82
|
+
if isinstance(details, dict):
|
|
83
|
+
return self.to_plain(details)
|
|
84
|
+
out: dict[str, Any] = {}
|
|
85
|
+
run = details.run
|
|
86
|
+
out["run"] = {
|
|
87
|
+
"id": _value(run, "id"),
|
|
88
|
+
"root_execution_id": _value(run, "root_execution_id"),
|
|
89
|
+
"created_at": _value(run, "created_at"),
|
|
90
|
+
"created_by": _value(run, "created_by"),
|
|
91
|
+
}
|
|
92
|
+
annotations = _value(run, "annotations")
|
|
93
|
+
if annotations:
|
|
94
|
+
out["run"]["annotations"] = self.to_plain(annotations)
|
|
95
|
+
if details.execution:
|
|
96
|
+
out["execution"] = self.serialize_execution(details.execution)
|
|
97
|
+
if details.annotations:
|
|
98
|
+
out["annotations"] = self.to_plain(details.annotations)
|
|
99
|
+
if details.execution_state:
|
|
100
|
+
out["execution_state"] = {
|
|
101
|
+
"totals": self.to_plain(_value(details.execution_state, "status_totals")),
|
|
102
|
+
"per_execution": self.to_plain(_value(details.execution_state, "child_execution_status_stats")),
|
|
103
|
+
}
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
def get_run_details_output(
|
|
107
|
+
self,
|
|
108
|
+
run_id: str,
|
|
109
|
+
*,
|
|
110
|
+
include_implementations: bool = False,
|
|
111
|
+
include_annotations: bool = False,
|
|
112
|
+
include_execution_state: bool = False,
|
|
113
|
+
execution_id: str | None = None,
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
"""Fetch run details and return serialized output."""
|
|
116
|
+
|
|
117
|
+
kwargs: dict[str, Any] = {
|
|
118
|
+
"include_annotations": include_annotations,
|
|
119
|
+
"include_execution_state": include_execution_state,
|
|
120
|
+
}
|
|
121
|
+
if include_implementations:
|
|
122
|
+
kwargs["include_implementations"] = include_implementations
|
|
123
|
+
if execution_id is not None:
|
|
124
|
+
kwargs["execution_id"] = execution_id
|
|
125
|
+
details = self._require_client().get_run_details(run_id, **kwargs)
|
|
126
|
+
return self.serialize_run_details(details)
|
|
127
|
+
|
|
128
|
+
def fetch_graph_state_one(self, run_id: str) -> dict[str, Any]:
|
|
129
|
+
"""Fetch graph state for a pipeline run id or root execution id."""
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
run = self._require_client().pipeline_runs_get(run_id)
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
response = getattr(exc, "response", None)
|
|
135
|
+
if getattr(response, "status_code", None) != 404:
|
|
136
|
+
raise
|
|
137
|
+
run = None
|
|
138
|
+
root_execution_id = _value(run, "root_execution_id") if run else None
|
|
139
|
+
root_execution_id = root_execution_id or run_id
|
|
140
|
+
state = self._require_client().executions_graph_execution_state(root_execution_id)
|
|
141
|
+
return {
|
|
142
|
+
"run_id": run_id,
|
|
143
|
+
"root_execution_id": root_execution_id,
|
|
144
|
+
"status_totals": self.to_plain(_value(state, "status_totals")),
|
|
145
|
+
"failed_execution_ids": self.to_plain(_value(state, "failed_execution_ids")),
|
|
146
|
+
"per_execution": self.to_plain(_value(state, "per_execution")),
|
|
147
|
+
"error": None,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
def get_graph_state_output(
|
|
151
|
+
self,
|
|
152
|
+
run_ids: list[str],
|
|
153
|
+
*,
|
|
154
|
+
timeout: float = 30.0,
|
|
155
|
+
) -> dict[str, Any]:
|
|
156
|
+
"""Fetch lightweight graph state for one or more run/execution IDs."""
|
|
157
|
+
|
|
158
|
+
results: list[dict[str, Any]] = []
|
|
159
|
+
for run_id in run_ids:
|
|
160
|
+
executor = ThreadPoolExecutor(max_workers=1)
|
|
161
|
+
try:
|
|
162
|
+
future = executor.submit(self.fetch_graph_state_one, run_id)
|
|
163
|
+
try:
|
|
164
|
+
results.append(future.result(timeout=timeout))
|
|
165
|
+
except FutureTimeoutError:
|
|
166
|
+
results.append(_error_result(run_id, f"timeout after {timeout}s"))
|
|
167
|
+
except Exception as exc:
|
|
168
|
+
results.append(_error_result(run_id, str(exc)))
|
|
169
|
+
finally:
|
|
170
|
+
executor.shutdown(wait=False)
|
|
171
|
+
return {"results": results}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _value(value: Any, key: str, default: Any = None) -> Any:
|
|
175
|
+
if isinstance(value, dict):
|
|
176
|
+
return value.get(key, default)
|
|
177
|
+
return getattr(value, key, default)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _to_plain(value: Any) -> Any:
|
|
181
|
+
if hasattr(value, "to_dict"):
|
|
182
|
+
return value.to_dict()
|
|
183
|
+
if hasattr(value, "model_dump"):
|
|
184
|
+
return value.model_dump(by_alias=True)
|
|
185
|
+
if isinstance(value, dict):
|
|
186
|
+
return {key: _to_plain(item) for key, item in value.items()}
|
|
187
|
+
if isinstance(value, list):
|
|
188
|
+
return [_to_plain(item) for item in value]
|
|
189
|
+
return value
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _error_result(run_id: str, message: str) -> dict[str, Any]:
|
|
193
|
+
return {
|
|
194
|
+
"run_id": run_id,
|
|
195
|
+
"root_execution_id": None,
|
|
196
|
+
"status_totals": None,
|
|
197
|
+
"failed_execution_ids": None,
|
|
198
|
+
"per_execution": None,
|
|
199
|
+
"error": message,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
__all__ = ["PipelineRunDetails"]
|