#!/usr/bin/env python3 """Shared utilities for Settled Reach asset/connector scripts. Provides `ensure_venv`, used by the asset connectors (audio_connector, audio_batch, image_connector, trellis_connector) to re-exec into the project .venv before their third-party imports, plus the connector config helpers (`load_config`, `get_base_url`, `get_api_key`) that were formerly copy-pasted across the connectors (T-1067 rider, S-25). The former settledreach.db connection helpers were removed when the ticket/decision tooling was retired (pql migration Phase 6); planning now lives in pql (`.pql/`). """ import json import os import sys from pathlib import Path # --------------------------------------------------------------------------- # Paths # --------------------------------------------------------------------------- SCRIPT_DIR = Path(__file__).resolve().parent WORKTREE_ROOT = (SCRIPT_DIR / ".." / "..").resolve() CONFIG_PATH = SCRIPT_DIR / "config.json" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def ensure_venv() -> None: """Re-exec into the project .venv Python if not already running there. Call this at the top of any script that uses third-party packages, before those imports. Usage:: from common import ensure_venv ensure_venv() import numpy as np # third-party import follows """ venv_python = WORKTREE_ROOT / ".venv" / "bin" / "python" # Already running inside the venv — nothing to do. if Path(sys.executable).resolve() == venv_python.resolve(): return # .venv not set up yet — fail with a helpful message. if not venv_python.exists(): print( "error: .venv not found — run `make setup-venv` first.", file=sys.stderr, ) sys.exit(1) # Re-exec into the venv Python, preserving all arguments. os.execv(str(venv_python), [str(venv_python)] + sys.argv) def load_config() -> dict: """Load the shared endpoint configuration from tooling/db/config.json.""" with open(CONFIG_PATH) as f: return json.load(f) def get_base_url(key: str, default: str) -> str: """Resolve a service base URL from config.json, with a fallback default. Usage:: base = get_base_url("trellis_url", "http://tower-of-joy:11510") """ return load_config().get(key, default) def get_api_key(env_var: str, config_key: str) -> str: """Get an API key from the environment or config.json. Checks the ``env_var`` environment variable first, then ``config_key`` in config.json. Prints a JSON error and exits 1 if neither is set — connector scripts emit machine-readable JSON on all paths. """ key = os.environ.get(env_var) if key: return key try: with open(CONFIG_PATH) as f: config = json.load(f) return config.get(config_key, "") except Exception: pass print(json.dumps({ "ok": False, "error": f"No {env_var} found in environment or config.json" }, indent=2)) sys.exit(1)