Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ a138ead7

History | View | Annotate | Download (10.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
20

    
21

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

    
24
import re
25
import operator
26

    
27
from ganeti import compat
28
from ganeti import utils
29
from ganeti import constants
30

    
31

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

    
34

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

38
  @param text: Text
39

40
  """
41
  text = str(text)
42

    
43
  if _PAREN_RE.match(text):
44
    return text
45
  else:
46
    return "(%s)" % text
47

    
48

    
49
class _WrapperBase(object):
50
  __slots__ = [
51
    "_fn",
52
    "_text",
53
    ]
54

    
55
  def __init__(self, text, fn):
56
    """Initializes this class.
57

58
    @param text: Description
59
    @param fn: Wrapped function
60

61
    """
62
    assert text.strip()
63

    
64
    self._text = text
65
    self._fn = fn
66

    
67
  def __call__(self, *args):
68
    return self._fn(*args)
69

    
70

    
71
class _DescWrapper(_WrapperBase):
72
  """Wrapper class for description text.
73

74
  """
75
  def __str__(self):
76
    return self._text
77

    
78

    
79
class _CommentWrapper(_WrapperBase):
80
  """Wrapper class for comment.
81

82
  """
83
  def __str__(self):
84
    return "%s [%s]" % (self._fn, self._text)
85

    
86

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

90
  @type text: string
91
  @param text: Description text
92
  @return: Callable class
93

94
  """
95
  assert text[0] == text[0].upper()
96

    
97
  return compat.partial(_DescWrapper, text)
98

    
99

    
100
def Comment(text):
101
  """Builds wrapper for adding comment to description text.
102

103
  @type text: string
104
  @param text: Comment text
105
  @return: Callable class
106

107
  """
108
  assert not frozenset(text).intersection("[]")
109

    
110
  return compat.partial(_CommentWrapper, text)
111

    
112

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

116
  @type op: string
117
  @param op: Operator as text (e.g. "and")
118
  @type args: list
119
  @param args: Operator arguments
120
  @type fn: callable
121
  @param fn: Wrapped function
122

123
  """
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")
129

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

    
135
  return WithDesc(descr)(fn)
136

    
137

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

    
141
@WithDesc(str([]))
142
def EmptyList():
143
  """Returns an empty list.
144

145
  """
146
  return []
147

    
148

    
149
@WithDesc(str({}))
150
def EmptyDict():
151
  """Returns an empty dict.
152

153
  """
154
  return {}
155

    
156

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

    
160

    
161
#: The no-type (value too complex to check it in the type system)
162
NoType = object()
163

    
164

    
165
# Some basic types
166
@WithDesc("Anything")
167
def TAny(_):
168
  """Accepts any value.
169

170
  """
171
  return True
172

    
173

    
174
@WithDesc("NotNone")
175
def TNotNone(val):
176
  """Checks if the given value is not None.
177

178
  """
179
  return val is not None
180

    
181

    
182
@WithDesc("None")
183
def TNone(val):
184
  """Checks if the given value is None.
185

186
  """
187
  return val is None
188

    
189

    
190
@WithDesc("Boolean")
191
def TBool(val):
192
  """Checks if the given value is a boolean.
193

194
  """
195
  return isinstance(val, bool)
196

    
197

    
198
@WithDesc("Integer")
199
def TInt(val):
200
  """Checks if the given value is an integer.
201

202
  """
203
  # For backwards compatibility with older Python versions, boolean values are
204
  # also integers and should be excluded in this test.
205
  #
206
  # >>> (isinstance(False, int), isinstance(True, int))
207
  # (True, True)
208
  return isinstance(val, (int, long)) and not isinstance(val, bool)
209

    
210

    
211
@WithDesc("Float")
212
def TFloat(val):
213
  """Checks if the given value is a float.
214

215
  """
216
  return isinstance(val, float)
217

    
218

    
219
@WithDesc("String")
220
def TString(val):
221
  """Checks if the given value is a string.
222

223
  """
224
  return isinstance(val, basestring)
225

    
226

    
227
@WithDesc("EvalToTrue")
228
def TTrue(val):
229
  """Checks if a given value evaluates to a boolean True value.
230

231
  """
232
  return bool(val)
233

    
234

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

238
  """
239
  def fn(val):
240
    return val in target_list
241

    
242
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
243

    
244

    
245
# Container types
246
@WithDesc("List")
247
def TList(val):
248
  """Checks if the given value is a list.
249

250
  """
251
  return isinstance(val, list)
252

    
253

    
254
@WithDesc("Dictionary")
255
def TDict(val):
256
  """Checks if the given value is a dictionary.
257

258
  """
259
  return isinstance(val, dict)
260

    
261

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

265
  """
266
  def fn(container):
267
    return len(container) == size
268

    
269
  return WithDesc("Length %s" % (size, ))(fn)
270

    
271

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

276
  """
277
  def fn(val):
278
    return compat.all(t(val) for t in args)
279

    
280
  return CombinationDesc("and", args, fn)
281

    
282

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

286
  """
287
  def fn(val):
288
    return compat.any(t(val) for t in args)
289

    
290
  return CombinationDesc("or", args, fn)
291

    
292

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

296
  """
297
  return WithDesc("Result of %s must be %s" %
298
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
299

    
300

    
301
def TRegex(pobj):
302
  """Checks whether a string matches a specific regular expression.
303

304
  @param pobj: Compiled regular expression as returned by C{re.compile}
305

306
  """
307
  desc = WithDesc("String matching regex \"%s\"" %
308
                  pobj.pattern.encode("string_escape"))
309

    
310
  return desc(TAnd(TString, pobj.match))
311

    
312

    
313
# Type aliases
314

    
315
#: a non-empty string
316
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
317

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

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

    
324
#: Maybe a dictionary (dict or None)
325
TMaybeDict = TOr(TNone, TDict)
326

    
327
#: a non-negative integer (value >= 0)
328
TNonNegativeInt = \
329
  TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
330

    
331
#: a positive integer (value > 0)
332
TPositiveInt = \
333
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
334

    
335
#: a maybe positive integer (positive integer or None)
336
TMaybePositiveInt = TOr(TNone, TPositiveInt)
337

    
338
#: a negative integer (value < 0)
339
TNegativeInt = \
340
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
341

    
342
#: a positive float
343
TNonNegativeFloat = \
344
  TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
345

    
346
#: Job ID
347
TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
348
                               TRegex(re.compile("^%s$" %
349
                                                 constants.JOB_ID_TEMPLATE))))
