""" BharatGraph Desktop -- PyInstaller entry point (Phase D2) Why this file exists, separately from app.py: the Docker deployment starts the server via `uvicorn app:app`, a CLI command that resolves the string "app:app" to a module and attribute at runtime using Python's normal import machinery. A PyInstaller-frozen executable does not have that machinery available the same way -- there is no `app.py` file on disk to resolve a string against once everything is bundled into a single binary. The standard, reliable fix (used by PyInstaller's own documentation for freezing ASGI apps) is to import the app object directly in Python code and call uvicorn.run() programmatically instead of via its CLI string-lookup path. This file is that direct entry point. Sets BHARATGRAPH_DESKTOP_MODE=true before importing anything else, so the Phase D1 admin-auth hardening in api/routes/admin.py (auto-generated, persisted per-install secret; no DEBUG_MODE bypass path) applies correctly whenever the app is started this way, and so config/settings.py's local config-file loading is the primary configuration path a desktop user actually has, rather than a code path that only matters at packaging time. P-1 fix: also chdir()s into the OS-appropriate app-data directory before any other import. Grepped every bare (non-__file__-anchored) CWD-relative path in the codebase: 30+ real call sites across all 19 scrapers' data/samples/*.json sample-fallback output, processing/pipeline.py, ai/multi_investigator.py, ai/risk_scorer.py, ai/self_learning/ schema_learner.py, processing/alias_graph.py, and graph/loader.py's own glob() read of pipeline output -- none of which reference the (equally dead) DATA_RAW_PATH/DATA_PROCESSED_PATH/DATA_SAMPLES_PATH constants in config/settings.py; each just hardcodes its own "data/processed/..." or "data/samples/..." string directly. On a real Windows install, a double-clicked app's CWD defaults to (and the install directory itself is typically read-only for) a non-admin user -- meaning every one of those 30+ writes would fail outright, not just lose regeneratable cache data. Verified every one of them is genuinely CWD-relative (confirmed via direct testing that python-dotenv's own load_dotenv() calls elsewhere in the codebase are NOT affected by this, since dotenv resolves relative to the calling file's own frame, not CWD) -- so a single chdir() here fixes all 30+ call sites identically, with zero changes needed to any of them, rather than repeating the explicit per-file redirection this branch's sibling commits used for case_memory.json/logs/dossier output (which were __file__-anchored, not CWD-relative, and so could NOT have been fixed this way). Web-deployment behavior is unchanged -- this file is never imported by the Docker/app.py path. Binds to 127.0.0.1 only, matching the target architecture ("bound to 127.0.0.1 only") -- this process is meant to be reached only by the Tauri shell's own webview (Phase D4), never by the network. """ import os import sys # Must happen before importing api.main (or anything that transitively # imports config.settings), so the desktop-mode admin-auth branch and the # local config-file loading are both active from the very first import. os.environ.setdefault("BHARATGRAPH_DESKTOP_MODE", "true") # PyInstaller sets sys.frozen and sys._MEIPASS when running from a frozen # bundle; ensure the app's own package root is importable either way, so # this entry point works identically when run as a plain script (for local # testing, exactly as this was verified) and when frozen. if getattr(sys, "frozen", False): _app_root = sys._MEIPASS # type: ignore[attr-defined] else: _app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, _app_root) # P-1 fix: chdir into a guaranteed-writable directory before any other # import that might (transitively, later, at request time) write a bare # CWD-relative path. Must come after sys.path is set up (needs to import # config.settings) but before uvicorn/api.main are imported. from config.settings import get_app_data_dir _workdir = get_app_data_dir() os.makedirs(_workdir, exist_ok=True) os.chdir(_workdir) import uvicorn from api.main import app def main() -> None: port = int(os.getenv("BHARATGRAPH_DESKTOP_PORT", "8000")) uvicorn.run( app, host="127.0.0.1", port=port, log_level=os.getenv("BHARATGRAPH_LOG_LEVEL", "info"), # Single worker: matches the existing Docker deployment's own # --workers 1 (api/main.py's docstring/Dockerfile), and a desktop # install is single-user by definition -- no reason to diverge. workers=1, ) if __name__ == "__main__": main()