diff --git a/src/app_controller.py b/src/app_controller.py index 7df1276a..62944e66 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -1188,6 +1188,30 @@ class AppController: #endregion: Configuration Map self._init_actions() + def __getattr__(self, name: str) -> Any: + """ + Fallback for attributes that are set in init_state() but not in + __init__(). Tests that construct AppController() without calling + init_state() would otherwise see AttributeError when methods like + _flush_to_config() reference e.g. self.ui_separate_task_dag. + + Returns None for any missing attribute. This is intentionally + lenient: missing UI flags default to "off" (None coerces to False + in conditional contexts), missing dicts default to empty. The + next legitimate access (e.g., init_state) will set the real value. + + Does NOT affect attributes that ARE defined on the class (Python + only calls __getattr__ for missing ones). + """ + # Avoid infinite recursion for dunder/sunder names (e.g. during + # pickling, copy, etc.) by returning AttributeError. + if name.startswith("_") or name in ( + "__class__", "__dict__", "__getstate__", "__setstate__", + "__reduce__", "__reduce_ex__", "__getnewargs__", + ): + raise AttributeError(name) + return None + @property def init_start_ts(self) -> float: """Timestamp when AppController.__init__ started (cold-start entry). [SDM: src/app_controller.py:init_start_ts]"""