Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ ab98e236

History | View | Annotate | Download (10.7 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("ValueNone")
191
def TValueNone(val):
192
  """Checks if the given value is L{constants.VALUE_NONE}.
193

194
  """
195
  return val == constants.VALUE_NONE
196

    
197

    
198
@WithDesc("Boolean")
199
def TBool(val):
200
  """Checks if the given value is a boolean.
201

202
  """
203
  return isinstance(val, bool)
204

    
205

    
206
@WithDesc("Integer")
207
def TInt(val):
208
  """Checks if the given value is an integer.
209

210
  """
211
  # For backwards compatibility with older Python versions, boolean values are
212
  # also integers and should be excluded in this test.
213
  #
214
  # >>> (isinstance(False, int), isinstance(True, int))
215
  # (True, True)
216
  return isinstance(val, (int, long)) and not isinstance(val, bool)
217

    
218

    
219
@WithDesc("Float")
220
def TFloat(val):
221
  """Checks if the given value is a float.
222

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

    
226

    
227
@WithDesc("String")
228
def TString(val):
229
  """Checks if the given value is a string.
230

231
  """
232
  return isinstance(val, basestring)
233

    
234

    
235
@WithDesc("EvalToTrue")
236
def TTrue(val):
237
  """Checks if a given value evaluates to a boolean True value.
238

239
  """
240
  return bool(val)
241

    
242

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

246
  """
247
  def fn(val):
248
    return val in target_list
249

    
250
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
251

    
252

    
253
# Container types
254
@WithDesc("List")
255
def TList(val):
256
  """Checks if the given value is a list.
257

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

    
261

    
262
@WithDesc("Tuple")
263
def TTuple(val):
264
  """Checks if the given value is a tuple.
265

266
  """
267
  return isinstance(val, tuple)
268

    
269

    
270
@WithDesc("Dictionary")
271
def TDict(val):
272
  """Checks if the given value is a dictionary.
273

274
  """
275
  return isinstance(val, dict)
276

    
277

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

281
  """
282
  def fn(container):
283
    return len(container) == size
284

    
285
  return WithDesc("Length %s" % (size, ))(fn)
286

    
287

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

292
  """
293
  def fn(val):
294
    return compat.all(t(val) for t in args)
295

    
296
  return CombinationDesc("and", args, fn)
297

    
298

    
299
def TOr(*args):
300
  """Combine multiple functions using an OR operation.
301

302
  """
303
  def fn(val):
304
    return compat.any(t(val) for t in args)
305

    
306
  return CombinationDesc("or", args, fn)
307

    
308

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

312
  """
313
  return WithDesc("Result of %s must be %s" %
314
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
315

    
316

    
317
def TRegex(pobj):
318
  """Checks whether a string matches a specific regular expression.
319

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

322
  """
323
  desc = WithDesc("String matching regex \"%s\"" %
324
                  pobj.pattern.encode("string_escape"))
325

    
326
  return desc(TAnd(TString, pobj.match))
327

    
328

    
329
def TMaybe(test):
330
  """Wrap a test in a TOr(TNone, test).
331

332
  This makes it easier to define TMaybe* types.
333

334
  """
335
  return TOr(TNone, test)
336

    
337

    
338
def TMaybeValueNone(test):
339
  """Used for unsetting values.
340

341
  """
342
  return TMaybe(TOr(TValueNone, test))
343

    
344

    
345
# Type aliases
346

    
347
#: a non-empty string
348
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
349

    
350
#: a maybe non-empty string
351
TMaybeString = TMaybe(TNonEmptyString)
352

    
353
#: a maybe boolean (bool or none)
354
TMaybeBool = TMaybe(TBool)
355

    
356
#: Maybe a dictionary (dict or None)
357
TMaybeDict = TMaybe(TDict)
358

    
359
#: Maybe a list (list or None)
360
TMaybeList = TMaybe(TList)
361

    
362
#: a non-negative integer (value >= 0)
363
TNonNegativeInt = \
364
  TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
365

    
366
#: a positive integer (value > 0)
367
TPositiveInt = \
368
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
369

    
370
#: a maybe positive integer (positive integer or None)
371
TMaybePositiveInt = TMaybe(TPositiveInt)
372

    
373
#: a negative integer (value < 0)
374
TNegativeInt = \
375
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
376

    
377
#: a positive float
378
TNonNegativeFloat = \
379
  TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
380

    
381
#: Job ID
382
TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
383
                               TRegex(re.compile("^%s$" %
384
                                                 constants.JOB_ID_TEMPLATE))))
385

    
386
#: Number
387
TNumber = TOr(TInt, TFloat)
388

    
389
#: Relative job ID
390
TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
391

    
392

    
393
def TInstanceOf(cls):
394
  """Checks if a given value is an instance of C{cls}.
395

396
  @type cls: class
397
  @param cls: Class object
398

399
  """
400
  name = "%s.%s" % (cls.__module__, cls.__name__)
401

    
402
  desc = WithDesc("Instance of %s" % (Parens(name), ))
403

    
404
  return desc(lambda val: isinstance(val, cls))
405

    
406

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

410
  """
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)))
413

    
414

    
415
TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
416

    
417

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

421
  """
422
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
423
                  (Parens(key_type), Parens(val_type)))
424

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

    
429
  return desc(TAnd(TDict, fn))
430

    
431

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

435
  """
436
  notfound_fn = lambda _: not exclusive
437

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

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

    
445

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

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
455

456
  """
457
  descparts = ["Dictionary containing"]
458

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

    
462
  if require_all:
463
    descparts.append(" required")
464

    
465
  if len(items) == 1:
466
    descparts.append(" key ")
467
  else:
468
    descparts.append(" keys ")
469

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

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

    
475
  return desc(TAnd(TDict,
476
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
477
                                  items)))
478

    
479

    
480
def TItems(items):
481
  """Checks individual items of a container.
482

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.
486

487
  @type items: list
488
  @param items: List of checks
489

490
  """
491
  assert items, "Need items"
492

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

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