4 # Copyright (C) 2010, 2011, 2012 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 _WrapperBase(object):
55 def __init__(self, text, fn):
56 """Initializes this class.
58 @param text: Description
59 @param fn: Wrapped function
67 def __call__(self, *args):
68 return self._fn(*args)
71 class _DescWrapper(_WrapperBase):
72 """Wrapper class for description text.
79 class _CommentWrapper(_WrapperBase):
80 """Wrapper class for comment.
84 return "%s [%s]" % (self._fn, self._text)
88 """Builds wrapper class with description text.
91 @param text: Description text
92 @return: Callable class
95 assert text[0] == text[0].upper()
97 return compat.partial(_DescWrapper, text)
101 """Builds wrapper for adding comment to description text.
104 @param text: Comment text
105 @return: Callable class
108 assert not frozenset(text).intersection("[]")
110 return compat.partial(_CommentWrapper, text)
113 def CombinationDesc(op, args, fn):
114 """Build description for combinating operator.
117 @param op: Operator as text (e.g. "and")
119 @param args: Operator arguments
121 @param fn: Wrapped function
124 # Some type descriptions are rather long. If "None" is listed at the
125 # end or somewhere in between it is easily missed. Therefore it should
126 # be at the beginning, e.g. "None or (long description)".
127 if __debug__ and TNone in args and args.index(TNone) > 0:
128 raise Exception("TNone must be listed first")
133 descr = (" %s " % op).join(Parens(i) for i in args)
135 return WithDesc(descr)(fn)
138 # Modifiable default values; need to define these here before the
143 """Returns an empty list.
151 """Returns an empty dict.
157 #: The without-default default value
161 #: The no-type (value too complex to check it in the type system)
166 @WithDesc("Anything")
168 """Accepts any value.
176 """Checks if the given value is not None.
179 return val is not None
184 """Checks if the given value is None.
190 @WithDesc("ValueNone")
192 """Checks if the given value is L{constants.VALUE_NONE}.
195 return val == constants.VALUE_NONE
200 """Checks if the given value is a boolean.
203 return isinstance(val, bool)
208 """Checks if the given value is an integer.
211 # For backwards compatibility with older Python versions, boolean values are
212 # also integers and should be excluded in this test.
214 # >>> (isinstance(False, int), isinstance(True, int))
216 return isinstance(val, (int, long)) and not isinstance(val, bool)
221 """Checks if the given value is a float.
224 return isinstance(val, float)
229 """Checks if the given value is a string.
232 return isinstance(val, basestring)
235 @WithDesc("EvalToTrue")
237 """Checks if a given value evaluates to a boolean True value.
243 def TElemOf(target_list):
244 """Builds a function that checks if a given value is a member of a list.
248 return val in target_list
250 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
256 """Checks if the given value is a list.
259 return isinstance(val, list)
264 """Checks if the given value is a tuple.
267 return isinstance(val, tuple)
270 @WithDesc("Dictionary")
272 """Checks if the given value is a dictionary.
275 return isinstance(val, dict)
279 """Check is the given container is of the given size.
283 return len(container) == size
285 return WithDesc("Length %s" % (size, ))(fn)
290 """Combine multiple functions using an AND operation.
294 return compat.all(t(val) for t in args)
296 return CombinationDesc("and", args, fn)
300 """Combine multiple functions using an OR operation.
304 return compat.any(t(val) for t in args)
306 return CombinationDesc("or", args, fn)
310 """Checks that a modified version of the argument passes the given test.
313 return WithDesc("Result of %s must be %s" %
314 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
318 """Checks whether a string matches a specific regular expression.
320 @param pobj: Compiled regular expression as returned by C{re.compile}
323 desc = WithDesc("String matching regex \"%s\"" %
324 pobj.pattern.encode("string_escape"))
326 return desc(TAnd(TString, pobj.match))
330 """Wrap a test in a TOr(TNone, test).
332 This makes it easier to define TMaybe* types.
335 return TOr(TNone, test)
338 def TMaybeValueNone(test):
339 """Used for unsetting values.
342 return TMaybe(TOr(TValueNone, test))
347 #: a non-empty string
348 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
350 #: a maybe non-empty string
351 TMaybeString = TMaybe(TNonEmptyString)
353 #: a maybe boolean (bool or none)
354 TMaybeBool = TMaybe(TBool)
356 #: Maybe a dictionary (dict or None)
357 TMaybeDict = TMaybe(TDict)
359 #: Maybe a list (list or None)
360 TMaybeList = TMaybe(TList)
362 #: a non-negative integer (value >= 0)
364 TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
366 #: a positive integer (value > 0)
368 TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
370 #: a maybe positive integer (positive integer or None)
371 TMaybePositiveInt = TMaybe(TPositiveInt)
373 #: a negative integer (value < 0)
375 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
378 TNonNegativeFloat = \
379 TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
382 TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
383 TRegex(re.compile("^%s$" %
384 constants.JOB_ID_TEMPLATE))))
387 TNumber = TOr(TInt, TFloat)
390 TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
393 def TInstanceOf(cls):
394 """Checks if a given value is an instance of C{cls}.
397 @param cls: Class object
400 name = "%s.%s" % (cls.__module__, cls.__name__)
402 desc = WithDesc("Instance of %s" % (Parens(name), ))
404 return desc(lambda val: isinstance(val, cls))
407 def TListOf(my_type):
408 """Checks if a given value is a list with all elements of the same type.
411 desc = WithDesc("List of %s" % (Parens(my_type), ))
412 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
415 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
418 def TDictOf(key_type, val_type):
419 """Checks a dict type for the type of its key/values.
422 desc = WithDesc("Dictionary with keys of %s and values of %s" %
423 (Parens(key_type), Parens(val_type)))
426 return (compat.all(key_type(v) for v in container.keys()) and
427 compat.all(val_type(v) for v in container.values()))
429 return desc(TAnd(TDict, fn))
432 def _TStrictDictCheck(require_all, exclusive, items, val):
433 """Helper function for L{TStrictDict}.
436 notfound_fn = lambda _: not exclusive
438 if require_all and not frozenset(val.keys()).issuperset(items.keys()):
439 # Requires items not found in value
442 return compat.all(items.get(key, notfound_fn)(value)
443 for (key, value) in val.items())
446 def TStrictDict(require_all, exclusive, items):
447 """Strict dictionary check with specific keys.
449 @type require_all: boolean
450 @param require_all: Whether all keys in L{items} are required
451 @type exclusive: boolean
452 @param exclusive: Whether only keys listed in L{items} should be accepted
453 @type items: dictionary
454 @param items: Mapping from key (string) to verification function
457 descparts = ["Dictionary containing"]
460 descparts.append(" none but the")
463 descparts.append(" required")
466 descparts.append(" key ")
468 descparts.append(" keys ")
470 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
471 for (key, value) in items.items()))
473 desc = WithDesc("".join(descparts))
475 return desc(TAnd(TDict,
476 compat.partial(_TStrictDictCheck, require_all, exclusive,
481 """Checks individual items of a container.
483 If the verified value and the list of expected items differ in length, this
484 check considers only as many items as are contained in the shorter list. Use
485 L{TIsLength} to enforce a certain length.
488 @param items: List of checks
491 assert items, "Need items"
493 text = ["Item", "item"]
494 desc = WithDesc(utils.CommaJoin("%s %s is %s" %
495 (text[int(idx > 0)], idx, Parens(check))
496 for (idx, check) in enumerate(items)))
498 return desc(lambda value: compat.all(check(i)
499 for (check, i) in zip(items, value)))