"""Executable entry point for CadHubManage.""" from __future__ import annotations import argparse import multiprocessing import os import shutil import sys from pathlib import Path def _copy_missing_tree(src: Path, dst: Path) -> None: if not src.exists(): return for item in src.rglob("*"): rel = item.relative_to(src) target = dst / rel if item.is_dir(): target.mkdir(parents=True, exist_ok=True) continue if target.exists(): continue target.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(item, target) def _materialize_packaged_resources(base_dir: Path) -> None: if not getattr(sys, "frozen", False): return meipass = Path(getattr(sys, "_MEIPASS", "")) if not meipass: return packaged_configs = meipass / "configs" target_configs = base_dir / "configs" _copy_missing_tree(packaged_configs, target_configs) packaged_env_example = meipass / ".env.example" target_env_example = base_dir / ".env.example" if packaged_env_example.exists() and not target_env_example.exists(): shutil.copy2(packaged_env_example, target_env_example) (base_dir / "logs" / "operation_logs").mkdir(parents=True, exist_ok=True) def prepare_environment() -> Path: raw_base_dir = os.environ.get("CADHUB_BASE_DIR") if raw_base_dir: base_dir = Path(raw_base_dir).resolve() elif getattr(sys, "frozen", False): base_dir = Path(sys.executable).resolve().parent else: base_dir = Path(__file__).resolve().parent base_dir.mkdir(parents=True, exist_ok=True) _materialize_packaged_resources(base_dir) os.environ.setdefault("CADHUB_BASE_DIR", str(base_dir)) os.chdir(base_dir) return base_dir BASE_DIR = prepare_environment() from app.main import app # noqa: E402 pylint: disable=wrong-import-position from app.config import settings # noqa: E402 pylint: disable=wrong-import-position import uvicorn # noqa: E402 pylint: disable=wrong-import-position def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="CadHubManage offline launcher") parser.add_argument("--host", default=settings.host, help="Host address") parser.add_argument("--port", type=int, default=settings.port, help="Port number") parser.add_argument("--workers", type=int, default=1, help="Number of worker processes") parser.add_argument("--reload", action="store_true", help="Enable auto-reload (development only)") parser.add_argument( "--log-level", default=settings.log_level.lower(), choices=["critical", "error", "warning", "info", "debug", "trace"], help="Logging level", ) return parser.parse_args() def main() -> None: args = parse_args() if args.reload and args.workers != 1: print("Reload mode requires workers=1, adjusting automatically.") args.workers = 1 uvicorn_app = app if args.reload or args.workers != 1: uvicorn_app = "app.main:app" uvicorn.run( uvicorn_app, host=args.host, port=args.port, reload=args.reload, workers=args.workers, log_level=args.log_level, ) if __name__ == "__main__": multiprocessing.freeze_support() main()