VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/base.py@ 57181

Last change on this file since 57181 was 56761, checked in by vboxsync, 9 years ago

baka

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 44.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: base.py 56761 2015-07-03 01:36:35Z vboxsync $
3# pylint: disable=C0302
4
5"""
6Test Manager Core - Base Class(es).
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2015 Oracle Corporation
12
13This file is part of VirtualBox Open Source Edition (OSE), as
14available from http://www.virtualbox.org. This file is free software;
15you can redistribute it and/or modify it under the terms of the GNU
16General Public License (GPL) as published by the Free Software
17Foundation, in version 2 as it comes in the "COPYING" file of the
18VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20
21The contents of this file may alternatively be used under the terms
22of the Common Development and Distribution License Version 1.0
23(CDDL) only, as it comes in the "COPYING.CDDL" file of the
24VirtualBox OSE distribution, in which case the provisions of the
25CDDL are applicable instead of those of the GPL.
26
27You may elect to license modified versions of this file under the
28terms and conditions of either the GPL or the CDDL or both.
29"""
30__version__ = "$Revision: 56761 $"
31
32
33# Standard python imports.
34import copy;
35import re;
36import socket;
37import sys;
38import uuid;
39import unittest;
40
41# Validation Kit imports.
42from common import utils;
43
44# Python 3 hacks:
45if sys.version_info[0] >= 3:
46 long = int # pylint: disable=W0622,C0103
47
48
49class TMExceptionBase(Exception):
50 """
51 For exceptions raised by any TestManager component.
52 """
53 pass;
54
55class TMTooManyRows(TMExceptionBase):
56 """
57 Too many rows in the result.
58 Used by ModelLogicBase decendants.
59 """
60 pass;
61
62
63class 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
75class 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
994class 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
1081class 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
1104class 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
1116class 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
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette