""" Command-line interface for FastAPI OpenAPI-first scaffolding utilities. --- ## Summary This CLI bootstraps OpenAPI-first FastAPI applications from versioned, bundled templates packaged with the library. """ import argparse import shutil from pathlib import Path from importlib import resources DEFAULT_TEMPLATE = "health_app" def available_templates() -> list[str]: """ Return a list of available application templates. Returns: list[str]: Sorted list of template names found in the internal templates directory. """ root = resources.files("openapi_first.templates") return sorted( item.name for item in root.iterdir() if item.is_dir() and not item.name.startswith("_") ) def copy_template(template: str, target_dir: Path) -> None: """ Copy a bundled OpenAPI-first application template into a directory. Args: template (str): Name of the template to copy. target_dir (Path): Filesystem path where the template should be copied. Raises: FileNotFoundError: If the requested template does not exist. """ target_dir = target_dir.resolve() target_dir.mkdir(parents=True, exist_ok=True) root = resources.files("openapi_first.templates") src = root / template if not src.exists(): raise FileNotFoundError( f"Template '{template}' not found. " f"Available templates: {', '.join(available_templates())}" ) with resources.as_file(src) as path: shutil.copytree(path, target_dir, dirs_exist_ok=True) def main() -> None: parser = argparse.ArgumentParser( description="FastAPI OpenAPI-first developer tools" ) subparsers = parser.add_subparsers(dest="command", help="Command to execute") # Scaffold command scaffold_parser = subparsers.add_parser( "scaffold", help="Scaffold a new OpenAPI-first FastAPI application" ) scaffold_parser.add_argument( "template", nargs="?", default=DEFAULT_TEMPLATE, help=f"Template name (default: {DEFAULT_TEMPLATE})", ) scaffold_parser.add_argument( "path", nargs="?", default=None, help="Target directory (defaults to template name)", ) scaffold_parser.add_argument( "--list", action="store_true", help="List available templates and exit", ) # Models command models_parser = subparsers.add_parser( "models", help="Generate Pydantic models from an OpenAPI specification" ) models_parser.add_argument( "spec", help="Path to the OpenAPI specification file", ) models_parser.add_argument( "-o", "--output", required=True, help="Path to the output Python file", ) # Routes command routes_parser = subparsers.add_parser( "routes", help="Generate route handler stubs from an OpenAPI specification", ) routes_parser.add_argument( "spec", help="Path to the OpenAPI specification file", ) routes_parser.add_argument( "-o", "--output-dir", required=True, help="Directory where route files will be created", ) routes_parser.add_argument( "--use-models", action="store_true", default=False, help="Import Pydantic models for request bodies", ) routes_parser.add_argument( "--models-module", default="models", help="Python module path for models (default: models)", ) args = parser.parse_args() if args.command == "models": from .codegen import generate_models try: generate_models(Path(args.spec), Path(args.output)) print(f"Models generated successfully at {args.output}") except Exception as exc: raise SystemExit(str(exc)) from exc return if args.command == "routes": from .codegen import generate_routes try: files = generate_routes( spec_path=Path(args.spec), output_dir=Path(args.output_dir), use_models=args.use_models, models_module=args.models_module, ) print(f"Generated {len(files)} route file(s):") for f in files: print(f" {f}") except Exception as exc: raise SystemExit(str(exc)) from exc return # Default to scaffold if no command or scaffold command if args.command == "scaffold" or args.command is None: # Handle the case where someone uses the old CLI style (openapi-first template path) # argparse with subparsers might not automatically handle this if positional args are passed # but let's assume we want to encourage the new style. # If no command was provided but positional args were, they might be for scaffolding # This is a bit tricky with argparse subparsers. # For simplicity, let's just support the new explicit commands. if args.command is None and not any(vars(args).values()): parser.print_help() return if getattr(args, "list", False): for name in available_templates(): print(name) return template = getattr(args, "template", DEFAULT_TEMPLATE) path_arg = getattr(args, "path", None) target = Path(path_arg or template.replace("_", "-")) try: copy_template(template, target) print(f"Template '{template}' created at {target}") except Exception as exc: raise SystemExit(str(exc)) from exc return parser.print_help() if __name__ == "__main__": main()