Source code for pyqt_reactive.services.flag_context_manager

"""
Metaprogrammed context manager factory for boolean flag management.

This module provides a universal context manager for managing temporary boolean flags
on objects, following the OpenHCS pattern from config_framework/context_manager.py.

Key features:
1. Single implementation handles all flag patterns
2. Automatic save/restore of previous values
3. Enum-based validation for type safety
4. Composable and nestable
5. Guaranteed cleanup even on exception

Pattern:
    Instead of:
        self._in_reset = True
        try:
            # ... logic
        finally:
            self._in_reset = False
    
    Use:
        with FlagContextManager.manage_flags(self, _in_reset=True):
            # ... logic

This eliminates duplicate try/finally patterns and ensures flags are always restored.
"""

from contextlib import contextmanager
from enum import Enum
from typing import Any, Dict, Set
import logging

logger = logging.getLogger(__name__)


[docs] class ManagerFlag(Enum): """ Registry of valid ParameterFormManager flags. This enum serves as: 1. Documentation of all available flags 2. Validation for FlagContextManager 3. Type-safe flag references Add new flags here as they're introduced to the codebase. """ IN_RESET = '_in_reset' BLOCK_CROSS_WINDOW = '_block_cross_window_updates' INITIAL_LOAD_COMPLETE = '_initial_load_complete'
[docs] class FlagContextManager: """ Metaprogrammed context manager factory for boolean flag management. This class provides a universal context manager that can manage any combination of boolean flags on an object, with automatic save/restore and validation. Examples: # Single flag: with FlagContextManager.manage_flags(self, _in_reset=True): self._reset_parameter_impl(param_name) # Multiple flags: with FlagContextManager.manage_flags(self, _in_reset=True, _block_cross_window_updates=True): for param_name in param_names: self._reset_parameter_impl(param_name) # Convenience method for reset: with FlagContextManager.reset_context(self): # ... reset logic """ # Registry of valid flags (extracted from enum) VALID_FLAGS: Set[str] = {flag.value for flag in ManagerFlag}
[docs] @staticmethod @contextmanager def manage_flags(obj: Any, **flags: bool): """ Context manager that sets flags on entry and restores previous values on exit. This is the core metaprogrammed context manager that handles all flag patterns. It saves previous values, sets new values, and guarantees restoration even on exception. Args: obj: Object to set flags on (typically ParameterFormManager instance) **flags: Flag names and values to set (e.g., _in_reset=True) Raises: ValueError: If any flag name is not in VALID_FLAGS registry Example: with FlagContextManager.manage_flags(self, _in_reset=True, _block_cross_window_updates=True): # Both flags are True here # ... logic # Both flags restored to previous values here """ # Validate flag names against registry invalid_flags = set(flags.keys()) - FlagContextManager.VALID_FLAGS if invalid_flags: raise ValueError( f"Invalid flags: {invalid_flags}. " f"Valid flags: {FlagContextManager.VALID_FLAGS}. " f"Add new flags to ManagerFlag enum." ) # Save previous values # ANTI-DUCK-TYPING: Use direct attribute access (fail-loud if flag doesn't exist) # All flags must be initialized in ParameterFormManager.__init__ prev_values: Dict[str, bool] = {} for flag_name in flags: prev_values[flag_name] = getattr(obj, flag_name) # No default - fail if missing logger.debug(f"Saving flag {flag_name}={prev_values[flag_name]} on {type(obj).__name__}") # Set new values for flag_name, flag_value in flags.items(): setattr(obj, flag_name, flag_value) logger.debug(f"Setting flag {flag_name}={flag_value} on {type(obj).__name__}") try: yield finally: # Restore previous values (guaranteed even on exception) for flag_name, prev_value in prev_values.items(): setattr(obj, flag_name, prev_value) logger.debug(f"Restoring flag {flag_name}={prev_value} on {type(obj).__name__}")
[docs] @staticmethod @contextmanager def reset_context(obj: Any, block_cross_window: bool = True): """ Convenience context manager for reset operations. This is a specialized version of manage_flags() for the common reset pattern. It sets _in_reset=True and optionally _block_cross_window_updates=True. Args: obj: Object to set flags on (typically ParameterFormManager instance) block_cross_window: If True, also block cross-window updates (default: True) Example: # Single parameter reset (don't block cross-window): with FlagContextManager.reset_context(self, block_cross_window=False): self._reset_parameter_impl(param_name) # Batch reset (block cross-window): with FlagContextManager.reset_context(self): for param_name in param_names: self._reset_parameter_impl(param_name) """ flags = {ManagerFlag.IN_RESET.value: True} if block_cross_window: flags[ManagerFlag.BLOCK_CROSS_WINDOW.value] = True with FlagContextManager.manage_flags(obj, **flags): yield
[docs] @staticmethod @contextmanager def initial_load_context(obj: Any): """ Convenience context manager for initial form load. Sets _initial_load_complete=False during form building, then sets it to True on exit. This disables live updates during initial form construction. Args: obj: Object to set flags on (typically ParameterFormManager instance) Example: with FlagContextManager.initial_load_context(self): self.build_form() # _initial_load_complete is now True """ # Set flag to False during load # ANTI-DUCK-TYPING: Use direct attribute access (fail-loud if flag doesn't exist) prev_value = getattr(obj, ManagerFlag.INITIAL_LOAD_COMPLETE.value) # No default - fail if missing setattr(obj, ManagerFlag.INITIAL_LOAD_COMPLETE.value, False) try: yield finally: # Set to True on exit (load complete) setattr(obj, ManagerFlag.INITIAL_LOAD_COMPLETE.value, True)
[docs] @staticmethod def is_flag_set(obj: Any, flag: ManagerFlag) -> bool: """ Check if a flag is currently set to True. Args: obj: Object to check flag on flag: ManagerFlag enum value Returns: True if flag is set, False otherwise Example: if FlagContextManager.is_flag_set(self, ManagerFlag.IN_RESET): return # Skip expensive operation during reset """ # ANTI-DUCK-TYPING: Use direct attribute access (fail-loud if flag doesn't exist) return getattr(obj, flag.value) # No default - fail if missing
[docs] @staticmethod def get_flag_state(obj: Any) -> Dict[str, bool]: """ Get current state of all registered flags. Useful for debugging and logging. Args: obj: Object to get flag state from Returns: Dict mapping flag names to their current values Example: state = FlagContextManager.get_flag_state(self) logger.debug(f"Flag state: {state}") """ # ANTI-DUCK-TYPING: Use direct attribute access (fail-loud if flag doesn't exist) return { flag.value: getattr(obj, flag.value) # No default - fail if missing for flag in ManagerFlag }