adagio-cli 0.1.0__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.
- adagio/__init__.py +15 -0
- adagio/app/parsers/pipeline.py +89 -0
- adagio/check.py +16 -0
- adagio/cli/__init__.py +1 -0
- adagio/cli/args.py +62 -0
- adagio/cli/cache.py +82 -0
- adagio/cli/config.py +183 -0
- adagio/cli/dynamic.py +656 -0
- adagio/cli/main.py +313 -0
- adagio/cli/pipeline.py +49 -0
- adagio/cli/pipeline_sources.py +322 -0
- adagio/cli/qapi.py +300 -0
- adagio/cli/runner.py +310 -0
- adagio/cli/runtime.py +530 -0
- adagio/cli/task_exec.py +474 -0
- adagio/convert.py +0 -0
- adagio/describe.py +487 -0
- adagio/executors/__init__.py +34 -0
- adagio/executors/apptainer.py +275 -0
- adagio/executors/base.py +92 -0
- adagio/executors/cache_support.py +82 -0
- adagio/executors/common.py +124 -0
- adagio/executors/conda.py +314 -0
- adagio/executors/container_support.py +260 -0
- adagio/executors/defaults.py +99 -0
- adagio/executors/docker.py +404 -0
- adagio/executors/path_utils.py +60 -0
- adagio/executors/serial_runner.py +318 -0
- adagio/executors/signature.py +140 -0
- adagio/executors/task_contract.py +125 -0
- adagio/executors/task_environments.py +760 -0
- adagio/model/__init__.py +0 -0
- adagio/model/arguments.py +43 -0
- adagio/model/ast.py +67 -0
- adagio/model/pipeline.py +85 -0
- adagio/model/task.py +149 -0
- adagio/monitor/__init__.py +0 -0
- adagio/monitor/api.py +102 -0
- adagio/monitor/base.py +0 -0
- adagio/monitor/composite.py +99 -0
- adagio/monitor/connected.py +212 -0
- adagio/monitor/log.py +83 -0
- adagio/monitor/tty.py +301 -0
- adagio/protocol/__init__.py +0 -0
- adagio/protocol/base.py +0 -0
- adagio/protocol/file.py +0 -0
- adagio/protocol/http.py +0 -0
- adagio/protocol/s3.py +0 -0
- adagio/qapi/__init__.py +13 -0
- adagio/qapi/build.py +418 -0
- adagio/qapi/client.py +56 -0
- adagio/type_format.py +149 -0
- adagio_cli-0.1.0.dist-info/METADATA +136 -0
- adagio_cli-0.1.0.dist-info/RECORD +57 -0
- adagio_cli-0.1.0.dist-info/WHEEL +4 -0
- adagio_cli-0.1.0.dist-info/entry_points.txt +3 -0
- adagio_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
adagio/cli/dynamic.py
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import math
|
|
3
|
+
import re
|
|
4
|
+
import types
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Annotated, Callable, Union, get_args, get_origin
|
|
7
|
+
|
|
8
|
+
from cyclopts import Group
|
|
9
|
+
from cyclopts import Parameter as CliParameter
|
|
10
|
+
|
|
11
|
+
from ..app.parsers.pipeline import Input as InputSpec
|
|
12
|
+
from ..app.parsers.pipeline import Output as OutputSpec
|
|
13
|
+
from ..app.parsers.pipeline import Parameter as ParamSpec
|
|
14
|
+
from ..executors.cache_support import (
|
|
15
|
+
CACHE_DIR_HELP,
|
|
16
|
+
RECYCLE_POOL_HELP,
|
|
17
|
+
REUSE_HELP,
|
|
18
|
+
)
|
|
19
|
+
from ..type_format import (
|
|
20
|
+
compact_type_text,
|
|
21
|
+
path_type_label,
|
|
22
|
+
render_type_text,
|
|
23
|
+
type_label_display_width,
|
|
24
|
+
)
|
|
25
|
+
from .args import ParamType, ShowParamsMode, dynamic_opt, to_identifier
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _PipelineGroupFormatter:
|
|
29
|
+
"""Render pipeline options in one aligned table."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, entry_metadata: dict[str, dict[str, Any]]):
|
|
32
|
+
self.entry_metadata = entry_metadata
|
|
33
|
+
|
|
34
|
+
def __call__(self, console: Any, options: Any, panel: Any) -> None:
|
|
35
|
+
from rich.console import Group as RichGroup
|
|
36
|
+
from rich.console import NewLine
|
|
37
|
+
|
|
38
|
+
from cyclopts.help.specs import PanelSpec, TableSpec
|
|
39
|
+
|
|
40
|
+
renderables: list[Any] = []
|
|
41
|
+
|
|
42
|
+
if panel.description:
|
|
43
|
+
renderables.append(panel.description)
|
|
44
|
+
if not panel.entries:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if renderables:
|
|
48
|
+
renderables.append(NewLine())
|
|
49
|
+
columns = _get_pipeline_parameter_columns(
|
|
50
|
+
console, panel.entries, self.entry_metadata
|
|
51
|
+
)
|
|
52
|
+
renderables.append(TableSpec().build(columns, panel.entries))
|
|
53
|
+
console.print(PanelSpec().build(RichGroup(*renderables), title=panel.title))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _entry_key(entry: Any) -> str:
|
|
57
|
+
options = entry.all_options if hasattr(entry, "all_options") else ()
|
|
58
|
+
return next((name for name in options if name.startswith("--")), "")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _unwrap_optional_type(type_hint: Any) -> Any:
|
|
62
|
+
origin = get_origin(type_hint)
|
|
63
|
+
if origin not in (types.UnionType, Union):
|
|
64
|
+
return type_hint
|
|
65
|
+
|
|
66
|
+
args = [arg for arg in get_args(type_hint) if arg is not type(None)]
|
|
67
|
+
return args[0] if len(args) == 1 else type_hint
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _pipeline_type_label(type_hint: Any) -> str:
|
|
71
|
+
type_hint = _unwrap_optional_type(type_hint)
|
|
72
|
+
if type_hint is bool:
|
|
73
|
+
return "BOOLEAN"
|
|
74
|
+
if type_hint is int:
|
|
75
|
+
return "INTEGER"
|
|
76
|
+
if type_hint is float:
|
|
77
|
+
return "NUMBER"
|
|
78
|
+
if type_hint is Path:
|
|
79
|
+
return "PATH"
|
|
80
|
+
return "TEXT"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _display_type_label(*, spec_type: str | None, type_hint: Any, is_input: bool) -> str:
|
|
84
|
+
if is_input:
|
|
85
|
+
return path_type_label(spec_type)
|
|
86
|
+
|
|
87
|
+
if spec_type:
|
|
88
|
+
compact = compact_type_text(spec_type)
|
|
89
|
+
if compact.startswith("["):
|
|
90
|
+
return compact
|
|
91
|
+
|
|
92
|
+
return _pipeline_type_label(type_hint)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _output_path_help(description: str | None) -> str:
|
|
96
|
+
cleaned = (description or "").strip()
|
|
97
|
+
if cleaned:
|
|
98
|
+
return f"{cleaned} Overrides --output-dir for this output."
|
|
99
|
+
return "Overrides --output-dir for this output."
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _render_pipeline_type(
|
|
103
|
+
entry: Any, entry_metadata: dict[str, dict[str, Any]], width: int
|
|
104
|
+
) -> Any:
|
|
105
|
+
label = entry_metadata.get(_entry_key(entry), {}).get("type_label", "TEXT")
|
|
106
|
+
return render_type_text(label, width)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _render_pipeline_description(
|
|
110
|
+
entry: Any, entry_metadata: dict[str, dict[str, Any]]
|
|
111
|
+
) -> Any:
|
|
112
|
+
from rich.text import Text
|
|
113
|
+
|
|
114
|
+
from cyclopts.help.inline_text import InlineText
|
|
115
|
+
|
|
116
|
+
metadata = entry_metadata.get(_entry_key(entry), {})
|
|
117
|
+
description = entry.description
|
|
118
|
+
if description is None:
|
|
119
|
+
description = InlineText(Text())
|
|
120
|
+
elif not isinstance(description, InlineText):
|
|
121
|
+
if hasattr(description, "__rich_console__"):
|
|
122
|
+
description = InlineText(description)
|
|
123
|
+
else:
|
|
124
|
+
description = InlineText(Text(str(description)))
|
|
125
|
+
|
|
126
|
+
default = metadata.get("default")
|
|
127
|
+
if default is not None:
|
|
128
|
+
description.append(Text(f"[default: {default}]", "dim"))
|
|
129
|
+
|
|
130
|
+
if metadata.get("required"):
|
|
131
|
+
description.append(Text("[required]", "dim red"))
|
|
132
|
+
|
|
133
|
+
return description
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _get_pipeline_parameter_columns(
|
|
137
|
+
console: Any,
|
|
138
|
+
entries: list[Any],
|
|
139
|
+
entry_metadata: dict[str, dict[str, Any]],
|
|
140
|
+
) -> tuple[Any, ...]:
|
|
141
|
+
from cyclopts.help.specs import (
|
|
142
|
+
ColumnSpec,
|
|
143
|
+
NameRenderer,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
max_width = math.ceil(console.width * 0.35)
|
|
147
|
+
type_width = max(
|
|
148
|
+
8,
|
|
149
|
+
min(
|
|
150
|
+
max(
|
|
151
|
+
type_label_display_width(
|
|
152
|
+
entry_metadata.get(_entry_key(entry), {}).get("type_label", "TEXT")
|
|
153
|
+
)
|
|
154
|
+
for entry in entries
|
|
155
|
+
),
|
|
156
|
+
max(28, min(70, math.ceil(console.width * 0.35))),
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
name_column = ColumnSpec(
|
|
160
|
+
renderer=NameRenderer(max_width=max_width),
|
|
161
|
+
header="Option",
|
|
162
|
+
justify="left",
|
|
163
|
+
style="cyan",
|
|
164
|
+
max_width=max_width,
|
|
165
|
+
)
|
|
166
|
+
type_column = ColumnSpec(
|
|
167
|
+
renderer=lambda entry: _render_pipeline_type(entry, entry_metadata, type_width),
|
|
168
|
+
header="Type",
|
|
169
|
+
justify="left",
|
|
170
|
+
no_wrap=True,
|
|
171
|
+
width=type_width,
|
|
172
|
+
min_width=type_width,
|
|
173
|
+
max_width=type_width,
|
|
174
|
+
)
|
|
175
|
+
description_column = ColumnSpec(
|
|
176
|
+
renderer=lambda entry: _render_pipeline_description(entry, entry_metadata),
|
|
177
|
+
header="Description",
|
|
178
|
+
justify="left",
|
|
179
|
+
overflow="fold",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return (name_column, type_column, description_column)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _spec_py_type(type_name: str) -> type:
|
|
186
|
+
"""Map pipeline type text to a Python type."""
|
|
187
|
+
normalized = re.sub(r"[^a-z0-9]+", " ", (type_name or "").lower()).strip()
|
|
188
|
+
tokens = set(normalized.split())
|
|
189
|
+
|
|
190
|
+
if {"bool", "boolean"} & tokens or "bool" in normalized:
|
|
191
|
+
return bool
|
|
192
|
+
if {"int", "integer"} & tokens or "int" in normalized:
|
|
193
|
+
return int
|
|
194
|
+
if {"float", "double", "number", "numeric", "real"} & tokens:
|
|
195
|
+
return float
|
|
196
|
+
if {"str", "string", "text"} & tokens:
|
|
197
|
+
return str
|
|
198
|
+
return str
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _default_py_type(default: Any) -> type | None:
|
|
202
|
+
"""Infer a Python type from a default value."""
|
|
203
|
+
if isinstance(default, bool):
|
|
204
|
+
return bool
|
|
205
|
+
if isinstance(default, int):
|
|
206
|
+
return int
|
|
207
|
+
if isinstance(default, float):
|
|
208
|
+
return float
|
|
209
|
+
if isinstance(default, str):
|
|
210
|
+
return str
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _resolve_param_type(type_name: str, default: Any) -> type:
|
|
215
|
+
"""Resolve the CLI parameter type from type text and default."""
|
|
216
|
+
declared = _spec_py_type(type_name)
|
|
217
|
+
inferred = _default_py_type(default)
|
|
218
|
+
if inferred is None:
|
|
219
|
+
return declared
|
|
220
|
+
if declared is str and inferred is not str:
|
|
221
|
+
return inferred
|
|
222
|
+
return declared
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _format_help_text(
|
|
226
|
+
*,
|
|
227
|
+
description: str | None = None,
|
|
228
|
+
) -> str:
|
|
229
|
+
"""Return plain description text for pipeline help rows."""
|
|
230
|
+
return (description or "").strip()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _is_required_param(spec: ParamSpec) -> bool:
|
|
234
|
+
return bool(spec.required and spec.default is None)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _is_collection_type(type_name: str) -> bool:
|
|
238
|
+
return type_name.startswith("List[") or type_name.startswith("Collection[")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def build_dynamic_run(
|
|
242
|
+
*,
|
|
243
|
+
input_specs: list[InputSpec],
|
|
244
|
+
param_specs: list[ParamSpec],
|
|
245
|
+
output_specs: list[OutputSpec],
|
|
246
|
+
visible_input_names: set[str] | None = None,
|
|
247
|
+
visible_param_names: set[str] | None = None,
|
|
248
|
+
visible_output_names: set[str] | None = None,
|
|
249
|
+
argument_inputs: dict[str, Any] | None = None,
|
|
250
|
+
argument_params: dict[str, Any] | None = None,
|
|
251
|
+
run_handler: Callable[
|
|
252
|
+
[
|
|
253
|
+
Path,
|
|
254
|
+
Path | None,
|
|
255
|
+
Path | None,
|
|
256
|
+
dict[str, Any],
|
|
257
|
+
list[tuple[str, str]],
|
|
258
|
+
list[tuple[str, str]],
|
|
259
|
+
list[tuple[str, str]],
|
|
260
|
+
str,
|
|
261
|
+
list[str],
|
|
262
|
+
list[str],
|
|
263
|
+
],
|
|
264
|
+
None,
|
|
265
|
+
],
|
|
266
|
+
):
|
|
267
|
+
"""Build a dynamic run command from pipeline input, parameter, and output specs."""
|
|
268
|
+
visible_input_names = (
|
|
269
|
+
set(visible_input_names) if visible_input_names is not None else None
|
|
270
|
+
)
|
|
271
|
+
visible_param_names = set(visible_param_names) if visible_param_names is not None else None
|
|
272
|
+
visible_output_names = (
|
|
273
|
+
set(visible_output_names) if visible_output_names is not None else None
|
|
274
|
+
)
|
|
275
|
+
input_bindings: list[tuple[str, str]] = []
|
|
276
|
+
param_bindings: list[tuple[str, str]] = []
|
|
277
|
+
output_bindings: list[tuple[str, str]] = []
|
|
278
|
+
required_inputs: list[str] = []
|
|
279
|
+
required_params: list[str] = []
|
|
280
|
+
seen_idents: set[str] = set()
|
|
281
|
+
entry_metadata: dict[str, dict[str, Any]] = {}
|
|
282
|
+
seen_opts: set[str] = {
|
|
283
|
+
"--pipeline",
|
|
284
|
+
"-p",
|
|
285
|
+
"--arguments",
|
|
286
|
+
"--config",
|
|
287
|
+
"--show-params",
|
|
288
|
+
"--cache-dir",
|
|
289
|
+
"--reuse",
|
|
290
|
+
"--no-reuse",
|
|
291
|
+
"--output-dir",
|
|
292
|
+
"--recycle-pool",
|
|
293
|
+
"--log-dir",
|
|
294
|
+
"--targets",
|
|
295
|
+
}
|
|
296
|
+
argument_inputs = argument_inputs or {}
|
|
297
|
+
argument_params = argument_params or {}
|
|
298
|
+
command_group = Group("Command Options", sort_key=0)
|
|
299
|
+
pipeline_group = Group(
|
|
300
|
+
"Pipeline",
|
|
301
|
+
sort_key=1,
|
|
302
|
+
help_formatter=_PipelineGroupFormatter(entry_metadata),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
annotations: dict[str, Any] = {
|
|
306
|
+
"pipeline": Annotated[
|
|
307
|
+
Path,
|
|
308
|
+
CliParameter(
|
|
309
|
+
name=("--pipeline", "-p"),
|
|
310
|
+
group=command_group,
|
|
311
|
+
help="Path to the pipeline file or a catalog reference like @adagio/slug.",
|
|
312
|
+
),
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
annotations["arguments_file"] = Annotated[
|
|
317
|
+
Path | None,
|
|
318
|
+
CliParameter(
|
|
319
|
+
name=("--arguments",),
|
|
320
|
+
group=command_group,
|
|
321
|
+
help="Path to a JSON arguments file. Values are applied before CLI overrides.",
|
|
322
|
+
),
|
|
323
|
+
]
|
|
324
|
+
annotations["show_params"] = Annotated[
|
|
325
|
+
ShowParamsMode,
|
|
326
|
+
CliParameter(
|
|
327
|
+
name=("--show-params",),
|
|
328
|
+
group=command_group,
|
|
329
|
+
help="Parameter display mode: all, missing, or required.",
|
|
330
|
+
),
|
|
331
|
+
]
|
|
332
|
+
annotations["config_file"] = Annotated[
|
|
333
|
+
Path | None,
|
|
334
|
+
CliParameter(
|
|
335
|
+
name=("--config",),
|
|
336
|
+
group=command_group,
|
|
337
|
+
help="Path to a TOML runtime config file.",
|
|
338
|
+
),
|
|
339
|
+
]
|
|
340
|
+
annotations["cache_dir"] = Annotated[
|
|
341
|
+
Path,
|
|
342
|
+
CliParameter(
|
|
343
|
+
name=("--cache-dir",),
|
|
344
|
+
group=command_group,
|
|
345
|
+
help=CACHE_DIR_HELP,
|
|
346
|
+
),
|
|
347
|
+
]
|
|
348
|
+
annotations["reuse"] = Annotated[
|
|
349
|
+
bool,
|
|
350
|
+
CliParameter(
|
|
351
|
+
name=("--reuse",),
|
|
352
|
+
negative=("--no-reuse",),
|
|
353
|
+
group=command_group,
|
|
354
|
+
help=REUSE_HELP,
|
|
355
|
+
),
|
|
356
|
+
]
|
|
357
|
+
annotations["output_dir"] = Annotated[
|
|
358
|
+
Path | None,
|
|
359
|
+
CliParameter(
|
|
360
|
+
name=("--output-dir",),
|
|
361
|
+
group=command_group,
|
|
362
|
+
help="Directory for all pipeline outputs.",
|
|
363
|
+
),
|
|
364
|
+
]
|
|
365
|
+
annotations["recycle_pool"] = Annotated[
|
|
366
|
+
str | None,
|
|
367
|
+
CliParameter(
|
|
368
|
+
name=("--recycle-pool",),
|
|
369
|
+
group=command_group,
|
|
370
|
+
help=RECYCLE_POOL_HELP,
|
|
371
|
+
),
|
|
372
|
+
]
|
|
373
|
+
annotations["log_dir"] = Annotated[
|
|
374
|
+
Path | None,
|
|
375
|
+
CliParameter(
|
|
376
|
+
name=("--log-dir",),
|
|
377
|
+
group=command_group,
|
|
378
|
+
help=(
|
|
379
|
+
"Directory to copy each task's container log into before the "
|
|
380
|
+
"temp work dir is torn down."
|
|
381
|
+
),
|
|
382
|
+
),
|
|
383
|
+
]
|
|
384
|
+
annotations["targets"] = Annotated[
|
|
385
|
+
str | None,
|
|
386
|
+
CliParameter(
|
|
387
|
+
name=("--targets",),
|
|
388
|
+
group=command_group,
|
|
389
|
+
help=(
|
|
390
|
+
"Comma-separated node ids to produce; the plan is pruned to "
|
|
391
|
+
"their upstream closure. Defaults to all nodes."
|
|
392
|
+
),
|
|
393
|
+
),
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
parameters: list[inspect.Parameter] = [
|
|
397
|
+
inspect.Parameter(
|
|
398
|
+
name="pipeline",
|
|
399
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
400
|
+
annotation=annotations["pipeline"],
|
|
401
|
+
),
|
|
402
|
+
inspect.Parameter(
|
|
403
|
+
name="cache_dir",
|
|
404
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
405
|
+
annotation=annotations["cache_dir"],
|
|
406
|
+
),
|
|
407
|
+
inspect.Parameter(
|
|
408
|
+
name="arguments_file",
|
|
409
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
410
|
+
default=None,
|
|
411
|
+
annotation=annotations["arguments_file"],
|
|
412
|
+
),
|
|
413
|
+
inspect.Parameter(
|
|
414
|
+
name="show_params",
|
|
415
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
416
|
+
default=ShowParamsMode.REQUIRED,
|
|
417
|
+
annotation=annotations["show_params"],
|
|
418
|
+
),
|
|
419
|
+
inspect.Parameter(
|
|
420
|
+
name="config_file",
|
|
421
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
422
|
+
default=None,
|
|
423
|
+
annotation=annotations["config_file"],
|
|
424
|
+
),
|
|
425
|
+
inspect.Parameter(
|
|
426
|
+
name="reuse",
|
|
427
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
428
|
+
default=True,
|
|
429
|
+
annotation=annotations["reuse"],
|
|
430
|
+
),
|
|
431
|
+
inspect.Parameter(
|
|
432
|
+
name="output_dir",
|
|
433
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
434
|
+
default=None,
|
|
435
|
+
annotation=annotations["output_dir"],
|
|
436
|
+
),
|
|
437
|
+
inspect.Parameter(
|
|
438
|
+
name="recycle_pool",
|
|
439
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
440
|
+
default=None,
|
|
441
|
+
annotation=annotations["recycle_pool"],
|
|
442
|
+
),
|
|
443
|
+
inspect.Parameter(
|
|
444
|
+
name="log_dir",
|
|
445
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
446
|
+
default=None,
|
|
447
|
+
annotation=annotations["log_dir"],
|
|
448
|
+
),
|
|
449
|
+
inspect.Parameter(
|
|
450
|
+
name="targets",
|
|
451
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
452
|
+
default=None,
|
|
453
|
+
annotation=annotations["targets"],
|
|
454
|
+
),
|
|
455
|
+
]
|
|
456
|
+
|
|
457
|
+
def add_dynamic_option(
|
|
458
|
+
*,
|
|
459
|
+
ident: str,
|
|
460
|
+
opt: str,
|
|
461
|
+
required: bool,
|
|
462
|
+
py_type: Any,
|
|
463
|
+
help_text: str,
|
|
464
|
+
default: Any,
|
|
465
|
+
group: Group | tuple[Group, ...],
|
|
466
|
+
show: bool = True,
|
|
467
|
+
) -> None:
|
|
468
|
+
if opt in seen_opts:
|
|
469
|
+
raise ValueError(f"Conflicting CLI option generated: {opt!r}.")
|
|
470
|
+
seen_opts.add(opt)
|
|
471
|
+
|
|
472
|
+
annotation_type = py_type | None if default is None else py_type
|
|
473
|
+
annotations[ident] = Annotated[
|
|
474
|
+
annotation_type,
|
|
475
|
+
CliParameter(
|
|
476
|
+
name=(opt,),
|
|
477
|
+
group=group,
|
|
478
|
+
help=help_text,
|
|
479
|
+
required=required,
|
|
480
|
+
show=show,
|
|
481
|
+
),
|
|
482
|
+
]
|
|
483
|
+
parameters.append(
|
|
484
|
+
inspect.Parameter(
|
|
485
|
+
name=ident,
|
|
486
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
487
|
+
default=default,
|
|
488
|
+
annotation=annotations[ident],
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
required_input_specs = [spec for spec in input_specs if spec.required]
|
|
493
|
+
optional_input_specs = [spec for spec in input_specs if not spec.required]
|
|
494
|
+
required_param_specs = [spec for spec in param_specs if _is_required_param(spec)]
|
|
495
|
+
optional_param_specs = [spec for spec in param_specs if not _is_required_param(spec)]
|
|
496
|
+
|
|
497
|
+
def add_input_spec(spec: InputSpec) -> None:
|
|
498
|
+
original = spec.name
|
|
499
|
+
ident = to_identifier(original, "input")
|
|
500
|
+
if ident in seen_idents:
|
|
501
|
+
raise ValueError(
|
|
502
|
+
f"Duplicate pipeline input name after normalization: {original!r}."
|
|
503
|
+
)
|
|
504
|
+
seen_idents.add(ident)
|
|
505
|
+
input_bindings.append((ident, original))
|
|
506
|
+
argument_value = argument_inputs.get(original)
|
|
507
|
+
display_required = bool(spec.required and _is_missing(argument_value))
|
|
508
|
+
if spec.required:
|
|
509
|
+
required_inputs.append(original)
|
|
510
|
+
|
|
511
|
+
type_text = spec.type
|
|
512
|
+
opt = dynamic_opt(original, ParamType.INPUT)
|
|
513
|
+
show = visible_input_names is None or original in visible_input_names
|
|
514
|
+
entry_metadata[opt] = {
|
|
515
|
+
"type_label": _display_type_label(
|
|
516
|
+
spec_type=type_text, type_hint=str, is_input=True
|
|
517
|
+
),
|
|
518
|
+
"default": None,
|
|
519
|
+
"required": display_required,
|
|
520
|
+
}
|
|
521
|
+
add_dynamic_option(
|
|
522
|
+
ident=ident,
|
|
523
|
+
opt=opt,
|
|
524
|
+
required=False,
|
|
525
|
+
py_type=list[str] if _is_collection_type(spec.type) else str,
|
|
526
|
+
help_text=_format_help_text(
|
|
527
|
+
description=spec.description,
|
|
528
|
+
),
|
|
529
|
+
default=None,
|
|
530
|
+
group=pipeline_group,
|
|
531
|
+
show=show,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
def add_param_spec(spec: ParamSpec) -> None:
|
|
535
|
+
original = spec.name
|
|
536
|
+
ident = to_identifier(original, "param")
|
|
537
|
+
if ident in seen_idents:
|
|
538
|
+
raise ValueError(
|
|
539
|
+
f"Duplicate pipeline parameter name after normalization: {original!r}."
|
|
540
|
+
)
|
|
541
|
+
seen_idents.add(ident)
|
|
542
|
+
param_bindings.append((ident, original))
|
|
543
|
+
|
|
544
|
+
default = spec.default
|
|
545
|
+
is_required = _is_required_param(spec)
|
|
546
|
+
argument_value = argument_params.get(original)
|
|
547
|
+
has_argument_default = not _is_missing(argument_value)
|
|
548
|
+
display_default = (
|
|
549
|
+
default if default is not None else (argument_value if has_argument_default else None)
|
|
550
|
+
)
|
|
551
|
+
display_required = is_required and display_default is None
|
|
552
|
+
param_default = None
|
|
553
|
+
param_type: Any = _resolve_param_type(spec.type, default)
|
|
554
|
+
opt = dynamic_opt(original, ParamType.PARAM)
|
|
555
|
+
show = visible_param_names is None or original in visible_param_names
|
|
556
|
+
if is_required:
|
|
557
|
+
required_params.append(original)
|
|
558
|
+
entry_metadata[opt] = {
|
|
559
|
+
"type_label": _display_type_label(
|
|
560
|
+
spec_type=spec.type, type_hint=param_type, is_input=False
|
|
561
|
+
),
|
|
562
|
+
"default": display_default,
|
|
563
|
+
"required": display_required,
|
|
564
|
+
}
|
|
565
|
+
add_dynamic_option(
|
|
566
|
+
ident=ident,
|
|
567
|
+
opt=opt,
|
|
568
|
+
required=False,
|
|
569
|
+
py_type=param_type,
|
|
570
|
+
help_text=_format_help_text(
|
|
571
|
+
description=spec.description,
|
|
572
|
+
),
|
|
573
|
+
default=param_default,
|
|
574
|
+
group=pipeline_group,
|
|
575
|
+
show=show,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
for spec in required_input_specs:
|
|
579
|
+
add_input_spec(spec)
|
|
580
|
+
for spec in required_param_specs:
|
|
581
|
+
add_param_spec(spec)
|
|
582
|
+
for spec in optional_input_specs:
|
|
583
|
+
add_input_spec(spec)
|
|
584
|
+
for spec in optional_param_specs:
|
|
585
|
+
add_param_spec(spec)
|
|
586
|
+
|
|
587
|
+
for spec in output_specs:
|
|
588
|
+
original = spec.name
|
|
589
|
+
ident = to_identifier(original, "output")
|
|
590
|
+
if ident in seen_idents:
|
|
591
|
+
raise ValueError(
|
|
592
|
+
f"Duplicate pipeline output name after normalization: {original!r}."
|
|
593
|
+
)
|
|
594
|
+
seen_idents.add(ident)
|
|
595
|
+
output_bindings.append((ident, original))
|
|
596
|
+
opt = dynamic_opt(original, ParamType.OUTPUT)
|
|
597
|
+
show = visible_output_names is None or original in visible_output_names
|
|
598
|
+
entry_metadata[opt] = {
|
|
599
|
+
"type_label": path_type_label(spec.type),
|
|
600
|
+
"default": None,
|
|
601
|
+
"required": False,
|
|
602
|
+
}
|
|
603
|
+
add_dynamic_option(
|
|
604
|
+
ident=ident,
|
|
605
|
+
opt=opt,
|
|
606
|
+
required=False,
|
|
607
|
+
py_type=str,
|
|
608
|
+
help_text=_format_help_text(
|
|
609
|
+
description=_output_path_help(spec.description),
|
|
610
|
+
),
|
|
611
|
+
default=None,
|
|
612
|
+
group=pipeline_group,
|
|
613
|
+
show=show,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
def run(
|
|
617
|
+
pipeline: Path,
|
|
618
|
+
arguments_file: Path | None = None,
|
|
619
|
+
show_params: ShowParamsMode = ShowParamsMode.REQUIRED,
|
|
620
|
+
config_file: Path | None = None,
|
|
621
|
+
output_dir: Path | None = None,
|
|
622
|
+
recycle_pool: str | None = None,
|
|
623
|
+
log_dir: Path | None = None,
|
|
624
|
+
targets: str | None = None,
|
|
625
|
+
**kwargs: Any,
|
|
626
|
+
) -> None:
|
|
627
|
+
_ = show_params
|
|
628
|
+
kwargs["output_dir"] = output_dir
|
|
629
|
+
kwargs["recycle_pool"] = recycle_pool
|
|
630
|
+
kwargs["log_dir"] = log_dir
|
|
631
|
+
kwargs["targets"] = targets
|
|
632
|
+
run_handler(
|
|
633
|
+
pipeline,
|
|
634
|
+
arguments_file,
|
|
635
|
+
config_file,
|
|
636
|
+
kwargs,
|
|
637
|
+
input_bindings,
|
|
638
|
+
param_bindings,
|
|
639
|
+
output_bindings,
|
|
640
|
+
"output_dir",
|
|
641
|
+
required_inputs,
|
|
642
|
+
required_params,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
run.__annotations__ = annotations
|
|
646
|
+
run.__signature__ = inspect.Signature(parameters)
|
|
647
|
+
run.__doc__ = (
|
|
648
|
+
"Run an Adagio pipeline.\n\n"
|
|
649
|
+
"Dynamic inputs, parameters, and outputs are loaded from the pipeline file and exposed as CLI options.\n"
|
|
650
|
+
"Use: adagio run --pipeline PATH-OR-@ADAGIO/SLUG --help"
|
|
651
|
+
)
|
|
652
|
+
return run
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def _is_missing(value: Any) -> bool:
|
|
656
|
+
return value is None or value == "" or value == "<fill me>" or value == [] or value == {}
|