Coverage for venv / lib / python3.12 / site-packages / combinatrix / models.py: 97%

154 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-05 00:09 +0100

1from combinatrix import constants 

2from combinatrix.exceptions import CombinatrixException 

3 

4 

5class Parameters(object): 

6 

7 def __init__(self, raw=None): 

8 self.data = [] if raw is None else raw.get("parameters", []) 

9 self._index = {} 

10 self._value_index = {} 

11 self._make_index() 

12 

13 def add_field(self, name, type): 

14 self.data.append({"name": name, "type": type}) 

15 self._index[name] = len(self.data) - 1 

16 

17 def set_default(self, name, default): 

18 obj = self.get(name) 

19 if obj.get("type") not in [constants.CONDITIONAL]: 

20 return 

21 obj["default"] = default 

22 

23 def add_value(self, name, value): 

24 obj = self.get(name) 

25 if obj.get("type") in [constants.INDEX, constants.COMMENT]: 

26 return 

27 if value == "": 

28 return 

29 if "values" not in obj: 

30 obj["values"] = {} 

31 if value not in obj["values"]: 

32 obj["values"][value] = {} 

33 

34 # keep an ordered list of values for iteration later 

35 real_name = obj.get("name") 

36 if real_name not in self._value_index: 

37 self._value_index[real_name] = [] 

38 if value not in self._value_index[real_name]: 

39 self._value_index[real_name].append(value) 

40 

41 def add_constraint(self, name, value, other_field, or_values=None, nor_values=None): 

42 if or_values is not None and nor_values is not None: 

43 raise CombinatrixException("you can't specify both or and nor values - choose one") 

44 

45 obj = self.get(name) 

46 if obj.get("type") not in [constants.GENERATED]: 

47 return 

48 self.add_value(name, value) 

49 if "constraints" not in obj["values"][value]: 

50 obj["values"][value]["constraints"] = {} 

51 context = obj["values"][value]["constraints"] 

52 

53 if other_field not in context: 

54 context[other_field] = {} 

55 

56 if or_values is not None: 

57 if "or" not in context[other_field]: 

58 context[other_field]["or"] = [] 

59 context[other_field]["or"] += or_values 

60 elif nor_values is not None: 

61 if "nor" not in context[other_field]: 

62 context[other_field]["nor"] = [] 

63 context[other_field]["nor"] += nor_values 

64 

65 def add_condition_set(self, name, value, conditions): 

66 obj = self.get(name) 

67 if obj.get("type") not in [constants.CONDITIONAL]: 

68 return 

69 self.add_value(name, value) 

70 if "conditions" not in obj["values"][value]: 

71 obj["values"][value]["conditions"] = [] 

72 context = obj["values"][value]["conditions"] 

73 context.append(conditions) 

74 

75 def field_names(self, types=None): 

76 if types is None: 

77 return [p["name"] for p in self.data] 

78 return [p["name"] for p in self.data if p["type"] in types] 

79 

80 def get_default(self, name): 

81 return self.get(name).get("default", "") 

82 

83 def get_values(self, name): 

84 obj = self.get(name) 

85 return self._value_index.get(obj.get("name")) 

86 

87 def get_constraints(self, name, value): 

88 obj = self.get(name) 

89 return obj.get("values", {}).get(value, {}).get("constraints", {}) 

90 

91 def get_conditions(self, name, value): 

92 obj = self.get(name) 

93 return obj.get("values", {}).get(value, {}).get("conditions", []) 

94 

95 def get(self, name): 

96 if isinstance(name, int): 

97 return self.data[name] 

98 else: 

99 return self.data[self._index[name]] 

100 

101 def as_dict(self): 

102 return {"parameters": self.data} 

103 

104 def _make_index(self): 

105 for i, obj in enumerate(self.data): 

106 self._index[obj.get("name")] = i 

107 self._value_index[obj.get("name")] = list(obj.get("values", {}).keys()) 

108 

109 

110class ComboIterator(object): 

111 def __init__(self, parameters): 

112 self.counters = {} 

113 self._index = [] 

114 self._init = True 

115 

116 generated_fields = parameters.field_names(types=[constants.GENERATED]) 

117 for gf in generated_fields: 

118 values = parameters.get_values(gf) 

119 s = {} 

120 for i, value in enumerate(values): 

121 constraints = parameters.get_constraints(gf, value) 

122 skip = {} 

123 for field, rules in constraints.items(): 

124 if "nor" in rules: 

125 skip[field] = self._values_to_indices(parameters, field, rules["nor"]) 

126 elif "or" in rules: 

127 skip[field] = self._mirror_to_indices(parameters, field, rules["or"]) 

128 if len(list(skip.keys())) > 0: 

129 s[i] = skip 

130 

131 self.counters[gf] = {"c": 0, "m": len(values) - 1, "s": s} 

132 self._index.append(gf) 

133 

134 def _mirror_to_indices(self, parameters, field, values): 

135 field_values = parameters.get_values(field) 

136 return [field_values.index(v) for v in field_values if v not in values] 

137 

138 def _values_to_indices(self, parameters, field, values): 

139 field_values = parameters.get_values(field) 

140 return [field_values.index(v) for v in values] 

141 

142 def get_current(self, name): 

143 return self.counters[name]["c"] 

144 

145 def reset_current(self, name): 

146 self.counters[name]["c"] = 0 

147 

148 def increment_current(self, name): 

149 self.counters[name]["c"] += 1 

150 

151 def get_max(self, name): 

152 return self.counters[name]["m"] 

153 

154 def get_skip(self, name): 

155 return self.counters[name]["s"] 

156 

157 def __next__(self): 

158 if self._init: 

159 self._init = False 

160 if self._check_position(): 

161 return True 

162 

163 while True: 

164 counted = False 

165 for i in range(len(self._index) -1, -1, -1): 

166 field = self._index[i] 

167 current = self.get_current(field) 

168 max = self.get_max(field) 

169 if current == max: 

170 self.reset_current(field) 

171 else: 

172 self.increment_current(field) 

173 counted = True 

174 break 

175 if not counted: 

176 break 

177 if self._check_position(): 

178 break 

179 return counted 

180 

181 def _check_position(self): 

182 for i in range(len(self._index)): 

183 field = self._index[i] 

184 current = self.get_current(field) 

185 skips = self.get_skip(field) 

186 if current not in skips: 

187 continue 

188 for other_field, skip_list in skips[current].items(): 

189 if self.get_current(other_field) in skip_list: 

190 return False 

191 return True