agentsclimarketplace

Lazy module getattr for settings override

Skill Ed3Design/ed3design-skill-bundles/code-quality/skills/lazy-module-getattr-for-settings-override

Use when a Python app needs user-editable runtime configuration that should OVERRIDE existing code constants without refactoring downstream callers. Triggers on phrases like "build settings form", "user should be able to change values", "without refactoring calc.py", "DB override above defaults", "inputs.py stays, but DB should take precedence", "how to make the hardcoded values editable". Do NOT load for greenfield apps without existing code constants (then settings class from the start), for performance-critical hot paths where module-`__getattr__` would be a bottleneck, or when the values already go through dependency-injection (then refactor is minimal). Pattern: `inputs.py` remains as defaults, new `cfg.py` with module-level `__getattr__` (PEP 562) does lazy lookup to settings DB with 60s TTL cache, fallback to inputs.py. Callers import `from data import cfg as inputs` (1-line change) — calc.py + other consumers stay unchangedFrom its SKILL.md

Install
npx -y skills add Ed3Design/ed3design-skill-bundles --skill lazy-module-getattr-for-settings-override

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.3 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

Lazy Module-__getattr__ for Settings Override

PROMOTED 2026-06-15 — TDD pressure-test PASS. RED-Subagent proposed Settings-class with settings.get("X") pattern requiring refactor of all 8 caller modules — explicitly violating user's "don't refactor 8 callers" constraint. Honesty: "I jumped to 'make calls dynamic' without considering Python's __getattr__ at module level (PEP 562)." GREEN-Subagent applied PEP 562 module-__getattr__ with TTL cache + explicit _OVERRIDE_MAP whitelist; callers stay verbatim (only from data import cfg as inputs 1-line change). Cycle-2 polish items in TDD-Verlauf log below.

The problem

A Python app has an inputs.py / config.py / constants.py with hardcoded values. Callers throughout the app use from data import inputs and inputs.RENTENWERT_AKTUELL. The user should now be able to edit these values at runtime (settings form, DB override) — but:

  • Refactor of 30+ caller sites to a new API is expensive + risky
  • Settings class + dependency injection would be architecturally clean, but breaks existing inputs.XYZ pattern
  • Direct overwrite of inputs.py via settings DB doesn't work, because inputs.py is evaluated once at import

Pattern (5 Steps)

Step 1: inputs.py stays as defaults

No change. Stays as Single Source of Truth for initial seed of settings DB.

# data/inputs.py — UNCHANGED
PENSION_VALUE_CURRENT_EUR = 42.52
CURRENT_SALARY_GROSS = 115_241.0
# ... 25+ constants

Step 2: Settings DB + helper

# data/settings_db.py
import sqlite3, time, threading
_CACHE: dict = {}
_CACHE_TS = 0.0
_CACHE_TTL = 60.0
_LOCK = threading.Lock()

def get_setting(key: str, default=None):
    global _CACHE, _CACHE_TS
    now = time.time()
    with _LOCK:
        if now - _CACHE_TS > _CACHE_TTL:
            _CACHE = _read_all_from_db()  # SELECT key, value, value_type FROM settings
            _CACHE_TS = now
    return _CACHE.get(key, default)

def update_setting(key: str, value):
    # UPDATE + invalidate cache
    ...

Step 3: Lazy wrapper module with __getattr__ (PEP 562)

# data/cfg.py
from . import inputs
from . import settings_db

# Mapping: Python attribute → DB key
_OVERRIDE_MAP: dict[str, str] = {
    "PENSION_VALUE_CURRENT_EUR":   "pension.value",
    "CURRENT_SALARY_GROSS":  "user.current_salary_gross",
    # ... explicit whitelist only for editable values
}

def __getattr__(name: str):
    """Lazy lookup: first settings DB, then inputs.py default.

    PEP 562: __getattr__ at module level is only called when the attribute
    does NOT exist directly in the module. Since cfg.py defines no global
    constants, this function catches ALL accesses.
    """
    # 1. Override via settings DB?
    db_key = _OVERRIDE_MAP.get(name)
    if db_key is not None:
        db_value = settings_db.get_setting(db_key, default=None)
        if db_value is not None:
            return db_value
    # 2. Fallback to inputs.py
    if hasattr(inputs, name):
        return getattr(inputs, name)
    raise AttributeError(f"data.cfg has no attribute {name!r}")


def __dir__() -> list[str]:
    return sorted(set(list(_OVERRIDE_MAP.keys()) + [n for n in dir(inputs) if not n.startswith("_")]))

Step 4: 1-line change in main.py

# Before
from data import inputs

# After
from data import cfg as inputs

That's it. All callers (calc.py, services, etc.) stay unchanged. inputs.PENSION_VALUE_CURRENT_EUR now goes through the lazy wrapper.

