Source code for analytic_continuation_server.cli

"""
CLI entry point for the Analytic Continuation Server.

Provides the `serve-analytic-continuation` command.

Usage
-----
Basic usage:

    serve-analytic-continuation --port 8000

With custom CORS:

    serve-analytic-continuation --cors-origin https://myapp.com --cors-origin https://api.myapp.com

Disable optional features:

    serve-analytic-continuation --disable-domaincolor

Using a config file:

    serve-analytic-continuation --config /path/to/config.json
"""

import argparse
import json
import sys
from typing import Optional


[docs] def load_config_file(path: str) -> dict: """Load configuration from a JSON file.""" with open(path, "r") as f: return json.load(f)
[docs] def main(args: Optional[list[str]] = None): """Main entry point for the CLI.""" parser = argparse.ArgumentParser( description="Run the Analytic Continuation Server", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "--host", default="0.0.0.0", help="Host to bind to", ) parser.add_argument( "--port", type=int, default=8000, help="Port to bind to", ) parser.add_argument( "--reload", action="store_true", help="Enable auto-reload for development", ) parser.add_argument( "--workers", type=int, default=1, help="Number of worker processes", ) parser.add_argument( "--log-level", choices=["debug", "info", "warning", "error", "critical"], default="info", help="Log level", ) parser.add_argument( "--cors-origin", dest="cors_origins", action="append", help="Allowed CORS origin (can be specified multiple times). " "Default: allow all origins (*)", ) parser.add_argument( "--config", metavar="FILE", help="Path to JSON configuration file", ) parser.add_argument( "--disable-domaincolor", action="store_true", help="Disable domain coloring endpoints", ) parser.add_argument( "--disable-laurent", action="store_true", help="Disable Laurent series fitting endpoints", ) parser.add_argument( "--debug", action="store_true", help="Enable debug mode", ) parser.add_argument( "--title", help="API title for OpenAPI docs", ) parser.add_argument( "--version-tag", dest="version", help="API version tag", ) parsed = parser.parse_args(args) try: import uvicorn except ImportError: print( "Error: uvicorn is required but not installed. " "Install with: pip install uvicorn[standard]", file=sys.stderr, ) sys.exit(1) # Build configuration from CLI args and config file from .config import ServerConfig config_dict: dict = {} # Load from config file first (if specified) if parsed.config: try: config_dict = load_config_file(parsed.config) except FileNotFoundError: print(f"Error: Config file not found: {parsed.config}", file=sys.stderr) sys.exit(1) except json.JSONDecodeError as e: print(f"Error: Invalid JSON in config file: {e}", file=sys.stderr) sys.exit(1) # Override with CLI arguments (CLI takes precedence) if parsed.cors_origins: config_dict["cors_origins"] = parsed.cors_origins elif "cors_origins" not in config_dict: config_dict["cors_origins"] = ["*"] if parsed.disable_domaincolor: config_dict["enable_domaincolor"] = False elif "enable_domaincolor" not in config_dict: config_dict["enable_domaincolor"] = True if parsed.disable_laurent: config_dict["enable_laurent"] = False elif "enable_laurent" not in config_dict: config_dict["enable_laurent"] = True config_dict["log_level"] = parsed.log_level if parsed.debug: config_dict["debug"] = True if parsed.title: config_dict["title"] = parsed.title if parsed.version: config_dict["version"] = parsed.version # Validate configuration try: config = ServerConfig(**config_dict) except Exception as e: print(f"Error: Invalid configuration: {e}", file=sys.stderr) sys.exit(1) # Create app with configuration for factory function reference # Store config in a module-level variable for the factory import analytic_continuation_server.cli as cli_module cli_module._cli_config = config print(f"Starting Analytic Continuation Server on {parsed.host}:{parsed.port}") print(f" Documentation: http://{parsed.host}:{parsed.port}/docs") print(f" Health check: http://{parsed.host}:{parsed.port}/api/health") if not config.enable_domaincolor: print(" Domain coloring endpoints: DISABLED") if not config.enable_laurent: print(" Laurent fitting endpoints: DISABLED") if config.debug: print(" Debug mode: ENABLED") # When using reload, uvicorn needs a string reference # For custom config, we need to use factory mode if parsed.reload or parsed.workers > 1: # Use factory string for reload/workers mode uvicorn.run( "analytic_continuation_server.cli:_create_app_from_cli", host=parsed.host, port=parsed.port, reload=parsed.reload, workers=parsed.workers if not parsed.reload else 1, log_level=parsed.log_level, factory=True, ) else: # Direct app creation for single-worker mode from .app import create_app app = create_app(config) uvicorn.run( app, host=parsed.host, port=parsed.port, log_level=parsed.log_level, )
# Module-level config storage for factory mode _cli_config: Optional["ServerConfig"] = None # type: ignore def _create_app_from_cli(): """Factory function for uvicorn when using reload/workers mode.""" from .app import create_app from .config import ServerConfig global _cli_config if _cli_config is not None: return create_app(_cli_config) else: # Fallback to default config return create_app() if __name__ == "__main__": main()