Coverage for manila/scheduler/evaluator/evaluator.py: 99%

182 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16import operator 

17import re 

18 

19import pyparsing 

20 

21from manila import exception 

22from manila.i18n import _ 

23 

24 

25def _operatorOperands(tokenList): 

26 it = iter(tokenList) 

27 while 1: 

28 try: 

29 op1 = next(it) 

30 op2 = next(it) 

31 yield (op1, op2) 

32 except StopIteration: 

33 break 

34 

35 

36class EvalConstant(object): 

37 def __init__(self, toks): 

38 self.value = toks[0] 

39 

40 def eval(self): 

41 result = self.value 

42 if (isinstance(result, str) and 

43 re.match(r"^[a-zA-Z_]+\.[a-zA-Z_]+$", result)): 

44 (which_dict, entry) = result.split('.') 

45 try: 

46 result = _vars[which_dict][entry] 

47 except KeyError as e: 

48 msg = _("KeyError: %s") % e 

49 raise exception.EvaluatorParseException(reason=msg) 

50 except TypeError as e: 

51 msg = _("TypeError: %s") % e 

52 raise exception.EvaluatorParseException(reason=msg) 

53 

54 try: 

55 result = int(result) 

56 except ValueError: 

57 try: 

58 result = float(result) 

59 except ValueError: 

60 if isinstance(result, str): 60 ↛ 63line 60 didn't jump to line 63 because the condition on line 60 was always true

61 result = result.replace('"', '').replace('\'', '') 

62 

63 return result 

64 

65 

66class EvalSignOp(object): 

67 operations = { 

68 '+': 1, 

69 '-': -1, 

70 } 

71 

72 def __init__(self, toks): 

73 self.sign, self.value = toks[0] 

74 

75 def eval(self): 

76 return self.operations[self.sign] * self.value.eval() 

77 

78 

79class EvalAddOp(object): 

80 def __init__(self, toks): 

81 self.value = toks[0] 

82 

83 def eval(self): 

84 sum = self.value[0].eval() 

85 for op, val in _operatorOperands(self.value[1:]): 

86 if op == '+': 

87 sum += val.eval() 

88 elif op == '-': 88 ↛ 85line 88 didn't jump to line 85 because the condition on line 88 was always true

89 sum -= val.eval() 

90 return sum 

91 

92 

93class EvalMultOp(object): 

94 def __init__(self, toks): 

95 self.value = toks[0] 

96 

97 def eval(self): 

98 prod = self.value[0].eval() 

99 for op, val in _operatorOperands(self.value[1:]): 

100 try: 

101 if op == '*': 

102 prod *= val.eval() 

103 elif op == '/': 103 ↛ 99line 103 didn't jump to line 99 because the condition on line 103 was always true

104 prod /= float(val.eval()) 

105 except ZeroDivisionError as e: 

106 msg = _("ZeroDivisionError: %s") % e 

107 raise exception.EvaluatorParseException(reason=msg) 

108 return prod 

109 

110 

111class EvalPowerOp(object): 

112 def __init__(self, toks): 

113 self.value = toks[0] 

114 

115 def eval(self): 

116 prod = self.value[0].eval() 

117 for op, val in _operatorOperands(self.value[1:]): 

118 prod = pow(prod, val.eval()) 

119 return prod 

120 

121 

122class EvalNegateOp(object): 

123 def __init__(self, toks): 

124 self.negation, self.value = toks[0] 

125 

126 def eval(self): 

127 return not self.value.eval() 

128 

129 

130class EvalComparisonOp(object): 

131 operations = { 

132 "<": operator.lt, 

133 "<=": operator.le, 

134 ">": operator.gt, 

135 ">=": operator.ge, 

136 "!=": operator.ne, 

137 "==": operator.eq, 

138 "<>": operator.ne, 

139 } 

140 

141 def __init__(self, toks): 

142 self.value = toks[0] 

143 

144 def eval(self): 

145 val1 = self.value[0].eval() 

146 for op, val in _operatorOperands(self.value[1:]): 

147 fn = self.operations[op] 

148 val2 = val.eval() 

149 if not fn(val1, val2): 

150 break 

151 val1 = val2 

152 else: 

153 return True 

154 return False 

155 

156 

157class EvalTernaryOp(object): 

158 def __init__(self, toks): 

159 self.value = toks[0] 

160 

161 def eval(self): 

162 condition = self.value[0].eval() 

163 if condition: 

164 return self.value[2].eval() 

165 else: 

166 return self.value[4].eval() 

167 

168 

169class EvalFunction(object): 

