section.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. # Track which fields were explicitly provided (to support explicit clears)
  24. self._explicit_fields: set[str] = set(data.keys())
  25. # Default "general" section to required=True, all others to required=False
  26. self.required: bool = data.get("required", data["key"] == "general")
  27. # Section dependencies - can be string or list of strings
  28. needs_value = data.get("needs")
  29. if needs_value:
  30. if isinstance(needs_value, str):
  31. self.needs: List[str] = [needs_value]
  32. elif isinstance(needs_value, list):
  33. self.needs: List[str] = needs_value
  34. else:
  35. raise ValueError(
  36. f"Section '{self.key}' has invalid 'needs' value: must be string or list"
  37. )
  38. else:
  39. self.needs: List[str] = []
  40. def to_dict(self) -> Dict[str, Any]:
  41. """Serialize VariableSection to a dictionary for storage."""
  42. section_dict = {
  43. "required": self.required,
  44. "vars": {name: var.to_dict() for name, var in self.variables.items()},
  45. }
  46. # Add optional fields if present
  47. for field in ("title", "description", "toggle"):
  48. if value := getattr(self, field):
  49. section_dict[field] = value
  50. # Store dependencies (single value if only one, list otherwise)
  51. if self.needs:
  52. section_dict["needs"] = (
  53. self.needs[0] if len(self.needs) == 1 else self.needs
  54. )
  55. return section_dict
  56. def is_enabled(self) -> bool:
  57. """Check if section is currently enabled based on toggle variable.
  58. Returns:
  59. True if section is enabled (no toggle or toggle is True), False otherwise
  60. """
  61. if not self.toggle:
  62. return True
  63. toggle_var = self.variables.get(self.toggle)
  64. if not toggle_var:
  65. return True
  66. try:
  67. return bool(toggle_var.convert(toggle_var.value))
  68. except Exception:
  69. return False
  70. def clone(self, origin_update: Optional[str] = None) -> "VariableSection":
  71. """Create a deep copy of the section with all variables.
  72. This is more efficient than converting to dict and back when copying sections.
  73. Args:
  74. origin_update: Optional origin string to apply to all cloned variables
  75. Returns:
  76. New VariableSection instance with deep-copied variables
  77. Example:
  78. section2 = section1.clone(origin_update='template')
  79. """
  80. # Create new section with same metadata
  81. cloned = VariableSection(
  82. {
  83. "key": self.key,
  84. "title": self.title,
  85. "description": self.description,
  86. "toggle": self.toggle,
  87. "required": self.required,
  88. "needs": self.needs.copy() if self.needs else None,
  89. }
  90. )
  91. # Deep copy all variables
  92. for var_name, variable in self.variables.items():
  93. if origin_update:
  94. cloned.variables[var_name] = variable.clone(
  95. update={"origin": origin_update}
  96. )
  97. else:
  98. cloned.variables[var_name] = variable.clone()
  99. return cloned