4 # Copyright (C) 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module implementing the parameter types code."""
27 from ganeti import compat
28 from ganeti import utils
29 from ganeti import constants
32 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
36 """Enclose text in parens if necessary.
43 if _PAREN_RE.match(text):
49 class _DescWrapper(object):
55 def __init__(self, text, fn):
56 """Initializes this class.
58 @param text: Description
59 @param fn: Wrapped function
65 def __call__(self, *args):
66 return self._fn(*args)
73 """Builds wrapper class with description text.
76 @param text: Description text
77 @return: Callable class
80 assert text[0] == text[0].upper()
82 return compat.partial(_DescWrapper, text)
85 def CombinationDesc(op, args, fn):
86 """Build description for combinating operator.
89 @param op: Operator as text (e.g. "and")
91 @param args: Operator arguments
93 @param fn: Wrapped function
99 descr = (" %s " % op).join(Parens(i) for i in args)
101 return WithDesc(descr)(fn)
104 # Modifiable default values; need to define these here before the
109 """Returns an empty list.
117 """Returns an empty dict.
123 #: The without-default default value
127 #: The no-type (value too complex to check it in the type system)
132 @WithDesc("Anything")
134 """Accepts any value.
142 """Checks if the given value is not None.
145 return val is not None
150 """Checks if the given value is None.
158 """Checks if the given value is a boolean.
161 return isinstance(val, bool)
166 """Checks if the given value is an integer.
169 # For backwards compatibility with older Python versions, boolean values are
170 # also integers and should be excluded in this test.
172 # >>> (isinstance(False, int), isinstance(True, int))
174 return isinstance(val, (int, long)) and not isinstance(val, bool)
179 """Checks if the given value is a float.
182 return isinstance(val, float)
187 """Checks if the given value is a string.
190 return isinstance(val, basestring)
193 @WithDesc("EvalToTrue")
195 """Checks if a given value evaluates to a boolean True value.
201 def TElemOf(target_list):
202 """Builds a function that checks if a given value is a member of a list.
206 return val in target_list
208 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
214 """Checks if the given value is a list.
217 return isinstance(val, list)
220 @WithDesc("Dictionary")
222 """Checks if the given value is a dictionary.
225 return isinstance(val, dict)
229 """Check is the given container is of the given size.
233 return len(container) == size
235 return WithDesc("Length %s" % (size, ))(fn)
240 """Combine multiple functions using an AND operation.
244 return compat.all(t(val) for t in args)
246 return CombinationDesc("and", args, fn)
250 """Combine multiple functions using an AND operation.
254 return compat.any(t(val) for t in args)
256 return CombinationDesc("or", args, fn)
260 """Checks that a modified version of the argument passes the given test.
263 return WithDesc("Result of %s must be %s" %
264 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
268 """Checks whether a string matches a specific regular expression.
270 @param pobj: Compiled regular expression as returned by C{re.compile}
273 desc = WithDesc("String matching regex \"%s\"" %
274 pobj.pattern.encode("string_escape"))
276 return desc(TAnd(TString, pobj.match))
281 #: a non-empty string
282 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
284 #: a maybe non-empty string
285 TMaybeString = TOr(TNonEmptyString, TNone)
287 #: a maybe boolean (bool or none)
288 TMaybeBool = TOr(TBool, TNone)
290 #: Maybe a dictionary (dict or None)
291 TMaybeDict = TOr(TDict, TNone)
293 #: a positive integer
295 TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
297 #: a strictly positive integer
298 TStrictPositiveInt = \
299 TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
301 #: a strictly negative integer (0 > value)
302 TStrictNegativeInt = \
303 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
307 TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
310 TJobId = WithDesc("JobId")(TOr(TPositiveInt,
311 TRegex(re.compile("^%s$" %
312 constants.JOB_ID_TEMPLATE))))
315 TNumber = TOr(TInt, TFloat)
318 TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
321 def TListOf(my_type):
322 """Checks if a given value is a list with all elements of the same type.
325 desc = WithDesc("List of %s" % (Parens(my_type), ))
326 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
329 def TDictOf(key_type, val_type):
330 """Checks a dict type for the type of its key/values.
333 desc = WithDesc("Dictionary with keys of %s and values of %s" %
334 (Parens(key_type), Parens(val_type)))
337 return (compat.all(key_type(v) for v in container.keys()) and
338 compat.all(val_type(v) for v in container.values()))
340 return desc(TAnd(TDict, fn))
343 def _TStrictDictCheck(require_all, exclusive, items, val):
344 """Helper function for L{TStrictDict}.
347 notfound_fn = lambda _: not exclusive
349 if require_all and not frozenset(val.keys()).issuperset(items.keys()):
350 # Requires items not found in value
353 return compat.all(items.get(key, notfound_fn)(value)
354 for (key, value) in val.items())
357 def TStrictDict(require_all, exclusive, items):
358 """Strict dictionary check with specific keys.
360 @type require_all: boolean
361 @param require_all: Whether all keys in L{items} are required
362 @type exclusive: boolean
363 @param exclusive: Whether only keys listed in L{items} should be accepted
364 @type items: dictionary
365 @param items: Mapping from key (string) to verification function
368 descparts = ["Dictionary containing"]
371 descparts.append(" none but the")
374 descparts.append(" required")
377 descparts.append(" key ")
379 descparts.append(" keys ")
381 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
382 for (key, value) in items.items()))
384 desc = WithDesc("".join(descparts))
386 return desc(TAnd(TDict,
387 compat.partial(_TStrictDictCheck, require_all, exclusive,
392 """Checks individual items of a container.
394 If the verified value and the list of expected items differ in length, this
395 check considers only as many items as are contained in the shorter list. Use
396 L{TIsLength} to enforce a certain length.
399 @param items: List of checks
402 assert items, "Need items"
404 text = ["Item", "item"]
405 desc = WithDesc(utils.CommaJoin("%s %s is %s" %
406 (text[int(idx > 0)], idx, Parens(check))
407 for (idx, check) in enumerate(items)))
409 return desc(lambda value: compat.all(check(i)
410 for (check, i) in zip(items, value)))