From 28820c91eb205c49c69c049fbc643d0c791b016e Mon Sep 17 00:00:00 2001 From: haikdc Date: Sun, 5 Apr 2026 20:47:18 -0700 Subject: [PATCH] [Haik]: ckpt, abstracted helpers within NineRouterProcess --- .../NineRouterProcess.py | 55 +++---------------- .../helpers/find_9router_dir.py | 26 +++++++++ .../NineRouterProcess/helpers/find_node.py | 15 +++++ .../helpers/forward_output.py | 18 ++++++ backend/apps/subscriptions/subscriptions.py | 2 +- 5 files changed, 67 insertions(+), 49 deletions(-) rename backend/apps/subscriptions/NineRouter/{ => NineRouterProcess}/NineRouterProcess.py (76%) create mode 100644 backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_9router_dir.py create mode 100644 backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_node.py create mode 100644 backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/forward_output.py diff --git a/backend/apps/subscriptions/NineRouter/NineRouterProcess.py b/backend/apps/subscriptions/NineRouter/NineRouterProcess/NineRouterProcess.py similarity index 76% rename from backend/apps/subscriptions/NineRouter/NineRouterProcess.py rename to backend/apps/subscriptions/NineRouter/NineRouterProcess/NineRouterProcess.py index b336a777..8727851d 100644 --- a/backend/apps/subscriptions/NineRouter/NineRouterProcess.py +++ b/backend/apps/subscriptions/NineRouter/NineRouterProcess/NineRouterProcess.py @@ -9,31 +9,21 @@ import os import shutil import subprocess import threading +from typing import Optional import httpx from typeguard import typechecked from backend.ports import NINE_ROUTER_PORT from backend.apps.subscriptions.NineRouter.constants import NINE_ROUTER_V1, NINE_ROUTER_URL +from backend.apps.subscriptions.NineRouter.NineRouterProcess.helpers.forward_output import forward_output +from backend.apps.subscriptions.NineRouter.NineRouterProcess.helpers.find_9router_dir import find_9router_dir +from backend.apps.subscriptions.NineRouter.NineRouterProcess.helpers.find_node import find_node P_PROCESS: subprocess.Popen | None = None P_THIS_DIR: str = os.path.dirname(os.path.abspath(__file__)) -def _forward_output(pipe) -> None: - try: - for line in iter(pipe.readline, b""): - text = line.decode("utf-8", errors="replace").rstrip() - if text: - print(f"[9router] {text}", flush=True) - except Exception: - pass - finally: - try: - pipe.close() - except Exception: - pass - @typechecked def is_running() -> bool: @@ -44,37 +34,6 @@ def is_running() -> bool: return False -@typechecked -def _find_9router_dir() -> str | None: - """Locate the bundled 9Router directory (dev or packaged).""" - _is_packaged: bool = os.environ.get("OPENSWARM_PACKAGED") == "1" - - if _is_packaged: - _resources: str = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(P_THIS_DIR)))) - _candidate: str = os.path.join(_resources, "9router") - if os.path.isdir(_candidate): - return _candidate - else: - _backend_dir: str = os.path.dirname(os.path.dirname(os.path.dirname(P_THIS_DIR))) - _project_root: str = os.path.dirname(_backend_dir) - _candidate = os.path.join(_project_root, "9router") - if os.path.isdir(_candidate): - return _candidate - - return None - - -@typechecked -def _find_node() -> str | None: - node: str | None = shutil.which("node") - if node: - return node - electron_path: str | None = os.environ.get("OPENSWARM_ELECTRON_PATH") - if electron_path and os.path.exists(electron_path): - return electron_path - return None - - @typechecked async def ensure_running() -> None: """Start 9Router if not already running.""" @@ -99,7 +58,7 @@ async def ensure_running() -> None: else: return - _9router_dir: str | None = _find_9router_dir() + _9router_dir: Optional[str] = find_9router_dir(P_THIS_DIR) cmd: list[str] cwd: str | None env: dict[str, str] @@ -112,7 +71,7 @@ async def ensure_running() -> None: print("9Router: standalone build not found in", _9router_dir, flush=True) return - node: str | None = _find_node() + node: Optional[str] = find_node() if not node: print("9Router: Node.js not found, cannot start in packaged mode", flush=True) return @@ -175,7 +134,7 @@ async def ensure_running() -> None: stderr=subprocess.STDOUT, env=env, ) - threading.Thread(target=_forward_output, args=(P_PROCESS.stdout,), daemon=True).start() + threading.Thread(target=forward_output, args=(P_PROCESS.stdout,), daemon=True).start() timeout: int = 20 if _is_packaged else 30 for _ in range(timeout * 2): diff --git a/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_9router_dir.py b/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_9router_dir.py new file mode 100644 index 00000000..5a48b813 --- /dev/null +++ b/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_9router_dir.py @@ -0,0 +1,26 @@ +import os +from typing import Optional +from typeguard import typechecked + + +# TODO: type spec this entirely +@typechecked +def find_9router_dir( + root_dir: str, +) -> Optional[str]: + """Locate the bundled 9Router directory (dev or packaged).""" + p_is_packaged: bool = os.environ.get("OPENSWARM_PACKAGED") == "1" + + if p_is_packaged: + p_resources: str = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(root_dir)))) + p_candidate: str = os.path.join(p_resources, "9router") + if os.path.isdir(p_candidate): + return p_candidate + else: + p_backend_dir: str = os.path.dirname(os.path.dirname(os.path.dirname(root_dir))) + p_project_root: str = os.path.dirname(p_backend_dir) + p_candidate = os.path.join(p_project_root, "9router") + if os.path.isdir(p_candidate): + return p_candidate + + return None diff --git a/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_node.py b/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_node.py new file mode 100644 index 00000000..5b411414 --- /dev/null +++ b/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/find_node.py @@ -0,0 +1,15 @@ +import os +import shutil +from typing import Optional +from typeguard import typechecked + + +@typechecked +def find_node() -> Optional[str]: + node: Optional[str] = shutil.which("node") + if node: + return node + electron_path: Optional[str] = os.environ.get("OPENSWARM_ELECTRON_PATH") + if electron_path and os.path.exists(electron_path): + return electron_path + return None \ No newline at end of file diff --git a/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/forward_output.py b/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/forward_output.py new file mode 100644 index 00000000..e0eb2395 --- /dev/null +++ b/backend/apps/subscriptions/NineRouter/NineRouterProcess/helpers/forward_output.py @@ -0,0 +1,18 @@ +from typeguard import typechecked + + +# TODO: type spec this entirely +@typechecked +def forward_output(pipe) -> None: + try: + for line in iter(pipe.readline, b""): + text = line.decode("utf-8", errors="replace").rstrip() + if text: + print(f"[9router] {text}", flush=True) + except Exception: + pass + finally: + try: + pipe.close() + except Exception: + pass diff --git a/backend/apps/subscriptions/subscriptions.py b/backend/apps/subscriptions/subscriptions.py index 1ff207bb..48524c83 100644 --- a/backend/apps/subscriptions/subscriptions.py +++ b/backend/apps/subscriptions/subscriptions.py @@ -15,7 +15,7 @@ from fastapi import HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse from backend.config.Apps import SubApp -from backend.apps.subscriptions.NineRouter.NineRouterProcess import ( +from backend.apps.subscriptions.NineRouter.NineRouterProcess.NineRouterProcess import ( is_running, ensure_running, stop, ) from backend.apps.subscriptions.NineRouter.NineRouterClient import (