section.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. from __future__ import annotations
  2. from collections import OrderedDict
  3. from typing import Any, Dict, List, Optional
  4. from .variable import Variable
  5. class VariableSection:
  6. """Groups variables together with shared metadata for presentation."""
  7. def __init__(self, data: dict[str, Any]) -> None:
  8. """Initialize VariableSection from a dictionary.
  9. Args:
  10. data: Dictionary containing section specification with required 'key' and 'title' keys
  11. """
  12. if not isinstance(data, dict):
  13. raise ValueError("VariableSection data must be a dictionary")
  14. if "key" not in data:
  15. raise ValueError("VariableSection data must contain 'key'")
  16. if "title" not in data:
  17. raise ValueError("VariableSection data must contain 'title'")
  18. self.key: str = data["key"]
  19. self.title: str = data["title"]
  20. self.variables: OrderedDict[str, Variable] = OrderedDict()
  21. self.description: Optional[str] = data.get("description")
  22. self.toggle: Optional[str] = data.get("toggle")
  23. # Default "general" section to required=True, all others to required=False
  24. self.required: bool = data.get("required", data["key"] == "general")
  25. # Section dependencies - can be string or list of strings
  26. needs_value = data.get("needs")
  27. if needs_value:
  28. if isinstance(needs_value, str):
  29. self.needs: List[str] = [needs_value]
  30. elif isinstance(needs_value, list):
  31. self.needs: List[str] = needs_value
  32. else:
  33. raise ValueError(f"Section '{self.key}' has invalid 'needs' value: must be string or list")
  34. else:
  35. self.needs: List[str] = []
  36. def to_dict(self) -> Dict[str, Any]:
  37. """Serialize VariableSection to a dictionary for storage."""
  38. section_dict = {
  39. 'required': self.required,
  40. 'vars': {name: var.to_dict() for name, var in self.variables.items()}
  41. }
  42. # Add optional fields if present
  43. for field in ('title', 'description', 'toggle'):
  44. if value := getattr(self, field):
  45. section_dict[field] = value
  46. # Store dependencies (single value if only one, list otherwise)
  47. if self.needs:
  48. section_dict['needs'] = self.needs[0] if len(self.needs) == 1 else self.needs
  49. return section_dict
  50. def is_enabled(self) -> bool:
  51. """Check if section is currently enabled based on toggle variable.
  52. Returns:
  53. True if section is enabled (no toggle or toggle is True), False otherwise
  54. """
  55. if not self.toggle:
  56. return True
  57. toggle_var = self.variables.get(self.toggle)
  58. if not toggle_var:
  59. return True
  60. try:
  61. return bool(toggle_var.convert(toggle_var.value))
  62. except Exception:
  63. return False
  64. def clone(self, origin_update: Optional[str] = None) -> 'VariableSection':
  65. """Create a deep copy of the section with all variables.
  66. This is more efficient than converting to dict and back when copying sections.
  67. Args:
  68. origin_update: Optional origin string to apply to all cloned variables
  69. Returns:
  70. New VariableSection instance with deep-copied variables
  71. Example:
  72. section2 = section1.clone(origin_update='template')
  73. """
  74. # Create new section with same metadata
  75. cloned = VariableSection({
  76. 'key': self.key,
  77. 'title': self.title,
  78. 'description': self.description,
  79. 'toggle': self.toggle,
  80. 'required': self.required,
  81. 'needs': self.needs.copy() if self.needs else None,
  82. })
  83. # Deep copy all variables
  84. for var_name, variable in self.variables.items():
  85. if origin_update:
  86. cloned.variables[var_name] = variable.clone(update={'origin': origin_update})
  87. else:
  88. cloned.variables[var_name] = variable.clone()
  89. return cloned