120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
# 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 (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:
|
||
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 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)
|