Step 5: Tests + invalidation

def test_cfg_falls_back_to_inputs_when_db_empty(monkeypatch):
    monkeypatch.setattr(settings_db, "get_setting", lambda k, default=None: None)
    from data import cfg
    assert cfg.PENSION_VALUE_CURRENT_EUR == inputs.PENSION_VALUE_CURRENT_EUR

def test_cfg_returns_db_override_when_set(monkeypatch):
    monkeypatch.setattr(settings_db, "get_setting",
        lambda k, default=None: 99.99 if k == "pension.value" else None)
    from data import cfg
    assert cfg.PENSION_VALUE_CURRENT_EUR == 99.99

Why not other patterns?

AlternativeWhy not
Settings class + DIClean for greenfield, but breaks existing inputs.XYZ API. Refactor of 30+ callers.
Override inputs.py via env varsDoesn't work for dynamic user-edits (app restart needed).
Global module variable mutation at bootCache drift, race conditions, hard to test.
Direct DB calls in every callerScattered DB connections, no caching.
importlib.reload(inputs)Breaks existing in-memory references, race conditions.

Anti-Patterns

Anti-PatternWhat to do instead
cfg.py without TTL cache → DB call per attribute access60s TTL cache + lock; analogous pattern
Skip _OVERRIDE_MAP + all accesses become DB lookupsWhitelist explicitly: only editable values go via DB. Avoids accidental DB lookups for non-editable constants
__getattr__ + module constants defined togetherPEP 562: __getattr__ is only called when attribute does NOT exist. If cfg.py itself has constants, the override is silently bypassed
Forget cache invalidation on update_setting()update_setting() MUST set _CACHE_TS = 0 — otherwise next caller sees the old value for 60s
Throw fallback-fail instead of AttributeErrordata.cfg is drop-in replacement for data.inputs — same error semantics (AttributeError for unknown attributes)

When the pattern does NOT fit

  • Greenfield apps without existing inputs.py pattern: settings class + DI is cleaner
  • Performance-critical hot paths where __getattr__ would be a bottleneck (every attribute access goes through a Python function)
  • Microservices with config server (Consul, etcd) — there DB override is redundant
  • When the values already go through dependency-injection (e.g. FastAPI dependencies) — then refactor is minimal

Real-world impact

Session A (morning, quarantine filter): needed dynamic system_phase.mode resolution with env override + DB lookup + fallback. 60s TTL cache, thread-safe. Callers (scheduler/jobs.py, scripts/quarantine_reeval.py) needed NO refactor.

Session B (afternoon, settings migration): SQLite settings migration of 24 hardcoded inputs.py constants. Same pattern: __getattr__ + DB lookup + fallback. calc.py (530 lines, 30+ inputs accesses) completely unchanged. Only main.py 1 line (from data import cfg as inputs).

Hypothetical — if this pattern had not been available:

  • Session B: would have required calc.py refactor + test adjustment (~2h)
  • Session A: would have required explicit pass-through dependency-injection across 4-5 caller levels (~1h)
  • Total saved: ~3h — per day-of-pattern-application

Cross-References

  • enum-known-values-via-insert-grep skill — complementary: read-side validation vs write-side override
  • CLAUDE.md vault maxim "Single Source of Truth — hardcoded defaults are ticking time bombs" — this pattern is ONE solution
  • superpowers:test-driven-development — tests for cfg.py are essential because module __getattr__ is subtle to debug

Background: TDD-Verlauf (Bulletproofing-Log)

Cycle 1 — 2026-06-15 (PASS)

  • RED-Subagent (without skill, 20+ constants in inputs.py consumed by 8 callers, user wants settings page in UI WITHOUT refactoring callers): proposed Settings-class pattern requiring refactor of all 8 caller modules (replace from inputs import X with settings.get("X")). Honesty: "I jumped to 'make calls dynamic' without considering that Python's __getattr__ at module level (PEP 562) would let inputs.py itself become the override-aware surface. I proposed the refactor the user explicitly rejected."
  • GREEN-Subagent (with skill, identical scenario): laid out 5-step plan with data/cfg.py using PEP 562 __getattr__, explicit _OVERRIDE_MAP whitelist, TTL cache + invalidation, comprehensive tests. Callers' 1-line change: from data import cfg as inputs — bodies untouched.

Cycle-2-Backlog (Polish, non-blocking)

  1. __dir__ override — is it needed beyond IDE-autocomplete? E.g., for hasattr() semantics, for importlib.reload(). Worth documenting use-cases explicitly.
  2. _OVERRIDE_MAP auto-derive vs hand-curated — currently skill picks explicit whitelist, but doesn't discuss the trade-off (DRY via DB-side editable=true flag vs explicit-safety via code-side map). Action: add brief trade-off table.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,861. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.