shaiwatcher/modules/common/settings.py
Franz Rolfsvaag 4e77cddc92 0.3.9.2.a10
Restructured runtime environment variables passing to cogs
2025-08-10 21:46:15 +02:00

120 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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 (dont 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:
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, "")
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(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:
s = self.get(key, "")
if s == "":
return default
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 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 arent 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)