Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ bdfd7802

History | View | Annotate | Download (8.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011 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 _DescWrapper(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
    self._text = text
63
    self._fn = fn
64

    
65
  def __call__(self, *args):
66
    return self._fn(*args)
67

    
68
  def __str__(self):
69
    return self._text
70

    
71

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

75
  @type text: string
76
  @param text: Description text
77
  @return: Callable class
78

79
  """
80
  assert text[0] == text[0].upper()
81

    
82
  return compat.partial(_DescWrapper, text)
83

    
84

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

88
  @type op: string
89
  @param op: Operator as text (e.g. "and")
90
  @type args: list
91
  @param args: Operator arguments
92
  @type fn: callable
93
  @param fn: Wrapped function
94

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

    
101
  return WithDesc(descr)(fn)
102

    
103

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

    
107
@WithDesc(str([]))
108
def EmptyList():
109
  """Returns an empty list.
110

111
  """
112
  return []
113

    
114

    
115
@WithDesc(str({}))
116
def EmptyDict():
117
  """Returns an empty dict.
118

119
  """
120
  return {}
121

    
122

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

    
126

    
127
#: The no-type (value too complex to check it in the type system)
128
NoType = object()
129

    
130

    
131
# Some basic types
132
@WithDesc("Anything")
133
def TAny(_):
134
  """Accepts any value.
135

136
  """
137
  return True
138

    
139

    
140
@WithDesc("NotNone")
141
def TNotNone(val):
142
  """Checks if the given value is not None.
143

144
  """
145
  return val is not None
146

    
147

    
148
@WithDesc("None")
149
def TNone(val):
150
  """Checks if the given value is None.
151

152
  """
153
  return val is None
154

    
155

    
156
@WithDesc("Boolean")
157
def TBool(val):
158
  """Checks if the given value is a boolean.
159

160
  """
161
  return isinstance(val, bool)
162

    
163

    
164
@WithDesc("Integer")
165
def TInt(val):
166
  """Checks if the given value is an integer.
167

168
  """
169
  # For backwards compatibility with older Python versions, boolean values are
170
  # also integers and should be excluded in this test.
171
  #
172
  # >>> (isinstance(False, int), isinstance(True, int))
173
  # (True, True)
174
  return isinstance(val, (int, long)) and not isinstance(val, bool)
175

    
176

    
177
@WithDesc("Float")
178
def TFloat(val):
179
  """Checks if the given value is a float.
180

181
  """
182
  return isinstance(val, float)
183

    
184

    
185
@WithDesc("String")
186
def TString(val):
187
  """Checks if the given value is a string.
188

189
  """
190
  return isinstance(val, basestring)
191

    
192

    
193
@WithDesc("EvalToTrue")
194
def TTrue(val):
195
  """Checks if a given value evaluates to a boolean True value.
196

197
  """
198
  return bool(val)
199

    
200

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

204
  """
205
  def fn(val):
206
    return val in target_list
207

    
208
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
209

    
210

    
211
# Container types
212
@WithDesc("List")
213
def TList(val):
214
  """Checks if the given value is a list.
215

216
  """
217
  return isinstance(val, list)
218

    
219

    
220
@WithDesc("Dictionary")
221
def TDict(val):
222
  """Checks if the given value is a dictionary.
223

224
  """
225
  return isinstance(val, dict)
226

    
227

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

231
  """
232
  def fn(container):
233
    return len(container) == size
234

    
235
  return WithDesc("Length %s" % (size, ))(fn)
236

    
237

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

242
  """
243
  def fn(val):
244
    return compat.all(t(val) for t in args)
245

    
246
  return CombinationDesc("and", args, fn)
247

    
248

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

252
  """
253
  def fn(val):
254
    return compat.any(t(val) for t in args)
255

    
256
  return CombinationDesc("or", args, fn)
257

    
258

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

262
  """
263
  return WithDesc("Result of %s must be %s" %
264
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
265

    
266

    
267
def TRegex(pobj):
268
  """Checks whether a string matches a specific regular expression.
269

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

272
  """
273
  desc = WithDesc("String matching regex \"%s\"" %
274
                  pobj.pattern.encode("string_escape"))
275

    
276
  return desc(TAnd(TString, pobj.match))
277

    
278

    
279
# Type aliases
280

    
281
#: a non-empty string
282
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
283

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

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

    
290
#: Maybe a dictionary (dict or None)
291
TMaybeDict = TOr(TDict, TNone)
292

    
293
#: a positive integer
294
TPositiveInt = \
295
  TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
296

    
297
#: a strictly positive integer
298
TStrictPositiveInt = \
299
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
300

    
301
#: a strictly negative integer (0 > value)
302
TStrictNegativeInt = \
303
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
304

    
305
#: a positive float
306
TPositiveFloat = \
307
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
308

    
309
#: Job ID
310
TJobId = WithDesc("JobId")(TOr(TPositiveInt,
311
                               TRegex(re.compile("^%s$" %
312
                                                 constants.JOB_ID_TEMPLATE))))
313

    
314
#: Number
315
TNumber = TOr(TInt, TFloat)
316

    
317
#: Relative job ID
318
TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
319

    
320

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

324
  """
325
  desc = WithDesc("List of %s" % (Parens(my_type), ))
326
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
327

    
328

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

332
  """
333
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
334
                  (Parens(key_type), Parens(val_type)))
335

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

    
340
  return desc(TAnd(TDict, fn))
341

    
342

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

346
  """
347
  notfound_fn = lambda _: not exclusive
348

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

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

    
356

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

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

367
  """
368
  descparts = ["Dictionary containing"]
369

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

    
373
  if require_all:
374
    descparts.append(" required")
375

    
376
  if len(items) == 1:
377
    descparts.append(" key ")
378
  else:
379
    descparts.append(" keys ")
380

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

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

    
386
  return desc(TAnd(TDict,
387
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
388
                                  items)))
389

    
390

    
391
def TItems(items):
392
  """Checks individual items of a container.
393

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

398
  @type items: list
399
  @param items: List of checks
400

401
  """
402
  assert items, "Need items"
403

    
404
  text = ["Item", "item"]
405
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
406
                                  (text[int(idx > 0)], idx, Parens(check))
407
                                  for (idx, check) in enumerate(items)))
408

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