Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 2c9fa1ff

History | View | Annotate | Download (9.8 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
  if len(args) == 1:
125
    descr = str(args[0])
126
  else:
127
    descr = (" %s " % op).join(Parens(i) for i in args)
128

    
129
  return WithDesc(descr)(fn)
130

    
131

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

    
135
@WithDesc(str([]))
136
def EmptyList():
137
  """Returns an empty list.
138

139
  """
140
  return []
141

    
142

    
143
@WithDesc(str({}))
144
def EmptyDict():
145
  """Returns an empty dict.
146

147
  """
148
  return {}
149

    
150

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

    
154

    
155
#: The no-type (value too complex to check it in the type system)
156
NoType = object()
157

    
158

    
159
# Some basic types
160
@WithDesc("Anything")
161
def TAny(_):
162
  """Accepts any value.
163

164
  """
165
  return True
166

    
167

    
168
@WithDesc("NotNone")
169
def TNotNone(val):
170
  """Checks if the given value is not None.
171

172
  """
173
  return val is not None
174

    
175

    
176
@WithDesc("None")
177
def TNone(val):
178
  """Checks if the given value is None.
179

180
  """
181
  return val is None
182

    
183

    
184
@WithDesc("Boolean")
185
def TBool(val):
186
  """Checks if the given value is a boolean.
187

188
  """
189
  return isinstance(val, bool)
190

    
191

    
192
@WithDesc("Integer")
193
def TInt(val):
194
  """Checks if the given value is an integer.
195

196
  """
197
  # For backwards compatibility with older Python versions, boolean values are
198
  # also integers and should be excluded in this test.
199
  #
200
  # >>> (isinstance(False, int), isinstance(True, int))
201
  # (True, True)
202
  return isinstance(val, (int, long)) and not isinstance(val, bool)
203

    
204

    
205
@WithDesc("Float")
206
def TFloat(val):
207
  """Checks if the given value is a float.
208

209
  """
210
  return isinstance(val, float)
211

    
212

    
213
@WithDesc("String")
214
def TString(val):
215
  """Checks if the given value is a string.
216

217
  """
218
  return isinstance(val, basestring)
219

    
220

    
221
@WithDesc("EvalToTrue")
222
def TTrue(val):
223
  """Checks if a given value evaluates to a boolean True value.
224

225
  """
226
  return bool(val)
227

    
228

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

232
  """
233
  def fn(val):
234
    return val in target_list
235

    
236
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
237

    
238

    
239
# Container types
240
@WithDesc("List")
241
def TList(val):
242
  """Checks if the given value is a list.
243

244
  """
245
  return isinstance(val, list)
246

    
247

    
248
@WithDesc("Dictionary")
249
def TDict(val):
250
  """Checks if the given value is a dictionary.
251

252
  """
253
  return isinstance(val, dict)
254

    
255

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

259
  """
260
  def fn(container):
261
    return len(container) == size
262

    
263
  return WithDesc("Length %s" % (size, ))(fn)
264

    
265

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

270
  """
271
  def fn(val):
272
    return compat.all(t(val) for t in args)
273

    
274
  return CombinationDesc("and", args, fn)
275

    
276

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

280
  """
281
  def fn(val):
282
    return compat.any(t(val) for t in args)
283

    
284
  return CombinationDesc("or", args, fn)
285

    
286

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

290
  """
291
  return WithDesc("Result of %s must be %s" %
292
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
293

    
294

    
295
def TRegex(pobj):
296
  """Checks whether a string matches a specific regular expression.
297

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

300
  """
301
  desc = WithDesc("String matching regex \"%s\"" %
302
                  pobj.pattern.encode("string_escape"))
303

    
304
  return desc(TAnd(TString, pobj.match))
305

    
306

    
307
# Type aliases
308

    
309
#: a non-empty string
310
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
311

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

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

    
318
#: Maybe a dictionary (dict or None)
319
TMaybeDict = TOr(TDict, TNone)
320

    
321
#: a non-negative integer (value >= 0)
322
TNonNegativeInt = \
323
  TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
324

    
325
#: a positive integer (value > 0)
326
TPositiveInt = \
327
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
328

    
329
#: a maybe positive integer (positive integer or None)
330
TMaybePositiveInt = TOr(TPositiveInt, TNone)
331

    
332
#: a negative integer (value < 0)
333
TNegativeInt = \
334
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
335

    
336
#: a positive float
337
TNonNegativeFloat = \
338
  TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
339

    
340
#: Job ID
341
TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
342
                               TRegex(re.compile("^%s$" %
343
                                                 constants.JOB_ID_TEMPLATE))))
344

    
345
#: Number
346
TNumber = TOr(TInt, TFloat)
347

    
348
#: Relative job ID
349
TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
350

    
351

    
352
def TInstanceOf(my_inst):
353
  """Checks if a given value is an instance of my_inst.
354

355
  """
356
  desc = WithDesc("Instance of %s" % (Parens(my_inst), ))
357
  return desc(lambda val: isinstance(val, my_inst))
358

    
359

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

363
  """
364
  desc = WithDesc("List of %s" % (Parens(my_type), ))
365
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
366

    
367

    
368
TMaybeListOf = lambda item_type: TOr(TNone, TListOf(item_type))
369

    
370

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

374
  """
375
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
376
                  (Parens(key_type), Parens(val_type)))
377

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

    
382
  return desc(TAnd(TDict, fn))
383

    
384

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

388
  """
389
  notfound_fn = lambda _: not exclusive
390

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

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

    
398

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

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

409
  """
410
  descparts = ["Dictionary containing"]
411

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

    
415
  if require_all:
416
    descparts.append(" required")
417

    
418
  if len(items) == 1:
419
    descparts.append(" key ")
420
  else:
421
    descparts.append(" keys ")
422

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

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

    
428
  return desc(TAnd(TDict,
429
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
430
                                  items)))
431

    
432

    
433
def TItems(items):
434
  """Checks individual items of a container.
435

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

440
  @type items: list
441
  @param items: List of checks
442

443
  """
444
  assert items, "Need items"
445

    
446
  text = ["Item", "item"]
447
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
448
                                  (text[int(idx > 0)], idx, Parens(check))
449
                                  for (idx, check) in enumerate(items)))
450

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