[Haik]: classes now extend basemodel and private vars properly dont start with an underscore

This commit is contained in:
haikdc
2026-04-05 21:17:03 -07:00
parent 2dec5b6283
commit e5ba3412f2
3 changed files with 54 additions and 55 deletions
@@ -8,66 +8,65 @@ ensure-task, HTTP client) lives in one place.
import asyncio
from typing import ClassVar, Optional
from pydantic import BaseModel, Field
from typeguard import typechecked
from backend.apps.subscriptions.NineRouter.helpers.NineRouterProcess.NineRouterProcess import NineRouterProcess
from backend.apps.subscriptions.NineRouter.helpers.NineRouterClient.NineRouterClient import NineRouterClient
class NineRouter:
_instance: ClassVar[Optional["NineRouter"]] = None
def __init__(self) -> None:
self._process: NineRouterProcess = NineRouterProcess()
self._client: NineRouterClient = NineRouterClient()
self._ensure_task: Optional[asyncio.Task] = None
class NineRouter(BaseModel):
p_instance: ClassVar[Optional["NineRouter"]] = None
p_process: NineRouterProcess = Field(default_factory=NineRouterProcess)
p_client: NineRouterClient = Field(default_factory=NineRouterClient)
p_ensure_task: Optional[asyncio.Task] = None
@classmethod
def get(cls) -> "NineRouter":
if cls._instance is None:
cls._instance = cls()
return cls._instance
if cls.p_instance is None:
cls.p_instance = cls()
return cls.p_instance
# -- lifecycle -------------------------------------------------------------
@typechecked
def is_running(self) -> bool:
return self._process.is_running()
return self.p_process.is_running()
@typechecked
async def ensure_running(self) -> None:
await self._process.ensure_running()
await self.p_process.ensure_running()
@typechecked
async def stop(self) -> None:
self.cancel_ensure_task()
await self._client.aclose()
self._process.stop()
await self.p_client.aclose()
self.p_process.stop()
@typechecked
async def ensure_running_background(self) -> None:
"""Kick off ensure_running as a background task if not already in flight."""
if self._ensure_task is None or self._ensure_task.done():
self._ensure_task = asyncio.create_task(self._process.ensure_running())
if self.p_ensure_task is None or self.p_ensure_task.done():
self.p_ensure_task = asyncio.create_task(self.p_process.ensure_running())
@typechecked
def cancel_ensure_task(self) -> None:
if self._ensure_task and not self._ensure_task.done():
self._ensure_task.cancel()
if self.p_ensure_task and not self.p_ensure_task.done():
self.p_ensure_task.cancel()
# -- client ----------------------------------------------------------------
@typechecked
async def get_providers(self) -> list[dict] | dict:
return await self._client.get_providers()
return await self.p_client.get_providers()
@typechecked
async def get_models(self) -> list[dict]:
return await self._client.get_models()
return await self.p_client.get_models()
@typechecked
async def start_oauth(self, provider: str) -> dict:
return await self._client.start_oauth(provider)
return await self.p_client.start_oauth(provider)
@typechecked
async def poll_oauth(
@@ -77,7 +76,7 @@ class NineRouter:
code_verifier: str | None = None,
extra_data: dict | None = None,
) -> dict:
return await self._client.poll_oauth(provider, device_code, code_verifier=code_verifier, extra_data=extra_data)
return await self.p_client.poll_oauth(provider, device_code, code_verifier=code_verifier, extra_data=extra_data)
@typechecked
async def exchange_oauth(
@@ -88,8 +87,8 @@ class NineRouter:
code_verifier: str,
state: str = "",
) -> dict:
return await self._client.exchange_oauth(provider, code, redirect_uri, code_verifier, state)
return await self.p_client.exchange_oauth(provider, code, redirect_uri, code_verifier, state)
@typechecked
async def disconnect_provider(self, provider_id: str) -> bool:
return await self._client.disconnect_provider(provider_id)
return await self.p_client.disconnect_provider(provider_id)
@@ -1,23 +1,23 @@
"""HTTP client for 9Router's REST API."""
import httpx
from pydantic import Field, BaseModel
from typeguard import typechecked
from backend.apps.subscriptions.NineRouter.helpers.constants import NINE_ROUTER_API, NINE_ROUTER_V1
from backend.ports import NINE_ROUTER_PORT
class NineRouterClient:
def __init__(self) -> None:
self._http: httpx.AsyncClient = httpx.AsyncClient(timeout=15.0)
class NineRouterClient(BaseModel):
p_http: httpx.AsyncClient = Field(default_factory=httpx.AsyncClient(timeout=15.0))
async def aclose(self) -> None:
await self._http.aclose()
await self.p_http.aclose()
@typechecked
async def get_providers(self) -> list[dict] | dict:
try:
r = await self._http.get(f"{NINE_ROUTER_API}/providers", timeout=5.0)
r = await self.p_http.get(f"{NINE_ROUTER_API}/providers", timeout=5.0)
if r.status_code == 200:
return r.json()
except Exception as e:
@@ -32,7 +32,7 @@ class NineRouterClient:
authorization_code providers: returns {authUrl, codeVerifier, state}
"""
try:
r = await self._http.get(f"{NINE_ROUTER_API}/oauth/{provider}/device-code")
r = await self.p_http.get(f"{NINE_ROUTER_API}/oauth/{provider}/device-code")
if r.status_code == 200:
data: dict = r.json()
return {
@@ -47,7 +47,7 @@ class NineRouterClient:
pass
callback_url: str = f"http://localhost:{NINE_ROUTER_PORT}/callback"
r = await self._http.get(
r = await self.p_http.get(
f"{NINE_ROUTER_API}/oauth/{provider}/authorize",
params={"redirect_uri": callback_url},
)
@@ -75,7 +75,7 @@ class NineRouterClient:
if extra_data:
body["extraData"] = extra_data
r = await self._http.post(f"{NINE_ROUTER_API}/oauth/{provider}/poll", json=body)
r = await self.p_http.post(f"{NINE_ROUTER_API}/oauth/{provider}/poll", json=body)
r.raise_for_status()
return r.json()
@@ -95,7 +95,7 @@ class NineRouterClient:
"state": state,
}
print(f"exchange_oauth: provider={provider} redirect_uri={redirect_uri}")
r = await self._http.post(f"{NINE_ROUTER_API}/oauth/{provider}/exchange", json=payload)
r = await self.p_http.post(f"{NINE_ROUTER_API}/oauth/{provider}/exchange", json=payload)
print(f"exchange_oauth: status={r.status_code}")
r.raise_for_status()
return r.json()
@@ -103,7 +103,7 @@ class NineRouterClient:
@typechecked
async def get_models(self) -> list[dict]:
try:
r = await self._http.get(f"{NINE_ROUTER_V1}/models", timeout=5.0)
r = await self.p_http.get(f"{NINE_ROUTER_V1}/models", timeout=5.0)
if r.status_code == 200:
data: dict = r.json()
models: list = data.get("data", [])
@@ -122,5 +122,5 @@ class NineRouterClient:
@typechecked
async def disconnect_provider(self, provider_id: str) -> bool:
r = await self._http.delete(f"{NINE_ROUTER_API}/providers/{provider_id}", timeout=10.0)
r = await self.p_http.delete(f"{NINE_ROUTER_API}/providers/{provider_id}", timeout=10.0)
return r.status_code == 200
@@ -12,6 +12,7 @@ import threading
from typing import Optional
import httpx
from pydantic import Field, BaseModel
from typeguard import typechecked
from backend.ports import NINE_ROUTER_PORT
@@ -20,12 +21,11 @@ from backend.apps.subscriptions.NineRouter.helpers.NineRouterProcess.helpers.for
from backend.apps.subscriptions.NineRouter.helpers.NineRouterProcess.helpers.find_9router_dir import find_9router_dir
from backend.apps.subscriptions.NineRouter.helpers.NineRouterProcess.helpers.find_node import find_node
_THIS_DIR: str = os.path.dirname(os.path.abspath(__file__))
P_THIS_DIR: str = os.path.dirname(os.path.abspath(__file__))
class NineRouterProcess:
def __init__(self) -> None:
self._process: Optional[subprocess.Popen] = None
class NineRouterProcess(BaseModel):
p_process: Optional[subprocess.Popen] = Field(default=None)
@typechecked
def is_running(self) -> bool:
@@ -58,17 +58,17 @@ class NineRouterProcess:
else:
return
_9router_dir: Optional[str] = find_9router_dir(_THIS_DIR)
p_9router_dir: Optional[str] = find_9router_dir(P_THIS_DIR)
cmd: list[str]
cwd: str | None
env: dict[str, str]
if _is_packaged and _9router_dir:
standalone_server: str = os.path.join(_9router_dir, "server.js")
if _is_packaged and p_9router_dir:
standalone_server: str = os.path.join(p_9router_dir, "server.js")
if not os.path.exists(standalone_server):
standalone_server = os.path.join(_9router_dir, ".next", "standalone", "server.js")
standalone_server = os.path.join(p_9router_dir, ".next", "standalone", "server.js")
if not os.path.exists(standalone_server):
print("9Router: standalone build not found in", _9router_dir, flush=True)
print("9Router: standalone build not found in", p_9router_dir, flush=True)
return
node: Optional[str] = find_node()
@@ -88,24 +88,24 @@ class NineRouterProcess:
if node == os.environ.get("OPENSWARM_ELECTRON_PATH"):
env["ELECTRON_RUN_AS_NODE"] = "1"
elif _9router_dir:
elif p_9router_dir:
npx: str | None = shutil.which("npx")
if not npx:
print("9Router: npx not found, cannot auto-start", flush=True)
return
if not os.path.isdir(os.path.join(_9router_dir, "node_modules")):
if not os.path.isdir(os.path.join(p_9router_dir, "node_modules")):
print("9Router: installing dependencies...", flush=True)
npm: str | None = shutil.which("npm")
if npm:
subprocess.run(
[npm, "install"], cwd=_9router_dir,
[npm, "install"], cwd=p_9router_dir,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=120,
)
print(f"9Router: starting (dev) on port {NINE_ROUTER_PORT}...", flush=True)
cmd = [npx, "next", "dev", "--webpack", "-p", str(NINE_ROUTER_PORT)]
cwd = _9router_dir
cwd = p_9router_dir
env = {
**os.environ,
"PORT": str(NINE_ROUTER_PORT),
@@ -127,14 +127,14 @@ class NineRouterProcess:
}
try:
self._process = subprocess.Popen(
self.p_process = subprocess.Popen(
cmd, cwd=cwd,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
)
threading.Thread(target=forward_output, args=(self._process.stdout,), daemon=True).start()
threading.Thread(target=forward_output, args=(self.p_process.stdout,), daemon=True).start()
timeout: int = 20 if _is_packaged else 30
for _ in range(timeout * 2):
@@ -149,14 +149,14 @@ class NineRouterProcess:
@typechecked
def stop(self) -> None:
if self._process:
if self.p_process:
try:
self._process.terminate()
self._process.wait(timeout=5)
self.p_process.terminate()
self.p_process.wait(timeout=5)
except Exception:
try:
self._process.kill()
self.p_process.kill()
except Exception:
pass
self._process = None
self.p_process = None
print("9Router stopped")