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,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"]