18 This module is an implementation of eHive's Param module.
19 It defines ParamContainer which is an attribute of BaseRunnable
20 and not its base class as in eHive's class hierarchy.
21 All the specific warnings and exceptions inherit from ParamWarning
31 """Used by process.BaseRunnable"""
35 class ParamException(Exception):
36 """Base class for parameters-related exceptions"""
39 """Raised when the parameter name is not a string"""
41 return '"{0}" (type {1}) is not a valid parameter name'.format(self.args[0], type(self.args[0]).__name__)
43 """Raised when ParamContainer tried to substitute an unexpected structure (only dictionaries and lists are accepted)"""
45 return 'Cannot substitute elements in objects of type "{0}"'.format(str(type(self.args[0])))
47 """Raised when parameters depend on each other, forming a loop"""
49 return "Substitution loop has been detected on {0}. Parameter-substitution stack: {1}".format(self.args[0], list(self.args[1].keys()))
51 """Raised when a parameter cannot be required because it is null (None)"""
53 return "{0} is None".format(self.args[0])
57 """Equivalent of eHive's Param module"""
59 def __init__(self, unsubstituted_params, debug=False):
60 """Constructor. "unsubstituted_params" is a dictionary"""
70 """Setter. Returns the new value"""
76 """Getter. Performs the parameter substitution"""
81 except (KeyError, SyntaxError, ParamException)
as e:
83 raise e.with_traceback(
None)
86 """Returns a boolean. It checks both substituted and unsubstituted parameters"""
91 """Apply the parameter substitution to the string"""
95 except (KeyError, SyntaxError, ParamException)
as e:
97 raise e.with_traceback(
None)
102 """Tells whether "param_name" is a non-empty string"""
103 if not isinstance(param_name, str)
or (param_name ==
''):
107 """Print debug information if the debug flag is turned on (cf constructor)"""
109 print(*args, **kwargs)
112 """Equivalent of get_param() that assumes "param_name" is a valid parameter name and hence, doesn't have to raise ParamNameException.
113 It is only used internally"""
123 Take any structure and replace the pairs of hashes with the values of the parameters / expression they represent
124 Compatible types: numbers, strings, lists, dictionaries (otherwise, ParamSubstitutionException is raised)
128 if structure
is None:
131 elif isinstance(structure, list):
134 elif isinstance(structure, dict):
139 elif isinstance(structure, numbers.Number):
142 elif isinstance(structure, str):
146 if structure[:6] ==
'#expr(' and structure[-6:] ==
')expr#' and structure.count(
'#expr(', 6, -6) == 0
and structure.count(
')expr#', 6, -6) == 0:
149 if structure[0] ==
'#' and structure[-1] ==
'#' and structure.count(
'#', 1, -1) == 0:
150 if len(structure) <= 2:
163 Parse "structure" and replace all the pairs of hashes by the result of calling callback() on the pair content
164 #expr()expr# are treated differently by calling subst_one_hashpair()
165 The result is a string (like structure)
170 if structure.count(
"#") == 1:
175 (head,_,tmp) = structure.partition(
'#')
178 return ''.join(result)
179 if tmp.startswith(
'expr('):
180 i = tmp.find(
')expr#')
182 raise SyntaxError(
"Unmatched '#expr(' token")
186 (middle_param,_,tail) = tmp.partition(
'#')
188 raise SyntaxError(
"Unmatched '#' token")
189 if middle_param ==
'':
192 val = callback(middle_param)
193 result.append(str(val))
199 Run the parameter substitution for a single pair of hashes.
200 Here, we only need to handle #expr()expr#, #func:params# and #param_name#
201 as each condition has been parsed in the other methods
203 self.
debug_print(
"subst_one_hashpair", inside_hashes, is_expr)
212 s = self.
subst_all_hashpairs(inside_hashes[5:-5].strip(),
'self.internal_get_param("{0}")'.format)
215 elif ':' in inside_hashes:
216 (func_name,_,parameters) = inside_hashes.partition(
':')
220 raise SyntaxError(
"Unknown method: " + func_name)
227 raise SyntaxError(func_name +
" is not callable")
239 with self.assertRaises(ParamInfiniteLoopException):
243 with self.assertRaises(KeyError):
247 with self.assertRaises(ParamNameException):
254 TestParamEntry = collections.namedtuple(
'TestParamEntry', [
'name',
'seed_value',
'eval_value'])
264 TestParamEntry(
'gamma_prime',
'#expr( #gamma# )expr#', [10, 20, 33, 15]),
265 TestParamEntry(
'gamma_second',
'#expr( list(#gamma#) )expr#', [10, 20, 33, 15]),
267 TestParamEntry(
'age', {
'Alice': 17,
'Bob': 20,
'Chloe': 21}, {
'Alice': 17,
'Bob': 20,
'Chloe': 21}),
268 TestParamEntry(
'age_prime',
'#expr( #age# )expr#', {
'Alice': 17,
'Bob': 20,
'Chloe': 21}),
269 TestParamEntry(
'age_second',
'#expr( dict(#age#) )expr#', {
'Alice': 17,
'Bob': 20,
'Chloe': 21}),
272 TestParamEntry(
'csv_prime',
'#expr( #csv# )expr#',
'[123,456,789]'),
273 TestParamEntry(
'listref',
'#expr( eval(#csv#) )expr#', [123, 456, 789]),
280 seed_params_dict = {p.name: p.seed_value
for p
in seed_params_list}
286 """Helper method to execute the substitution and check the result"""
287 value = self.
params.substitute_string(param_string)
288 self.assertEqual(value, expected_value, msg)
292 self.assertEqual(self.
params.get_param(p.name), p.eval_value, p.name +
" can be retrieved")
296 '#alpha# and another: #beta# and again one: #alpha# and the other: #beta# . Their product: #delta#',
297 '2 and another: 5 and again one: 2 and the other: 5 . Their product: 10',
298 'Scalar substitutions'
305 'gamma not stringified'
308 '#expr( #gamma# )expr#',
310 'expr-gamma not stringified'
313 '#expr( "~".join([str(_) for _ in sorted(#gamma#)]) )expr#',
315 'gamma stringification'
318 '#expr( "~".join([str(_) for _ in sorted(#gamma_prime#)]) )expr#',
320 'gamma_prime stringification'
326 {
'Alice': 17,
'Bob': 20,
'Chloe': 21},
327 'age not stringified'
330 '#expr( #age# )expr#',
331 {
'Alice': 17,
'Bob': 20,
'Chloe': 21},
332 'age not stringified'
335 '#expr( " and ".join(["{0} is {1} years old".format(p,a) for (p,a) in sorted(#age#.items())]) )expr#',
336 'Alice is 17 years old and Bob is 20 years old and Chloe is 21 years old',
337 'complex fold of age'
340 '#expr( " and ".join(["{0} is {1} years old".format(p,a) for (p,a) in sorted(#age_prime#.items())]) )expr#',
341 'Alice is 17 years old and Bob is 20 years old and Chloe is 21 years old',
342 'complex fold of age_prime'
347 '#expr( sum(#gamma#) )expr#',
352 '#expr( min(#gamma#) )expr#',
357 '#expr( max(#gamma#) )expr#',
364 '#expr( #age#["Alice"]+max(#gamma#)+#listref#[0] )expr#',
366 'adding indexed and keyed values'
372 self.
params.get_param(
'gamma_prime')
373 self.
params.get_param(
'gamma_second')
375 self.
params.get_param(
'gamma').append(
"val0")
381 self.
params.get_param(
'gamma'),
382 [10, 20, 33, 15,
'val0'],
386 self.
params.get_param(
'gamma_prime'),
387 [10, 20, 33, 15,
'val0'],
391 self.
params.get_param(
'gamma_second'),