X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/a464ce7135ef8e0f7b107e80d978e2bd4f00cdf4..3697def03c4778bd13f365e882e1131cb1e3e09b:/lib/ht.py diff --git a/lib/ht.py b/lib/ht.py index a0c787e..a449e82 100644 --- a/lib/ht.py +++ b/lib/ht.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2010, 2011 Google Inc. +# Copyright (C) 2010, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,9 +22,11 @@ """Module implementing the parameter types code.""" import re +import operator from ganeti import compat from ganeti import utils +from ganeti import constants _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$") @@ -44,6 +46,44 @@ def Parens(text): return "(%s)" % text +class _WrapperBase(object): + __slots__ = [ + "_fn", + "_text", + ] + + def __init__(self, text, fn): + """Initializes this class. + + @param text: Description + @param fn: Wrapped function + + """ + assert text.strip() + + self._text = text + self._fn = fn + + def __call__(self, *args): + return self._fn(*args) + + +class _DescWrapper(_WrapperBase): + """Wrapper class for description text. + + """ + def __str__(self): + return self._text + + +class _CommentWrapper(_WrapperBase): + """Wrapper class for comment. + + """ + def __str__(self): + return "%s [%s]" % (self._fn, self._text) + + def WithDesc(text): """Builds wrapper class with description text. @@ -54,21 +94,20 @@ def WithDesc(text): """ assert text[0] == text[0].upper() - class wrapper(object): # pylint: disable-msg=C0103 - __slots__ = ["__call__"] + return compat.partial(_DescWrapper, text) - def __init__(self, fn): - """Initializes this class. - @param fn: Wrapped function +def Comment(text): + """Builds wrapper for adding comment to description text. - """ - self.__call__ = fn + @type text: string + @param text: Comment text + @return: Callable class - def __str__(self): - return text + """ + assert not frozenset(text).intersection("[]") - return wrapper + return compat.partial(_CommentWrapper, text) def CombinationDesc(op, args, fn): @@ -82,6 +121,12 @@ def CombinationDesc(op, args, fn): @param fn: Wrapped function """ + # Some type descriptions are rather long. If "None" is listed at the + # end or somewhere in between it is easily missed. Therefore it should + # be at the beginning, e.g. "None or (long description)". + if __debug__ and TNone in args and args.index(TNone) > 0: + raise Exception("TNone must be listed first") + if len(args) == 1: descr = str(args[0]) else: @@ -118,6 +163,14 @@ NoType = object() # Some basic types +@WithDesc("Anything") +def TAny(_): + """Accepts any value. + + """ + return True + + @WithDesc("NotNone") def TNotNone(val): """Checks if the given value is not None. @@ -134,6 +187,14 @@ def TNone(val): return val is None +@WithDesc("ValueNone") +def TValueNone(val): + """Checks if the given value is L{constants.VALUE_NONE}. + + """ + return val == constants.VALUE_NONE + + @WithDesc("Boolean") def TBool(val): """Checks if the given value is a boolean. @@ -152,7 +213,7 @@ def TInt(val): # # >>> (isinstance(False, int), isinstance(True, int)) # (True, True) - return isinstance(val, int) and not isinstance(val, bool) + return isinstance(val, (int, long)) and not isinstance(val, bool) @WithDesc("Float") @@ -198,6 +259,14 @@ def TList(val): return isinstance(val, list) +@WithDesc("Tuple") +def TTuple(val): + """Checks if the given value is a tuple. + + """ + return isinstance(val, tuple) + + @WithDesc("Dictionary") def TDict(val): """Checks if the given value is a dictionary. @@ -245,31 +314,91 @@ def TMap(fn, test): (Parens(fn), Parens(test)))(lambda val: test(fn(val))) +def TRegex(pobj): + """Checks whether a string matches a specific regular expression. + + @param pobj: Compiled regular expression as returned by C{re.compile} + + """ + desc = WithDesc("String matching regex \"%s\"" % + pobj.pattern.encode("string_escape")) + + return desc(TAnd(TString, pobj.match)) + + +def TMaybe(test): + """Wrap a test in a TOr(TNone, test). + + This makes it easier to define TMaybe* types. + + """ + return TOr(TNone, test) + + +def TMaybeValueNone(test): + """Used for unsetting values. + + """ + return TMaybe(TOr(TValueNone, test)) + + # Type aliases #: a non-empty string TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue)) #: a maybe non-empty string -TMaybeString = TOr(TNonEmptyString, TNone) +TMaybeString = TMaybe(TNonEmptyString) #: a maybe boolean (bool or none) -TMaybeBool = TOr(TBool, TNone) +TMaybeBool = TMaybe(TBool) #: Maybe a dictionary (dict or None) -TMaybeDict = TOr(TDict, TNone) +TMaybeDict = TMaybe(TDict) -#: a positive integer -TPositiveInt = \ - TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0)) +#: a non-negative integer (value >= 0) +TNonNegativeInt = \ + TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0)) -#: a strictly positive integer -TStrictPositiveInt = \ +#: a positive integer (value > 0) +TPositiveInt = \ TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0)) +#: a maybe positive integer (positive integer or None) +TMaybePositiveInt = TMaybe(TPositiveInt) + +#: a negative integer (value < 0) +TNegativeInt = \ + TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0))) + #: a positive float -TPositiveFloat = \ - TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0)) +TNonNegativeFloat = \ + TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0)) + +#: Job ID +TJobId = WithDesc("JobId")(TOr(TNonNegativeInt, + TRegex(re.compile("^%s$" % + constants.JOB_ID_TEMPLATE)))) + +#: Number +TNumber = TOr(TInt, TFloat) + +#: Relative job ID +TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt) + + +def TInstanceOf(cls): + """Checks if a given value is an instance of C{cls}. + + @type cls: class + @param cls: Class object + + """ + name = "%s.%s" % (cls.__module__, cls.__name__) + + desc = WithDesc("Instance of %s" % (Parens(name), )) + + return desc(lambda val: isinstance(val, cls)) def TListOf(my_type): @@ -280,6 +409,9 @@ def TListOf(my_type): return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst))) +TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type)) + + def TDictOf(key_type, val_type): """Checks a dict type for the type of its key/values. @@ -340,3 +472,25 @@ def TStrictDict(require_all, exclusive, items): return desc(TAnd(TDict, compat.partial(_TStrictDictCheck, require_all, exclusive, items))) + + +def TItems(items): + """Checks individual items of a container. + + If the verified value and the list of expected items differ in length, this + check considers only as many items as are contained in the shorter list. Use + L{TIsLength} to enforce a certain length. + + @type items: list + @param items: List of checks + + """ + assert items, "Need items" + + text = ["Item", "item"] + desc = WithDesc(utils.CommaJoin("%s %s is %s" % + (text[int(idx > 0)], idx, Parens(check)) + for (idx, check) in enumerate(items))) + + return desc(lambda value: compat.all(check(i) + for (check, i) in zip(items, value)))