Coverage for openhcs/formats/func_arg_prep.py: 41.4%
79 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1from typing import List, Dict
2def prepare_patterns_and_functions(patterns, processing_funcs, component='default'):
3 """
4 Prepare patterns, processing functions, and processing args for processing.
6 This function handles three main tasks:
7 1. Ensuring patterns are in a component-keyed dictionary format
8 2. Determining which processing functions to use for each component
9 3. Determining which processing args to use for each component
11 Args:
12 patterns (list or dict): Patterns to process, either as a flat list or grouped by component
13 processing_funcs (callable, list, dict, tuple, optional): Processing functions to apply.
14 Can be a single callable, a tuple of (callable, kwargs), a list of either,
15 or a dictionary mapping component values to any of these.
16 component (str): Component name for grouping (only used for clarity in the result)
18 Returns:
19 tuple: (grouped_patterns, component_to_funcs, component_to_args)
20 - grouped_patterns: Dictionary mapping component values to patterns
21 - component_to_funcs: Dictionary mapping component values to processing functions
22 - component_to_args: Dictionary mapping component values to processing args
23 """
24 import logging
25 logger = logging.getLogger(__name__)
27 # Debug: Log what we received
28 logger.debug(f"🔍 PATTERN DEBUG: prepare_patterns_and_functions called")
29 logger.debug(f"🔍 PATTERN DEBUG: patterns type: {type(patterns)}")
30 logger.debug(f"🔍 PATTERN DEBUG: patterns keys/content: {list(patterns.keys()) if isinstance(patterns, dict) else f'List with {len(patterns)} items'}")
31 logger.debug(f"🔍 PATTERN DEBUG: processing_funcs type: {type(processing_funcs)}")
32 logger.debug(f"🔍 PATTERN DEBUG: processing_funcs keys: {list(processing_funcs.keys()) if isinstance(processing_funcs, dict) else 'Not a dict'}")
33 logger.debug(f"🔍 PATTERN DEBUG: component: {component}")
35 # Ensure patterns are in a dictionary format
36 # If already a dict, use as is; otherwise wrap the list in a dictionary
37 grouped_patterns = patterns if isinstance(patterns, dict) else {component: patterns}
39 logger.debug(f"🔍 PATTERN DEBUG: grouped_patterns keys: {list(grouped_patterns.keys())}")
41 # SMART FILTERING: If processing_funcs is a dict, only process components that have function definitions
42 if isinstance(processing_funcs, dict) and isinstance(grouped_patterns, dict): 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true
43 original_components = set(grouped_patterns.keys())
44 function_components = set(processing_funcs.keys())
46 # Handle type mismatches (string vs int keys)
47 available_function_keys = set()
48 for key in function_components:
49 available_function_keys.add(key)
50 available_function_keys.add(str(key)) # Add string version
51 if isinstance(key, str) and key.isdigit():
52 available_function_keys.add(int(key)) # Add int version if string is numeric
54 # Filter to only components that have function definitions
55 filtered_grouped_patterns = {
56 comp_value: patterns
57 for comp_value, patterns in grouped_patterns.items()
58 if comp_value in available_function_keys
59 }
61 # Log what was filtered
62 filtered_out = original_components - set(filtered_grouped_patterns.keys())
63 if filtered_out:
64 logger.debug(f"🔍 PATTERN DEBUG: Filtered out components without function definitions: {filtered_out}")
66 logger.debug(f"🔍 PATTERN DEBUG: Processing components: {list(filtered_grouped_patterns.keys())}")
67 grouped_patterns = filtered_grouped_patterns
69 # Validate that we have at least one component to process
70 if not grouped_patterns:
71 available_keys = list(processing_funcs.keys())
72 discovered_keys = list(original_components)
73 raise ValueError(
74 f"No components match between discovered data and function pattern. "
75 f"Discovered components: {discovered_keys}. "
76 f"Function pattern keys: {available_keys}. "
77 f"Function pattern keys must match discovered component values."
78 )
80 # Initialize dictionaries for functions and args
81 component_to_funcs = {}
82 component_to_args = {}
84 # Helper function to extract function and args from a function item
85 def extract_func_and_args(func_item):
86 if isinstance(func_item, tuple) and len(func_item) == 2 and callable(func_item[0]):
87 # It's a (function, kwargs) tuple
88 return func_item[0], func_item[1]
89 if callable(func_item): 89 ↛ 92line 89 didn't jump to line 92 because the condition on line 89 was always true
90 # It's just a function, use default args
91 return func_item, {}
92 if isinstance(func_item, dict):
93 # It's a dictionary pattern - this should be handled at a higher level
94 # This indicates a logic error where the entire dict was passed instead of individual components
95 raise ValueError(
96 f"Dictionary pattern passed to extract_func_and_args: {func_item}. "
97 f"This indicates a component lookup failure in prepare_patterns_and_functions. "
98 f"Dictionary patterns should be resolved to individual function lists before reaching this point."
99 )
100 # Fail loudly and early if the function item is invalid
101 raise ValueError(f"Invalid function item for pattern processing: {func_item}")
103 for comp_value in grouped_patterns.keys():
104 # Get functions and args for this component
105 # No special handling for 'channel' component (Clause 77: Rot Intolerance)
106 import logging
107 logger = logging.getLogger(__name__)
108 logger.debug(f"Processing component value: '{comp_value}' (type: {type(comp_value)})")
109 logger.debug(f"Function pattern keys: {list(processing_funcs.keys()) if isinstance(processing_funcs, dict) else 'Not a dict'}")
111 if isinstance(processing_funcs, dict): 111 ↛ 114line 111 didn't jump to line 114 because the condition on line 111 was never true
112 # Direct lookup with type conversion fallback
113 # Compile-time validation guarantees dict keys are valid
114 if comp_value in processing_funcs:
115 func_item = processing_funcs[comp_value]
116 logger.debug(f"Found direct match for '{comp_value}': {type(func_item)}")
117 else:
118 # Handle type mismatch: pattern detection returns strings, but function pattern might use integers
119 logger.debug(f"No direct match for '{comp_value}', trying integer conversion")
120 try:
121 comp_value_int = int(comp_value)
122 if comp_value_int in processing_funcs:
123 func_item = processing_funcs[comp_value_int]
124 else:
125 # Try converting keys to int for comparison
126 found = False
127 for key in processing_funcs.keys():
128 try:
129 if int(key) == comp_value_int:
130 func_item = processing_funcs[key]
131 found = True
132 break
133 except (ValueError, TypeError):
134 continue
135 if not found:
136 # This should not happen due to compile-time validation
137 func_item = processing_funcs[comp_value]
138 except (ValueError, TypeError):
139 # This should not happen due to compile-time validation
140 func_item = processing_funcs[comp_value]
141 else:
142 # Use the same function for all components
143 func_item = processing_funcs
145 # Extract function and args
146 logger.debug(f"Processing func_item for '{comp_value}': {type(func_item)}")
147 if isinstance(func_item, list):
148 # List of functions or function tuples
149 logger.debug(f"func_item is a list with {len(func_item)} items")
150 component_to_funcs[comp_value] = func_item
151 # For lists, we'll extract args during processing
152 component_to_args[comp_value] = {}
153 else:
154 # Single function or function tuple
155 logger.debug(f"Calling extract_func_and_args with: {type(func_item)}")
156 func, args = extract_func_and_args(func_item)
157 component_to_funcs[comp_value] = func
158 component_to_args[comp_value] = args
160 return grouped_patterns, component_to_funcs, component_to_args