geronimo.cli.main
Main CLI entrypoint for Geronimo.
This module provides the primary CLI interface using Typer.
1"""Main CLI entrypoint for Geronimo. 2 3This module provides the primary CLI interface using Typer. 4""" 5 6import typer 7from rich.console import Console 8from rich.panel import Panel 9 10from geronimo import __version__ 11from geronimo.constants import MODULES 12 13# Create the main Typer app 14app = typer.Typer( 15 name="geronimo", 16 help="MLOps deployment platform - automate ML model deployments to AWS", 17 add_completion=False, 18 no_args_is_help=True, 19 rich_markup_mode="rich", 20) 21 22# Console for rich output 23console = Console() 24 25 26def version_callback(value: bool) -> None: 27 """Print version and exit.""" 28 if value: 29 console.print( 30 Panel.fit( 31 f"[bold blue]Geronimo[/bold blue] v{__version__}\n" 32 "[dim]MLOps Deployment Platform[/dim]", 33 border_style="blue", 34 ) 35 ) 36 raise typer.Exit() 37 38 39@app.callback() 40def main( 41 version: bool = typer.Option( 42 None, 43 "--version", 44 "-v", 45 callback=version_callback, 46 is_eager=True, 47 help="Show version and exit.", 48 ), 49) -> None: 50 """Geronimo - Automate ML model deployments to AWS. 51 52 Generate production-ready Terraform, Docker, and CI/CD pipelines 53 for your ML models with industry best practices built-in. 54 """ 55 pass 56 57 58# ============================================================================ 59# INIT Command 60# ============================================================================ 61 62 63@app.command() 64def init( 65 name: str = typer.Option( 66 None, 67 "--name", 68 "-n", 69 prompt="Project name", 70 help="Name of the ML project.", 71 ), 72 framework: str = typer.Option( 73 "sklearn", 74 "--framework", 75 "-f", 76 help="ML framework (sklearn, pytorch, tensorflow).", 77 ), 78 template: str = typer.Option( 79 "realtime", 80 "--template", 81 "-t", 82 help="Project type: 'realtime' (API endpoints), 'batch' (pipelines), or 'both'.", 83 ), 84 output_dir: str = typer.Option( 85 ".", 86 "--output", 87 "-o", 88 help="Output directory for the project.", 89 ), 90) -> None: 91 """Initialize a new ML deployment project. 92 93 Scaffolds a complete ML project with Geronimo SDK: 94 95 Templates: 96 - realtime: FastAPI endpoints with Endpoint class 97 - batch: Metaflow pipelines with BatchPipeline class 98 - both: Combined real-time and batch support 99 """ 100 from geronimo.generators.project import ProjectGenerator 101 102 # Validate template 103 valid_templates = {"realtime", "batch", "both"} 104 if template not in valid_templates: 105 console.print(f"[bold red]Error:[/bold red] Invalid template '{template}'. Choose from: {valid_templates}") 106 raise typer.Exit(code=1) 107 108 console.print(f"\n[bold blue]Initializing project:[/bold blue] {name}") 109 console.print(f" Template: [cyan]{template}[/cyan]") 110 111 generator = ProjectGenerator( 112 project_name=name, 113 framework=framework, 114 output_dir=output_dir, 115 template=template, 116 ) 117 118 try: 119 generator.generate() 120 121 # SDK scaffolding is now handled by ProjectGenerator.generate() 122 # which creates sdk/endpoint.py, sdk/pipeline.py, app.py, flow.py, etc. 123 124 next_steps = [ 125 f"cd {name}", 126 "uv sync", 127 ] 128 if template in ("realtime", "both"): 129 next_steps.append(f"uvicorn {name.replace('-', '_')}.app:app --reload # Run API server") 130 if template in ("batch", "both"): 131 next_steps.append(f"python -m {name.replace('-', '_')}.flow run # Run batch pipeline") 132 133 console.print( 134 Panel.fit( 135 f"[bold green]✓ Project '{name}' created successfully![/bold green]\n\n" 136 f"Template: [cyan]{template}[/cyan]\n\n" 137 f"Next steps:\n" + "\n".join(f" {i+1}. {step}" for i, step in enumerate(next_steps)), 138 border_style="green", 139 ) 140 ) 141 except Exception as e: 142 console.print(f"[bold red]Error:[/bold red] {e}") 143 raise typer.Exit(code=1) 144 145 146def _generate_sdk_scaffold(name: str, output_dir: str, template: str) -> None: 147 """Generate Geronimo SDK scaffold files.""" 148 from pathlib import Path 149 150 project_path = Path(output_dir) / name 151 sdk_dir = project_path / "src" / name.replace("-", "_") / "sdk" 152 sdk_dir.mkdir(parents=True, exist_ok=True) 153 154 # SDK __init__.py 155 (sdk_dir / "__init__.py").write_text('"""Geronimo SDK components."""\n') 156 157 # Features file 158 (sdk_dir / "features.py").write_text('''"""Feature definitions for the model.""" 159 160from geronimo.features import FeatureSet, Feature 161# from sklearn.preprocessing import StandardScaler, OneHotEncoder 162 163 164class ProjectFeatures(FeatureSet): 165 """Define your features here. 166 167 Example: 168 age = Feature(dtype='numeric', transformer=StandardScaler()) 169 category = Feature(dtype='categorical', encoder=OneHotEncoder()) 170 """ 171 172 pass 173''') 174 175 # Data sources file 176 (sdk_dir / "data_sources.py").write_text('''"""Data source definitions.""" 177 178from geronimo.data_sources import DataSource, Query 179 180# Example query-based source: 181# training_data = DataSource( 182# name="training", 183# source="snowflake", 184# query=Query.from_file("queries/train.sql"), 185# ) 186 187# Example file-based source: 188# local_data = DataSource(name="local", source="file", path="data/train.csv") 189''') 190 191 # Model file 192 (sdk_dir / "model.py").write_text(f'''"""Model definition.""" 193 194from geronimo.models import Model, HyperParams 195 196# from .features import ProjectFeatures 197 198 199class ProjectModel(Model): 200 """Main model class.""" 201 202 name = "{name}" 203 version = "1.0.0" 204 # features = ProjectFeatures() 205 206 def train(self, X, y, params: HyperParams) -> None: 207 """Train the model.""" 208 # self.estimator = YourModel(**params.to_dict()) 209 # self.estimator.fit(X, y) 210 raise NotImplementedError("Implement train() method") 211 212 def predict(self, X): 213 """Generate predictions.""" 214 # return self.estimator.predict(X) 215 raise NotImplementedError("Implement predict() method") 216''') 217 218 # Endpoint or pipeline based on template 219 if template in ("realtime", "both"): 220 (sdk_dir / "endpoint.py").write_text(f'''"""Endpoint definition for real-time serving.""" 221 222from geronimo.serving import Endpoint 223 224# from .model import ProjectModel 225 226 227class PredictEndpoint(Endpoint): 228 """Prediction endpoint.""" 229 230 # model_class = ProjectModel 231 232 def preprocess(self, request: dict): 233 """Preprocess incoming request.""" 234 # df = pd.DataFrame([request["data"]]) 235 # return self.model.features.transform(df) 236 raise NotImplementedError("Implement preprocess() method") 237 238 def postprocess(self, prediction): 239 """Postprocess model output.""" 240 # return {{"score": float(prediction[0])}} 241 raise NotImplementedError("Implement postprocess() method") 242''') 243 244 if template in ("batch", "both"): 245 (sdk_dir / "pipeline.py").write_text(f'''"""Batch pipeline definition.""" 246 247from geronimo.batch import BatchPipeline, Schedule 248 249# from .model import ProjectModel 250 251 252class ScoringPipeline(BatchPipeline): 253 """Batch scoring pipeline.""" 254 255 # model_class = ProjectModel 256 schedule = Schedule.daily(hour=6) 257 258 def run(self): 259 """Main pipeline logic.""" 260 # data = self.model.features.data_source.load() 261 # X = self.model.features.transform(data) 262 # predictions = self.model.predict(X) 263 # self.save_results(predictions) 264 raise NotImplementedError("Implement run() method") 265''') 266 267 268# ============================================================================ 269# GENERATE Command Group 270# ============================================================================ 271 272generate_app = typer.Typer( 273 name="generate", 274 help="Generate deployment artifacts (Terraform, Docker, pipelines).", 275 no_args_is_help=True, 276) 277app.add_typer(generate_app, name="generate") 278 279# Import and register keys CLI 280from geronimo.cli.keys_cmd import keys_app 281app.add_typer(keys_app, name="keys") 282 283# Import and register auth CLI 284from geronimo.cli.auth_cmd import auth_app 285app.add_typer(auth_app, name="auth") 286 287# Import and register config CLI 288from geronimo.cli.config_cmd import config_app 289app.add_typer(config_app, name="config") 290 291 292@generate_app.command("terraform") 293def generate_terraform( 294 config_path: str = typer.Option( 295 "geronimo.yaml", 296 "--config", 297 "-c", 298 help="Path to geronimo.yaml configuration file.", 299 ), 300 output_dir: str = typer.Option( 301 "infrastructure", 302 "--output", 303 "-o", 304 help="Output directory for Terraform files.", 305 ), 306) -> None: 307 """Generate Terraform infrastructure files. 308 309 Creates modular Terraform configuration for: 310 - ECR repository 311 - ECS Fargate task and service 312 - Application Load Balancer 313 - CloudWatch logging and monitoring 314 """ 315 from geronimo.config.loader import load_config 316 from geronimo.generators.terraform import TerraformGenerator 317 318 console.print("\n[bold blue]Generating Terraform...[/bold blue]") 319 320 try: 321 config = load_config(config_path) 322 generator = TerraformGenerator(config=config, output_dir=output_dir) 323 files = generator.generate() 324 325 console.print(f"[green]✓ Generated {len(files)} Terraform files:[/green]") 326 for f in files: 327 console.print(f" • {f}") 328 329 except FileNotFoundError: 330 console.print( 331 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 332 ) 333 raise typer.Exit(code=1) 334 except Exception as e: 335 console.print(f"[bold red]Error:[/bold red] {e}") 336 raise typer.Exit(code=1) 337 338 339@generate_app.command("dockerfile") 340def generate_dockerfile( 341 config_path: str = typer.Option( 342 "geronimo.yaml", 343 "--config", 344 "-c", 345 help="Path to geronimo.yaml configuration file.", 346 ), 347 output_path: str = typer.Option( 348 "Dockerfile", 349 "--output", 350 "-o", 351 help="Output path for Dockerfile.", 352 ), 353) -> None: 354 """Generate an optimized Dockerfile for ML serving. 355 356 Creates a multi-stage Dockerfile with: 357 - UV for fast dependency installation 358 - Non-root user for security 359 - Proper signal handling for graceful shutdown 360 """ 361 from geronimo.config.loader import load_config 362 from geronimo.generators.docker import DockerGenerator 363 364 console.print("\n[bold blue]Generating Dockerfile...[/bold blue]") 365 366 try: 367 config = load_config(config_path) 368 generator = DockerGenerator(config=config, output_path=output_path) 369 generator.generate() 370 371 console.print(f"[green]✓ Generated Dockerfile:[/green] {output_path}") 372 373 except FileNotFoundError: 374 console.print( 375 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 376 ) 377 raise typer.Exit(code=1) 378 except Exception as e: 379 console.print(f"[bold red]Error:[/bold red] {e}") 380 raise typer.Exit(code=1) 381 382 383@generate_app.command("pipeline") 384def generate_pipeline( 385 config_path: str = typer.Option( 386 "geronimo.yaml", 387 "--config", 388 "-c", 389 help="Path to geronimo.yaml configuration file.", 390 ), 391 output_path: str = typer.Option( 392 "azure-pipelines.yaml", 393 "--output", 394 "-o", 395 help="Output path for pipeline file.", 396 ), 397) -> None: 398 """Generate CI/CD pipeline configuration. 399 400 Creates Azure DevOps pipeline YAML with: 401 - Build and test stage 402 - Security scanning 403 - Multi-environment deployments 404 - Approval gates 405 """ 406 from geronimo.config.loader import load_config 407 from geronimo.generators.pipeline import PipelineGenerator 408 409 console.print("\n[bold blue]Generating pipeline...[/bold blue]") 410 411 try: 412 config = load_config(config_path) 413 generator = PipelineGenerator(config=config, output_path=output_path) 414 generator.generate() 415 416 console.print(f"[green]✓ Generated pipeline:[/green] {output_path}") 417 418 except FileNotFoundError: 419 console.print( 420 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 421 ) 422 raise typer.Exit(code=1) 423 except Exception as e: 424 console.print(f"[bold red]Error:[/bold red] {e}") 425 raise typer.Exit(code=1) 426 427 428@generate_app.command("all") 429def generate_all( 430 config_path: str = typer.Option( 431 "geronimo.yaml", 432 "--config", 433 "-c", 434 help="Path to geronimo.yaml configuration file.", 435 ), 436) -> None: 437 """Generate all deployment artifacts. 438 439 Generates Terraform, Dockerfile, and CI/CD pipeline in one command. 440 """ 441 console.print("\n[bold blue]Generating all artifacts...[/bold blue]") 442 443 # Call individual generators 444 generate_terraform(config_path=config_path, output_dir="infrastructure") 445 generate_dockerfile(config_path=config_path, output_path="Dockerfile") 446 generate_pipeline(config_path=config_path, output_path="azure-pipelines.yaml") 447 448 console.print("\n[bold green]✓ All artifacts generated successfully![/bold green]") 449 450 451 452# ============================================================================ 453# VALIDATE Command 454# ============================================================================ 455 456 457@app.command() 458def validate( 459 config_path: str = typer.Option( 460 "geronimo.yaml", 461 "--config", 462 "-c", 463 help="Path to geronimo.yaml configuration file.", 464 ), 465) -> None: 466 """Validate project configuration against deployment rules. 467 468 Checks configuration for: 469 - Required fields 470 - Valid resource specifications 471 - Compliance with deployment policies 472 """ 473 from geronimo.config.loader import load_config 474 from geronimo.validation.engine import ValidationEngine 475 476 console.print("\n[bold blue]Validating configuration...[/bold blue]") 477 478 try: 479 config = load_config(config_path) 480 engine = ValidationEngine() 481 results = engine.validate(config) 482 483 if results.is_valid: 484 console.print( 485 Panel.fit( 486 "[bold green]✓ Configuration is valid![/bold green]\n" 487 f"Checked {results.rules_checked} rules.", 488 border_style="green", 489 ) 490 ) 491 else: 492 console.print("[bold red]✗ Validation failed:[/bold red]") 493 for error in results.errors: 494 console.print(f" [red]•[/red] {error}") 495 raise typer.Exit(code=1) 496 497 except FileNotFoundError: 498 console.print( 499 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 500 ) 501 raise typer.Exit(code=1) 502 except Exception as e: 503 console.print(f"[bold red]Error:[/bold red] {e}") 504 raise typer.Exit(code=1) 505 506 507# NOTE: 'monitor' command removed - feature being redesigned 508 509 510 511# ============================================================================ 512# DEPLOY Command Group 513# ============================================================================ 514 515deploy_app = typer.Typer( 516 name="deploy", 517 help="Deploy infrastructure using Pulumi (requires: pip install geronimo[pulumi]).", 518 no_args_is_help=True, 519) 520app.add_typer(deploy_app, name="deploy") 521 522 523@deploy_app.command("up") 524def deploy_up( 525 project: str = typer.Option( 526 ..., 527 "--project", 528 "-p", 529 help="Project name.", 530 ), 531 target: str = typer.Option( 532 "aws", 533 "--target", 534 "-t", 535 help="Cloud target (aws, gcp, azure, cloud).", 536 ), 537 region: str = typer.Option( 538 "us-east-1", 539 "--region", 540 "-r", 541 help="Cloud region.", 542 ), 543 component: str = typer.Option( 544 None, 545 "--component", 546 "-c", 547 help="Specific component to deploy (artifacts, serving, batch).", 548 ), 549 stack: str = typer.Option( 550 "dev", 551 "--stack", 552 "-s", 553 help="Pulumi stack name.", 554 ), 555) -> None: 556 """Deploy infrastructure to the cloud. 557 558 Requires Pulumi: pip install geronimo[pulumi] 559 560 Examples: 561 geronimo deploy up --project iris --target aws 562 geronimo deploy up --project iris --component artifacts 563 """ 564 from geronimo.deploy import DeploymentConfig, deploy 565 from geronimo.deploy.targets import PulumiNotInstalledError 566 567 console.print(f"\n[bold blue]Deploying {project} to {target}...[/bold blue]") 568 569 try: 570 config = DeploymentConfig( 571 project=project, 572 target=target, 573 region=region, 574 stack_name=stack, 575 ) 576 577 result = deploy(config, component=component) 578 579 console.print( 580 Panel( 581 f"[green]✓ Deployment complete![/green]\n\n" 582 f"Stack: {stack}\n" 583 f"Outputs:\n" + "\n".join(f" {k}: {v}" for k, v in result.get("outputs", {}).items()), 584 title="Deployment Success", 585 border_style="green", 586 ) 587 ) 588 589 except PulumiNotInstalledError as e: 590 console.print(f"[bold yellow]Warning:[/bold yellow] {e}") 591 console.print("\nAlternatives:") 592 console.print(" 1. Install Pulumi: [cyan]pip install geronimo[pulumi][/cyan]") 593 console.print(" 2. Generate static IaC: [cyan]geronimo generate terraform[/cyan]") 594 raise typer.Exit(code=1) 595 except Exception as e: 596 console.print(f"[bold red]Error:[/bold red] {e}") 597 raise typer.Exit(code=1) 598 599 600@deploy_app.command("destroy") 601def deploy_destroy( 602 project: str = typer.Option( 603 ..., 604 "--project", 605 "-p", 606 help="Project name.", 607 ), 608 target: str = typer.Option( 609 "aws", 610 "--target", 611 "-t", 612 help="Cloud target.", 613 ), 614 stack: str = typer.Option( 615 "dev", 616 "--stack", 617 "-s", 618 help="Pulumi stack name.", 619 ), 620 force: bool = typer.Option( 621 False, 622 "--force", 623 "-f", 624 help="Skip confirmation prompt.", 625 ), 626) -> None: 627 """Destroy deployed infrastructure. 628 629 Removes all resources created by 'deploy up'. 630 """ 631 if not force: 632 confirm = typer.confirm(f"Destroy all resources for {project}/{stack}?") 633 if not confirm: 634 console.print("[yellow]Aborted.[/yellow]") 635 raise typer.Exit() 636 637 console.print(f"\n[bold red]Destroying {project}/{stack}...[/bold red]") 638 639 try: 640 from geronimo.deploy.config import DeploymentConfig 641 from geronimo.deploy.providers.aws import destroy_aws 642 643 config = DeploymentConfig( 644 project=project, 645 target=target, 646 stack_name=stack, 647 ) 648 649 if target == "aws": 650 destroy_aws(config) 651 else: 652 console.print(f"[yellow]Destroy not implemented for {target}[/yellow]") 653 raise typer.Exit(code=1) 654 655 console.print("[green]✓ Resources destroyed.[/green]") 656 657 except Exception as e: 658 console.print(f"[bold red]Error:[/bold red] {e}") 659 raise typer.Exit(code=1) 660 661 662# NOTE: 'import' command removed - feature being redesigned 663 664 665# ============================================================================ 666# DOCS Functions (for maintainer scripts, not exposed in CLI) 667# ============================================================================ 668 669docs_app = typer.Typer( 670 name="docs", 671 help="Generate API documentation.", 672 no_args_is_help=True, 673) 674# NOTE: docs_app is intentionally NOT registered with app.add_typer() 675# End users don't need this command. Maintainers can run docs generation via: 676# uv run python -m pdoc --output-directory docs/api geronimo 677# Or use the functions below directly in scripts. 678 679 680@docs_app.command("generate") 681def docs_generate( 682 output: str = typer.Option( 683 "docs/api", 684 "--output", 685 "-o", 686 help="Output directory for generated documentation.", 687 ), 688) -> None: 689 """Generate API documentation for the Geronimo library. 690 691 Uses pdoc to extract documentation from Python docstrings 692 and generate static HTML files. 693 """ 694 import subprocess 695 import sys 696 from pathlib import Path 697 698 console.print("\n[bold blue]Generating API documentation...[/bold blue]") 699 700 # Find the project root (where pyproject.toml is) 701 current = Path(__file__).resolve() 702 project_root = current.parent.parent.parent.parent # cli -> geronimo -> src -> root 703 704 # All public modules to document 705 modules = MODULES 706 707 output_dir = Path(output) 708 output_dir.mkdir(parents=True, exist_ok=True) 709 710 cmd = [ 711 sys.executable, 712 "-m", 713 "pdoc", 714 "--output-directory", 715 str(output_dir), 716 *modules, 717 ] 718 719 # Set PYTHONPATH to include src 720 src_path = project_root / "src" 721 env = {**subprocess.os.environ, "PYTHONPATH": str(src_path)} 722 723 try: 724 result = subprocess.run(cmd, cwd=project_root, env=env, check=True) 725 console.print( 726 Panel.fit( 727 f"[bold green]✓ Documentation generated![/bold green]\n\n" 728 f"Output: [cyan]{output_dir.resolve()}[/cyan]\n" 729 f"Modules: {len(modules)}", 730 border_style="green", 731 ) 732 ) 733 except subprocess.CalledProcessError as e: 734 console.print(f"[bold red]Error:[/bold red] pdoc failed with exit code {e.returncode}") 735 console.print("[dim]Make sure pdoc is installed: pip install pdoc[/dim]") 736 raise typer.Exit(code=1) 737 except FileNotFoundError: 738 console.print("[bold red]Error:[/bold red] pdoc not found") 739 console.print("[dim]Install with: pip install pdoc[/dim]") 740 raise typer.Exit(code=1) 741 742 743@docs_app.command("serve") 744def docs_serve( 745 port: int = typer.Option( 746 8080, 747 "--port", 748 "-p", 749 help="Port to serve documentation on.", 750 ), 751) -> None: 752 """Start a live-reloading documentation server. 753 754 Opens a local development server that automatically 755 rebuilds documentation when source files change. 756 """ 757 import subprocess 758 import sys 759 from pathlib import Path 760 761 console.print(f"\n[bold blue]Starting documentation server on port {port}...[/bold blue]") 762 763 # Find the project root 764 current = Path(__file__).resolve() 765 project_root = current.parent.parent.parent.parent 766 767 modules = MODULES 768 769 cmd = [ 770 sys.executable, 771 "-m", 772 "pdoc", 773 "--host", 774 "localhost", 775 "--port", 776 str(port), 777 *modules, 778 ] 779 780 src_path = project_root / "src" 781 env = {**subprocess.os.environ, "PYTHONPATH": str(src_path)} 782 783 console.print(f"[dim]Open http://localhost:{port} in your browser[/dim]") 784 console.print("[dim]Press Ctrl+C to stop[/dim]\n") 785 786 try: 787 subprocess.run(cmd, cwd=project_root, env=env) 788 except KeyboardInterrupt: 789 console.print("\n[yellow]Server stopped.[/yellow]") 790 791 792if __name__ == "__main__": 793 app()
27def version_callback(value: bool) -> None: 28 """Print version and exit.""" 29 if value: 30 console.print( 31 Panel.fit( 32 f"[bold blue]Geronimo[/bold blue] v{__version__}\n" 33 "[dim]MLOps Deployment Platform[/dim]", 34 border_style="blue", 35 ) 36 ) 37 raise typer.Exit()
Print version and exit.
40@app.callback() 41def main( 42 version: bool = typer.Option( 43 None, 44 "--version", 45 "-v", 46 callback=version_callback, 47 is_eager=True, 48 help="Show version and exit.", 49 ), 50) -> None: 51 """Geronimo - Automate ML model deployments to AWS. 52 53 Generate production-ready Terraform, Docker, and CI/CD pipelines 54 for your ML models with industry best practices built-in. 55 """ 56 pass
Geronimo - Automate ML model deployments to AWS.
Generate production-ready Terraform, Docker, and CI/CD pipelines for your ML models with industry best practices built-in.
64@app.command() 65def init( 66 name: str = typer.Option( 67 None, 68 "--name", 69 "-n", 70 prompt="Project name", 71 help="Name of the ML project.", 72 ), 73 framework: str = typer.Option( 74 "sklearn", 75 "--framework", 76 "-f", 77 help="ML framework (sklearn, pytorch, tensorflow).", 78 ), 79 template: str = typer.Option( 80 "realtime", 81 "--template", 82 "-t", 83 help="Project type: 'realtime' (API endpoints), 'batch' (pipelines), or 'both'.", 84 ), 85 output_dir: str = typer.Option( 86 ".", 87 "--output", 88 "-o", 89 help="Output directory for the project.", 90 ), 91) -> None: 92 """Initialize a new ML deployment project. 93 94 Scaffolds a complete ML project with Geronimo SDK: 95 96 Templates: 97 - realtime: FastAPI endpoints with Endpoint class 98 - batch: Metaflow pipelines with BatchPipeline class 99 - both: Combined real-time and batch support 100 """ 101 from geronimo.generators.project import ProjectGenerator 102 103 # Validate template 104 valid_templates = {"realtime", "batch", "both"} 105 if template not in valid_templates: 106 console.print(f"[bold red]Error:[/bold red] Invalid template '{template}'. Choose from: {valid_templates}") 107 raise typer.Exit(code=1) 108 109 console.print(f"\n[bold blue]Initializing project:[/bold blue] {name}") 110 console.print(f" Template: [cyan]{template}[/cyan]") 111 112 generator = ProjectGenerator( 113 project_name=name, 114 framework=framework, 115 output_dir=output_dir, 116 template=template, 117 ) 118 119 try: 120 generator.generate() 121 122 # SDK scaffolding is now handled by ProjectGenerator.generate() 123 # which creates sdk/endpoint.py, sdk/pipeline.py, app.py, flow.py, etc. 124 125 next_steps = [ 126 f"cd {name}", 127 "uv sync", 128 ] 129 if template in ("realtime", "both"): 130 next_steps.append(f"uvicorn {name.replace('-', '_')}.app:app --reload # Run API server") 131 if template in ("batch", "both"): 132 next_steps.append(f"python -m {name.replace('-', '_')}.flow run # Run batch pipeline") 133 134 console.print( 135 Panel.fit( 136 f"[bold green]✓ Project '{name}' created successfully![/bold green]\n\n" 137 f"Template: [cyan]{template}[/cyan]\n\n" 138 f"Next steps:\n" + "\n".join(f" {i+1}. {step}" for i, step in enumerate(next_steps)), 139 border_style="green", 140 ) 141 ) 142 except Exception as e: 143 console.print(f"[bold red]Error:[/bold red] {e}") 144 raise typer.Exit(code=1)
Initialize a new ML deployment project.
Scaffolds a complete ML project with Geronimo SDK:
Templates:
- realtime: FastAPI endpoints with Endpoint class
- batch: Metaflow pipelines with BatchPipeline class
- both: Combined real-time and batch support
293@generate_app.command("terraform") 294def generate_terraform( 295 config_path: str = typer.Option( 296 "geronimo.yaml", 297 "--config", 298 "-c", 299 help="Path to geronimo.yaml configuration file.", 300 ), 301 output_dir: str = typer.Option( 302 "infrastructure", 303 "--output", 304 "-o", 305 help="Output directory for Terraform files.", 306 ), 307) -> None: 308 """Generate Terraform infrastructure files. 309 310 Creates modular Terraform configuration for: 311 - ECR repository 312 - ECS Fargate task and service 313 - Application Load Balancer 314 - CloudWatch logging and monitoring 315 """ 316 from geronimo.config.loader import load_config 317 from geronimo.generators.terraform import TerraformGenerator 318 319 console.print("\n[bold blue]Generating Terraform...[/bold blue]") 320 321 try: 322 config = load_config(config_path) 323 generator = TerraformGenerator(config=config, output_dir=output_dir) 324 files = generator.generate() 325 326 console.print(f"[green]✓ Generated {len(files)} Terraform files:[/green]") 327 for f in files: 328 console.print(f" • {f}") 329 330 except FileNotFoundError: 331 console.print( 332 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 333 ) 334 raise typer.Exit(code=1) 335 except Exception as e: 336 console.print(f"[bold red]Error:[/bold red] {e}") 337 raise typer.Exit(code=1)
Generate Terraform infrastructure files.
Creates modular Terraform configuration for:
- ECR repository
- ECS Fargate task and service
- Application Load Balancer
- CloudWatch logging and monitoring
340@generate_app.command("dockerfile") 341def generate_dockerfile( 342 config_path: str = typer.Option( 343 "geronimo.yaml", 344 "--config", 345 "-c", 346 help="Path to geronimo.yaml configuration file.", 347 ), 348 output_path: str = typer.Option( 349 "Dockerfile", 350 "--output", 351 "-o", 352 help="Output path for Dockerfile.", 353 ), 354) -> None: 355 """Generate an optimized Dockerfile for ML serving. 356 357 Creates a multi-stage Dockerfile with: 358 - UV for fast dependency installation 359 - Non-root user for security 360 - Proper signal handling for graceful shutdown 361 """ 362 from geronimo.config.loader import load_config 363 from geronimo.generators.docker import DockerGenerator 364 365 console.print("\n[bold blue]Generating Dockerfile...[/bold blue]") 366 367 try: 368 config = load_config(config_path) 369 generator = DockerGenerator(config=config, output_path=output_path) 370 generator.generate() 371 372 console.print(f"[green]✓ Generated Dockerfile:[/green] {output_path}") 373 374 except FileNotFoundError: 375 console.print( 376 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 377 ) 378 raise typer.Exit(code=1) 379 except Exception as e: 380 console.print(f"[bold red]Error:[/bold red] {e}") 381 raise typer.Exit(code=1)
Generate an optimized Dockerfile for ML serving.
Creates a multi-stage Dockerfile with:
- UV for fast dependency installation
- Non-root user for security
- Proper signal handling for graceful shutdown
384@generate_app.command("pipeline") 385def generate_pipeline( 386 config_path: str = typer.Option( 387 "geronimo.yaml", 388 "--config", 389 "-c", 390 help="Path to geronimo.yaml configuration file.", 391 ), 392 output_path: str = typer.Option( 393 "azure-pipelines.yaml", 394 "--output", 395 "-o", 396 help="Output path for pipeline file.", 397 ), 398) -> None: 399 """Generate CI/CD pipeline configuration. 400 401 Creates Azure DevOps pipeline YAML with: 402 - Build and test stage 403 - Security scanning 404 - Multi-environment deployments 405 - Approval gates 406 """ 407 from geronimo.config.loader import load_config 408 from geronimo.generators.pipeline import PipelineGenerator 409 410 console.print("\n[bold blue]Generating pipeline...[/bold blue]") 411 412 try: 413 config = load_config(config_path) 414 generator = PipelineGenerator(config=config, output_path=output_path) 415 generator.generate() 416 417 console.print(f"[green]✓ Generated pipeline:[/green] {output_path}") 418 419 except FileNotFoundError: 420 console.print( 421 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 422 ) 423 raise typer.Exit(code=1) 424 except Exception as e: 425 console.print(f"[bold red]Error:[/bold red] {e}") 426 raise typer.Exit(code=1)
Generate CI/CD pipeline configuration.
Creates Azure DevOps pipeline YAML with:
- Build and test stage
- Security scanning
- Multi-environment deployments
- Approval gates
429@generate_app.command("all") 430def generate_all( 431 config_path: str = typer.Option( 432 "geronimo.yaml", 433 "--config", 434 "-c", 435 help="Path to geronimo.yaml configuration file.", 436 ), 437) -> None: 438 """Generate all deployment artifacts. 439 440 Generates Terraform, Dockerfile, and CI/CD pipeline in one command. 441 """ 442 console.print("\n[bold blue]Generating all artifacts...[/bold blue]") 443 444 # Call individual generators 445 generate_terraform(config_path=config_path, output_dir="infrastructure") 446 generate_dockerfile(config_path=config_path, output_path="Dockerfile") 447 generate_pipeline(config_path=config_path, output_path="azure-pipelines.yaml") 448 449 console.print("\n[bold green]✓ All artifacts generated successfully![/bold green]")
Generate all deployment artifacts.
Generates Terraform, Dockerfile, and CI/CD pipeline in one command.
458@app.command() 459def validate( 460 config_path: str = typer.Option( 461 "geronimo.yaml", 462 "--config", 463 "-c", 464 help="Path to geronimo.yaml configuration file.", 465 ), 466) -> None: 467 """Validate project configuration against deployment rules. 468 469 Checks configuration for: 470 - Required fields 471 - Valid resource specifications 472 - Compliance with deployment policies 473 """ 474 from geronimo.config.loader import load_config 475 from geronimo.validation.engine import ValidationEngine 476 477 console.print("\n[bold blue]Validating configuration...[/bold blue]") 478 479 try: 480 config = load_config(config_path) 481 engine = ValidationEngine() 482 results = engine.validate(config) 483 484 if results.is_valid: 485 console.print( 486 Panel.fit( 487 "[bold green]✓ Configuration is valid![/bold green]\n" 488 f"Checked {results.rules_checked} rules.", 489 border_style="green", 490 ) 491 ) 492 else: 493 console.print("[bold red]✗ Validation failed:[/bold red]") 494 for error in results.errors: 495 console.print(f" [red]•[/red] {error}") 496 raise typer.Exit(code=1) 497 498 except FileNotFoundError: 499 console.print( 500 f"[bold red]Error:[/bold red] Config file not found: {config_path}" 501 ) 502 raise typer.Exit(code=1) 503 except Exception as e: 504 console.print(f"[bold red]Error:[/bold red] {e}") 505 raise typer.Exit(code=1)
Validate project configuration against deployment rules.
Checks configuration for:
- Required fields
- Valid resource specifications
- Compliance with deployment policies
524@deploy_app.command("up") 525def deploy_up( 526 project: str = typer.Option( 527 ..., 528 "--project", 529 "-p", 530 help="Project name.", 531 ), 532 target: str = typer.Option( 533 "aws", 534 "--target", 535 "-t", 536 help="Cloud target (aws, gcp, azure, cloud).", 537 ), 538 region: str = typer.Option( 539 "us-east-1", 540 "--region", 541 "-r", 542 help="Cloud region.", 543 ), 544 component: str = typer.Option( 545 None, 546 "--component", 547 "-c", 548 help="Specific component to deploy (artifacts, serving, batch).", 549 ), 550 stack: str = typer.Option( 551 "dev", 552 "--stack", 553 "-s", 554 help="Pulumi stack name.", 555 ), 556) -> None: 557 """Deploy infrastructure to the cloud. 558 559 Requires Pulumi: pip install geronimo[pulumi] 560 561 Examples: 562 geronimo deploy up --project iris --target aws 563 geronimo deploy up --project iris --component artifacts 564 """ 565 from geronimo.deploy import DeploymentConfig, deploy 566 from geronimo.deploy.targets import PulumiNotInstalledError 567 568 console.print(f"\n[bold blue]Deploying {project} to {target}...[/bold blue]") 569 570 try: 571 config = DeploymentConfig( 572 project=project, 573 target=target, 574 region=region, 575 stack_name=stack, 576 ) 577 578 result = deploy(config, component=component) 579 580 console.print( 581 Panel( 582 f"[green]✓ Deployment complete![/green]\n\n" 583 f"Stack: {stack}\n" 584 f"Outputs:\n" + "\n".join(f" {k}: {v}" for k, v in result.get("outputs", {}).items()), 585 title="Deployment Success", 586 border_style="green", 587 ) 588 ) 589 590 except PulumiNotInstalledError as e: 591 console.print(f"[bold yellow]Warning:[/bold yellow] {e}") 592 console.print("\nAlternatives:") 593 console.print(" 1. Install Pulumi: [cyan]pip install geronimo[pulumi][/cyan]") 594 console.print(" 2. Generate static IaC: [cyan]geronimo generate terraform[/cyan]") 595 raise typer.Exit(code=1) 596 except Exception as e: 597 console.print(f"[bold red]Error:[/bold red] {e}") 598 raise typer.Exit(code=1)
Deploy infrastructure to the cloud.
Requires Pulumi: pip install geronimo[pulumi]
Examples: geronimo deploy up --project iris --target aws geronimo deploy up --project iris --component artifacts
601@deploy_app.command("destroy") 602def deploy_destroy( 603 project: str = typer.Option( 604 ..., 605 "--project", 606 "-p", 607 help="Project name.", 608 ), 609 target: str = typer.Option( 610 "aws", 611 "--target", 612 "-t", 613 help="Cloud target.", 614 ), 615 stack: str = typer.Option( 616 "dev", 617 "--stack", 618 "-s", 619 help="Pulumi stack name.", 620 ), 621 force: bool = typer.Option( 622 False, 623 "--force", 624 "-f", 625 help="Skip confirmation prompt.", 626 ), 627) -> None: 628 """Destroy deployed infrastructure. 629 630 Removes all resources created by 'deploy up'. 631 """ 632 if not force: 633 confirm = typer.confirm(f"Destroy all resources for {project}/{stack}?") 634 if not confirm: 635 console.print("[yellow]Aborted.[/yellow]") 636 raise typer.Exit() 637 638 console.print(f"\n[bold red]Destroying {project}/{stack}...[/bold red]") 639 640 try: 641 from geronimo.deploy.config import DeploymentConfig 642 from geronimo.deploy.providers.aws import destroy_aws 643 644 config = DeploymentConfig( 645 project=project, 646 target=target, 647 stack_name=stack, 648 ) 649 650 if target == "aws": 651 destroy_aws(config) 652 else: 653 console.print(f"[yellow]Destroy not implemented for {target}[/yellow]") 654 raise typer.Exit(code=1) 655 656 console.print("[green]✓ Resources destroyed.[/green]") 657 658 except Exception as e: 659 console.print(f"[bold red]Error:[/bold red] {e}") 660 raise typer.Exit(code=1)
Destroy deployed infrastructure.
Removes all resources created by 'deploy up'.
681@docs_app.command("generate") 682def docs_generate( 683 output: str = typer.Option( 684 "docs/api", 685 "--output", 686 "-o", 687 help="Output directory for generated documentation.", 688 ), 689) -> None: 690 """Generate API documentation for the Geronimo library. 691 692 Uses pdoc to extract documentation from Python docstrings 693 and generate static HTML files. 694 """ 695 import subprocess 696 import sys 697 from pathlib import Path 698 699 console.print("\n[bold blue]Generating API documentation...[/bold blue]") 700 701 # Find the project root (where pyproject.toml is) 702 current = Path(__file__).resolve() 703 project_root = current.parent.parent.parent.parent # cli -> geronimo -> src -> root 704 705 # All public modules to document 706 modules = MODULES 707 708 output_dir = Path(output) 709 output_dir.mkdir(parents=True, exist_ok=True) 710 711 cmd = [ 712 sys.executable, 713 "-m", 714 "pdoc", 715 "--output-directory", 716 str(output_dir), 717 *modules, 718 ] 719 720 # Set PYTHONPATH to include src 721 src_path = project_root / "src" 722 env = {**subprocess.os.environ, "PYTHONPATH": str(src_path)} 723 724 try: 725 result = subprocess.run(cmd, cwd=project_root, env=env, check=True) 726 console.print( 727 Panel.fit( 728 f"[bold green]✓ Documentation generated![/bold green]\n\n" 729 f"Output: [cyan]{output_dir.resolve()}[/cyan]\n" 730 f"Modules: {len(modules)}", 731 border_style="green", 732 ) 733 ) 734 except subprocess.CalledProcessError as e: 735 console.print(f"[bold red]Error:[/bold red] pdoc failed with exit code {e.returncode}") 736 console.print("[dim]Make sure pdoc is installed: pip install pdoc[/dim]") 737 raise typer.Exit(code=1) 738 except FileNotFoundError: 739 console.print("[bold red]Error:[/bold red] pdoc not found") 740 console.print("[dim]Install with: pip install pdoc[/dim]") 741 raise typer.Exit(code=1)
Generate API documentation for the Geronimo library.
Uses pdoc to extract documentation from Python docstrings and generate static HTML files.
744@docs_app.command("serve") 745def docs_serve( 746 port: int = typer.Option( 747 8080, 748 "--port", 749 "-p", 750 help="Port to serve documentation on.", 751 ), 752) -> None: 753 """Start a live-reloading documentation server. 754 755 Opens a local development server that automatically 756 rebuilds documentation when source files change. 757 """ 758 import subprocess 759 import sys 760 from pathlib import Path 761 762 console.print(f"\n[bold blue]Starting documentation server on port {port}...[/bold blue]") 763 764 # Find the project root 765 current = Path(__file__).resolve() 766 project_root = current.parent.parent.parent.parent 767 768 modules = MODULES 769 770 cmd = [ 771 sys.executable, 772 "-m", 773 "pdoc", 774 "--host", 775 "localhost", 776 "--port", 777 str(port), 778 *modules, 779 ] 780 781 src_path = project_root / "src" 782 env = {**subprocess.os.environ, "PYTHONPATH": str(src_path)} 783 784 console.print(f"[dim]Open http://localhost:{port} in your browser[/dim]") 785 console.print("[dim]Press Ctrl+C to stop[/dim]\n") 786 787 try: 788 subprocess.run(cmd, cwd=project_root, env=env) 789 except KeyboardInterrupt: 790 console.print("\n[yellow]Server stopped.[/yellow]")
Start a live-reloading documentation server.
Opens a local development server that automatically rebuilds documentation when source files change.