1import itertools 2import os 3import sys 4from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, \ 5 TypeVar, Optional 6 7# local import 8sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname( 9 os.path.abspath(__file__))))) 10import lib.print_utils as print_utils 11 12T = TypeVar('T') 13NamedTupleMeta = Callable[ 14 ..., T] # approximation of a (S : NamedTuple<T> where S() == T) metatype. 15FilterFuncType = Callable[[NamedTuple], bool] 16 17def dict_lookup_any_key(dictionary: dict, *keys: List[Any]): 18 for k in keys: 19 if k in dictionary: 20 return dictionary[k] 21 22 23 print_utils.debug_print("None of the keys {} were in the dictionary".format( 24 keys)) 25 return [None] 26 27def generate_run_combinations(named_tuple: NamedTupleMeta[T], 28 opts_dict: Dict[str, List[Optional[object]]], 29 loop_count: int = 1) -> Iterable[T]: 30 """ 31 Create all possible combinations given the values in opts_dict[named_tuple._fields]. 32 33 :type T: type annotation for the named_tuple type. 34 :param named_tuple: named tuple type, whose fields are used to make combinations for 35 :param opts_dict: dictionary of keys to value list. keys correspond to the named_tuple fields. 36 :param loop_count: number of repetitions. 37 :return: an iterable over named_tuple instances. 38 """ 39 combinations_list = [] 40 for k in named_tuple._fields: 41 # the key can be either singular or plural , e.g. 'package' or 'packages' 42 val = dict_lookup_any_key(opts_dict, k, k + "s") 43 44 # treat {'x': None} key value pairs as if it was [None] 45 # otherwise itertools.product throws an exception about not being able to iterate None. 46 combinations_list.append(val or [None]) 47 48 print_utils.debug_print("opts_dict: ", opts_dict) 49 print_utils.debug_print_nd("named_tuple: ", named_tuple) 50 print_utils.debug_print("combinations_list: ", combinations_list) 51 52 for i in range(loop_count): 53 for combo in itertools.product(*combinations_list): 54 yield named_tuple(*combo) 55 56def filter_run_combinations(named_tuple: NamedTuple, 57 filters: List[FilterFuncType]) -> bool: 58 for filter in filters: 59 if filter(named_tuple): 60 return False 61 return True 62 63def generate_group_run_combinations(run_combinations: Iterable[NamedTuple], 64 dst_nt: NamedTupleMeta[T]) \ 65 -> Iterable[Tuple[T, Iterable[NamedTuple]]]: 66 def group_by_keys(src_nt): 67 src_d = src_nt._asdict() 68 # now remove the keys that aren't legal in dst. 69 for illegal_key in set(src_d.keys()) - set(dst_nt._fields): 70 if illegal_key in src_d: 71 del src_d[illegal_key] 72 73 return dst_nt(**src_d) 74 75 for args_list_it in itertools.groupby(run_combinations, group_by_keys): 76 (group_key_value, args_it) = args_list_it 77 yield (group_key_value, args_it) 78