Orcca 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.
orcca/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Public Orcca package surface."""
2
+
3
+ from orcca.workflow import Workflow
4
+ from orcca.launchers import register_launcher
5
+
6
+ __all__ = ["Workflow", "register_launcher"]
7
+
orcca/cli.py ADDED
@@ -0,0 +1,617 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import importlib.util
5
+ import inspect
6
+ import json
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from orcca.dagger import _DagBuildError
13
+ from orcca.executor import _ExecutionError
14
+ from orcca.resolver import _ManifestResolutionError
15
+ from orcca.runtime_launchers import _register_custom_launcher
16
+ from orcca.validator import (
17
+ _ManifestValidationError,
18
+ _format_validation_report,
19
+ )
20
+ from orcca.workflow import Workflow
21
+ from orcca.workflow_runner import _WorkflowRunError, _run_workflow
22
+
23
+
24
+ class CliBootstrapError(Exception):
25
+ """Raised when CLI bootstrap steps fail."""
26
+
27
+
28
+ def _find_project_launchers_dir(start_dir: Path) -> Path | None:
29
+ cur = start_dir.resolve()
30
+ for candidate_root in [cur, *cur.parents]:
31
+ launchers_dir = candidate_root / "launchers"
32
+ if launchers_dir.exists() and launchers_dir.is_dir():
33
+ return launchers_dir
34
+ return None
35
+
36
+
37
+ def _load_module_from_path(py_file: Path, module_name: str) -> Any:
38
+ spec = importlib.util.spec_from_file_location(module_name, py_file)
39
+ if spec is None or spec.loader is None:
40
+ raise CliBootstrapError(f"Unable to load launcher module from {py_file}")
41
+ module = importlib.util.module_from_spec(spec)
42
+ try:
43
+ spec.loader.exec_module(module)
44
+ except Exception as exc: # noqa: BLE001
45
+ raise CliBootstrapError(f"Failed to execute launcher module: {py_file}: {exc}") from exc
46
+ return module
47
+
48
+
49
+ def _autoload_launcher_modules_from_dir(launchers_dir: Path) -> None:
50
+ launcher_files = sorted(
51
+ p for p in launchers_dir.glob("*.py")
52
+ if p.name != "register.py" and not p.name.startswith("_")
53
+ )
54
+ for launcher_file in launcher_files:
55
+ launcher_id = launcher_file.stem
56
+ module_name = f"orcca_project_launcher_{abs(hash(str(launcher_file.resolve())))}"
57
+ module = _load_module_from_path(launcher_file, module_name)
58
+ candidate = getattr(module, launcher_id, None)
59
+ if candidate is None or not inspect.isfunction(candidate):
60
+ raise CliBootstrapError(
61
+ f"Launcher file '{launcher_file.name}' must define a function named '{launcher_id}'"
62
+ )
63
+ _register_custom_launcher(launcher_id, candidate)
64
+
65
+
66
+ def _autoload_project_launchers(args: argparse.Namespace) -> None:
67
+ start_dir: Path
68
+ project_root = getattr(args, "project_root", None)
69
+ workspace_root = getattr(args, "workspace_root", None)
70
+ manifest_path = getattr(args, "manifest", None)
71
+ if isinstance(project_root, str) and project_root:
72
+ start_dir = Path(project_root).resolve()
73
+ elif isinstance(workspace_root, str) and workspace_root:
74
+ start_dir = Path(workspace_root).resolve()
75
+ elif isinstance(manifest_path, str) and manifest_path:
76
+ start_dir = Path(manifest_path).resolve().parent
77
+ else:
78
+ start_dir = Path.cwd()
79
+
80
+ launchers_dir = _find_project_launchers_dir(start_dir)
81
+ if launchers_dir is None:
82
+ return
83
+
84
+ register_file = launchers_dir / "register.py"
85
+ if register_file.exists() and register_file.is_file():
86
+ module_name = f"orcca_project_launchers_{abs(hash(str(register_file.resolve())))}"
87
+ _load_module_from_path(register_file, module_name)
88
+
89
+ _autoload_launcher_modules_from_dir(launchers_dir)
90
+
91
+
92
+ def _log(message: str) -> None:
93
+ print(f"[orcca] {message}")
94
+
95
+
96
+ def validate(
97
+ manifest: str | Path,
98
+ report_path: str | Path = ".orcca/validation/manifest_validation_report.html",
99
+ ) -> dict[str, Any]:
100
+ """Resolve, validate, and write a manifest validation report.
101
+
102
+ This is the Python counterpart to `orcca validate`.
103
+
104
+ Parameters
105
+ ----------
106
+ manifest : str or pathlib.Path
107
+ Path to the root workflow manifest YAML file.
108
+ report_path : str or pathlib.Path, default=".orcca/validation/manifest_validation_report.html"
109
+ Destination for the HTML validation report.
110
+ Returns
111
+ -------
112
+ dict[str, Any]
113
+ Payload containing the resolved validation result and report path.
114
+ """
115
+ workflow = Workflow(manifest)
116
+ return workflow.validate(report_path=report_path)
117
+
118
+
119
+ def resolve(
120
+ manifest: str | Path,
121
+ output_path: str | Path = ".orcca/resolved_manifest.yaml",
122
+ ) -> dict[str, Any]:
123
+ """Resolve a manifest and write the resolved YAML to disk.
124
+
125
+ This is the Python counterpart to `orcca resolve`.
126
+
127
+ Parameters
128
+ ----------
129
+ manifest : str or pathlib.Path
130
+ Path to the root workflow manifest YAML file.
131
+ output_path : str or pathlib.Path, default=".orcca/resolved_manifest.yaml"
132
+ Destination for the resolved manifest YAML file.
133
+ Returns
134
+ -------
135
+ dict[str, Any]
136
+ Payload containing the resolved manifest path and resolved manifest.
137
+ """
138
+ workflow = Workflow(manifest)
139
+ return workflow.write_resolved_manifest(output_path=output_path)
140
+
141
+
142
+ def plan(
143
+ manifest: str | Path,
144
+ ) -> dict[str, Any]:
145
+ """Resolve, validate, and compute the workflow plan.
146
+
147
+ This is the Python counterpart to `orcca plan`.
148
+
149
+ Parameters
150
+ ----------
151
+ manifest : str or pathlib.Path
152
+ Path to the root workflow manifest YAML file.
153
+ Returns
154
+ -------
155
+ dict[str, Any]
156
+ Planning payload, including validity and dependency information.
157
+ """
158
+ workflow = Workflow(manifest)
159
+ return workflow.plan()
160
+
161
+
162
+ def run(
163
+ manifest: str | Path,
164
+ project_root: str | Path = ".",
165
+ run_dir: str | Path | None = None,
166
+ workers: int | None = None,
167
+ task_ids: list[str] | None = None,
168
+ artifact_ids: list[str] | None = None,
169
+ run_validations: bool = False,
170
+ validation_ids: list[str] | None = None,
171
+ no_execute: bool = False,
172
+ no_env_cache: bool = False,
173
+ refresh_env: bool = False,
174
+ ) -> Any:
175
+ """Run the full workflow pipeline.
176
+
177
+ This is the Python counterpart to `orcca run`.
178
+ """
179
+ workflow = Workflow(manifest, workspace_root=project_root, runs_root=Path(project_root) / ".orcca" / "runs")
180
+ if validation_ids and not run_validations:
181
+ raise _WorkflowRunError("--validation-id requires --run-validations")
182
+ return workflow.run(
183
+ run_dir=run_dir,
184
+ execute=not no_execute,
185
+ workers=workers,
186
+ task_ids=task_ids,
187
+ artifact_ids=artifact_ids,
188
+ run_validations=run_validations,
189
+ validation_ids=validation_ids,
190
+ use_env_cache=not no_env_cache,
191
+ refresh_env=refresh_env,
192
+ progress=_log,
193
+ )
194
+
195
+
196
+ def dag(
197
+ manifest: str | Path,
198
+ project_root: str | Path = ".",
199
+ output_path: str | Path = ".orcca/graphs/artifact-dag.html",
200
+ artifact_id: str | None = None,
201
+ ) -> dict[str, Any]:
202
+ """Export an artifact dependency graph as an HTML file.
203
+
204
+ This is the Python counterpart to `orcca dag`.
205
+
206
+ Parameters
207
+ ----------
208
+ manifest
209
+ Path to the root workflow manifest YAML file.
210
+ project_root
211
+ Project workspace root used to resolve the manifest.
212
+ output_path
213
+ Destination HTML file for the graph export.
214
+ artifact_id
215
+ Optional generated artifact id to focus on.
216
+
217
+ Returns
218
+ -------
219
+ dict[str, Any]
220
+ HTML path plus graph summary metadata.
221
+ """
222
+ workflow = Workflow(manifest, workspace_root=project_root, runs_root=Path(project_root) / ".orcca" / "runs")
223
+ return workflow.export_artifact_dag(output_path=output_path, artifact_id=artifact_id)
224
+
225
+
226
+ def env_cache(
227
+ action: str,
228
+ prune_all: bool = False,
229
+ output_format: str = "pretty",
230
+ ) -> dict[str, Any]:
231
+ """List or prune the package-manager environment cache.
232
+
233
+ This is the Python counterpart to `orcca env-cache`.
234
+ """
235
+ root = _env_cache_root()
236
+ root.mkdir(parents=True, exist_ok=True)
237
+ if action == "list":
238
+ entries: list[dict[str, Any]] = []
239
+ for d in sorted(p for p in root.iterdir() if p.is_dir()):
240
+ meta_path = d / "env_meta.json"
241
+ meta: dict[str, Any] = {}
242
+ if meta_path.exists():
243
+ try:
244
+ meta = json.loads(meta_path.read_text(encoding="utf-8"))
245
+ except Exception:
246
+ meta = {"error": "invalid_meta_json"}
247
+ entries.append({"path": str(d), "meta": meta})
248
+ return {"cache_root": str(root), "entries": entries}
249
+
250
+ if action == "prune":
251
+ removed = 0
252
+ for d in sorted(p for p in root.iterdir() if p.is_dir()):
253
+ if prune_all or not (d / "env_meta.json").exists():
254
+ shutil.rmtree(d, ignore_errors=True)
255
+ removed += 1
256
+ return {"cache_root": str(root), "removed": removed}
257
+
258
+ raise ValueError(f"unsupported env-cache action '{action}'")
259
+
260
+
261
+ def init(
262
+ path: str | Path = ".",
263
+ ) -> dict[str, Any]:
264
+ """Scaffold a language-agnostic starter project.
265
+
266
+ This is the Python counterpart to `orcca init`.
267
+ """
268
+ project_dir = Path(path).resolve()
269
+ project_dir.mkdir(parents=True, exist_ok=True)
270
+ (project_dir / "launchers").mkdir(parents=True, exist_ok=True)
271
+ (project_dir / "workflows").mkdir(parents=True, exist_ok=True)
272
+ (project_dir / "manifests").mkdir(parents=True, exist_ok=True)
273
+
274
+ (project_dir / "launchers" / "default.py").write_text(
275
+ "def default(*, task, runtime, root):\n"
276
+ " executable = runtime.get('executable', 'python')\n"
277
+ " options = runtime.get('options', [])\n"
278
+ " script = task['script']\n"
279
+ " return [executable, *options, script]\n",
280
+ encoding="utf-8",
281
+ )
282
+ (project_dir / "workflows" / "main.py").write_text(
283
+ "from pathlib import Path\nPath('output.txt').write_text('ok\\n', encoding='utf-8')\n",
284
+ encoding="utf-8",
285
+ )
286
+ (project_dir / "requirements.txt").write_text("pandas==2.3.2\n", encoding="utf-8")
287
+ manifest = (
288
+ "id: starter\nname: Starter Workflow\nstudy:\n id: ST-001\n"
289
+ "artifacts:\n - id: out\n name: Out\n type: report\n origin: generated\n path: output.txt\n"
290
+ "runtimes:\n - id: runtime\n programming_language: python\n version: '3.11'\n launcher: default\n"
291
+ " executable: python\n environment:\n type: package_manager\n manager: pip\n lockfile: requirements.txt\n"
292
+ "tasks:\n - id: t1\n runtimeId: runtime\n script: workflows/main.py\n outputs:\n artifactIds: [out]\n"
293
+ "execution:\n mode: serial\n"
294
+ )
295
+
296
+ (project_dir / "manifests" / "workflow.yaml").write_text(manifest, encoding="utf-8")
297
+ return {"initialized": str(project_dir)}
298
+
299
+
300
+ def cmd_validate(args: argparse.Namespace) -> int:
301
+ """Resolve a manifest, validate it, and write a report.
302
+
303
+ This command checks the manifest structure and workflow rules without
304
+ running any workflow tasks. It is the CLI entry point for preflight
305
+ validation and is useful in both local development and CI pipelines.
306
+
307
+ Parameters
308
+ ----------
309
+ args : argparse.Namespace
310
+ Parsed CLI arguments for the `validate` command.
311
+
312
+ Returns
313
+ -------
314
+ int
315
+ Exit code (`0` when valid, `1` when invalid).
316
+ """
317
+ result = Workflow(args.manifest).validate(report_path=args.report_path)
318
+
319
+ print(f"validation_report_path: {result['validation_report_path']}")
320
+ print(f"valid: {result['valid']}")
321
+ return 0 if result["valid"] else 1
322
+
323
+
324
+ def cmd_resolve(args: argparse.Namespace) -> int:
325
+ """Resolve a manifest and write the expanded YAML to disk.
326
+
327
+ The resolved manifest includes the final values after includes and config
328
+ files have been applied. Use this when you want to inspect or archive the
329
+ exact manifest that downstream tools will consume.
330
+ """
331
+ result = resolve(args.manifest, output_path=args.output_path)
332
+ print(f"resolved_manifest_path: {result['resolved_manifest_path']}")
333
+ return 0
334
+
335
+
336
+ def cmd_plan(args: argparse.Namespace) -> int:
337
+ """Resolve a manifest, validate it, and print planning metadata.
338
+
339
+ The plan output is intended for inspection rather than execution. It helps
340
+ you understand task ordering, generated-artifact dependencies, and whether
341
+ the manifest is valid before any runtime work begins.
342
+
343
+ Examples
344
+ --------
345
+ ```{python}
346
+ from orcca.cli import cmd_plan, cmd_run, cmd_env_cache, cmd_init, cmd_inspect_run, build_parser, main
347
+ isinstance(cmd_plan, object)
348
+ ```
349
+ True
350
+ """
351
+ result = Workflow(args.manifest).plan()
352
+
353
+ if not result["valid"]:
354
+ validation = result["validation"]
355
+ print(_format_validation_report(validation))
356
+ return 1
357
+
358
+ print(result)
359
+
360
+ return 0
361
+
362
+
363
+ def cmd_run(args: argparse.Namespace) -> int:
364
+ """Run the workflow from manifest resolution through task execution.
365
+
366
+ The run command performs the complete workflow lifecycle: it resolves the
367
+ manifest, validates it, prepares runtimes, executes selected tasks, and
368
+ writes an HTML run report into the run directory.
369
+
370
+ Examples
371
+ --------
372
+ ```{python}
373
+ from orcca.cli import cmd_plan, cmd_run, cmd_env_cache, cmd_init, cmd_inspect_run, build_parser, main
374
+ isinstance(cmd_run, object)
375
+ ```
376
+ True
377
+ """
378
+ _log("Starting workflow run")
379
+ result = Workflow(args.manifest, workspace_root=args.project_root, runs_root=Path(args.project_root) / ".orcca" / "runs").run(
380
+ run_dir=args.run_dir,
381
+ execute=not args.no_execute,
382
+ workers=args.workers,
383
+ task_ids=args.task_ids,
384
+ artifact_ids=args.artifact_ids,
385
+ run_validations=args.run_validations,
386
+ validation_ids=args.validation_ids,
387
+ use_env_cache=not args.no_env_cache,
388
+ refresh_env=args.refresh_env,
389
+ progress=_log,
390
+ )
391
+
392
+ status = "SUCCESS" if result.succeeded else "FAILED"
393
+ print(f"Run status: {status}")
394
+ print(f"Run ID: {result.run_id}")
395
+ print(f"Run directory: {result.run_dir}")
396
+ print(f"Run report: {result.run_report_path}")
397
+ if args.run_validations:
398
+ if result.validation_execution is None:
399
+ print("Validation task run: no validation tasks executed")
400
+ else:
401
+ print(f"Validation task run: {'SUCCESS' if result.validation_execution.succeeded else 'FAILED'}")
402
+
403
+ return 0 if result.succeeded else 1
404
+
405
+
406
+ def cmd_dag(args: argparse.Namespace) -> int:
407
+ """Export the workflow artifact dependency graph as HTML.
408
+
409
+ Use this command to visualize how generated artifacts depend on one
410
+ another. You can export the full graph or focus on the upstream lineage of
411
+ a single artifact, which is especially helpful when debugging complex
412
+ workflows.
413
+
414
+ Examples
415
+ --------
416
+ ```{python}
417
+ from orcca.cli import cmd_dag, build_parser
418
+ isinstance(cmd_dag, object)
419
+ ```
420
+ True
421
+ """
422
+ result = dag(
423
+ args.manifest,
424
+ project_root=args.project_root,
425
+ output_path=args.output_path,
426
+ artifact_id=args.artifact_id,
427
+ )
428
+ print(f"html_path: {result['html_path']}")
429
+ print(f"artifact_id: {result['artifact_id']}")
430
+ print(f"node_count: {result['node_count']}")
431
+ print(f"edge_count: {result['edge_count']}")
432
+ return 0
433
+
434
+
435
+ def _env_cache_root() -> Path:
436
+ return (Path(".") / ".orcca" / "cache" / "environments").resolve()
437
+
438
+
439
+ def cmd_env_cache(args: argparse.Namespace) -> int:
440
+ """Inspect or prune the cached package-manager environments.
441
+
442
+ This command is useful when you want to see which runtime environments are
443
+ already prepared, or when you need to clean out stale environments before
444
+ a rebuild or CI run.
445
+
446
+ Examples
447
+ --------
448
+ ```{python}
449
+ from orcca.cli import cmd_plan, cmd_run, cmd_env_cache, cmd_init, cmd_inspect_run, build_parser, main
450
+ isinstance(cmd_env_cache, object)
451
+ ```
452
+ True
453
+ """
454
+ try:
455
+ result = env_cache(args.action, prune_all=args.all)
456
+ except ValueError as exc:
457
+ print(f"Error: {exc}", file=sys.stderr)
458
+ return 2
459
+ print(result)
460
+ return 0
461
+
462
+
463
+ def cmd_init(args: argparse.Namespace) -> int:
464
+ """Create a starter project template.
465
+
466
+ This command sets up the minimum project structure needed to get started
467
+ with Orcca, including a manifest, workflow directory, launcher file, and
468
+ default runtime wiring.
469
+
470
+ Examples
471
+ --------
472
+ ```{python}
473
+ from orcca.cli import cmd_plan, cmd_run, cmd_env_cache, cmd_init, cmd_inspect_run, build_parser, main
474
+ isinstance(cmd_init, object)
475
+ ```
476
+ True
477
+ """
478
+ result = init(path=args.path)
479
+ print(result)
480
+ return 0
481
+
482
+
483
+ def build_parser() -> argparse.ArgumentParser:
484
+ """Build the Orcca top-level argument parser and subcommands.
485
+
486
+ Examples
487
+ --------
488
+ ```{python}
489
+ from orcca.cli import cmd_plan, cmd_run, cmd_env_cache, cmd_init, cmd_inspect_run, build_parser, main
490
+ parser = build_parser()
491
+ parser.prog is not None
492
+ ```
493
+ True
494
+ """
495
+ parser = argparse.ArgumentParser(
496
+ description="Orcca CLI for validating, planning, and running workflow manifests.",
497
+ epilog="Example: orcca run manifests/workflow.yaml --project-root . --run-dir .orcca/runs/demo",
498
+ )
499
+ subparsers = parser.add_subparsers(dest="command", required=True)
500
+
501
+ resolve_parser = subparsers.add_parser("resolve", help="Resolve a manifest and write the resolved YAML")
502
+ resolve_parser.add_argument("manifest", help="Path to root workflow manifest YAML")
503
+ resolve_parser.add_argument(
504
+ "--output-path",
505
+ default=".orcca/resolved_manifest.yaml",
506
+ help="YAML output path for the resolved manifest",
507
+ )
508
+ resolve_parser.set_defaults(func=cmd_resolve)
509
+
510
+ validate_parser = subparsers.add_parser("validate", help="Resolve and validate a workflow manifest")
511
+ validate_parser.add_argument("manifest", help="Path to root workflow manifest YAML")
512
+ validate_parser.add_argument(
513
+ "--report-path",
514
+ default=".orcca/validation/manifest_validation_report.html",
515
+ help="Path to write the manifest validation report HTML",
516
+ )
517
+ validate_parser.set_defaults(func=cmd_validate)
518
+
519
+ plan_parser = subparsers.add_parser("plan", help="Resolve, validate, and print execution planning details")
520
+ plan_parser.add_argument("manifest", help="Path to root workflow manifest YAML")
521
+ plan_parser.set_defaults(func=cmd_plan)
522
+
523
+ run_parser = subparsers.add_parser("run", help="Run end-to-end workflow pipeline")
524
+ run_parser.add_argument("manifest", help="Path to root workflow manifest YAML")
525
+ run_parser.add_argument(
526
+ "--project-root",
527
+ default=".",
528
+ help="Project root used for runtime execution and run artifacts (stored under .orcca/runs)",
529
+ )
530
+ run_parser.add_argument(
531
+ "--run-dir",
532
+ default=None,
533
+ help="Explicit run directory to write artifacts into instead of creating a timestamped subdirectory",
534
+ )
535
+ run_parser.add_argument("--workers", type=int, default=None, help="Override the number of concurrent workers")
536
+ run_parser.add_argument("--task", dest="task_ids", action="append", default=[], help="Target task id to execute; repeat to select multiple tasks")
537
+ run_parser.add_argument("--artifact", dest="artifact_ids", action="append", default=[], help="Target artifact id to generate; repeat to select multiple artifacts")
538
+ run_parser.add_argument(
539
+ "--run-validations",
540
+ action="store_true",
541
+ help="Execute validation tasks declared in validations[].validationTaskId",
542
+ )
543
+ run_parser.add_argument(
544
+ "--validation-id",
545
+ dest="validation_ids",
546
+ action="append",
547
+ default=[],
548
+ help="Validation id to execute (can be repeated). Requires --run-validations.",
549
+ )
550
+ run_parser.add_argument("--no-execute", action="store_true", help="Resolve and validate only; skip task execution")
551
+ run_parser.add_argument("--no-env-cache", action="store_true", help="Disable environment cache reuse for package-manager runtimes")
552
+ run_parser.add_argument("--refresh-env", action="store_true", help="Force rebuild or restore of package-manager environments")
553
+ run_parser.set_defaults(func=cmd_run)
554
+
555
+ dag_parser = subparsers.add_parser("dag", help="Export the artifact dependency graph as HTML")
556
+ dag_parser.add_argument("manifest", help="Path to root workflow manifest YAML")
557
+ dag_parser.add_argument(
558
+ "--project-root",
559
+ default=".",
560
+ help="Project root used to resolve the manifest and default graph output location",
561
+ )
562
+ dag_parser.add_argument(
563
+ "--artifact-id",
564
+ default=None,
565
+ help="Optional generated artifact id to export a focused upstream dependency graph",
566
+ )
567
+ dag_parser.add_argument(
568
+ "--output-path",
569
+ default=".orcca/graphs/artifact-dag.html",
570
+ help="HTML output path for the graph export",
571
+ )
572
+ dag_parser.set_defaults(func=cmd_dag)
573
+
574
+ env_cache_parser = subparsers.add_parser("env-cache", help="List or prune package-manager environment cache")
575
+ env_cache_parser.add_argument("action", choices=["list", "prune"], help="Cache action to perform")
576
+ env_cache_parser.add_argument("--all", action="store_true", help="When pruning, remove every cached environment")
577
+ env_cache_parser.set_defaults(func=cmd_env_cache)
578
+
579
+ init_parser = subparsers.add_parser("init", help="Scaffold a starter project")
580
+ init_parser.add_argument("path", nargs="?", default=".", help="Target project directory")
581
+ init_parser.set_defaults(func=cmd_init)
582
+
583
+ return parser
584
+
585
+
586
+ def main(argv: list[str] | None = None) -> int:
587
+ """CLI entrypoint for the `orcca` command.
588
+
589
+ Examples
590
+ --------
591
+ ```{python}
592
+ from orcca.cli import cmd_plan, cmd_run, cmd_env_cache, cmd_init, cmd_inspect_run, build_parser, main
593
+ # Run help:
594
+ # main(["--help"])
595
+ isinstance(main, object)
596
+ ```
597
+ True
598
+ """
599
+ parser = build_parser()
600
+ effective_argv = argv if argv is not None else sys.argv[1:]
601
+ if not effective_argv:
602
+ parser.print_help()
603
+ return 0
604
+ args = parser.parse_args(effective_argv)
605
+
606
+ try:
607
+ _autoload_project_launchers(args)
608
+ return int(args.func(args))
609
+ except (CliBootstrapError, _ManifestResolutionError, _ManifestValidationError, _WorkflowRunError, _ExecutionError, _DagBuildError) as exc:
610
+ print(f"Error: {exc}", file=sys.stderr)
611
+ return 2
612
+
613
+
614
+ if __name__ == "__main__":
615
+ raise SystemExit(main())
616
+
617
+