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
« 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.
16import operator
17import re
19import pyparsing
21from manila import exception
22from manila.i18n import _
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
36class EvalConstant(object):
37 def __init__(self, toks):
38 self.value = toks[0]
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)
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('\'', '')
63 return result
66class EvalSignOp(object):
67 operations = {
68 '+': 1,
69 '-': -1,
70 }
72 def __init__(self, toks):
73 self.sign, self.value = toks[0]
75 def eval(self):
76 return self.operations[self.sign] * self.value.eval()
79class EvalAddOp(object):
80 def __init__(self, toks):
81 self.value = toks[0]
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
93class EvalMultOp(object):
94 def __init__(self, toks):
95 self.value = toks[0]
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
111class EvalPowerOp(object):
112 def __init__(self, toks):
113 self.value = toks[0]
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
122class EvalNegateOp(object):
123 def __init__(self, toks):
124 self.negation, self.value = toks[0]
126 def eval(self):
127 return not self.value.eval()
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 }
141 def __init__(self, toks):
142 self.value = toks[0]
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
157class EvalTernaryOp(object):
158 def __init__(self, toks):
159 self.value = toks[0]
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()
169class EvalFunction(object):
170 functions = {
171 "abs": abs,
172 "max": max,
173 "min": min,
174 }
176 def __init__(self, toks):
177 self.func, self.value = toks[0]
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)
187class EvalCommaSeperator(object):
188 def __init__(self, toks):
189 self.value = toks[0]
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
201 return [val1, val2]
204class EvalBoolAndOp(object):
205 def __init__(self, toks):
206 self.value = toks[0]
208 def eval(self):
209 left = self.value[0].eval()
210 right = self.value[2].eval()
211 return left and right
214class EvalBoolOrOp(object):
215 def __init__(self, toks):
216 self.value = toks[0]
218 def eval(self):
219 left = self.value[0].eval()
220 right = self.value[2].eval()
221 return left or right
224_parser = None
225_vars = {}
228def _def_parser():
229 # Enabling packrat parsing greatly speeds up the parsing.
230 pyparsing.ParserElement.enablePackrat() # pylint: disable = no-value-for-parameter # noqa:E501
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
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
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 !')
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), ])
273 return expr
276def evaluate(expression, **kwargs):
277 """Evaluates an expression.
279 Provides the facility to evaluate mathematical expressions, and to
280 substitute variables from dictionaries into those expressions.
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()
289 global _vars
290 _vars = kwargs
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)
298 return result.eval()