algorithm-nexus 0.2.1__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.
- algorithm_nexus/__init__.py +4 -0
- algorithm_nexus/cli.py +101 -0
- algorithm_nexus/commands/__init__.py +6 -0
- algorithm_nexus/commands/ado_validator.py +176 -0
- algorithm_nexus/commands/benchmark_manager.py +1264 -0
- algorithm_nexus/commands/get.py +120 -0
- algorithm_nexus/commands/list.py +349 -0
- algorithm_nexus/commands/run.py +162 -0
- algorithm_nexus/commands/utils.py +584 -0
- algorithm_nexus/commands/validate.py +683 -0
- algorithm_nexus/commands/venv_manager.py +158 -0
- algorithm_nexus/models.py +408 -0
- algorithm_nexus-0.2.1.dist-info/METADATA +319 -0
- algorithm_nexus-0.2.1.dist-info/RECORD +18 -0
- algorithm_nexus-0.2.1.dist-info/WHEEL +5 -0
- algorithm_nexus-0.2.1.dist-info/entry_points.txt +2 -0
- algorithm_nexus-0.2.1.dist-info/licenses/LICENSE +201 -0
- algorithm_nexus-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Copyright IBM Corp. 2026
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Get commands for Algorithm Nexus CLI."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import typer
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
except ImportError:
|
|
16
|
+
print(
|
|
17
|
+
"Error: CLI dependencies are not installed.\n"
|
|
18
|
+
"Please install them with: pip install algorithm-nexus[cli]",
|
|
19
|
+
file=sys.stderr,
|
|
20
|
+
)
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
from algorithm_nexus.commands.utils import (
|
|
24
|
+
find_package_config,
|
|
25
|
+
output_benchmark_requirements_table,
|
|
26
|
+
output_requirements_txt,
|
|
27
|
+
validate_txt_only_format,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_benchmark_requirements(
|
|
34
|
+
nexus_package: Annotated[
|
|
35
|
+
str,
|
|
36
|
+
typer.Argument(
|
|
37
|
+
help="Name of the Nexus package to get benchmark requirements for",
|
|
38
|
+
),
|
|
39
|
+
],
|
|
40
|
+
packages_root: Annotated[
|
|
41
|
+
Path,
|
|
42
|
+
typer.Argument(
|
|
43
|
+
help="Path to the packages root directory (default: ./packages).",
|
|
44
|
+
dir_okay=True,
|
|
45
|
+
file_okay=False,
|
|
46
|
+
readable=True,
|
|
47
|
+
resolve_path=True,
|
|
48
|
+
),
|
|
49
|
+
] = Path("./packages"),
|
|
50
|
+
output_format: Annotated[
|
|
51
|
+
str | None,
|
|
52
|
+
typer.Option(
|
|
53
|
+
"-o",
|
|
54
|
+
"--output-format",
|
|
55
|
+
help="Output format: 'txt' (requirements file format). Default is table output.",
|
|
56
|
+
),
|
|
57
|
+
] = None,
|
|
58
|
+
output_file: Annotated[
|
|
59
|
+
Path | None,
|
|
60
|
+
typer.Option(
|
|
61
|
+
"--output-file",
|
|
62
|
+
help="File path to write output to. Only used with -o txt.",
|
|
63
|
+
),
|
|
64
|
+
] = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Get the list of benchmark requirement specifiers for a specific Nexus package.
|
|
67
|
+
|
|
68
|
+
This command retrieves all benchmark packages registered in the specified
|
|
69
|
+
Nexus package's nexus.yaml file and displays their requirement specifiers.
|
|
70
|
+
|
|
71
|
+
Use -o txt to output in requirements.txt format (one requirement per line),
|
|
72
|
+
suitable for use with pip install -r.
|
|
73
|
+
"""
|
|
74
|
+
validate_txt_only_format(output_format)
|
|
75
|
+
|
|
76
|
+
if not packages_root.is_dir():
|
|
77
|
+
console.print(f"[red]Error:[/red] {packages_root} is not a directory")
|
|
78
|
+
raise typer.Exit(code=1)
|
|
79
|
+
|
|
80
|
+
# Find and load the package configuration
|
|
81
|
+
result = find_package_config(nexus_package, packages_root)
|
|
82
|
+
if result is None:
|
|
83
|
+
console.print(
|
|
84
|
+
f"[red]Error:[/red] Nexus package '{nexus_package}' not found in {packages_root}"
|
|
85
|
+
)
|
|
86
|
+
raise typer.Exit(code=1)
|
|
87
|
+
|
|
88
|
+
package_dir, package_config = result
|
|
89
|
+
|
|
90
|
+
# Check if package has benchmark packages
|
|
91
|
+
if not package_config.package.benchmark_packages:
|
|
92
|
+
console.print(
|
|
93
|
+
f"\n[yellow]No benchmark packages found for Nexus package '{nexus_package}'[/yellow]\n"
|
|
94
|
+
)
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
# Handle txt format (requirements.txt style)
|
|
98
|
+
if output_format == "txt":
|
|
99
|
+
requirements = [
|
|
100
|
+
bench_pkg.requirement_specifier
|
|
101
|
+
for bench_pkg in package_config.package.benchmark_packages
|
|
102
|
+
]
|
|
103
|
+
output_requirements_txt(requirements, output_file)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Handle table output (default) and other formats
|
|
107
|
+
output_benchmark_requirements_table(
|
|
108
|
+
package_config.package.benchmark_packages,
|
|
109
|
+
nexus_package,
|
|
110
|
+
output_format,
|
|
111
|
+
output_file,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if not output_format:
|
|
115
|
+
console.print(
|
|
116
|
+
f"\n[bold]Total:[/bold] {len(package_config.package.benchmark_packages)} benchmark requirement(s)\n"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Made with Bob
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Copyright IBM Corp. 2026
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""List commands for Algorithm Nexus CLI."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import typer
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
except ImportError:
|
|
16
|
+
print(
|
|
17
|
+
"Error: CLI dependencies are not installed.\n"
|
|
18
|
+
"Please install them with: pip install algorithm-nexus[cli]",
|
|
19
|
+
file=sys.stderr,
|
|
20
|
+
)
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
from algorithm_nexus.commands.utils import (
|
|
24
|
+
collect_benchmark_data,
|
|
25
|
+
output_data,
|
|
26
|
+
try_load_package_config,
|
|
27
|
+
validate_output_format,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def list_packages(
|
|
34
|
+
packages_root: Annotated[
|
|
35
|
+
Path,
|
|
36
|
+
typer.Argument(
|
|
37
|
+
help="Path to the packages root directory (default: ./packages).",
|
|
38
|
+
dir_okay=True,
|
|
39
|
+
file_okay=False,
|
|
40
|
+
readable=True,
|
|
41
|
+
resolve_path=True,
|
|
42
|
+
),
|
|
43
|
+
] = Path("./packages"),
|
|
44
|
+
output_format: Annotated[
|
|
45
|
+
str | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"-o",
|
|
48
|
+
"--output-format",
|
|
49
|
+
help="Output format: 'csv' or 'json'. Default is table output.",
|
|
50
|
+
),
|
|
51
|
+
] = None,
|
|
52
|
+
output_file: Annotated[
|
|
53
|
+
Path | None,
|
|
54
|
+
typer.Option(
|
|
55
|
+
"--output-file",
|
|
56
|
+
help="File path to write output to. Only used with -o csv or json.",
|
|
57
|
+
),
|
|
58
|
+
] = None,
|
|
59
|
+
strict: Annotated[
|
|
60
|
+
bool,
|
|
61
|
+
typer.Option(
|
|
62
|
+
"--strict",
|
|
63
|
+
help="Warn on stderr when packages fail to load due to invalid YAML or schema errors.",
|
|
64
|
+
),
|
|
65
|
+
] = False,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""List all Nexus packages discovered in the packages directory."""
|
|
68
|
+
validate_output_format(output_format)
|
|
69
|
+
|
|
70
|
+
if not packages_root.is_dir():
|
|
71
|
+
console.print(f"[red]Error:[/red] {packages_root} is not a directory")
|
|
72
|
+
raise typer.Exit(code=1)
|
|
73
|
+
|
|
74
|
+
# Collect all nexus packages
|
|
75
|
+
nexus_packages: list[str] = []
|
|
76
|
+
|
|
77
|
+
for package_dir in packages_root.iterdir():
|
|
78
|
+
if not package_dir.is_dir() or package_dir.name.startswith("."):
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
package_config = try_load_package_config(package_dir, warn_on_error=strict)
|
|
82
|
+
if package_config:
|
|
83
|
+
nexus_packages.append(package_config.package.name)
|
|
84
|
+
|
|
85
|
+
if not nexus_packages:
|
|
86
|
+
console.print("\n[yellow]No Nexus packages found[/yellow]\n")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# Prepare data for output
|
|
90
|
+
data = [{"Nexus Package": pkg} for pkg in sorted(nexus_packages)]
|
|
91
|
+
headers = ["Nexus Package"]
|
|
92
|
+
|
|
93
|
+
output_data(
|
|
94
|
+
data=data,
|
|
95
|
+
headers=headers,
|
|
96
|
+
output_format=output_format,
|
|
97
|
+
output_file=output_file,
|
|
98
|
+
table_title="Discovered Nexus Packages",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if not output_format:
|
|
102
|
+
console.print(f"\n[bold]Total:[/bold] {len(nexus_packages)} packages\n")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def list_benchmark_packages(
|
|
106
|
+
packages_root: Annotated[
|
|
107
|
+
Path,
|
|
108
|
+
typer.Argument(
|
|
109
|
+
help="Path to the packages root directory (default: ./packages).",
|
|
110
|
+
dir_okay=True,
|
|
111
|
+
file_okay=False,
|
|
112
|
+
readable=True,
|
|
113
|
+
resolve_path=True,
|
|
114
|
+
),
|
|
115
|
+
] = Path("./packages"),
|
|
116
|
+
nexus_package: Annotated[
|
|
117
|
+
str | None,
|
|
118
|
+
typer.Option(
|
|
119
|
+
"--nexus-package",
|
|
120
|
+
help="Filter results to show only benchmark packages used by the specified nexus package",
|
|
121
|
+
),
|
|
122
|
+
] = None,
|
|
123
|
+
output_format: Annotated[
|
|
124
|
+
str | None,
|
|
125
|
+
typer.Option(
|
|
126
|
+
"-o",
|
|
127
|
+
"--output-format",
|
|
128
|
+
help="Output format: 'csv' or 'json'. Default is table output.",
|
|
129
|
+
),
|
|
130
|
+
] = None,
|
|
131
|
+
output_file: Annotated[
|
|
132
|
+
Path | None,
|
|
133
|
+
typer.Option(
|
|
134
|
+
"--output-file",
|
|
135
|
+
help="File path to write output to. Only used with -o csv or json.",
|
|
136
|
+
),
|
|
137
|
+
] = None,
|
|
138
|
+
strict: Annotated[
|
|
139
|
+
bool,
|
|
140
|
+
typer.Option(
|
|
141
|
+
"--strict",
|
|
142
|
+
help="Warn on stderr when packages fail to load due to invalid YAML or schema errors.",
|
|
143
|
+
),
|
|
144
|
+
] = False,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""List all benchmark packages discovered across all Nexus packages.
|
|
147
|
+
|
|
148
|
+
By default, shows a deduplicated table of benchmark packages with the nexus
|
|
149
|
+
packages that use them. Use --nexus-package to filter results for a specific
|
|
150
|
+
nexus package.
|
|
151
|
+
"""
|
|
152
|
+
validate_output_format(output_format)
|
|
153
|
+
|
|
154
|
+
if not packages_root.is_dir():
|
|
155
|
+
console.print(f"[red]Error:[/red] {packages_root} is not a directory")
|
|
156
|
+
raise typer.Exit(code=1)
|
|
157
|
+
|
|
158
|
+
# Collect all benchmark data
|
|
159
|
+
benchmark_data = collect_benchmark_data(packages_root, warn_on_error=strict)
|
|
160
|
+
|
|
161
|
+
if not benchmark_data:
|
|
162
|
+
console.print(
|
|
163
|
+
"\n[yellow]No benchmark packages found in any packages[/yellow]\n"
|
|
164
|
+
)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Filter by nexus package if specified
|
|
168
|
+
title = "Discovered Benchmark Packages"
|
|
169
|
+
if nexus_package:
|
|
170
|
+
filtered_data: dict[str, dict[str, list[str]]] = {}
|
|
171
|
+
for bench_pkg, experiments in benchmark_data.items():
|
|
172
|
+
for exp_id, pkg_list in experiments.items():
|
|
173
|
+
if nexus_package in pkg_list:
|
|
174
|
+
if bench_pkg not in filtered_data:
|
|
175
|
+
filtered_data[bench_pkg] = {}
|
|
176
|
+
filtered_data[bench_pkg][exp_id] = [nexus_package]
|
|
177
|
+
|
|
178
|
+
if not filtered_data:
|
|
179
|
+
console.print(
|
|
180
|
+
f"\n[yellow]No benchmark packages found for nexus package '{nexus_package}'[/yellow]\n"
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
benchmark_data = filtered_data
|
|
185
|
+
title = f"Benchmark Packages for Nexus Package: {nexus_package}"
|
|
186
|
+
|
|
187
|
+
# Prepare data for output
|
|
188
|
+
if nexus_package:
|
|
189
|
+
headers = ["Benchmark Package"]
|
|
190
|
+
data = [
|
|
191
|
+
{"Benchmark Package": bench_pkg}
|
|
192
|
+
for bench_pkg in sorted(benchmark_data.keys())
|
|
193
|
+
]
|
|
194
|
+
else:
|
|
195
|
+
headers = ["Benchmark Package", "Registered By"]
|
|
196
|
+
data = []
|
|
197
|
+
for bench_pkg in sorted(benchmark_data.keys()):
|
|
198
|
+
all_packages = set()
|
|
199
|
+
for exp_packages in benchmark_data[bench_pkg].values():
|
|
200
|
+
all_packages.update(exp_packages)
|
|
201
|
+
data.append(
|
|
202
|
+
{
|
|
203
|
+
"Benchmark Package": bench_pkg,
|
|
204
|
+
"Registered By": ", ".join(sorted(all_packages)),
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
output_data(
|
|
209
|
+
data=data,
|
|
210
|
+
headers=headers,
|
|
211
|
+
output_format=output_format,
|
|
212
|
+
output_file=output_file,
|
|
213
|
+
table_title=title,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if not output_format:
|
|
217
|
+
console.print(
|
|
218
|
+
f"\n[bold]Total:[/bold] {len(benchmark_data)} benchmark packages\n"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def list_benchmark_experiments(
|
|
223
|
+
packages_root: Annotated[
|
|
224
|
+
Path,
|
|
225
|
+
typer.Argument(
|
|
226
|
+
help="Path to the packages root directory (default: ./packages).",
|
|
227
|
+
dir_okay=True,
|
|
228
|
+
file_okay=False,
|
|
229
|
+
readable=True,
|
|
230
|
+
resolve_path=True,
|
|
231
|
+
),
|
|
232
|
+
] = Path("./packages"),
|
|
233
|
+
nexus_package: Annotated[
|
|
234
|
+
str | None,
|
|
235
|
+
typer.Option(
|
|
236
|
+
"--nexus-package",
|
|
237
|
+
help="Filter results to show only experiments used by the specified nexus package",
|
|
238
|
+
),
|
|
239
|
+
] = None,
|
|
240
|
+
output_format: Annotated[
|
|
241
|
+
str | None,
|
|
242
|
+
typer.Option(
|
|
243
|
+
"-o",
|
|
244
|
+
"--output-format",
|
|
245
|
+
help="Output format: 'csv' or 'json'. Default is table output.",
|
|
246
|
+
),
|
|
247
|
+
] = None,
|
|
248
|
+
output_file: Annotated[
|
|
249
|
+
Path | None,
|
|
250
|
+
typer.Option(
|
|
251
|
+
"--output-file",
|
|
252
|
+
help="File path to write output to. Only used with -o csv or json.",
|
|
253
|
+
),
|
|
254
|
+
] = None,
|
|
255
|
+
strict: Annotated[
|
|
256
|
+
bool,
|
|
257
|
+
typer.Option(
|
|
258
|
+
"--strict",
|
|
259
|
+
help="Warn on stderr when packages fail to load due to invalid YAML or schema errors.",
|
|
260
|
+
),
|
|
261
|
+
] = False,
|
|
262
|
+
) -> None:
|
|
263
|
+
"""List all benchmark experiments discovered across all Nexus packages.
|
|
264
|
+
|
|
265
|
+
By default, shows a unified table with experiments and their nexus packages.
|
|
266
|
+
Use --nexus-package to filter results for a specific nexus package.
|
|
267
|
+
"""
|
|
268
|
+
validate_output_format(output_format)
|
|
269
|
+
|
|
270
|
+
if not packages_root.is_dir():
|
|
271
|
+
console.print(f"[red]Error:[/red] {packages_root} is not a directory")
|
|
272
|
+
raise typer.Exit(code=1)
|
|
273
|
+
|
|
274
|
+
# Collect all benchmark data
|
|
275
|
+
benchmark_data = collect_benchmark_data(packages_root, warn_on_error=strict)
|
|
276
|
+
|
|
277
|
+
if not benchmark_data:
|
|
278
|
+
console.print(
|
|
279
|
+
"\n[yellow]No benchmark experiments found in any packages[/yellow]\n"
|
|
280
|
+
)
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
# Filter by nexus package if specified
|
|
284
|
+
if nexus_package:
|
|
285
|
+
filtered_data: dict[str, dict[str, list[str]]] = {}
|
|
286
|
+
for bench_pkg, experiments in benchmark_data.items():
|
|
287
|
+
for exp_id, pkg_list in experiments.items():
|
|
288
|
+
if nexus_package in pkg_list:
|
|
289
|
+
if bench_pkg not in filtered_data:
|
|
290
|
+
filtered_data[bench_pkg] = {}
|
|
291
|
+
filtered_data[bench_pkg][exp_id] = [nexus_package]
|
|
292
|
+
|
|
293
|
+
if not filtered_data:
|
|
294
|
+
console.print(
|
|
295
|
+
f"\n[yellow]No benchmark experiments found for nexus package '{nexus_package}'[/yellow]\n"
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
benchmark_data = filtered_data
|
|
300
|
+
|
|
301
|
+
# Prepare data for output
|
|
302
|
+
if nexus_package:
|
|
303
|
+
headers = ["Experiment ID", "Benchmark Package"]
|
|
304
|
+
title = f"Benchmark Experiments for Nexus Package: {nexus_package}"
|
|
305
|
+
data = [
|
|
306
|
+
{
|
|
307
|
+
"Experiment ID": exp_id,
|
|
308
|
+
"Benchmark Package": bench_pkg,
|
|
309
|
+
}
|
|
310
|
+
for bench_pkg in sorted(benchmark_data.keys())
|
|
311
|
+
for exp_id in sorted(benchmark_data[bench_pkg].keys())
|
|
312
|
+
]
|
|
313
|
+
else:
|
|
314
|
+
headers = ["Experiment ID", "Benchmark Package", "Used By"]
|
|
315
|
+
title = "Discovered Benchmark Experiments"
|
|
316
|
+
data = []
|
|
317
|
+
for bench_pkg in sorted(benchmark_data.keys()):
|
|
318
|
+
for exp_id in sorted(benchmark_data[bench_pkg].keys()):
|
|
319
|
+
nexus_packages = sorted(benchmark_data[bench_pkg][exp_id])
|
|
320
|
+
data.append(
|
|
321
|
+
{
|
|
322
|
+
"Experiment ID": exp_id,
|
|
323
|
+
"Benchmark Package": bench_pkg,
|
|
324
|
+
"Used By": ", ".join(nexus_packages),
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
output_data(
|
|
329
|
+
data=data,
|
|
330
|
+
headers=headers,
|
|
331
|
+
output_format=output_format,
|
|
332
|
+
output_file=output_file,
|
|
333
|
+
table_title=title,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if not output_format:
|
|
337
|
+
console.print(
|
|
338
|
+
f"\n[bold]Total:[/bold] {len(data)} experiments across {len(benchmark_data)} benchmark packages\n"
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Add instructions for getting more details
|
|
342
|
+
console.print("[bold]For further details on each experiment:[/bold]")
|
|
343
|
+
console.print("1. Install the Benchmark package the experiment belongs to")
|
|
344
|
+
console.print(" [cyan]uv pip install <benchmark_package>[/cyan]")
|
|
345
|
+
console.print("2. Describe the experiment")
|
|
346
|
+
console.print(" [cyan]ado describe experiment <experiment_id>[/cyan]\n")
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# Made with Bob
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Copyright IBM Corp. 2026
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Run commands for Algorithm Nexus CLI."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
from algorithm_nexus.commands.benchmark_manager import BenchmarkManager
|
|
13
|
+
from algorithm_nexus.commands.utils import (
|
|
14
|
+
determine_output_format,
|
|
15
|
+
print_human_readable_results,
|
|
16
|
+
print_structured_results,
|
|
17
|
+
validate_output_format,
|
|
18
|
+
write_results_to_file,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import typer
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
except ImportError:
|
|
25
|
+
print(
|
|
26
|
+
"Error: CLI dependencies are not installed.\n"
|
|
27
|
+
"Please install them with: pip install algorithm-nexus[cli]",
|
|
28
|
+
file=sys.stderr,
|
|
29
|
+
)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
console = Console()
|
|
33
|
+
console_err = Console(stderr=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_benchmarks(
|
|
37
|
+
pr: Annotated[
|
|
38
|
+
str,
|
|
39
|
+
typer.Option(
|
|
40
|
+
"--pr",
|
|
41
|
+
help="GitHub Pull Request URL (e.g., https://github.com/IBM/algorithm-nexus/pull/123)",
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
remote: Annotated[
|
|
45
|
+
Path | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"--remote",
|
|
48
|
+
help="Execute operations on remote Ray cluster using the specified context file",
|
|
49
|
+
exists=True,
|
|
50
|
+
file_okay=True,
|
|
51
|
+
dir_okay=False,
|
|
52
|
+
readable=True,
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
context: Annotated[
|
|
56
|
+
Path | None,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--context",
|
|
59
|
+
help="Path to ADO context YAML file (samplestore context)",
|
|
60
|
+
exists=True,
|
|
61
|
+
file_okay=True,
|
|
62
|
+
dir_okay=False,
|
|
63
|
+
readable=True,
|
|
64
|
+
),
|
|
65
|
+
] = None,
|
|
66
|
+
dry_run: Annotated[
|
|
67
|
+
bool,
|
|
68
|
+
typer.Option(
|
|
69
|
+
"--dry-run",
|
|
70
|
+
help="List benchmarks without executing them (dry run)",
|
|
71
|
+
),
|
|
72
|
+
] = False,
|
|
73
|
+
output_file: Annotated[
|
|
74
|
+
Path | None,
|
|
75
|
+
typer.Option(
|
|
76
|
+
"--output-file",
|
|
77
|
+
help="Output file path for execution results. If not specified, results are printed to screen.",
|
|
78
|
+
dir_okay=False,
|
|
79
|
+
writable=True,
|
|
80
|
+
),
|
|
81
|
+
] = None,
|
|
82
|
+
output_format: Annotated[
|
|
83
|
+
str | None,
|
|
84
|
+
typer.Option(
|
|
85
|
+
"-o",
|
|
86
|
+
"--output-format",
|
|
87
|
+
help="Output format: 'json' or 'yaml'. Only used with --output-file.",
|
|
88
|
+
),
|
|
89
|
+
] = None,
|
|
90
|
+
actuator_configuration_mappings: Annotated[
|
|
91
|
+
list[str] | None,
|
|
92
|
+
typer.Option(
|
|
93
|
+
"--use-actuator-configuration",
|
|
94
|
+
help=(
|
|
95
|
+
"Map an actuatorIdentifier to an actuatorConfigurationId using the format "
|
|
96
|
+
"'actuator-id=config-id'. Pass multiple mappings either by repeating the flag "
|
|
97
|
+
"or as a comma-separated list: 'id1=cfg-1,id2=cfg-2'. "
|
|
98
|
+
"Experiments whose actuatorIdentifier matches a key will have the "
|
|
99
|
+
"corresponding actuatorConfigurationId added to the operation's "
|
|
100
|
+
"actuatorConfigurationIdentifiers list."
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
] = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Execute benchmarks from a GitHub Pull Request.
|
|
106
|
+
|
|
107
|
+
Identifies new or changed benchmark instances in a PR and optionally
|
|
108
|
+
executes them using the ADO CLI. When executing with --remote flag,
|
|
109
|
+
automatically installs required benchmark packages in the Ray environment.
|
|
110
|
+
|
|
111
|
+
The command automatically checks if the local repository is on the same
|
|
112
|
+
commit as the PR. If not, it will checkout the PR code in a temporary
|
|
113
|
+
directory.
|
|
114
|
+
"""
|
|
115
|
+
# Validate output format if specified
|
|
116
|
+
if output_format:
|
|
117
|
+
validate_output_format(output_format, allow_yaml=True, allow_csv=False)
|
|
118
|
+
|
|
119
|
+
actuator_configuration_id_map: dict[str, str] = {}
|
|
120
|
+
for entry in actuator_configuration_mappings or []:
|
|
121
|
+
for pair in entry.split(","):
|
|
122
|
+
if "=" not in pair:
|
|
123
|
+
raise typer.BadParameter(
|
|
124
|
+
f"Invalid format '{pair}': expected 'actuator-id=config-id'",
|
|
125
|
+
param_hint="--use-actuator-configuration",
|
|
126
|
+
)
|
|
127
|
+
key, _, value = pair.partition("=")
|
|
128
|
+
|
|
129
|
+
actuator_configuration_id_map[key] = value
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
manager = BenchmarkManager(
|
|
133
|
+
pr_url=pr,
|
|
134
|
+
execute=not dry_run,
|
|
135
|
+
remote_context_file=remote,
|
|
136
|
+
context_file=context,
|
|
137
|
+
actuator_configuration_id_map=actuator_configuration_id_map or None,
|
|
138
|
+
)
|
|
139
|
+
results = manager.run()
|
|
140
|
+
|
|
141
|
+
# Output results
|
|
142
|
+
fmt = determine_output_format(output_format, output_file, default="table")
|
|
143
|
+
|
|
144
|
+
if output_file:
|
|
145
|
+
write_results_to_file(results, output_file, fmt)
|
|
146
|
+
elif fmt in ("json", "yaml"):
|
|
147
|
+
print_structured_results(results, fmt)
|
|
148
|
+
else:
|
|
149
|
+
print_human_readable_results(results)
|
|
150
|
+
|
|
151
|
+
if not dry_run and results.get("summary") and results["summary"]["failed"] > 0:
|
|
152
|
+
raise typer.Exit(code=1)
|
|
153
|
+
|
|
154
|
+
except KeyboardInterrupt:
|
|
155
|
+
console_err.print("\n[yellow]Interrupted by user[/yellow]")
|
|
156
|
+
raise typer.Exit(code=130)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
console_err.print(f"[red]Error:[/red] {e}")
|
|
159
|
+
raise typer.Exit(code=1)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Made with Bob
|