Coverage for openhcs/ui/shared/pattern_data_manager.py: 17.2%
80 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
1"""
2Pattern Data Manager - Pure data operations for function patterns.
4This service handles pattern data structure operations and transformations
5with order determinism and immutable operations.
7Framework-agnostic - can be used by any UI framework (PyQt, Textual, etc.).
8"""
10import copy
11from typing import Union, List, Dict, Tuple, Optional, Callable, Any
14class PatternDataManager:
15 """
16 Pure data operations for function patterns.
18 Handles List↔Dict conversions, cloning, and data transformations
19 with order determinism and immutable operations.
20 """
22 @staticmethod
23 def clone_pattern(pattern: Union[List, Dict]) -> Union[List, Dict]:
24 """
25 Deep clone preserving callable references exactly.
27 Args:
28 pattern: Pattern to clone (List or Dict)
30 Returns:
31 Deep cloned pattern with preserved callable references
32 """
33 if pattern is None:
34 return []
35 return copy.deepcopy(pattern)
37 @staticmethod
38 def convert_list_to_dict(pattern: List) -> Dict:
39 """
40 Convert List pattern to empty Dict - user must add component keys manually.
42 Args:
43 pattern: List pattern to convert (will be discarded)
45 Returns:
46 Empty dict for user to populate with experimental component identifiers
47 """
48 if not isinstance(pattern, list):
49 raise ValueError(f"Expected list, got {type(pattern)}")
51 # Return empty dict - user will add experimental component keys manually
52 return {}
54 @staticmethod
55 def convert_dict_to_list(pattern: Dict) -> Union[List, Dict]:
56 """
57 Convert Dict pattern to List when empty.
59 Args:
60 pattern: Dict pattern to potentially convert
62 Returns:
63 Empty list if dict is empty, otherwise returns original dict
64 """
65 if not isinstance(pattern, dict):
66 raise ValueError(f"Expected dict, got {type(pattern)}")
68 # Convert to empty list if dict is empty
69 if not pattern:
70 return []
72 # Keep as dict if it has keys
73 return pattern
75 @staticmethod
76 def extract_func_and_kwargs(func_item) -> Tuple[Optional[Callable], Dict]:
77 """
78 Parse (func, kwargs) tuples and bare callables.
80 Handles both tuple format and bare callable format exactly as current logic.
82 Args:
83 func_item: Either (callable, kwargs) tuple or bare callable
85 Returns:
86 Tuple of (callable, kwargs_dict)
87 """
88 # EXACT current logic preservation
89 if isinstance(func_item, tuple) and len(func_item) == 2 and callable(func_item[0]):
90 result = func_item[0], func_item[1]
91 print(f"🔍 PATTERN DATA MANAGER extract_func_and_kwargs: tuple case - returning {result}")
92 return result
93 elif callable(func_item):
94 result = func_item, {}
95 print(f"🔍 PATTERN DATA MANAGER extract_func_and_kwargs: callable case - returning {result}")
96 return result
97 else:
98 print("🔍 PATTERN DATA MANAGER extract_func_and_kwargs: neither tuple nor callable - returning None, {}")
99 return None, {}
101 @staticmethod
102 def validate_pattern_structure(pattern: Union[List, Dict]) -> bool:
103 """
104 Basic structural validation of pattern.
106 Args:
107 pattern: Pattern to validate
109 Returns:
110 True if structure is valid, False otherwise
111 """
112 if pattern is None:
113 return True
115 if isinstance(pattern, list):
116 # Validate list items are callables or (callable, dict) tuples
117 for item in pattern:
118 func, kwargs = PatternDataManager.extract_func_and_kwargs(item)
119 if func is None:
120 return False
121 if not isinstance(kwargs, dict):
122 return False
123 return True
125 elif isinstance(pattern, dict):
126 # Validate dict values are lists of callables
127 for key, value in pattern.items():
128 if not isinstance(value, list):
129 return False
130 # Recursively validate the list
131 if not PatternDataManager.validate_pattern_structure(value):
132 return False
133 return True
135 else:
136 return False
138 @staticmethod
139 def get_current_functions(pattern: Union[List, Dict], key: Any, is_dict: bool) -> List:
140 """
141 Extract function list for current context.
143 Args:
144 pattern: Full pattern (List or Dict)
145 key: Current key (for Dict patterns)
146 is_dict: Whether pattern is currently in dict mode
148 Returns:
149 List of functions for current context
150 """
151 if is_dict and isinstance(pattern, dict):
152 return pattern.get(key, [])
153 elif not is_dict and isinstance(pattern, list):
154 return pattern
155 else:
156 return []
158 @staticmethod
159 def update_pattern_functions(pattern: Union[List, Dict], key: Any, is_dict: bool,
160 new_functions: List) -> Union[List, Dict]:
161 """
162 Update functions in pattern for current context.
164 Returns new pattern object (immutable operation).
166 Args:
167 pattern: Original pattern
168 key: Current key (for Dict patterns)
169 is_dict: Whether pattern is in dict mode
170 new_functions: New function list
172 Returns:
173 New pattern with updated functions
174 """
175 if is_dict and isinstance(pattern, dict):
176 new_pattern = copy.deepcopy(pattern)
177 new_pattern[key] = new_functions
178 return new_pattern
179 elif not is_dict and isinstance(pattern, list):
180 return copy.deepcopy(new_functions)
181 else:
182 # Fallback - return original pattern
183 return copy.deepcopy(pattern)
185 @staticmethod
186 def add_new_key(pattern: Dict, new_key: str) -> Dict:
187 """
188 Add new key to dict pattern.
190 Args:
191 pattern: Dict pattern
192 new_key: Key to add
194 Returns:
195 New dict with added key
196 """
197 new_pattern = copy.deepcopy(pattern)
198 if new_key not in new_pattern:
199 new_pattern[new_key] = []
200 return new_pattern
202 @staticmethod
203 def remove_key(pattern: Dict, key_to_remove: Any) -> Union[List, Dict]:
204 """
205 Remove key from dict pattern.
207 If dict becomes empty after removal, converts back to list.
209 Args:
210 pattern: Dict pattern
211 key_to_remove: Key to remove
213 Returns:
214 New pattern (List if dict becomes empty, Dict otherwise)
215 """
216 new_pattern = copy.deepcopy(pattern)
217 if key_to_remove in new_pattern:
218 del new_pattern[key_to_remove]
220 # Check if should convert back to list (when empty)
221 return PatternDataManager.convert_dict_to_list(new_pattern)