350

    
351
#: Number
352
TNumber = TOr(TInt, TFloat)
353

    
354
#: Relative job ID
355
TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
356

    
357

    
358
def TInstanceOf(my_inst):
359
  """Checks if a given value is an instance of my_inst.
360

361
  """
362
  desc = WithDesc("Instance of %s" % (Parens(my_inst), ))
363
  return desc(lambda val: isinstance(val, my_inst))
364

    
365

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

369
  """
370
  desc = WithDesc("List of %s" % (Parens(my_type), ))
371
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
372

    
373

    
374
TMaybeListOf = lambda item_type: TOr(TNone, TListOf(item_type))
375

    
376

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

380
  """
381
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
382
                  (Parens(key_type), Parens(val_type)))
383

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

    
388
  return desc(TAnd(TDict, fn))
389

    
390

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

394
  """
395
  notfound_fn = lambda _: not exclusive
396

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

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

    
404

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

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

415
  """
416
  descparts = ["Dictionary containing"]
417

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

    
421
  if require_all:
422
    descparts.append(" required")
423

    
424
  if len(items) == 1:
425
    descparts.append(" key ")
426
  else:
427
    descparts.append(" keys ")
428

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

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

    
434
  return desc(TAnd(TDict,
435
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
436
                                  items)))
437

    
438

    
439
def TItems(items):
440
  """Checks individual items of a container.
441

442
  If the verified value and the list of expected items differ in length, this
443
  check considers only as many items as are contained in the shorter list. Use
444
  L{TIsLength} to enforce a certain length.
445

446
  @type items: list
447
  @param items: List of checks
448

449
  """
450
  assert items, "Need items"
451

    
452
  text = ["Item", "item"]
453
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
454
                                  (text[int(idx > 0)], idx, Parens(check))
455
                                  for (idx, check) in enumerate(items)))
456

    
457
  return desc(lambda value: compat.all(check(i)
458
                                       for (check, i) in zip(items, value)))