Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ b7a1c816

History | View | Annotate | Download (8.4 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

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

    
30

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

    
33

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

37
  @param text: Text
38

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

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

    
47

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

51
  @type text: string
52
  @param text: Description text
53
  @return: Callable class
54

55
  """
56
  assert text[0] == text[0].upper()
57

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

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

64
      @param fn: Wrapped function
65

66
      """
67
      self.__call__ = fn
68

    
69
    def __str__(self):
70
      return text
71

    
72
  return wrapper
73

    
74

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

78
  @type op: string
79
  @param op: Operator as text (e.g. "and")
80
  @type args: list
81
  @param args: Operator arguments
82
  @type fn: callable
83
  @param fn: Wrapped function
84

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

    
91
  return WithDesc(descr)(fn)
92

    
93

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

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

101
  """
102
  return []
103

    
104

    
105
@WithDesc(str({}))
106
def EmptyDict():
107
  """Returns an empty dict.
108

109
  """
110
  return {}
111

    
112

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

    
116

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

    
120

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

126
  """
127
  return True
128

    
129

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

134
  """
135
  return val is not None
136

    
137

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

142
  """
143
  return val is None
144

    
145

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

150
  """
151
  return isinstance(val, bool)
152

    
153

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

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

    
166

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

171
  """
172
  return isinstance(val, float)
173

    
174

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

179
  """
180
  return isinstance(val, basestring)
181

    
182

    
183
@WithDesc("EvalToTrue")
184
def TTrue(val):
185
  """Checks if a given value evaluates to a boolean True value.
186

187
  """
188
  return bool(val)
189

    
190

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

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

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

    
200

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

206
  """
207
  return isinstance(val, list)
208

    
209

    
210
@WithDesc("Dictionary")
211
def TDict(val):
212
  """Checks if the given value is a dictionary.
213

214
  """
215
  return isinstance(val, dict)
216

    
217

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

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

    
225
  return WithDesc("Length %s" % (size, ))(fn)
226

    
227

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

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

    
236
  return CombinationDesc("and", args, fn)
237

    
238

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

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

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

    
248

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

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

    
256

    
257
def TRegex(pobj):
258
  """Checks whether a string matches a specific regular expression.
259

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

262
  """
263
  desc = WithDesc("String matching regex \"%s\"" %
264
                  pobj.pattern.encode("string_escape"))
265

    
266
  return desc(TAnd(TString, pobj.match))
267

    
268

    
269
# Type aliases
270

    
271
#: a non-empty string
272
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
273

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

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

    
280
#: Maybe a dictionary (dict or None)
281
TMaybeDict = TOr(TDict, TNone)
282

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

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

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

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

    
299

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

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

    
307

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

311
  """
312
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
313
                  (Parens(key_type), Parens(val_type)))
314

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

    
319
  return desc(TAnd(TDict, fn))
320

    
321

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

325
  """
326
  notfound_fn = lambda _: not exclusive
327

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

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

    
335

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

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

346
  """
347
  descparts = ["Dictionary containing"]
348

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

    
352
  if require_all:
353
    descparts.append(" required")
354

    
355
  if len(items) == 1:
356
    descparts.append(" key ")
357
  else:
358
    descparts.append(" keys ")
359

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

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

    
365
  return desc(TAnd(TDict,
366
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
367
                                  items)))
368

    
369

    
370
def TItems(items):
371
  """Checks individual items of a container.
372

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

377
  @type items: list
378
  @param items: List of checks
379

380
  """
381
  assert items, "Need items"
382

    
383
  text = ["Item", "item"]
384
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
385
                                  (text[int(idx > 0)], idx, Parens(check))
386
                                  for (idx, check) in enumerate(items)))
387

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