From 4e77cddc9291ef7ef879455eaaa1d0ac1bd608ca Mon Sep 17 00:00:00 2001 From: Franz Rolfsvaag Date: Sun, 10 Aug 2025 21:46:15 +0200 Subject: [PATCH] 0.3.9.2.a10 Restructured runtime environment variables passing to cogs --- bot.py | 2 +- modules/common/settings.py | 121 +++++++++++++++++++++++++++++++------ 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/bot.py b/bot.py index 3a38db3..2372200 100644 --- a/bot.py +++ b/bot.py @@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice # Version consists of: # Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update) -VERSION = "0.3.9.2.a9" +VERSION = "0.3.9.2.a10" # ---------- Env loading ---------- diff --git a/modules/common/settings.py b/modules/common/settings.py index 2bc291c..df2df78 100644 --- a/modules/common/settings.py +++ b/modules/common/settings.py @@ -1,34 +1,119 @@ # modules/common/settings.py import os +from typing import Any, Dict, Iterable, Optional + + +def _clean(s: Optional[str]) -> str: + s = (s or "").strip() + # strip accidental quotes Portainer sometimes adds + if (s.startswith('"') and s.endswith('"')) or (s.startswith("'") and s.endswith("'")): + s = s[1:-1].strip() + return s + + +def _collect_shai_env() -> Dict[str, str]: + """ + Build a {key_without_prefix_lower: cleaned_value} mapping + from all environment variables that start with SHAI_. + """ + out: Dict[str, str] = {} + for k, v in os.environ.items(): + if not k.startswith("SHAI_"): + continue + key = k[5:].lower() # SHAI_MOD_CHANNEL_ID -> mod_channel_id + out[key] = _clean(v) + return out + class ConfigView: - """Unified read: ENV first, then optional bot.config['DEFAULT'], then fallback.""" - def __init__(self, bot): - self._env = os.environ - self._default = {} - try: - self._default = getattr(bot, "config", {}).get("DEFAULT", {}) or {} - except Exception: - pass + """ + Unified config view. + - Primary: SHAI_* envs (prefix removed, lowercased keys) + - Secondary: bot.config['DEFAULT'] (if present) + - Helpers: get/int/bool/float/list + - Can mirror values back into os.environ as SHAI_* (opt-in) + """ + def __init__(self, bot=None, *, mirror_to_env: bool = False): + self._env_map = _collect_shai_env() + # Optional: also look into bot.config['DEFAULT'] as a fallback + self._default: Dict[str, Any] = {} + try: + self._default = (getattr(bot, "config", {}) or {}).get("DEFAULT", {}) or {} + except Exception: + self._default = {} + + if mirror_to_env: + # Ensure os.environ has SHAI_* for everything we know (don’t clobber existing non-empty) + for k, v in self._env_map.items(): + env_key = f"SHAI_{k.upper()}" + if not os.environ.get(env_key): + os.environ[env_key] = v + + # ---- core accessors ---- def get(self, key: str, default: str = "") -> str: - v = self._env.get(key.upper(), "") - if not v: + key = key.lower() + if key in self._env_map: + v = _clean(self._env_map[key]) + return v if v != "" else default + + # Fallback to DEFAULT mapping (ConfigParser-like or our shim) + try: v = self._default.get(key, "") - v = (v or "").strip().strip('"').strip("'") - return v if v else default + except Exception: + v = "" + v = _clean(str(v)) + return v if v != "" else default def int(self, key: str, default: int = 0) -> int: + s = self.get(key, "") try: - return int(self.get(key, "")) + return int(s) + except Exception: + return default + + def float(self, key: str, default: float = 0.0) -> float: + s = self.get(key, "") + try: + return float(s) except Exception: return default def bool(self, key: str, default: bool = False) -> bool: - v = self.get(key, "") - if not v: + s = self.get(key, "") + if s == "": return default - return v.lower() in ("1", "true", "yes", "on") + s = s.lower() + if s in ("1", "true", "yes", "on", "y", "t"): + return True + if s in ("0", "false", "no", "off", "n", "f"): + return False + return default -def cfg(bot) -> ConfigView: - return ConfigView(bot) + def list(self, key: str, default: Optional[Iterable[str]] = None, sep: str = ",") -> Iterable[str]: + s = self.get(key, "") + if s == "": + return list(default or []) + parts = [p.strip() for p in s.split(sep)] + return [p for p in parts if p] + + # expose the resolved map if you ever want to dump it for debug + def to_dict(self) -> Dict[str, str]: + d = dict(self._env_map) + # Include defaults that aren’t already in env_map + for k in getattr(self._default, "keys", lambda: [])(): + d.setdefault(k, _clean(str(self._default.get(k, "")))) + return d + + +def cfg(bot=None, *, mirror_to_env: bool = False) -> ConfigView: + """ + Usage in cogs: + r = cfg(bot) + trigger_id = r.int('trigger_channel_id', 0) + prefix = r.get('vc_name_prefix', 'Room') + + If you want to also ensure SHAI_* are present in os.environ at runtime: + r = cfg(bot, mirror_to_env=True) + """ + return ConfigView(bot, mirror_to_env=mirror_to_env)