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.
@@ -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