""" pyserve init - Initialize a new pyserve project """ from pathlib import Path import click TEMPLATES = { "basic": { "description": "Basic configuration with static files and routing", "filename": "config.yaml", }, "orchestration": { "description": "Process orchestration with multiple ASGI/WSGI apps", "filename": "config.yaml", }, "asgi": { "description": "ASGI mount configuration for in-process apps", "filename": "config.yaml", }, "full": { "description": "Full configuration with all features", "filename": "config.yaml", }, } BASIC_TEMPLATE = """\ # PyServe Configuration # Generated by: pyserve init http: static_dir: ./static templates_dir: ./templates server: host: 0.0.0.0 port: 8080 backlog: 100 proxy_timeout: 30.0 ssl: enabled: false cert_file: ./ssl/cert.pem key_file: ./ssl/key.pem logging: level: INFO console_output: true format: type: standard use_colors: true show_module: true timestamp_format: "%Y-%m-%d %H:%M:%S" console: level: INFO format: type: standard use_colors: true files: - path: ./logs/pyserve.log level: INFO format: type: standard use_colors: false extensions: - type: routing config: regex_locations: # Health check endpoint "=/health": return: "200 OK" content_type: "text/plain" # Static files "^/static/": root: "./static" strip_prefix: "/static" # Default fallback "__default__": spa_fallback: true root: "./static" index_file: "index.html" """ ORCHESTRATION_TEMPLATE = """\ # PyServe Process Orchestration Configuration # Generated by: pyserve init --template orchestration # # This configuration runs multiple ASGI/WSGI apps as isolated processes # with automatic health monitoring and restart. server: host: 0.0.0.0 port: 8080 backlog: 2048 proxy_timeout: 60.0 logging: level: INFO console_output: true format: type: standard use_colors: true files: - path: ./logs/pyserve.log level: DEBUG format: type: standard use_colors: false extensions: # Process Orchestration - runs each app in its own process - type: process_orchestration config: port_range: [9000, 9999] health_check_enabled: true proxy_timeout: 60.0 apps: # Example: FastAPI application - name: api path: /api app_path: myapp.api:app module_path: "." workers: 2 health_check_path: /health health_check_interval: 10.0 health_check_timeout: 5.0 health_check_retries: 3 max_restart_count: 5 restart_delay: 1.0 strip_path: true env: APP_ENV: "production" # Example: Flask application (WSGI) # - name: admin # path: /admin # app_path: myapp.admin:app # app_type: wsgi # module_path: "." # workers: 1 # health_check_path: /health # strip_path: true # Static files routing - type: routing config: regex_locations: "=/health": return: "200 OK" content_type: "text/plain" "^/static/": root: "./static" strip_prefix: "/static" """ ASGI_TEMPLATE = """\ # PyServe ASGI Mount Configuration # Generated by: pyserve init --template asgi # # This configuration mounts ASGI apps in-process (like ASGI Lifespan). # More efficient but apps share the same process. server: host: 0.0.0.0 port: 8080 backlog: 100 proxy_timeout: 30.0 logging: level: INFO console_output: true format: type: standard use_colors: true files: - path: ./logs/pyserve.log level: DEBUG extensions: - type: asgi_mount config: mounts: # FastAPI app mounted at /api - path: /api app: myapp.api:app # factory: false # Set to true if app is a factory function # Starlette app mounted at /web # - path: /web # app: myapp.web:app - type: routing config: regex_locations: "=/health": return: "200 OK" content_type: "text/plain" "^/static/": root: "./static" strip_prefix: "/static" "__default__": spa_fallback: true root: "./static" index_file: "index.html" """ FULL_TEMPLATE = """\ # PyServe Full Configuration # Generated by: pyserve init --template full # # Comprehensive configuration showcasing all PyServe features. http: static_dir: ./static templates_dir: ./templates server: host: 0.0.0.0 port: 8080 backlog: 2048 default_root: false proxy_timeout: 60.0 redirect_instructions: "/old-path": "/new-path" ssl: enabled: false cert_file: ./ssl/cert.pem key_file: ./ssl/key.pem logging: level: INFO console_output: true format: type: standard use_colors: true show_module: true timestamp_format: "%Y-%m-%d %H:%M:%S" console: level: DEBUG format: type: standard use_colors: true files: # Main log file - path: ./logs/pyserve.log level: DEBUG format: type: standard use_colors: false # JSON logs for log aggregation - path: ./logs/pyserve.json level: INFO format: type: json # Access logs - path: ./logs/access.log level: INFO loggers: ["pyserve.access"] max_bytes: 10485760 # 10MB backup_count: 10 extensions: # Process Orchestration for background services - type: process_orchestration config: port_range: [9000, 9999] health_check_enabled: true proxy_timeout: 60.0 apps: - name: api path: /api app_path: myapp.api:app module_path: "." workers: 2 health_check_path: /health strip_path: true env: APP_ENV: "production" # Advanced routing with regex - type: routing config: regex_locations: # API versioning "~^/api/v(?P\\\\d+)/": proxy_pass: "http://localhost:9001" headers: - "API-Version: {version}" - "X-Forwarded-For: $remote_addr" # Static files with caching "~*\\\\.(js|css|png|jpg|gif|ico|svg|woff2?)$": root: "./static" cache_control: "public, max-age=31536000" headers: - "Access-Control-Allow-Origin: *" # Health check "=/health": return: "200 OK" content_type: "text/plain" # Static files "^/static/": root: "./static" strip_prefix: "/static" # SPA fallback "__default__": spa_fallback: true root: "./static" index_file: "index.html" """ def get_template_content(template: str) -> str: templates = { "basic": BASIC_TEMPLATE, "orchestration": ORCHESTRATION_TEMPLATE, "asgi": ASGI_TEMPLATE, "full": FULL_TEMPLATE, } return templates.get(template, BASIC_TEMPLATE) @click.command("init") @click.option( "-t", "--template", "template", type=click.Choice(list(TEMPLATES.keys())), default="basic", help="Configuration template to use", ) @click.option( "-o", "--output", "output_file", default="config.yaml", help="Output file path (default: config.yaml)", ) @click.option( "-f", "--force", is_flag=True, help="Overwrite existing configuration", ) @click.option( "--list-templates", is_flag=True, help="List available templates", ) @click.pass_context def init_cmd( ctx: click.Context, template: str, output_file: str, force: bool, list_templates: bool, ) -> None: """ Initialize a new pyserve project. Creates a configuration file with sensible defaults and directory structure. \b Examples: pyserve init # Basic configuration pyserve init -t orchestration # Process orchestration setup pyserve init -t asgi # ASGI mount setup pyserve init -t full # All features pyserve init -o production.yaml # Custom output file """ from ..output import console, print_info, print_success, print_warning if list_templates: console.print("\n[bold]Available Templates:[/bold]\n") for name, info in TEMPLATES.items(): console.print(f" [cyan]{name:15}[/cyan] - {info['description']}") console.print() return output_path = Path(output_file) if output_path.exists() and not force: print_warning(f"Configuration file '{output_file}' already exists.") if not click.confirm("Do you want to overwrite it?"): raise click.Abort() dirs_to_create = ["static", "templates", "logs"] if template == "orchestration": dirs_to_create.append("apps") for dir_name in dirs_to_create: dir_path = Path(dir_name) if not dir_path.exists(): dir_path.mkdir(parents=True) print_info(f"Created directory: {dir_name}/") state_dir = Path(".pyserve") if not state_dir.exists(): state_dir.mkdir() print_info("Created directory: .pyserve/") content = get_template_content(template) output_path.write_text(content) print_success(f"Created configuration file: {output_file}") print_info(f"Template: {template}") gitignore_path = Path(".pyserve/.gitignore") if not gitignore_path.exists(): gitignore_path.write_text("*\n!.gitignore\n") console.print() console.print("[bold]Next steps:[/bold]") console.print(f" 1. Edit [cyan]{output_file}[/cyan] to configure your services") console.print(" 2. Run [cyan]pyserve config validate[/cyan] to check configuration") console.print(" 3. Run [cyan]pyserve up[/cyan] to start services") console.print()