170 functions = { 

171 "abs": abs, 

172 "max": max, 

173 "min": min, 

174 } 

175 

176 def __init__(self, toks): 

177 self.func, self.value = toks[0] 

178 

179 def eval(self): 

180 args = self.value.eval() 

181 if type(args) is list: 

182 return self.functions[self.func](*args) 

183 else: 

184 return self.functions[self.func](args) 

185 

186 

187class EvalCommaSeperator(object): 

188 def __init__(self, toks): 

189 self.value = toks[0] 

190 

191 def eval(self): 

192 val1 = self.value[0].eval() 

193 val2 = self.value[2].eval() 

194 if type(val2) is list: 

195 val_list = [] 

196 val_list.append(val1) 

197 for val in val2: 

198 val_list.append(val) 

199 return val_list 

200 

201 return [val1, val2] 

202 

203 

204class EvalBoolAndOp(object): 

205 def __init__(self, toks): 

206 self.value = toks[0] 

207 

208 def eval(self): 

209 left = self.value[0].eval() 

210 right = self.value[2].eval() 

211 return left and right 

212 

213 

214class EvalBoolOrOp(object): 

215 def __init__(self, toks): 

216 self.value = toks[0] 

217 

218 def eval(self): 

219 left = self.value[0].eval() 

220 right = self.value[2].eval() 

221 return left or right 

222 

223 

224_parser = None 

225_vars = {} 

226 

227 

228def _def_parser(): 

229 # Enabling packrat parsing greatly speeds up the parsing. 

230 pyparsing.ParserElement.enablePackrat() # pylint: disable = no-value-for-parameter # noqa:E501 

231 

232 alphas = pyparsing.alphas 

233 Combine = pyparsing.Combine 

234 Forward = pyparsing.Forward 

235 nums = pyparsing.nums 

236 quoted_string = pyparsing.quotedString 

237 oneOf = pyparsing.oneOf 

238 opAssoc = pyparsing.opAssoc 

239 infixNotation = pyparsing.infixNotation 

240 Word = pyparsing.Word 

241 

242 integer = Word(nums) 

243 real = Combine(Word(nums) + '.' + Word(nums)) 

244 variable = Word(alphas + '_' + '.') 

245 number = real | integer 

246 expr = Forward() 

247 fn = Word(alphas + '_' + '.') 

248 operand = number | variable | fn | quoted_string 

249 

250 signop = oneOf('+ -') 

251 addop = oneOf('+ -') 

252 multop = oneOf('* /') 

253 comparisonop = oneOf(' '.join(EvalComparisonOp.operations.keys())) 

254 ternaryop = ('?', ':') 

255 boolandop = oneOf('AND and &&') 

256 boolorop = oneOf('OR or ||') 

257 negateop = oneOf('NOT not !') 

258 

259 operand.setParseAction(EvalConstant) 

260 expr = infixNotation(operand, [ 

261 (fn, 1, opAssoc.RIGHT, EvalFunction), 

262 ("^", 2, opAssoc.RIGHT, EvalPowerOp), 

263 (signop, 1, opAssoc.RIGHT, EvalSignOp), 

264 (multop, 2, opAssoc.LEFT, EvalMultOp), 

265 (addop, 2, opAssoc.LEFT, EvalAddOp), 

266 (negateop, 1, opAssoc.RIGHT, EvalNegateOp), 

267 (comparisonop, 2, opAssoc.LEFT, EvalComparisonOp), 

268 (ternaryop, 3, opAssoc.LEFT, EvalTernaryOp), 

269 (boolandop, 2, opAssoc.LEFT, EvalBoolAndOp), 

270 (boolorop, 2, opAssoc.LEFT, EvalBoolOrOp), 

271 (',', 2, opAssoc.RIGHT, EvalCommaSeperator), ]) 

272 

273 return expr 

274 

275 

276def evaluate(expression, **kwargs): 

277 """Evaluates an expression. 

278 

279 Provides the facility to evaluate mathematical expressions, and to 

280 substitute variables from dictionaries into those expressions. 

281 

282 Supports both integer and floating point values, and automatic 

283 promotion where necessary. 

284 """ 

285 global _parser 

286 if _parser is None: 

287 _parser = _def_parser() 

288 

289 global _vars 

290 _vars = kwargs 

291 

292 try: 

293 result = _parser.parseString(expression, parseAll=True)[0] 

294 except pyparsing.ParseException as e: 

295 msg = _("ParseException: %s") % e 

296 raise exception.EvaluatorParseException(reason=msg) 

297 

298 return result.eval()