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,271 @@
|
|
|
1
|
+
"""`tangle sdk pipelines` local pipeline commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pathlib
|
|
6
|
+
from typing import Annotated, Any
|
|
7
|
+
|
|
8
|
+
from cyclopts import App, Parameter
|
|
9
|
+
|
|
10
|
+
from .cli_helpers import LazyTangleApiClient, load_config_or_exit, optional_path
|
|
11
|
+
from .cli_options import (
|
|
12
|
+
AuthHeaderOption,
|
|
13
|
+
BaseUrlOption,
|
|
14
|
+
ConfigOption,
|
|
15
|
+
HeaderOption,
|
|
16
|
+
LogTypeOption,
|
|
17
|
+
TokenOption,
|
|
18
|
+
)
|
|
19
|
+
from .logger import logger_for_log_type
|
|
20
|
+
from .pipelines import (
|
|
21
|
+
PipelineValidationError,
|
|
22
|
+
generate_mermaid,
|
|
23
|
+
hydrate_pipeline_file,
|
|
24
|
+
layout_pipeline_file,
|
|
25
|
+
validate_pipeline_file,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app = App(
|
|
29
|
+
name="pipelines",
|
|
30
|
+
help="Validate and visualize local Tangle pipeline specs.",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command(name="validate")
|
|
35
|
+
def pipelines_validate(
|
|
36
|
+
pipeline_path: pathlib.Path,
|
|
37
|
+
*,
|
|
38
|
+
log_type: LogTypeOption = "console",
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Validate a local pipeline YAML file."""
|
|
41
|
+
|
|
42
|
+
logger, finalize_logs = logger_for_log_type(log_type)
|
|
43
|
+
try:
|
|
44
|
+
try:
|
|
45
|
+
validate_pipeline_file(pipeline_path)
|
|
46
|
+
except PipelineValidationError as exc:
|
|
47
|
+
raise SystemExit(str(exc)) from exc
|
|
48
|
+
print(f"Valid pipeline: {pipeline_path}")
|
|
49
|
+
finally:
|
|
50
|
+
finalize_logs()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command(name="diagram")
|
|
54
|
+
def pipelines_diagram(
|
|
55
|
+
pipeline_path: pathlib.Path,
|
|
56
|
+
*,
|
|
57
|
+
log_type: LogTypeOption = "console",
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Print a Mermaid dependency diagram for a local pipeline YAML file."""
|
|
60
|
+
|
|
61
|
+
logger, finalize_logs = logger_for_log_type(log_type)
|
|
62
|
+
try:
|
|
63
|
+
try:
|
|
64
|
+
pipeline = validate_pipeline_file(pipeline_path)
|
|
65
|
+
except PipelineValidationError as exc:
|
|
66
|
+
raise SystemExit(str(exc)) from exc
|
|
67
|
+
print(generate_mermaid(pipeline))
|
|
68
|
+
finally:
|
|
69
|
+
finalize_logs()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _optional_str(value: object) -> str | None:
|
|
73
|
+
return value if isinstance(value, str) else None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _header_entries(cli_header: list[str] | None, config: dict[str, object]) -> list[str] | None:
|
|
77
|
+
if cli_header is not None:
|
|
78
|
+
return cli_header
|
|
79
|
+
config_header = config.get("header")
|
|
80
|
+
if isinstance(config_header, list):
|
|
81
|
+
return [str(entry) for entry in config_header]
|
|
82
|
+
if isinstance(config_header, str):
|
|
83
|
+
return [config_header]
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _trusted_hydration_config(config: dict[str, object]) -> dict[str, Any]:
|
|
88
|
+
trusted = config.get("trusted_hydration", {})
|
|
89
|
+
return trusted if isinstance(trusted, dict) else {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _trusted_sources(
|
|
93
|
+
cli_sources: list[str] | None,
|
|
94
|
+
config: dict[str, object],
|
|
95
|
+
) -> list[str]:
|
|
96
|
+
sources: list[str] = []
|
|
97
|
+
config_sources = _trusted_hydration_config(config).get("trusted_python_sources", [])
|
|
98
|
+
if isinstance(config_sources, str):
|
|
99
|
+
sources.append(config_sources)
|
|
100
|
+
elif isinstance(config_sources, list):
|
|
101
|
+
sources.extend(str(source) for source in config_sources)
|
|
102
|
+
if cli_sources:
|
|
103
|
+
sources.extend(cli_sources)
|
|
104
|
+
return [source for source in sources if source]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _allow_all_hydration(
|
|
108
|
+
trusted_hydration: bool | None,
|
|
109
|
+
config: dict[str, object],
|
|
110
|
+
) -> bool:
|
|
111
|
+
return bool(trusted_hydration or _trusted_hydration_config(config).get("allow_all", False))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _parse_vars(values: list[str] | dict[str, object] | None) -> dict[str, str]:
|
|
115
|
+
parsed: dict[str, str] = {}
|
|
116
|
+
if isinstance(values, dict):
|
|
117
|
+
return {str(key): str(value) for key, value in values.items()}
|
|
118
|
+
for value in values or []:
|
|
119
|
+
if "=" not in value:
|
|
120
|
+
raise SystemExit("--var entries must use KEY=VALUE syntax")
|
|
121
|
+
key, parsed_value = value.split("=", 1)
|
|
122
|
+
if not key:
|
|
123
|
+
raise SystemExit("--var entries must use KEY=VALUE syntax")
|
|
124
|
+
parsed[key] = parsed_value
|
|
125
|
+
return parsed
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command(name="hydrate")
|
|
129
|
+
def pipelines_hydrate(
|
|
130
|
+
pipeline_path: pathlib.Path,
|
|
131
|
+
*,
|
|
132
|
+
output: Annotated[
|
|
133
|
+
pathlib.Path | None,
|
|
134
|
+
Parameter(
|
|
135
|
+
name="--output",
|
|
136
|
+
alias="-o",
|
|
137
|
+
help="Output path. Defaults to printing hydrated YAML to stdout.",
|
|
138
|
+
),
|
|
139
|
+
] = None,
|
|
140
|
+
var: Annotated[
|
|
141
|
+
list[str] | None,
|
|
142
|
+
Parameter(
|
|
143
|
+
name="--var",
|
|
144
|
+
help="Template override as KEY=VALUE. Repeat for multiple overrides.",
|
|
145
|
+
negative_iterable=(),
|
|
146
|
+
),
|
|
147
|
+
] = None,
|
|
148
|
+
base_url: BaseUrlOption = None,
|
|
149
|
+
token: TokenOption = None,
|
|
150
|
+
auth_header: AuthHeaderOption = None,
|
|
151
|
+
header: HeaderOption = None,
|
|
152
|
+
trusted_source: Annotated[
|
|
153
|
+
list[str] | None,
|
|
154
|
+
Parameter(
|
|
155
|
+
name="--trusted-source",
|
|
156
|
+
help="Trusted local_from_python source root or glob. Repeat for multiple.",
|
|
157
|
+
negative_iterable=(),
|
|
158
|
+
),
|
|
159
|
+
] = None,
|
|
160
|
+
trusted_hydration: Annotated[
|
|
161
|
+
bool | None,
|
|
162
|
+
Parameter(
|
|
163
|
+
name="--trusted-hydration",
|
|
164
|
+
help="Allow all local_from_python execution during hydration for trusted inputs.",
|
|
165
|
+
),
|
|
166
|
+
] = None,
|
|
167
|
+
config: ConfigOption = None,
|
|
168
|
+
log_type: LogTypeOption = "console",
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Hydrate a local pipeline YAML file."""
|
|
171
|
+
|
|
172
|
+
config_values = load_config_or_exit(config)
|
|
173
|
+
config_base_url = _optional_str(config_values.get("base_url"))
|
|
174
|
+
resolved_base_url = base_url if base_url is not None else config_base_url
|
|
175
|
+
include_env_credentials = not (base_url is None and config_base_url is not None)
|
|
176
|
+
resolved_var = var if var is not None else config_values.get("var")
|
|
177
|
+
resolved_token = token
|
|
178
|
+
if resolved_token is None:
|
|
179
|
+
resolved_token = _optional_str(config_values.get("token"))
|
|
180
|
+
|
|
181
|
+
logger, finalize_logs = logger_for_log_type(log_type)
|
|
182
|
+
try:
|
|
183
|
+
result = hydrate_pipeline_file(
|
|
184
|
+
pipeline_path,
|
|
185
|
+
output=output or optional_path(config_values.get("output")),
|
|
186
|
+
overrides=_parse_vars(resolved_var),
|
|
187
|
+
base_url=resolved_base_url,
|
|
188
|
+
token=resolved_token,
|
|
189
|
+
auth_header=(
|
|
190
|
+
auth_header
|
|
191
|
+
if auth_header is not None
|
|
192
|
+
else _optional_str(config_values.get("auth_header"))
|
|
193
|
+
),
|
|
194
|
+
header=_header_entries(header, config_values),
|
|
195
|
+
include_env_credentials=include_env_credentials,
|
|
196
|
+
logger=logger,
|
|
197
|
+
trusted_python_sources=_trusted_sources(trusted_source, config_values),
|
|
198
|
+
allow_all_hydration=_allow_all_hydration(trusted_hydration, config_values),
|
|
199
|
+
client=LazyTangleApiClient(
|
|
200
|
+
command_name="pipeline hydration with API-backed component references",
|
|
201
|
+
base_url=resolved_base_url,
|
|
202
|
+
token=resolved_token,
|
|
203
|
+
auth_header=(
|
|
204
|
+
auth_header
|
|
205
|
+
if auth_header is not None
|
|
206
|
+
else _optional_str(config_values.get("auth_header"))
|
|
207
|
+
),
|
|
208
|
+
header=_header_entries(header, config_values),
|
|
209
|
+
include_env_credentials=include_env_credentials,
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
except PipelineValidationError as exc:
|
|
213
|
+
raise SystemExit(str(exc)) from exc
|
|
214
|
+
finally:
|
|
215
|
+
finalize_logs()
|
|
216
|
+
|
|
217
|
+
if result.output_path is None:
|
|
218
|
+
print(result.content, end="" if result.content.endswith("\n") else "\n")
|
|
219
|
+
else:
|
|
220
|
+
print(
|
|
221
|
+
f"Hydrated {pipeline_path} -> {result.output_path} "
|
|
222
|
+
f"({result.resolved_components} component(s) resolved)."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.command(name="layout")
|
|
227
|
+
def pipelines_layout(
|
|
228
|
+
pipeline_path: pathlib.Path,
|
|
229
|
+
*,
|
|
230
|
+
output: Annotated[
|
|
231
|
+
pathlib.Path | None,
|
|
232
|
+
Parameter(
|
|
233
|
+
name="--output",
|
|
234
|
+
alias="-o",
|
|
235
|
+
help="Output path. Defaults to overwriting the input file.",
|
|
236
|
+
),
|
|
237
|
+
] = None,
|
|
238
|
+
recursive: Annotated[
|
|
239
|
+
bool | None,
|
|
240
|
+
Parameter(help="Also layout nested graph component specs."),
|
|
241
|
+
] = None,
|
|
242
|
+
x_spacing: Annotated[
|
|
243
|
+
int,
|
|
244
|
+
Parameter(help="Horizontal spacing between dependency layers."),
|
|
245
|
+
] = 300,
|
|
246
|
+
y_spacing: Annotated[
|
|
247
|
+
int,
|
|
248
|
+
Parameter(help="Vertical spacing between tasks in the same layer."),
|
|
249
|
+
] = 120,
|
|
250
|
+
log_type: LogTypeOption = "console",
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Add or update editor.position annotations in a local pipeline YAML file."""
|
|
253
|
+
|
|
254
|
+
logger, finalize_logs = logger_for_log_type(log_type)
|
|
255
|
+
try:
|
|
256
|
+
result = layout_pipeline_file(
|
|
257
|
+
pipeline_path,
|
|
258
|
+
output=output,
|
|
259
|
+
recursive=bool(recursive),
|
|
260
|
+
x_spacing=x_spacing,
|
|
261
|
+
y_spacing=y_spacing,
|
|
262
|
+
)
|
|
263
|
+
except PipelineValidationError as exc:
|
|
264
|
+
raise SystemExit(str(exc)) from exc
|
|
265
|
+
finally:
|
|
266
|
+
finalize_logs()
|
|
267
|
+
print(
|
|
268
|
+
f"Positioned {result.tasks_positioned} task(s) across "
|
|
269
|
+
f"{result.graphs_positioned} graph(s)."
|
|
270
|
+
)
|
|
271
|
+
print(f"Wrote layout to: {result.output_path}")
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""`tangle sdk published-components` command implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pathlib
|
|
6
|
+
from typing import Annotated, Any
|
|
7
|
+
|
|
8
|
+
from cyclopts import App, Parameter
|
|
9
|
+
|
|
10
|
+
from .cli_helpers import (
|
|
11
|
+
LazyTangleApiClient,
|
|
12
|
+
api_arg_specs,
|
|
13
|
+
include_env_credentials_for_args,
|
|
14
|
+
load_args_or_exit,
|
|
15
|
+
optional_path,
|
|
16
|
+
print_json,
|
|
17
|
+
)
|
|
18
|
+
from .cli_options import (
|
|
19
|
+
AuthHeaderOption,
|
|
20
|
+
BaseUrlOption,
|
|
21
|
+
ConfigOption,
|
|
22
|
+
HeaderOption,
|
|
23
|
+
LogTypeOption,
|
|
24
|
+
TokenOption,
|
|
25
|
+
)
|
|
26
|
+
from .component_publisher import ComponentPublisher, deprecate_component
|
|
27
|
+
from .logger import logger_for_log_type
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _client_from_options(
|
|
31
|
+
*,
|
|
32
|
+
base_url: str | None = None,
|
|
33
|
+
token: str | None = None,
|
|
34
|
+
auth_header: str | None = None,
|
|
35
|
+
header: list[str] | str | None = None,
|
|
36
|
+
include_env_credentials: bool = True,
|
|
37
|
+
command_name: str = "published-component commands",
|
|
38
|
+
) -> LazyTangleApiClient:
|
|
39
|
+
"""Create a lazy static client proxy for published-component commands.
|
|
40
|
+
|
|
41
|
+
Kept as a module-level seam so downstream wrappers/tests can monkeypatch
|
|
42
|
+
client construction without replacing the shared LazyTangleApiClient class.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return LazyTangleApiClient(
|
|
46
|
+
base_url=base_url,
|
|
47
|
+
token=token,
|
|
48
|
+
auth_header=auth_header,
|
|
49
|
+
header=header,
|
|
50
|
+
include_env_credentials=include_env_credentials,
|
|
51
|
+
command_name=command_name,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
app = App(
|
|
56
|
+
name="published-components",
|
|
57
|
+
help="Inspect and search published Tangle components from the registry.",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command(name="search")
|
|
62
|
+
def published_components_search(
|
|
63
|
+
name: str | None = None,
|
|
64
|
+
*,
|
|
65
|
+
include_deprecated: bool | None = None,
|
|
66
|
+
published_by: str | None = None,
|
|
67
|
+
digest: str | None = None,
|
|
68
|
+
base_url: BaseUrlOption = None,
|
|
69
|
+
token: TokenOption = None,
|
|
70
|
+
auth_header: AuthHeaderOption = None,
|
|
71
|
+
header: HeaderOption = None,
|
|
72
|
+
config: ConfigOption = None,
|
|
73
|
+
log_type: LogTypeOption = "console",
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Search published component metadata."""
|
|
76
|
+
|
|
77
|
+
for args in load_args_or_exit(
|
|
78
|
+
config,
|
|
79
|
+
name=(name, None),
|
|
80
|
+
include_deprecated=(include_deprecated, None),
|
|
81
|
+
published_by=(published_by, None),
|
|
82
|
+
digest=(digest, None),
|
|
83
|
+
log_type=(log_type, "console"),
|
|
84
|
+
**api_arg_specs(
|
|
85
|
+
base_url=base_url,
|
|
86
|
+
token=token,
|
|
87
|
+
auth_header=auth_header,
|
|
88
|
+
header=header,
|
|
89
|
+
),
|
|
90
|
+
):
|
|
91
|
+
logger, finalize_logs = logger_for_log_type(args.log_type)
|
|
92
|
+
try:
|
|
93
|
+
client = _client_from_options(
|
|
94
|
+
base_url=args.base_url,
|
|
95
|
+
token=args.token,
|
|
96
|
+
auth_header=args.auth_header,
|
|
97
|
+
header=args.header,
|
|
98
|
+
include_env_credentials=include_env_credentials_for_args(args, base_url),
|
|
99
|
+
command_name="published-component commands",
|
|
100
|
+
)
|
|
101
|
+
if require_available := getattr(client, "require_available", None):
|
|
102
|
+
require_available()
|
|
103
|
+
|
|
104
|
+
from .component_inspector import ComponentInspector
|
|
105
|
+
|
|
106
|
+
print_json(
|
|
107
|
+
ComponentInspector(client=client, logger=logger, base_url=args.base_url).search_components(
|
|
108
|
+
name=args.name,
|
|
109
|
+
include_deprecated=bool(args.include_deprecated),
|
|
110
|
+
published_by=args.published_by,
|
|
111
|
+
digest=args.digest,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
finally:
|
|
115
|
+
finalize_logs()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@app.command(name="inspect")
|
|
119
|
+
def published_components_inspect(
|
|
120
|
+
name: str | None = None,
|
|
121
|
+
*,
|
|
122
|
+
digest: str | None = None,
|
|
123
|
+
all_versions: bool | None = None,
|
|
124
|
+
include_deprecated: bool | None = None,
|
|
125
|
+
follow_deprecated: bool | None = None,
|
|
126
|
+
full_spec: bool | None = None,
|
|
127
|
+
published_by: str | None = None,
|
|
128
|
+
base_url: BaseUrlOption = None,
|
|
129
|
+
token: TokenOption = None,
|
|
130
|
+
auth_header: AuthHeaderOption = None,
|
|
131
|
+
header: HeaderOption = None,
|
|
132
|
+
config: ConfigOption = None,
|
|
133
|
+
log_type: LogTypeOption = "console",
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Inspect a published component by exact name or digest."""
|
|
136
|
+
|
|
137
|
+
for args in load_args_or_exit(
|
|
138
|
+
config,
|
|
139
|
+
name=(name, None),
|
|
140
|
+
digest=(digest, None),
|
|
141
|
+
all_versions=(all_versions, None),
|
|
142
|
+
include_deprecated=(include_deprecated, None),
|
|
143
|
+
follow_deprecated=(follow_deprecated, None),
|
|
144
|
+
full_spec=(full_spec, None),
|
|
145
|
+
published_by=(published_by, None),
|
|
146
|
+
log_type=(log_type, "console"),
|
|
147
|
+
**api_arg_specs(
|
|
148
|
+
base_url=base_url,
|
|
149
|
+
token=token,
|
|
150
|
+
auth_header=auth_header,
|
|
151
|
+
header=header,
|
|
152
|
+
),
|
|
153
|
+
):
|
|
154
|
+
logger, finalize_logs = logger_for_log_type(args.log_type)
|
|
155
|
+
try:
|
|
156
|
+
if bool(args.name) == bool(args.digest):
|
|
157
|
+
raise SystemExit("Provide exactly one of NAME or --digest DIGEST")
|
|
158
|
+
|
|
159
|
+
client = _client_from_options(
|
|
160
|
+
base_url=args.base_url,
|
|
161
|
+
token=args.token,
|
|
162
|
+
auth_header=args.auth_header,
|
|
163
|
+
header=args.header,
|
|
164
|
+
include_env_credentials=include_env_credentials_for_args(args, base_url),
|
|
165
|
+
command_name="published-component commands",
|
|
166
|
+
)
|
|
167
|
+
if require_available := getattr(client, "require_available", None):
|
|
168
|
+
require_available()
|
|
169
|
+
from .component_inspector import ComponentInspector
|
|
170
|
+
|
|
171
|
+
inspector = ComponentInspector(client=client, logger=logger, base_url=args.base_url)
|
|
172
|
+
if args.digest:
|
|
173
|
+
result = inspector.inspect_by_digest(
|
|
174
|
+
args.digest,
|
|
175
|
+
full_spec=bool(args.full_spec),
|
|
176
|
+
follow_deprecated=bool(args.follow_deprecated),
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
result = inspector.inspect_by_name(
|
|
180
|
+
args.name or "",
|
|
181
|
+
include_all_versions=bool(args.all_versions),
|
|
182
|
+
include_deprecated=bool(args.include_deprecated),
|
|
183
|
+
full_spec=bool(args.full_spec),
|
|
184
|
+
published_by=args.published_by,
|
|
185
|
+
)
|
|
186
|
+
print_json(result)
|
|
187
|
+
finally:
|
|
188
|
+
finalize_logs()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.command(name="library")
|
|
192
|
+
def published_components_library(
|
|
193
|
+
*,
|
|
194
|
+
base_url: BaseUrlOption = None,
|
|
195
|
+
token: TokenOption = None,
|
|
196
|
+
auth_header: AuthHeaderOption = None,
|
|
197
|
+
header: HeaderOption = None,
|
|
198
|
+
config: ConfigOption = None,
|
|
199
|
+
log_type: LogTypeOption = "console",
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Print the curated standard component library."""
|
|
202
|
+
|
|
203
|
+
for args in load_args_or_exit(
|
|
204
|
+
config,
|
|
205
|
+
log_type=(log_type, "console"),
|
|
206
|
+
**api_arg_specs(
|
|
207
|
+
base_url=base_url,
|
|
208
|
+
token=token,
|
|
209
|
+
auth_header=auth_header,
|
|
210
|
+
header=header,
|
|
211
|
+
),
|
|
212
|
+
):
|
|
213
|
+
logger, finalize_logs = logger_for_log_type(args.log_type)
|
|
214
|
+
try:
|
|
215
|
+
client = _client_from_options(
|
|
216
|
+
base_url=args.base_url,
|
|
217
|
+
token=args.token,
|
|
218
|
+
auth_header=args.auth_header,
|
|
219
|
+
header=args.header,
|
|
220
|
+
include_env_credentials=include_env_credentials_for_args(args, base_url),
|
|
221
|
+
command_name="published-component commands",
|
|
222
|
+
)
|
|
223
|
+
if require_available := getattr(client, "require_available", None):
|
|
224
|
+
require_available()
|
|
225
|
+
|
|
226
|
+
from .component_inspector import ComponentInspector
|
|
227
|
+
|
|
228
|
+
print_json(ComponentInspector(client=client, logger=logger, base_url=args.base_url).get_standard_library())
|
|
229
|
+
finally:
|
|
230
|
+
finalize_logs()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@app.command(name="publish")
|
|
234
|
+
def published_components_publish(
|
|
235
|
+
component_path: pathlib.Path | None = None,
|
|
236
|
+
*,
|
|
237
|
+
image: str | None = None,
|
|
238
|
+
name: str | None = None,
|
|
239
|
+
description: str | None = None,
|
|
240
|
+
annotations: Annotated[
|
|
241
|
+
str | None,
|
|
242
|
+
Parameter(help="Custom annotations as a JSON object."),
|
|
243
|
+
] = None,
|
|
244
|
+
dry_run: bool | None = None,
|
|
245
|
+
git_remote_sha: str | None = None,
|
|
246
|
+
git_remote_branch: str | None = None,
|
|
247
|
+
git_remote_url: str | None = None,
|
|
248
|
+
git_root: pathlib.Path | None = None,
|
|
249
|
+
published_by: str | None = None,
|
|
250
|
+
base_url: BaseUrlOption = None,
|
|
251
|
+
token: TokenOption = None,
|
|
252
|
+
auth_header: AuthHeaderOption = None,
|
|
253
|
+
header: HeaderOption = None,
|
|
254
|
+
config: ConfigOption = None,
|
|
255
|
+
log_type: LogTypeOption = "console",
|
|
256
|
+
) -> None:
|
|
257
|
+
"""Publish one component YAML file to a Tangle component registry."""
|
|
258
|
+
|
|
259
|
+
all_args = load_args_or_exit(
|
|
260
|
+
config,
|
|
261
|
+
component_path=("component_path", component_path, None, False, True, optional_path),
|
|
262
|
+
image=(image, None),
|
|
263
|
+
name=(name, None),
|
|
264
|
+
description=(description, None),
|
|
265
|
+
annotations=("annotations", annotations, None, True),
|
|
266
|
+
dry_run=(dry_run, None),
|
|
267
|
+
git_remote_sha=(git_remote_sha, None),
|
|
268
|
+
git_remote_branch=(git_remote_branch, None),
|
|
269
|
+
git_remote_url=(git_remote_url, None),
|
|
270
|
+
git_root=(git_root, None, optional_path),
|
|
271
|
+
published_by=(published_by, None),
|
|
272
|
+
log_type=(log_type, "console"),
|
|
273
|
+
**api_arg_specs(
|
|
274
|
+
base_url=base_url,
|
|
275
|
+
token=token,
|
|
276
|
+
auth_header=auth_header,
|
|
277
|
+
header=header,
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
results: list[dict[str, Any]] = []
|
|
281
|
+
for args in all_args:
|
|
282
|
+
logger, finalize_logs = logger_for_log_type(args.log_type)
|
|
283
|
+
try:
|
|
284
|
+
client = None if args.dry_run else _client_from_options(
|
|
285
|
+
base_url=args.base_url,
|
|
286
|
+
token=args.token,
|
|
287
|
+
auth_header=args.auth_header,
|
|
288
|
+
header=args.header,
|
|
289
|
+
include_env_credentials=include_env_credentials_for_args(args, base_url),
|
|
290
|
+
command_name="published-component commands",
|
|
291
|
+
)
|
|
292
|
+
publisher = ComponentPublisher(
|
|
293
|
+
dry_run=bool(args.dry_run),
|
|
294
|
+
git_remote_sha=args.git_remote_sha,
|
|
295
|
+
git_remote_branch=args.git_remote_branch,
|
|
296
|
+
git_remote_url=args.git_remote_url,
|
|
297
|
+
git_root=args.git_root,
|
|
298
|
+
published_by=args.published_by,
|
|
299
|
+
client=client,
|
|
300
|
+
logger=logger,
|
|
301
|
+
)
|
|
302
|
+
result = publisher.publish_component(
|
|
303
|
+
args.component_path,
|
|
304
|
+
image=args.image,
|
|
305
|
+
name=args.name,
|
|
306
|
+
description=args.description,
|
|
307
|
+
annotations=args.annotations,
|
|
308
|
+
)
|
|
309
|
+
result_dict = result.to_dict() if hasattr(result, "to_dict") else dict(result)
|
|
310
|
+
results.append({"component_path": str(args.component_path), **result_dict})
|
|
311
|
+
finally:
|
|
312
|
+
finalize_logs()
|
|
313
|
+
|
|
314
|
+
error_count = sum(1 for result in results if result.get("status") in {"error", "failed"})
|
|
315
|
+
summary = {
|
|
316
|
+
"status": "failed" if error_count else "success",
|
|
317
|
+
"components_count": len(results),
|
|
318
|
+
"error_count": error_count,
|
|
319
|
+
"results": results,
|
|
320
|
+
}
|
|
321
|
+
print_json(summary)
|
|
322
|
+
if error_count:
|
|
323
|
+
raise SystemExit(1)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@app.command(name="deprecate")
|
|
327
|
+
def published_components_deprecate(
|
|
328
|
+
digest: str | None = None,
|
|
329
|
+
*,
|
|
330
|
+
superseded_by: str | None = None,
|
|
331
|
+
base_url: BaseUrlOption = None,
|
|
332
|
+
token: TokenOption = None,
|
|
333
|
+
auth_header: AuthHeaderOption = None,
|
|
334
|
+
header: HeaderOption = None,
|
|
335
|
+
config: ConfigOption = None,
|
|
336
|
+
log_type: LogTypeOption = "console",
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Deprecate a published component by digest."""
|
|
339
|
+
|
|
340
|
+
for args in load_args_or_exit(
|
|
341
|
+
config,
|
|
342
|
+
digest=("digest", digest, None, False, True),
|
|
343
|
+
superseded_by=(superseded_by, None),
|
|
344
|
+
log_type=(log_type, "console"),
|
|
345
|
+
**api_arg_specs(
|
|
346
|
+
base_url=base_url,
|
|
347
|
+
token=token,
|
|
348
|
+
auth_header=auth_header,
|
|
349
|
+
header=header,
|
|
350
|
+
),
|
|
351
|
+
):
|
|
352
|
+
logger, finalize_logs = logger_for_log_type(args.log_type)
|
|
353
|
+
try:
|
|
354
|
+
client = _client_from_options(
|
|
355
|
+
base_url=args.base_url,
|
|
356
|
+
token=args.token,
|
|
357
|
+
auth_header=args.auth_header,
|
|
358
|
+
header=args.header,
|
|
359
|
+
include_env_credentials=include_env_credentials_for_args(args, base_url),
|
|
360
|
+
command_name="published-component commands",
|
|
361
|
+
)
|
|
362
|
+
result = deprecate_component(
|
|
363
|
+
client,
|
|
364
|
+
args.digest,
|
|
365
|
+
superseded_by=args.superseded_by,
|
|
366
|
+
logger=logger,
|
|
367
|
+
)
|
|
368
|
+
result_dict = result.to_dict() if hasattr(result, "to_dict") else result
|
|
369
|
+
print_json(result_dict)
|
|
370
|
+
if isinstance(result_dict, dict) and not result_dict.get("success", result_dict.get("status") != "failed"):
|
|
371
|
+
raise SystemExit(1)
|
|
372
|
+
finally:
|
|
373
|
+
finalize_logs()
|
tangle_cli/py.typed
ADDED
|
File without changes
|