1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: base.py 56761 2015-07-03 01:36:35Z vboxsync $
|
---|
3 | # pylint: disable=C0302
|
---|
4 |
|
---|
5 | """
|
---|
6 | Test Manager Core - Base Class(es).
|
---|
7 | """
|
---|
8 |
|
---|
9 | __copyright__ = \
|
---|
10 | """
|
---|
11 | Copyright (C) 2012-2015 Oracle Corporation
|
---|
12 |
|
---|
13 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
14 | available from http://www.virtualbox.org. This file is free software;
|
---|
15 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
16 | General Public License (GPL) as published by the Free Software
|
---|
17 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
18 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
19 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
20 |
|
---|
21 | The contents of this file may alternatively be used under the terms
|
---|
22 | of the Common Development and Distribution License Version 1.0
|
---|
23 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
24 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
25 | CDDL are applicable instead of those of the GPL.
|
---|
26 |
|
---|
27 | You may elect to license modified versions of this file under the
|
---|
28 | terms and conditions of either the GPL or the CDDL or both.
|
---|
29 | """
|
---|
30 | __version__ = "$Revision: 56761 $"
|
---|
31 |
|
---|
32 |
|
---|
33 | # Standard python imports.
|
---|
34 | import copy;
|
---|
35 | import re;
|
---|
36 | import socket;
|
---|
37 | import sys;
|
---|
38 | import uuid;
|
---|
39 | import unittest;
|
---|
40 |
|
---|
41 | # Validation Kit imports.
|
---|
42 | from common import utils;
|
---|
43 |
|
---|
44 | # Python 3 hacks:
|
---|
45 | if sys.version_info[0] >= 3:
|
---|
46 | long = int # pylint: disable=W0622,C0103
|
---|
47 |
|
---|
48 |
|
---|
49 | class TMExceptionBase(Exception):
|
---|
50 | """
|
---|
51 | For exceptions raised by any TestManager component.
|
---|
52 | """
|
---|
53 | pass;
|
---|
54 |
|
---|
55 | class TMTooManyRows(TMExceptionBase):
|
---|
56 | """
|
---|
57 | Too many rows in the result.
|
---|
58 | Used by ModelLogicBase decendants.
|
---|
59 | """
|
---|
60 | pass;
|
---|
61 |
|
---|
62 |
|
---|
63 | class ModelBase(object): # pylint: disable=R0903
|
---|
64 | """
|
---|
65 | Something all classes in the logical model inherits from.
|
---|
66 |
|
---|
67 | Not sure if 'logical model' is the right term here.
|
---|
68 | Will see if it has any purpose later on...
|
---|
69 | """
|
---|
70 |
|
---|
71 | def __init__(self):
|
---|
72 | pass;
|
---|
73 |
|
---|
74 |
|
---|
75 | class ModelDataBase(ModelBase): # pylint: disable=R0903
|
---|
76 | """
|
---|
77 | Something all classes in the data classes in the logical model inherits from.
|
---|
78 | """
|
---|
79 |
|
---|
80 | ## Child classes can use this to list array attributes which should use
|
---|
81 | # an empty array ([]) instead of None as database NULL value.
|
---|
82 | kasAltArrayNull = [];
|
---|
83 |
|
---|
84 |
|
---|
85 | def __init__(self):
|
---|
86 | ModelBase.__init__(self);
|
---|
87 |
|
---|
88 |
|
---|
89 | #
|
---|
90 | # Standard methods implemented by combining python magic and hungarian prefixes.
|
---|
91 | #
|
---|
92 |
|
---|
93 | def getDataAttributes(self):
|
---|
94 | """
|
---|
95 | Returns a list of data attributes.
|
---|
96 | """
|
---|
97 | asRet = [];
|
---|
98 | asAttrs = dir(self);
|
---|
99 | for sAttr in asAttrs:
|
---|
100 | if sAttr[0] == '_' or sAttr[0] == 'k':
|
---|
101 | continue;
|
---|
102 | oValue = getattr(self, sAttr);
|
---|
103 | if callable(oValue):
|
---|
104 | continue;
|
---|
105 | asRet.append(sAttr);
|
---|
106 | return asRet;
|
---|
107 |
|
---|
108 | def initFromOther(self, oOther):
|
---|
109 | """
|
---|
110 | Initialize this object with the values from another instance (child
|
---|
111 | class instance is accepted).
|
---|
112 |
|
---|
113 | This serves as a kind of copy constructor.
|
---|
114 |
|
---|
115 | Returns self. May raise exception if the type of other object differs
|
---|
116 | or is damaged.
|
---|
117 | """
|
---|
118 | for sAttr in self.getDataAttributes():
|
---|
119 | setattr(self, sAttr, getattr(oOther, sAttr));
|
---|
120 | return self;
|
---|
121 |
|
---|
122 | @staticmethod
|
---|
123 | def getHungarianPrefix(sName):
|
---|
124 | """
|
---|
125 | Returns the hungarian prefix of the given name.
|
---|
126 | """
|
---|
127 | for i in range(len(sName)):
|
---|
128 | if sName[i] not in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
---|
129 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']:
|
---|
130 | assert re.search('^[A-Z][a-zA-Z0-9]*$', sName[i:]) is not None;
|
---|
131 | return sName[:i];
|
---|
132 | return sName;
|
---|
133 |
|
---|
134 | def getAttributeParamNullValues(self, sAttr):
|
---|
135 | """
|
---|
136 | Returns a list of parameter NULL values, with the preferred one being
|
---|
137 | the first element.
|
---|
138 |
|
---|
139 | Child classes can override this to handle one or more attributes specially.
|
---|
140 | """
|
---|
141 | sPrefix = self.getHungarianPrefix(sAttr);
|
---|
142 | if sPrefix in ['id', 'uid', 'i', 'off', 'pct']:
|
---|
143 | return [-1, '', '-1',];
|
---|
144 | elif sPrefix in ['l', 'c',]:
|
---|
145 | return [long(-1), '', '-1',];
|
---|
146 | elif sPrefix == 'f':
|
---|
147 | return ['',];
|
---|
148 | elif sPrefix in ['enm', 'ip', 's', 'ts', 'uuid']:
|
---|
149 | return ['',];
|
---|
150 | elif sPrefix in ['ai', 'aid', 'al', 'as']:
|
---|
151 | return [[], '', None]; ## @todo ??
|
---|
152 | elif sPrefix == 'bm':
|
---|
153 | return ['', [],]; ## @todo bitmaps.
|
---|
154 | raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix));
|
---|
155 |
|
---|
156 | def isAttributeNull(self, sAttr, oValue):
|
---|
157 | """
|
---|
158 | Checks if the specified attribute value indicates NULL.
|
---|
159 | Return True/False.
|
---|
160 |
|
---|
161 | Note! This isn't entirely kosher actually.
|
---|
162 | """
|
---|
163 | if oValue is None:
|
---|
164 | return True;
|
---|
165 | aoNilValues = self.getAttributeParamNullValues(sAttr);
|
---|
166 | return oValue in aoNilValues;
|
---|
167 |
|
---|
168 | def _convertAttributeFromParamNull(self, sAttr, oValue):
|
---|
169 | """
|
---|
170 | Converts an attribute from parameter NULL to database NULL value.
|
---|
171 | Returns the new attribute value.
|
---|
172 | """
|
---|
173 | aoNullValues = self.getAttributeParamNullValues(sAttr);
|
---|
174 | if oValue in aoNullValues:
|
---|
175 | oValue = None if sAttr not in self.kasAltArrayNull else [];
|
---|
176 | #
|
---|
177 | # Perform deep conversion on ModelDataBase object and lists of them.
|
---|
178 | #
|
---|
179 | elif isinstance(oValue, list) and len(oValue) > 0 and isinstance(oValue[0], ModelDataBase):
|
---|
180 | oValue = copy.copy(oValue);
|
---|
181 | for i in range(len(oValue)):
|
---|
182 | assert isinstance(oValue[i], ModelDataBase);
|
---|
183 | oValue[i] = copy.copy(oValue[i]);
|
---|
184 | oValue[i].convertFromParamNull();
|
---|
185 |
|
---|
186 | elif isinstance(oValue, ModelDataBase):
|
---|
187 | oValue = copy.copy(oValue);
|
---|
188 | oValue.convertFromParamNull();
|
---|
189 |
|
---|
190 | return oValue;
|
---|
191 |
|
---|
192 | def convertFromParamNull(self):
|
---|
193 | """
|
---|
194 | Converts from parameter NULL values to database NULL values (None).
|
---|
195 | Returns self.
|
---|
196 | """
|
---|
197 | for sAttr in self.getDataAttributes():
|
---|
198 | oValue = getattr(self, sAttr);
|
---|
199 | oNewValue = self._convertAttributeFromParamNull(sAttr, oValue);
|
---|
200 | if oValue != oNewValue:
|
---|
201 | setattr(self, sAttr, oNewValue);
|
---|
202 | return self;
|
---|
203 |
|
---|
204 | def _convertAttributeToParamNull(self, sAttr, oValue):
|
---|
205 | """
|
---|
206 | Converts an attribute from database NULL to a sepcial value we can pass
|
---|
207 | thru parameter list.
|
---|
208 | Returns the new attribute value.
|
---|
209 | """
|
---|
210 | if oValue is None:
|
---|
211 | oValue = self.getAttributeParamNullValues(sAttr)[0];
|
---|
212 | #
|
---|
213 | # Perform deep conversion on ModelDataBase object and lists of them.
|
---|
214 | #
|
---|
215 | elif isinstance(oValue, list) and len(oValue) > 0 and isinstance(oValue[0], ModelDataBase):
|
---|
216 | oValue = copy.copy(oValue);
|
---|
217 | for i in range(len(oValue)):
|
---|
218 | assert isinstance(oValue[i], ModelDataBase);
|
---|
219 | oValue[i] = copy.copy(oValue[i]);
|
---|
220 | oValue[i].convertToParamNull();
|
---|
221 |
|
---|
222 | elif isinstance(oValue, ModelDataBase):
|
---|
223 | oValue = copy.copy(oValue);
|
---|
224 | oValue.convertToParamNull();
|
---|
225 |
|
---|
226 | return oValue;
|
---|
227 |
|
---|
228 | def convertToParamNull(self):
|
---|
229 | """
|
---|
230 | Converts from database NULL values (None) to special values we can
|
---|
231 | pass thru parameters list.
|
---|
232 | Returns self.
|
---|
233 | """
|
---|
234 | for sAttr in self.getDataAttributes():
|
---|
235 | oValue = getattr(self, sAttr);
|
---|
236 | oNewValue = self._convertAttributeToParamNull(sAttr, oValue);
|
---|
237 | if oValue != oNewValue:
|
---|
238 | setattr(self, sAttr, oNewValue);
|
---|
239 | return self;
|
---|
240 |
|
---|
241 | def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
|
---|
242 | """
|
---|
243 | Validates and convert one attribute.
|
---|
244 | Returns the converted value.
|
---|
245 |
|
---|
246 | Child classes can override this to handle one or more attributes specially.
|
---|
247 | Note! oDb can be None.
|
---|
248 | """
|
---|
249 | sPrefix = self.getHungarianPrefix(sAttr);
|
---|
250 |
|
---|
251 | if sPrefix in ['id', 'uid']:
|
---|
252 | (oNewValue, sError) = self.validateInt( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
253 | elif sPrefix in ['i', 'off', 'pct']:
|
---|
254 | (oNewValue, sError) = self.validateInt( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
255 | iMin = getattr(self, 'kiMin_' + sAttr, 0),
|
---|
256 | iMax = getattr(self, 'kiMax_' + sAttr, 0x7ffffffe));
|
---|
257 | elif sPrefix in ['l', 'c']:
|
---|
258 | (oNewValue, sError) = self.validateLong(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
259 | lMin = getattr(self, 'klMin_' + sAttr, 0),
|
---|
260 | lMax = getattr(self, 'klMax_' + sAttr, None));
|
---|
261 | elif sPrefix == 'f':
|
---|
262 | if oValue is '' and not fAllowNull: oValue = '0'; # HACK ALERT! Checkboxes are only added when checked.
|
---|
263 | (oNewValue, sError) = self.validateBool(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
264 | elif sPrefix == 'ts':
|
---|
265 | (oNewValue, sError) = self.validateTs( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
266 | elif sPrefix == 'ip':
|
---|
267 | (oNewValue, sError) = self.validateIp( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
268 | elif sPrefix == 'uuid':
|
---|
269 | (oNewValue, sError) = self.validateUuid(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
270 | elif sPrefix == 'enm':
|
---|
271 | (oNewValue, sError) = self.validateWord(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
272 | asValid = getattr(self, 'kasValidValues_' + sAttr)); # The list is required.
|
---|
273 | elif sPrefix == 's':
|
---|
274 | (oNewValue, sError) = self.validateStr( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
275 | cchMin = getattr(self, 'kcchMin_' + sAttr, 0),
|
---|
276 | cchMax = getattr(self, 'kcchMax_' + sAttr, 4096));
|
---|
277 | ## @todo al.
|
---|
278 | elif sPrefix == 'aid':
|
---|
279 | (oNewValue, sError) = self.validateListOfInts(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
280 | iMin = 1, iMax = 0x7ffffffe);
|
---|
281 | elif sPrefix == 'as':
|
---|
282 | (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
|
---|
283 | asValidValues = getattr(self, 'kasValidValues_' + sAttr, None),
|
---|
284 | cchMin = getattr(self, 'kcchMin_' + sAttr, 0 if fAllowNull else 1),
|
---|
285 | cchMax = getattr(self, 'kcchMax_' + sAttr, 4096));
|
---|
286 |
|
---|
287 | elif sPrefix == 'bm':
|
---|
288 | ## @todo figure out bitfields.
|
---|
289 | (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
|
---|
290 | else:
|
---|
291 | raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix));
|
---|
292 |
|
---|
293 | _ = sParam; _ = oDb;
|
---|
294 | return (oNewValue, sError);
|
---|
295 |
|
---|
296 | def _validateAndConvertWorker(self, asAllowNullAttributes, oDb):
|
---|
297 | """
|
---|
298 | Worker for implementing validateAndConvert().
|
---|
299 | """
|
---|
300 | dErrors = dict();
|
---|
301 | for sAttr in self.getDataAttributes():
|
---|
302 | oValue = getattr(self, sAttr);
|
---|
303 | sParam = getattr(self, 'ksParam_' + sAttr);
|
---|
304 | aoNilValues = self.getAttributeParamNullValues(sAttr);
|
---|
305 | aoNilValues.append(None);
|
---|
306 |
|
---|
307 | (oNewValue, sError) = self._validateAndConvertAttribute(sAttr, sParam, oValue, aoNilValues,
|
---|
308 | sAttr in asAllowNullAttributes, oDb);
|
---|
309 | if oValue != oNewValue:
|
---|
310 | setattr(self, sAttr, oNewValue);
|
---|
311 | if sError is not None:
|
---|
312 | dErrors[sParam] = sError;
|
---|
313 | return dErrors;
|
---|
314 |
|
---|
315 | def validateAndConvert(self, oDb):
|
---|
316 | """
|
---|
317 | Validates the input and converts valid fields to their right type.
|
---|
318 | Returns a dictionary with per field reports, only invalid fields will
|
---|
319 | be returned, so an empty dictionary means that the data is valid.
|
---|
320 |
|
---|
321 | The dictionary keys are ksParam_*.
|
---|
322 |
|
---|
323 | Child classes can override _validateAndConvertAttribute to handle
|
---|
324 | selected fields specially. There are also a few class variables that
|
---|
325 | can be used to advice the validation: kcchMin_sAttr, kcchMax_sAttr,
|
---|
326 | kiMin_iAttr, kiMax_iAttr, klMin_lAttr, klMax_lAttr,
|
---|
327 | kasValidValues_enmAttr, and kasAllowNullAttributes.
|
---|
328 | """
|
---|
329 | return self._validateAndConvertWorker(getattr(self, 'kasAllowNullAttributes', list()), oDb);
|
---|
330 |
|
---|
331 | def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
|
---|
332 | """
|
---|
333 | Calculate the attribute value when initialized from a parameter.
|
---|
334 |
|
---|
335 | Returns the new value, with parameter NULL values. Raises exception on
|
---|
336 | invalid parameter value.
|
---|
337 |
|
---|
338 | Child classes can override to do special parameter conversion jobs.
|
---|
339 | """
|
---|
340 | sPrefix = self.getHungarianPrefix(sAttr);
|
---|
341 | asValidValues = getattr(self, 'kasValidValues_' + sAttr, None);
|
---|
342 | if fStrict:
|
---|
343 | if sPrefix == 'f':
|
---|
344 | # HACK ALERT! Checkboxes are only present when checked, so we always have to provide a default.
|
---|
345 | oNewValue = oDisp.getStringParam(sParam, asValidValues, '0');
|
---|
346 | elif sPrefix[0] == 'a':
|
---|
347 | # HACK ALERT! List are not present if empty.
|
---|
348 | oNewValue = oDisp.getListOfStrParams(sParam, []);
|
---|
349 | else:
|
---|
350 | oNewValue = oDisp.getStringParam(sParam, asValidValues, None);
|
---|
351 | else:
|
---|
352 | if sPrefix[0] == 'a':
|
---|
353 | oNewValue = oDisp.getListOfStrParams(sParam, []);
|
---|
354 | else:
|
---|
355 | assert oValue is not None, 'sAttr=%s' % (sAttr,);
|
---|
356 | oNewValue = oDisp.getStringParam(sParam, asValidValues, oValue);
|
---|
357 | return oNewValue;
|
---|
358 |
|
---|
359 | def initFromParams(self, oDisp, fStrict = True):
|
---|
360 | """
|
---|
361 | Initialize the object from parameters.
|
---|
362 | The input is not validated at all, except that all parameters must be
|
---|
363 | present when fStrict is True.
|
---|
364 |
|
---|
365 | Returns self. Raises exception on invalid parameter value.
|
---|
366 |
|
---|
367 | Note! The returned object has parameter NULL values, not database ones!
|
---|
368 | """
|
---|
369 |
|
---|
370 | self.convertToParamNull()
|
---|
371 | for sAttr in self.getDataAttributes():
|
---|
372 | oValue = getattr(self, sAttr);
|
---|
373 | oNewValue = self.convertParamToAttribute(sAttr, getattr(self, 'ksParam_' + sAttr), oValue, oDisp, fStrict);
|
---|
374 | if oNewValue != oValue:
|
---|
375 | setattr(self, sAttr, oNewValue);
|
---|
376 | return self;
|
---|
377 |
|
---|
378 | def areAttributeValuesEqual(self, sAttr, sPrefix, oValue1, oValue2):
|
---|
379 | """
|
---|
380 | Called to compare two attribute values and python thinks differs.
|
---|
381 |
|
---|
382 | Returns True/False.
|
---|
383 |
|
---|
384 | Child classes can override this to do special compares of things like arrays.
|
---|
385 | """
|
---|
386 | # Just in case someone uses it directly.
|
---|
387 | if oValue1 == oValue2:
|
---|
388 | return True;
|
---|
389 |
|
---|
390 | #
|
---|
391 | # Timestamps can be both string (param) and object (db)
|
---|
392 | # depending on the data source. Compare string values to make
|
---|
393 | # sure we're doing the right thing here.
|
---|
394 | #
|
---|
395 | if sPrefix == 'ts':
|
---|
396 | return str(oValue1) == str(oValue2);
|
---|
397 |
|
---|
398 | #
|
---|
399 | # Some generic code handling ModelDataBase children.
|
---|
400 | #
|
---|
401 | if isinstance(oValue1, list) and isinstance(oValue2, list):
|
---|
402 | if len(oValue1) == len(oValue2):
|
---|
403 | for i in range(len(oValue1)):
|
---|
404 | if not isinstance(oValue1[i], ModelDataBase) \
|
---|
405 | or type(oValue1) != type(oValue2):
|
---|
406 | return False;
|
---|
407 | if not oValue1[i].isEqual(oValue2[i]):
|
---|
408 | return False;
|
---|
409 | return True;
|
---|
410 |
|
---|
411 | elif isinstance(oValue1, ModelDataBase) \
|
---|
412 | and type(oValue1) == type(oValue2):
|
---|
413 | return oValue1[i].isEqual(oValue2[i]);
|
---|
414 |
|
---|
415 | _ = sAttr;
|
---|
416 | return False;
|
---|
417 |
|
---|
418 | def isEqual(self, oOther):
|
---|
419 | """ Compares two instances. """
|
---|
420 | for sAttr in self.getDataAttributes():
|
---|
421 | if getattr(self, sAttr) != getattr(oOther, sAttr):
|
---|
422 | # Delegate the final decision to an overridable method.
|
---|
423 | if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
|
---|
424 | getattr(self, sAttr), getattr(oOther, sAttr)):
|
---|
425 | return False;
|
---|
426 | return True;
|
---|
427 |
|
---|
428 | def isEqualEx(self, oOther, asExcludeAttrs):
|
---|
429 | """ Compares two instances, omitting the given attributes. """
|
---|
430 | for sAttr in self.getDataAttributes():
|
---|
431 | if sAttr not in asExcludeAttrs \
|
---|
432 | and getattr(self, sAttr) != getattr(oOther, sAttr):
|
---|
433 | # Delegate the final decision to an overridable method.
|
---|
434 | if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
|
---|
435 | getattr(self, sAttr), getattr(oOther, sAttr)):
|
---|
436 | return False;
|
---|
437 | return True;
|
---|
438 |
|
---|
439 | def reinitToNull(self):
|
---|
440 | """
|
---|
441 | Reinitializes the object to (database) NULL values.
|
---|
442 | Returns self.
|
---|
443 | """
|
---|
444 | for sAttr in self.getDataAttributes():
|
---|
445 | setattr(self, sAttr, None);
|
---|
446 | return self;
|
---|
447 |
|
---|
448 | def toString(self):
|
---|
449 | """
|
---|
450 | Stringifies the object.
|
---|
451 | Returns string representation.
|
---|
452 | """
|
---|
453 |
|
---|
454 | sMembers = '';
|
---|
455 | for sAttr in self.getDataAttributes():
|
---|
456 | oValue = getattr(self, sAttr);
|
---|
457 | sMembers += ', %s=%s' % (sAttr, oValue);
|
---|
458 |
|
---|
459 | oClass = type(self);
|
---|
460 | if sMembers == '':
|
---|
461 | return '<%s>' % (oClass.__name__);
|
---|
462 | return '<%s: %s>' % (oClass.__name__, sMembers[2:]);
|
---|
463 |
|
---|
464 | def __str__(self):
|
---|
465 | return self.toString();
|
---|
466 |
|
---|
467 |
|
---|
468 |
|
---|
469 | #
|
---|
470 | # New validation helpers.
|
---|
471 | #
|
---|
472 | # These all return (oValue, sError), where sError is None when the value
|
---|
473 | # is valid and an error message when not. On success and in case of
|
---|
474 | # range errors, oValue is converted into the requested type.
|
---|
475 | #
|
---|
476 |
|
---|
477 | @staticmethod
|
---|
478 | def validateInt(sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, '']), fAllowNull = True):
|
---|
479 | """ Validates an integer field. """
|
---|
480 | if sValue in aoNilValues:
|
---|
481 | if fAllowNull:
|
---|
482 | return (None if sValue is None else aoNilValues[0], None);
|
---|
483 | return (sValue, 'Mandatory.');
|
---|
484 |
|
---|
485 | try:
|
---|
486 | if utils.isString(sValue):
|
---|
487 | iValue = int(sValue, 0);
|
---|
488 | else:
|
---|
489 | iValue = int(sValue);
|
---|
490 | except:
|
---|
491 | return (sValue, 'Not an integer');
|
---|
492 |
|
---|
493 | if iValue in aoNilValues:
|
---|
494 | return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
|
---|
495 |
|
---|
496 | if iValue < iMin:
|
---|
497 | return (iValue, 'Value too small (min %d)' % (iMin,));
|
---|
498 | elif iValue > iMax:
|
---|
499 | return (iValue, 'Value too high (max %d)' % (iMax,));
|
---|
500 | return (iValue, None);
|
---|
501 |
|
---|
502 | @staticmethod
|
---|
503 | def validateLong(sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, '']), fAllowNull = True):
|
---|
504 | """ Validates an long integer field. """
|
---|
505 | if sValue in aoNilValues:
|
---|
506 | if fAllowNull:
|
---|
507 | return (None if sValue is None else aoNilValues[0], None);
|
---|
508 | return (sValue, 'Mandatory.');
|
---|
509 | try:
|
---|
510 | if utils.isString(sValue):
|
---|
511 | lValue = long(sValue, 0);
|
---|
512 | else:
|
---|
513 | lValue = long(sValue);
|
---|
514 | except:
|
---|
515 | return (sValue, 'Not a long integer');
|
---|
516 |
|
---|
517 | if lValue in aoNilValues:
|
---|
518 | return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
|
---|
519 |
|
---|
520 | if lMin is not None and lValue < lMin:
|
---|
521 | return (lValue, 'Value too small (min %d)' % (lMin,));
|
---|
522 | elif lMax is not None and lValue > lMax:
|
---|
523 | return (lValue, 'Value too high (max %d)' % (lMax,));
|
---|
524 | return (lValue, None);
|
---|
525 |
|
---|
526 | @staticmethod
|
---|
527 | def validateTs(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
528 | """ Validates a timestamp field. """
|
---|
529 | if sValue in aoNilValues:
|
---|
530 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
531 | if not utils.isString(sValue):
|
---|
532 | return (sValue, None);
|
---|
533 |
|
---|
534 | sError = None;
|
---|
535 | if len(sValue) == len('2012-10-08 01:54:06.364207+02:00'):
|
---|
536 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{6}[+-](\d\d):(\d\d)', sValue);
|
---|
537 | if oRes is not None \
|
---|
538 | and ( int(oRes.group(6)) > 12 \
|
---|
539 | or int(oRes.group(7)) >= 60):
|
---|
540 | sError = 'Invalid timezone offset.';
|
---|
541 | elif len(sValue) == len('2012-10-08 01:54:06.00'):
|
---|
542 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{2}', sValue);
|
---|
543 | elif len(sValue) == len('9999-12-31 23:59:59.999999'):
|
---|
544 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{6}', sValue);
|
---|
545 | elif len(sValue) == len('999999-12-31 00:00:00.00'):
|
---|
546 | oRes = re.match(r'(\d{6})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{2}', sValue);
|
---|
547 | elif len(sValue) == len('9999-12-31T23:59:59.999999Z'):
|
---|
548 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d[Tt]([012]\d):[0-5]\d:([0-6]\d).\d{6}[Zz]', sValue);
|
---|
549 | elif len(sValue) == len('9999-12-31T23:59:59.999999999Z'):
|
---|
550 | oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d[Tt]([012]\d):[0-5]\d:([0-6]\d).\d{9}[Zz]', sValue);
|
---|
551 | else:
|
---|
552 | return (sValue, 'Invalid timestamp length.');
|
---|
553 |
|
---|
554 | if oRes is None:
|
---|
555 | sError = 'Invalid timestamp (format: 2012-10-08 01:54:06.364207+02:00).';
|
---|
556 | else:
|
---|
557 | iYear = int(oRes.group(1));
|
---|
558 | if iYear % 4 == 0 and (iYear % 100 != 0 or iYear % 400 == 0):
|
---|
559 | acDaysOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
---|
560 | else:
|
---|
561 | acDaysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
---|
562 | iMonth = int(oRes.group(2));
|
---|
563 | iDay = int(oRes.group(3));
|
---|
564 | iHour = int(oRes.group(4));
|
---|
565 | iSec = int(oRes.group(5));
|
---|
566 | if iMonth > 12:
|
---|
567 | sError = 'Invalid timestamp month.';
|
---|
568 | elif iDay > acDaysOfMonth[iMonth - 1]:
|
---|
569 | sError = 'Invalid timestamp day-of-month (%02d has %d days).' % (iMonth, acDaysOfMonth[iMonth - 1]);
|
---|
570 | elif iHour > 23:
|
---|
571 | sError = 'Invalid timestamp hour.'
|
---|
572 | elif iSec >= 61:
|
---|
573 | sError = 'Invalid timestamp second.'
|
---|
574 | elif iSec >= 60:
|
---|
575 | sError = 'Invalid timestamp: no leap seconds, please.'
|
---|
576 | return (sValue, sError);
|
---|
577 |
|
---|
578 | @staticmethod
|
---|
579 | def validateIp(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
580 | """ Validates an IP address field. """
|
---|
581 | if sValue in aoNilValues:
|
---|
582 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
583 |
|
---|
584 | if sValue == '::1':
|
---|
585 | return (sValue, None);
|
---|
586 |
|
---|
587 | try:
|
---|
588 | socket.inet_pton(socket.AF_INET, sValue); # pylint: disable=E1101
|
---|
589 | except:
|
---|
590 | try:
|
---|
591 | socket.inet_pton(socket.AF_INET6, sValue); # pylint: disable=E1101
|
---|
592 | except:
|
---|
593 | return (sValue, 'Not a valid IP address.');
|
---|
594 |
|
---|
595 | return (sValue, None);
|
---|
596 |
|
---|
597 | @staticmethod
|
---|
598 | def validateBool(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
599 | """ Validates a boolean field. """
|
---|
600 | if sValue in aoNilValues:
|
---|
601 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
602 |
|
---|
603 | if sValue in ('True', 'true', '1', True):
|
---|
604 | return (True, None);
|
---|
605 | if sValue in ('False', 'false', '0', False):
|
---|
606 | return (False, None);
|
---|
607 | return (sValue, 'Invalid boolean value.');
|
---|
608 |
|
---|
609 | @staticmethod
|
---|
610 | def validateUuid(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
611 | """ Validates an UUID field. """
|
---|
612 | if sValue in aoNilValues:
|
---|
613 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
614 |
|
---|
615 | try:
|
---|
616 | sValue = str(uuid.UUID(sValue));
|
---|
617 | except:
|
---|
618 | return (sValue, 'Invalid UUID value.');
|
---|
619 | return (sValue, None);
|
---|
620 |
|
---|
621 | @staticmethod
|
---|
622 | def validateWord(sValue, cchMin = 1, cchMax = 64, asValid = None, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
623 | """ Validates a word field. """
|
---|
624 | if sValue in aoNilValues:
|
---|
625 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
626 |
|
---|
627 | if re.search('[^a-zA-Z0-9_-]', sValue) is not None:
|
---|
628 | sError = 'Single word ([a-zA-Z0-9_-]), please.';
|
---|
629 | elif cchMin is not None and len(sValue) < cchMin:
|
---|
630 | sError = 'Too short, min %s chars' % (cchMin,);
|
---|
631 | elif cchMax is not None and len(sValue) > cchMax:
|
---|
632 | sError = 'Too long, max %s chars' % (cchMax,);
|
---|
633 | elif asValid is not None and sValue not in asValid:
|
---|
634 | sError = 'Invalid value "%s", must be one of: %s' % (sValue, asValid);
|
---|
635 | else:
|
---|
636 | sError = None;
|
---|
637 | return (sValue, sError);
|
---|
638 |
|
---|
639 | @staticmethod
|
---|
640 | def validateStr(sValue, cchMin = 0, cchMax = 4096, aoNilValues = tuple([None, '']), fAllowNull = True,
|
---|
641 | fAllowUnicodeSymbols = False):
|
---|
642 | """ Validates a string field. """
|
---|
643 | if sValue in aoNilValues:
|
---|
644 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
645 |
|
---|
646 | if cchMin is not None and len(sValue) < cchMin:
|
---|
647 | sError = 'Too short, min %s chars' % (cchMin,);
|
---|
648 | elif cchMax is not None and len(sValue) > cchMax:
|
---|
649 | sError = 'Too long, max %s chars' % (cchMax,);
|
---|
650 | elif fAllowUnicodeSymbols is False and utils.hasNonAsciiCharacters(sValue):
|
---|
651 | sError = 'Non-ascii characters not allowed'
|
---|
652 | else:
|
---|
653 | sError = None;
|
---|
654 | return (sValue, sError);
|
---|
655 |
|
---|
656 | @staticmethod
|
---|
657 | def validateEmail(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
|
---|
658 | """ Validates a email field."""
|
---|
659 | if sValue in aoNilValues:
|
---|
660 | return (sValue, None if fAllowNull else 'Mandatory.');
|
---|
661 |
|
---|
662 | if re.match(r'.+@.+\..+', sValue) is None:
|
---|
663 | return (sValue,'Invalid e-mail format.');
|
---|
664 | return (sValue, None);
|
---|
665 |
|
---|
666 | @staticmethod
|
---|
667 | def validateListOfSomething(asValues, aoNilValues = tuple([[], None]), fAllowNull = True):
|
---|
668 | """ Validate a list of some uniform values. Returns a copy of the list (if list it is). """
|
---|
669 | if asValues in aoNilValues or (len(asValues) == 0 and not fAllowNull):
|
---|
670 | return (asValues, None if fAllowNull else 'Mandatory.')
|
---|
671 |
|
---|
672 | if not isinstance(asValues, list):
|
---|
673 | return (asValues, 'Invalid data type (%s).' % (type(asValues),));
|
---|
674 |
|
---|
675 | asValues = list(asValues); # copy the list.
|
---|
676 | if len(asValues) > 0:
|
---|
677 | oType = type(asValues[0]);
|
---|
678 | for i in range(1, len(asValues)):
|
---|
679 | if type(asValues[i]) is not oType:
|
---|
680 | return (asValues, 'Invalid entry data type ([0]=%s vs [%d]=%s).' % (oType, i, type(asValues[i])) );
|
---|
681 |
|
---|
682 | return (asValues, None);
|
---|
683 |
|
---|
684 | @staticmethod
|
---|
685 | def validateListOfStr(asValues, cchMin = None, cchMax = None, asValidValues = None,
|
---|
686 | aoNilValues = tuple([[], None]), fAllowNull = True):
|
---|
687 | """ Validates a list of text items."""
|
---|
688 | (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
|
---|
689 |
|
---|
690 | if sError is None and asValues not in aoNilValues and len(asValues) > 0:
|
---|
691 | if not utils.isString(asValues[0]):
|
---|
692 | return (asValues, 'Invalid item data type.');
|
---|
693 |
|
---|
694 | if not fAllowNull and cchMin is None:
|
---|
695 | cchMin = 1;
|
---|
696 |
|
---|
697 | for sValue in asValues:
|
---|
698 | if asValidValues is not None and sValue not in asValidValues:
|
---|
699 | sThisErr = 'Invalid value "%s".' % (sValue,);
|
---|
700 | elif cchMin is not None and len(sValue) < cchMin:
|
---|
701 | sThisErr = 'Value "%s" is too short, min length is %u chars.' % (sValue, cchMin);
|
---|
702 | elif cchMax is not None and len(sValue) > cchMax:
|
---|
703 | sThisErr = 'Value "%s" is too long, max length is %u chars.' % (sValue, cchMax);
|
---|
704 | else:
|
---|
705 | continue;
|
---|
706 |
|
---|
707 | if sError is None:
|
---|
708 | sError = sThisErr;
|
---|
709 | else:
|
---|
710 | sError += ' ' + sThisErr;
|
---|
711 |
|
---|
712 | return (asValues, sError);
|
---|
713 |
|
---|
714 | @staticmethod
|
---|
715 | def validateListOfInts(asValues, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([[], None]), fAllowNull = True):
|
---|
716 | """ Validates a list of integer items."""
|
---|
717 | (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
|
---|
718 |
|
---|
719 | if sError is None and asValues not in aoNilValues and len(asValues) > 0:
|
---|
720 | for i in range(len(asValues)):
|
---|
721 | sValue = asValues[i];
|
---|
722 |
|
---|
723 | sThisErr = '';
|
---|
724 | try:
|
---|
725 | iValue = int(sValue);
|
---|
726 | except:
|
---|
727 | sThisErr = 'Invalid integer value "%s".' % (sValue,);
|
---|
728 | else:
|
---|
729 | asValues[i] = iValue;
|
---|
730 | if iValue < iMin:
|
---|
731 | sThisErr = 'Value %d is too small (min %d)' % (iValue, iMin,);
|
---|
732 | elif iValue > iMax:
|
---|
733 | sThisErr = 'Value %d is too high (max %d)' % (iValue, iMax,);
|
---|
734 | else:
|
---|
735 | continue;
|
---|
736 |
|
---|
737 | if sError is None:
|
---|
738 | sError = sThisErr;
|
---|
739 | else:
|
---|
740 | sError += ' ' + sThisErr;
|
---|
741 |
|
---|
742 | return (asValues, sError);
|
---|
743 |
|
---|
744 |
|
---|
745 |
|
---|
746 | #
|
---|
747 | # Old validation helpers.
|
---|
748 | #
|
---|
749 |
|
---|
750 | @staticmethod
|
---|
751 | def _validateInt(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
|
---|
752 | """ Validates an integer field. """
|
---|
753 | (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = True);
|
---|
754 | if sError is not None:
|
---|
755 | dErrors[sName] = sError;
|
---|
756 | return sValue;
|
---|
757 |
|
---|
758 | @staticmethod
|
---|
759 | def _validateIntNN(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
|
---|
760 | """ Validates an integer field, not null. """
|
---|
761 | (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = False);
|
---|
762 | if sError is not None:
|
---|
763 | dErrors[sName] = sError;
|
---|
764 | return sValue;
|
---|
765 |
|
---|
766 | @staticmethod
|
---|
767 | def _validateLong(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
|
---|
768 | """ Validates an long integer field. """
|
---|
769 | (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = False);
|
---|
770 | if sError is not None:
|
---|
771 | dErrors[sName] = sError;
|
---|
772 | return sValue;
|
---|
773 |
|
---|
774 | @staticmethod
|
---|
775 | def _validateLongNN(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
|
---|
776 | """ Validates an long integer field, not null. """
|
---|
777 | (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = True);
|
---|
778 | if sError is not None:
|
---|
779 | dErrors[sName] = sError;
|
---|
780 | return sValue;
|
---|
781 |
|
---|
782 | @staticmethod
|
---|
783 | def _validateTs(dErrors, sName, sValue):
|
---|
784 | """ Validates a timestamp field. """
|
---|
785 | (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = True);
|
---|
786 | if sError is not None:
|
---|
787 | dErrors[sName] = sError;
|
---|
788 | return sValue;
|
---|
789 |
|
---|
790 | @staticmethod
|
---|
791 | def _validateTsNN(dErrors, sName, sValue):
|
---|
792 | """ Validates a timestamp field, not null. """
|
---|
793 | (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = False);
|
---|
794 | if sError is not None:
|
---|
795 | dErrors[sName] = sError;
|
---|
796 | return sValue;
|
---|
797 |
|
---|
798 | @staticmethod
|
---|
799 | def _validateIp(dErrors, sName, sValue):
|
---|
800 | """ Validates an IP address field. """
|
---|
801 | (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = True);
|
---|
802 | if sError is not None:
|
---|
803 | dErrors[sName] = sError;
|
---|
804 | return sValue;
|
---|
805 |
|
---|
806 | @staticmethod
|
---|
807 | def _validateIpNN(dErrors, sName, sValue):
|
---|
808 | """ Validates an IP address field, not null. """
|
---|
809 | (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = False);
|
---|
810 | if sError is not None:
|
---|
811 | dErrors[sName] = sError;
|
---|
812 | return sValue;
|
---|
813 |
|
---|
814 | @staticmethod
|
---|
815 | def _validateBool(dErrors, sName, sValue):
|
---|
816 | """ Validates a boolean field. """
|
---|
817 | (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = True);
|
---|
818 | if sError is not None:
|
---|
819 | dErrors[sName] = sError;
|
---|
820 | return sValue;
|
---|
821 |
|
---|
822 | @staticmethod
|
---|
823 | def _validateBoolNN(dErrors, sName, sValue):
|
---|
824 | """ Validates a boolean field, not null. """
|
---|
825 | (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = False);
|
---|
826 | if sError is not None:
|
---|
827 | dErrors[sName] = sError;
|
---|
828 | return sValue;
|
---|
829 |
|
---|
830 | @staticmethod
|
---|
831 | def _validateUuid(dErrors, sName, sValue):
|
---|
832 | """ Validates an UUID field. """
|
---|
833 | (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = True);
|
---|
834 | if sError is not None:
|
---|
835 | dErrors[sName] = sError;
|
---|
836 | return sValue;
|
---|
837 |
|
---|
838 | @staticmethod
|
---|
839 | def _validateUuidNN(dErrors, sName, sValue):
|
---|
840 | """ Validates an UUID field, not null. """
|
---|
841 | (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = False);
|
---|
842 | if sError is not None:
|
---|
843 | dErrors[sName] = sError;
|
---|
844 | return sValue;
|
---|
845 |
|
---|
846 | @staticmethod
|
---|
847 | def _validateWord(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
|
---|
848 | """ Validates a word field. """
|
---|
849 | (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = True);
|
---|
850 | if sError is not None:
|
---|
851 | dErrors[sName] = sError;
|
---|
852 | return sValue;
|
---|
853 |
|
---|
854 | @staticmethod
|
---|
855 | def _validateWordNN(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
|
---|
856 | """ Validates a boolean field, not null. """
|
---|
857 | (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = False);
|
---|
858 | if sError is not None:
|
---|
859 | dErrors[sName] = sError;
|
---|
860 | return sValue;
|
---|
861 |
|
---|
862 | @staticmethod
|
---|
863 | def _validateStr(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
|
---|
864 | """ Validates a string field. """
|
---|
865 | (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = True);
|
---|
866 | if sError is not None:
|
---|
867 | dErrors[sName] = sError;
|
---|
868 | return sValue;
|
---|
869 |
|
---|
870 | @staticmethod
|
---|
871 | def _validateStrNN(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
|
---|
872 | """ Validates a string field, not null. """
|
---|
873 | (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = False);
|
---|
874 | if sError is not None:
|
---|
875 | dErrors[sName] = sError;
|
---|
876 | return sValue;
|
---|
877 |
|
---|
878 | @staticmethod
|
---|
879 | def _validateEmail(dErrors, sName, sValue):
|
---|
880 | """ Validates a email field."""
|
---|
881 | (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = True);
|
---|
882 | if sError is not None:
|
---|
883 | dErrors[sName] = sError;
|
---|
884 | return sValue;
|
---|
885 |
|
---|
886 | @staticmethod
|
---|
887 | def _validateEmailNN(dErrors, sName, sValue):
|
---|
888 | """ Validates a email field."""
|
---|
889 | (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = False);
|
---|
890 | if sError is not None:
|
---|
891 | dErrors[sName] = sError;
|
---|
892 | return sValue;
|
---|
893 |
|
---|
894 | @staticmethod
|
---|
895 | def _validateListOfStr(dErrors, sName, asValues, asValidValues = None):
|
---|
896 | """ Validates a list of text items."""
|
---|
897 | (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = True);
|
---|
898 | if sError is not None:
|
---|
899 | dErrors[sName] = sError;
|
---|
900 | return sValue;
|
---|
901 |
|
---|
902 | @staticmethod
|
---|
903 | def _validateListOfStrNN(dErrors, sName, asValues, asValidValues = None):
|
---|
904 | """ Validates a list of text items, not null and len >= 1."""
|
---|
905 | (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = False);
|
---|
906 | if sError is not None:
|
---|
907 | dErrors[sName] = sError;
|
---|
908 | return sValue;
|
---|
909 |
|
---|
910 | #
|
---|
911 | # Various helpers.
|
---|
912 | #
|
---|
913 |
|
---|
914 | @staticmethod
|
---|
915 | def formatSimpleNowAndPeriod(oDb, tsNow = None, sPeriodBack = None,
|
---|
916 | sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
|
---|
917 | """
|
---|
918 | Formats a set of tsNow and sPeriodBack arguments for a standard testmanager
|
---|
919 | table.
|
---|
920 |
|
---|
921 | If sPeriodBack is given, the query is effective for the period
|
---|
922 | (tsNow - sPeriodBack) thru (tsNow).
|
---|
923 |
|
---|
924 | If tsNow isn't given, it defaults to current time.
|
---|
925 |
|
---|
926 | Returns the final portion of a WHERE query (start with AND) and maybe an
|
---|
927 | ORDER BY and LIMIT bit if sPeriodBack is given.
|
---|
928 | """
|
---|
929 | if tsNow is not None:
|
---|
930 | if sPeriodBack is not None:
|
---|
931 | sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (%s::timestamp - %s::interval)\n'
|
---|
932 | ' AND tsEffective <= %s\n'
|
---|
933 | 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
|
---|
934 | 'LIMIT 1\n'
|
---|
935 | , ( tsNow, sPeriodBack, tsNow));
|
---|
936 | else:
|
---|
937 | sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > %s\n'
|
---|
938 | ' AND ' + sTablePrefix + sEffCol + ' <= %s\n'
|
---|
939 | , ( tsNow, tsNow, ));
|
---|
940 | else:
|
---|
941 | if sPeriodBack is not None:
|
---|
942 | sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (CURRENT_TIMESTAMP - %s::interval)\n'
|
---|
943 | ' AND ' + sTablePrefix + sEffCol + ' <= CURRENT_TIMESTAMP\n'
|
---|
944 | 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
|
---|
945 | 'LIMIT 1\n'
|
---|
946 | , ( sPeriodBack, ));
|
---|
947 | else:
|
---|
948 | sRet = ' AND ' + sTablePrefix + sExpCol + ' = \'infinity\'::timestamp\n';
|
---|
949 | return sRet;
|
---|
950 |
|
---|
951 | @staticmethod
|
---|
952 | def formatSimpleNowAndPeriodQuery(oDb, sQuery, aBindArgs, tsNow = None, sPeriodBack = None,
|
---|
953 | sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
|
---|
954 | """
|
---|
955 | Formats a simple query for a standard testmanager table with optional
|
---|
956 | tsNow and sPeriodBack arguments.
|
---|
957 |
|
---|
958 | The sQuery and sBindArgs are passed along to oDb.formatBindArgs to form
|
---|
959 | the first part of the query. Must end with an open WHERE statement as
|
---|
960 | we'll be adding the time part starting with 'AND something...'.
|
---|
961 |
|
---|
962 | See formatSimpleNowAndPeriod for tsNow and sPeriodBack description.
|
---|
963 |
|
---|
964 | Returns the final portion of a WHERE query (start with AND) and maybe an
|
---|
965 | ORDER BY and LIMIT bit if sPeriodBack is given.
|
---|
966 |
|
---|
967 | """
|
---|
968 | return oDb.formatBindArgs(sQuery, aBindArgs) \
|
---|
969 | + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol);
|
---|
970 |
|
---|
971 | #
|
---|
972 | # Sub-classes.
|
---|
973 | #
|
---|
974 |
|
---|
975 | class DispWrapper(object):
|
---|
976 | """Proxy object."""
|
---|
977 | def __init__(self, oDisp, sAttrFmt):
|
---|
978 | self.oDisp = oDisp;
|
---|
979 | self.sAttrFmt = sAttrFmt;
|
---|
980 | def getStringParam(self, sName, asValidValues = None, sDefault = None):
|
---|
981 | """See WuiDispatcherBase.getStringParam."""
|
---|
982 | return self.oDisp.getStringParam(self.sAttrFmt % (sName,), asValidValues, sDefault);
|
---|
983 | def getListOfStrParams(self, sName, asDefaults = None):
|
---|
984 | """See WuiDispatcherBase.getListOfStrParams."""
|
---|
985 | return self.oDisp.getListOfStrParams(self.sAttrFmt % (sName,), asDefaults);
|
---|
986 | def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
|
---|
987 | """See WuiDispatcherBase.getListOfIntParams."""
|
---|
988 | return self.oDisp.getListOfIntParams(self.sAttrFmt % (sName,), iMin, iMax, aiDefaults);
|
---|
989 |
|
---|
990 |
|
---|
991 |
|
---|
992 |
|
---|
993 | # pylint: disable=E1101,C0111,R0903
|
---|
994 | class ModelDataBaseTestCase(unittest.TestCase):
|
---|
995 | """
|
---|
996 | Base testcase for ModelDataBase decendants.
|
---|
997 | Derive from this and override setUp.
|
---|
998 | """
|
---|
999 |
|
---|
1000 | def setUp(self):
|
---|
1001 | """
|
---|
1002 | Override this! Don't call super!
|
---|
1003 | The subclasses are expected to set aoSamples to an array of instance
|
---|
1004 | samples. The first entry must be a default object, the subsequent ones
|
---|
1005 | are optional and their contents freely choosen.
|
---|
1006 | """
|
---|
1007 | self.aoSamples = [ModelDataBase(),];
|
---|
1008 |
|
---|
1009 | def testEquality(self):
|
---|
1010 | for oSample in self.aoSamples:
|
---|
1011 | self.assertEqual(oSample.isEqual(copy.copy(oSample)), True);
|
---|
1012 | self.assertIsNotNone(oSample.isEqual(self.aoSamples[0]));
|
---|
1013 |
|
---|
1014 | def testNullConversion(self):
|
---|
1015 | if len(self.aoSamples[0].getDataAttributes()) == 0:
|
---|
1016 | return;
|
---|
1017 | for oSample in self.aoSamples:
|
---|
1018 | oCopy = copy.copy(oSample);
|
---|
1019 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1020 | self.assertEqual(oCopy.isEqual(oSample), False);
|
---|
1021 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1022 | self.assertEqual(oCopy.isEqual(oSample), True, '\ngot : %s\nexpected: %s' % (oCopy, oSample,));
|
---|
1023 |
|
---|
1024 | oCopy = copy.copy(oSample);
|
---|
1025 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1026 | oCopy2 = copy.copy(oCopy);
|
---|
1027 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1028 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1029 | self.assertEqual(oCopy.convertToParamNull(), oCopy);
|
---|
1030 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1031 |
|
---|
1032 | oCopy = copy.copy(oSample);
|
---|
1033 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1034 | oCopy2 = copy.copy(oCopy);
|
---|
1035 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1036 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1037 | self.assertEqual(oCopy.convertFromParamNull(), oCopy);
|
---|
1038 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1039 |
|
---|
1040 | def testReinitToNull(self):
|
---|
1041 | oFirst = copy.copy(self.aoSamples[0]);
|
---|
1042 | self.assertEqual(oFirst.reinitToNull(), oFirst);
|
---|
1043 | for oSample in self.aoSamples:
|
---|
1044 | oCopy = copy.copy(oSample);
|
---|
1045 | self.assertEqual(oCopy.reinitToNull(), oCopy);
|
---|
1046 | self.assertEqual(oCopy.isEqual(oFirst), True);
|
---|
1047 |
|
---|
1048 | def testValidateAndConvert(self):
|
---|
1049 | for oSample in self.aoSamples:
|
---|
1050 | oCopy = copy.copy(oSample);
|
---|
1051 | oCopy.convertToParamNull();
|
---|
1052 | dError1 = oCopy.validateAndConvert(None);
|
---|
1053 |
|
---|
1054 | oCopy2 = copy.copy(oCopy);
|
---|
1055 | self.assertEqual(oCopy.validateAndConvert(None), dError1);
|
---|
1056 | self.assertEqual(oCopy.isEqual(oCopy2), True);
|
---|
1057 |
|
---|
1058 | def testInitFromParams(self):
|
---|
1059 | class DummyDisp(object):
|
---|
1060 | def getStringParam(self, sName, asValidValues = None, sDefault = None):
|
---|
1061 | _ = sName; _ = asValidValues;
|
---|
1062 | return sDefault;
|
---|
1063 | def getListOfStrParams(self, sName, asDefaults = None):
|
---|
1064 | _ = sName;
|
---|
1065 | return asDefaults;
|
---|
1066 | def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
|
---|
1067 | _ = sName; _ = iMin; _ = iMax;
|
---|
1068 | return aiDefaults;
|
---|
1069 |
|
---|
1070 | for oSample in self.aoSamples:
|
---|
1071 | oCopy = copy.copy(oSample);
|
---|
1072 | self.assertEqual(oCopy.initFromParams(DummyDisp(), fStrict = False), oCopy);
|
---|
1073 |
|
---|
1074 | def testToString(self):
|
---|
1075 | for oSample in self.aoSamples:
|
---|
1076 | self.assertIsNotNone(oSample.toString());
|
---|
1077 |
|
---|
1078 | # pylint: enable=E1101,C0111,R0903
|
---|
1079 |
|
---|
1080 |
|
---|
1081 | class ModelLogicBase(ModelBase): # pylint: disable=R0903
|
---|
1082 | """
|
---|
1083 | Something all classes in the logic classes the logical model inherits from.
|
---|
1084 | """
|
---|
1085 |
|
---|
1086 | def __init__(self, oDb):
|
---|
1087 | ModelBase.__init__(self);
|
---|
1088 |
|
---|
1089 | #
|
---|
1090 | # Note! Do not create a connection here if None, we need to DB share
|
---|
1091 | # connection with all other logic objects so we can perform half
|
---|
1092 | # complex transactions involving several logic objects.
|
---|
1093 | #
|
---|
1094 | self._oDb = oDb;
|
---|
1095 |
|
---|
1096 | def getDbConnection(self):
|
---|
1097 | """
|
---|
1098 | Gets the database connection.
|
---|
1099 | This should only be used for instantiating other ModelLogicBase children.
|
---|
1100 | """
|
---|
1101 | return self._oDb;
|
---|
1102 |
|
---|
1103 |
|
---|
1104 | class AttributeChangeEntry(object): # pylint: disable=R0903
|
---|
1105 | """
|
---|
1106 | Data class representing the changes made to one attribute.
|
---|
1107 | """
|
---|
1108 |
|
---|
1109 | def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText):
|
---|
1110 | self.sAttr = sAttr;
|
---|
1111 | self.oNewRaw = oNewRaw;
|
---|
1112 | self.oOldRaw = oOldRaw;
|
---|
1113 | self.sNewText = sNewText;
|
---|
1114 | self.sOldText = sOldText;
|
---|
1115 |
|
---|
1116 | class ChangeLogEntry(object): # pylint: disable=R0903
|
---|
1117 | """
|
---|
1118 | A change log entry returned by the fetchChangeLog method typically
|
---|
1119 | implemented by ModelLogicBase child classes.
|
---|
1120 | """
|
---|
1121 |
|
---|
1122 | def __init__(self, uidAuthor, sAuthor, tsEffective, tsExpire, oNewRaw, oOldRaw, aoChanges):
|
---|
1123 | self.uidAuthor = uidAuthor;
|
---|
1124 | self.sAuthor = sAuthor;
|
---|
1125 | self.tsEffective = tsEffective;
|
---|
1126 | self.tsExpire = tsExpire;
|
---|
1127 | self.oNewRaw = oNewRaw;
|
---|
1128 | self.oOldRaw = oOldRaw; # Note! NULL for the last entry.
|
---|
1129 | self.aoChanges = aoChanges;
|
---|
1130 |
|
---|