ht.py 8.42 KB
Newer Older
1
2
3
#
#

4
# Copyright (C) 2010, 2011 Google Inc.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Module implementing the parameter types code."""

24
25
import re

26
from ganeti import compat
27
from ganeti import utils
28
from ganeti import constants
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92


_PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")


def Parens(text):
  """Enclose text in parens if necessary.

  @param text: Text

  """
  text = str(text)

  if _PAREN_RE.match(text):
    return text
  else:
    return "(%s)" % text


def WithDesc(text):
  """Builds wrapper class with description text.

  @type text: string
  @param text: Description text
  @return: Callable class

  """
  assert text[0] == text[0].upper()

  class wrapper(object): # pylint: disable-msg=C0103
    __slots__ = ["__call__"]

    def __init__(self, fn):
      """Initializes this class.

      @param fn: Wrapped function

      """
      self.__call__ = fn

    def __str__(self):
      return text

  return wrapper


def CombinationDesc(op, args, fn):
  """Build description for combinating operator.

  @type op: string
  @param op: Operator as text (e.g. "and")
  @type args: list
  @param args: Operator arguments
  @type fn: callable
  @param fn: Wrapped function

  """
  if len(args) == 1:
    descr = str(args[0])
  else:
    descr = (" %s " % op).join(Parens(i) for i in args)

  return WithDesc(descr)(fn)

93
94
95
96

# Modifiable default values; need to define these here before the
# actual LUs

97
@WithDesc(str([]))
98
99
100
101
102
103
104
def EmptyList():
  """Returns an empty list.

  """
  return []


105
@WithDesc(str({}))
106
107
108
109
110
111
112
113
114
115
116
def EmptyDict():
  """Returns an empty dict.

  """
  return {}


#: The without-default default value
NoDefault = object()


117
#: The no-type (value too complex to check it in the type system)
118
119
120
121
NoType = object()


# Some basic types
122
123
124
125
126
127
128
129
@WithDesc("Anything")
def TAny(_):
  """Accepts any value.

  """
  return True


130
@WithDesc("NotNone")
131
132
133
134
135
136
137
def TNotNone(val):
  """Checks if the given value is not None.

  """
  return val is not None


138
@WithDesc("None")
139
140
141
142
143
144
145
def TNone(val):
  """Checks if the given value is None.

  """
  return val is None


146
@WithDesc("Boolean")
147
148
149
150
151
152
153
def TBool(val):
  """Checks if the given value is a boolean.

  """
  return isinstance(val, bool)


154
@WithDesc("Integer")
155
156
157
158
def TInt(val):
  """Checks if the given value is an integer.

  """
159
160
161
162
163
164
  # For backwards compatibility with older Python versions, boolean values are
  # also integers and should be excluded in this test.
  #
  # >>> (isinstance(False, int), isinstance(True, int))
  # (True, True)
  return isinstance(val, int) and not isinstance(val, bool)
165
166


167
@WithDesc("Float")
168
169
170
171
172
173
174
def TFloat(val):
  """Checks if the given value is a float.

  """
  return isinstance(val, float)


175
@WithDesc("String")
176
177
178
179
180
181
182
def TString(val):
  """Checks if the given value is a string.

  """
  return isinstance(val, basestring)


183
@WithDesc("EvalToTrue")
184
185
186
187
188
189
190
191
192
193
194
def TTrue(val):
  """Checks if a given value evaluates to a boolean True value.

  """
  return bool(val)


def TElemOf(target_list):
  """Builds a function that checks if a given value is a member of a list.

  """
195
196
197
198
  def fn(val):
    return val in target_list

  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
199
200
201


# Container types
202
@WithDesc("List")
203
204
205
206
207
208
209
def TList(val):
  """Checks if the given value is a list.

  """
  return isinstance(val, list)


210
@WithDesc("Dictionary")
211
212
213
214
215
216
217
218
219
220
221
def TDict(val):
  """Checks if the given value is a dictionary.

  """
  return isinstance(val, dict)


def TIsLength(size):
  """Check is the given container is of the given size.

  """
222
223
224
225
  def fn(container):
    return len(container) == size

  return WithDesc("Length %s" % (size, ))(fn)
226
227
228
229
230
231
232
233
234


# Combinator types
def TAnd(*args):
  """Combine multiple functions using an AND operation.

  """
  def fn(val):
    return compat.all(t(val) for t in args)
235
236

  return CombinationDesc("and", args, fn)
237
238
239
240
241
242
243
244


def TOr(*args):
  """Combine multiple functions using an AND operation.

  """
  def fn(val):
    return compat.any(t(val) for t in args)
245
246

  return CombinationDesc("or", args, fn)
247
248
249
250
251
252


def TMap(fn, test):
  """Checks that a modified version of the argument passes the given test.

  """
253
254
  return WithDesc("Result of %s must be %s" %
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
255
256


257
258
259
260
261
262
263
264
265
266
267
268
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))


269
270
271
# Type aliases

#: a non-empty string
272
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
273
274
275
276
277
278
279

#: a maybe non-empty string
TMaybeString = TOr(TNonEmptyString, TNone)

#: a maybe boolean (bool or none)
TMaybeBool = TOr(TBool, TNone)

Michael Hanselmann's avatar
Michael Hanselmann committed
280
281
#: Maybe a dictionary (dict or None)
TMaybeDict = TOr(TDict, TNone)
282
283

#: a positive integer
284
285
TPositiveInt = \
  TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
286
287

#: a strictly positive integer
288
289
TStrictPositiveInt = \
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
290

291
292
293
294
#: a positive float
TPositiveFloat = \
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))

295
296
297
298
#: Job ID
TJobId = TOr(TPositiveInt,
             TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))

299
300
301
302
303

def TListOf(my_type):
  """Checks if a given value is a list with all elements of the same type.

  """
304
305
  desc = WithDesc("List of %s" % (Parens(my_type), ))
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
306
307
308
309
310
311


def TDictOf(key_type, val_type):
  """Checks a dict type for the type of its key/values.

  """
312
313
314
315
316
317
318
319
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
                  (Parens(key_type), Parens(val_type)))

  def fn(container):
    return (compat.all(key_type(v) for v in container.keys()) and
            compat.all(val_type(v) for v in container.values()))

  return desc(TAnd(TDict, fn))
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367


def _TStrictDictCheck(require_all, exclusive, items, val):
  """Helper function for L{TStrictDict}.

  """
  notfound_fn = lambda _: not exclusive

  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
    # Requires items not found in value
    return False

  return compat.all(items.get(key, notfound_fn)(value)
                    for (key, value) in val.items())


def TStrictDict(require_all, exclusive, items):
  """Strict dictionary check with specific keys.

  @type require_all: boolean
  @param require_all: Whether all keys in L{items} are required
  @type exclusive: boolean
  @param exclusive: Whether only keys listed in L{items} should be accepted
  @type items: dictionary
  @param items: Mapping from key (string) to verification function

  """
  descparts = ["Dictionary containing"]

  if exclusive:
    descparts.append(" none but the")

  if require_all:
    descparts.append(" required")

  if len(items) == 1:
    descparts.append(" key ")
  else:
    descparts.append(" keys ")

  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
                                   for (key, value) in items.items()))

  desc = WithDesc("".join(descparts))

  return desc(TAnd(TDict,
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
                                  items)))
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389


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)))