""" Settings: Konfiguration aus einer Google-Sheet-Tabelle. Spalten: Name, Type, Value (optional: Label, Wertebereich). Typen: TIA_table / csv / PRISK_table (URLs), int / float (Skalare). s = Settings.load() url = s.table_source_url("TIA") val = s["TEST_U"] """ from __future__ import annotations import csv import io from dataclasses import dataclass from typing import Any from helpers import ( http_get_body, normalize_csv_source_url, normalize_https_csv_url_or_error, parse_float, parse_int, ) from ..table.table import Table from ..TIA.table.tiaTable import TiaTable from ..PRISK.table.priskTable import PriskTable from ..SYSRISK.table.sysriskTable import SysriskTable from ..TMV.table.tmvTable import TmvTable from ..LPIX.table.lpixTable import LpixTable from ..BPIX.table.bpixTable import BpixTable from ..VPIX.table.vpixTable import VpixTable from ..LSIX.table.lsixTable import LsixTable from ..KRIX.table.krixTable import KrixTable from ..TIA_SYS.table.tiaSysTable import TiaSysTable DEFAULT_SETTINGS_SHEET_URL = ( "https://docs.google.com/spreadsheets/d/1sgVVTS2z9ndCedTLG4DN_CeQCRzHeQ1QIl6eQLKqvmc/export?format=csv&gid=0" ) _TABLE_CLS: dict[str, type] = { "tia_table": TiaTable, "prisk_table": PriskTable, "sysrisk_table": SysriskTable, "tmv_table": TmvTable, "lpix_table": LpixTable, "bpix_table": BpixTable, "vpix_table": VpixTable, "lsix_table": LsixTable, "krix_table": KrixTable, "tia_sys_table": TiaSysTable, } _DEFAULT_TABLE_CLS = Table @dataclass(frozen=True) class SettingMeta: """Eine Zeile aus der Settings-Tabelle.""" name: str type: str label: str value_raw: str class Settings: """Einstellungen aus der Settings-CSV. Tabellen werden lazy geladen.""" def __init__( self, *, meta: dict[str, SettingMeta], scalars: dict[str, int | float | None], table_specs: dict[str, tuple[str, str]], ) -> None: self._meta = meta self._scalars = scalars self._table_specs = table_specs self._table_cache: dict[str, Any] = {} # ------------------------------------------------------------------ # Fabrik # ------------------------------------------------------------------ @classmethod def load(cls, url: str | None = None) -> Settings: raw_url = (url or DEFAULT_SETTINGS_SHEET_URL).strip() csv_url, err = normalize_https_csv_url_or_error(raw_url) if err: raise ValueError(err) raw = http_get_body(csv_url) if not raw or not raw.strip(): raise ValueError("Settings-Tabelle konnte nicht geladen werden.") return cls._from_csv(raw) @classmethod def _from_csv(cls, text: str) -> Settings: if text.startswith("\ufeff"): text = text[1:] reader = csv.DictReader(io.StringIO(text)) if not reader.fieldnames: return cls(meta={}, scalars={}, table_specs={}) col = {f.strip().casefold(): f for f in reader.fieldnames} h_name, h_type = col.get("name"), col.get("type") h_label, h_value = col.get("label"), col.get("value") if not h_name or not h_type: raise ValueError('CSV erfordert Spalten "Name" und "Type".') def val(row: dict[str, str], key: str | None) -> str: if not key: return "" return (row.get(key) or "").strip() meta: dict[str, SettingMeta] = {} scalars: dict[str, int | float | None] = {} table_specs: dict[str, tuple[str, str]] = {} for row in reader: name = val(row, h_name) if not name: continue typ = val(row, h_type).casefold() label = val(row, h_label) v = val(row, h_value) meta[name] = SettingMeta(name=name, type=typ, label=label, value_raw=v) if typ.endswith("_table") or typ == "csv": if v.startswith("http"): table_specs[name] = (typ, normalize_csv_source_url(v)) elif typ == "int": scalars[name] = parse_int(v) elif typ == "float": scalars[name] = parse_float(v) return cls(meta=meta, scalars=scalars, table_specs=table_specs) # ------------------------------------------------------------------ # Zugriff # ------------------------------------------------------------------ def table_source_url(self, name: str) -> str: """URL einer Tabellen-Settings-Zeile (z.B. 'TIA').""" if name not in self._table_specs: raise KeyError(name) return self._table_specs[name][1] def __getitem__(self, name: str) -> Any: if name in self._scalars: return self._scalars[name] if name in self._table_cache: return self._table_cache[name] if name in self._table_specs: typ, url = self._table_specs[name] cls = _TABLE_CLS.get(typ, _DEFAULT_TABLE_CLS) obj = cls.get(url) self._table_cache[name] = obj return obj raise KeyError(name) def get(self, name: str, default: Any = None) -> Any: if name not in self._meta: return default return self[name] def preload(self) -> dict[str, str]: """Alle Tabellen aus den Settings vorladen und im Cache halten.""" result: dict[str, str] = {} for name in self._table_specs: try: self[name] result[name] = "ok" except Exception as e: result[name] = str(e) return result def getData(self) -> list[dict]: """Alle Settings als Tabelle (Name, Type, Label, Value).""" return [ {"Name": m.name, "Type": m.type, "Label": m.label, "Value": m.value_raw} for m in self._meta.values() ]