VirtualBox

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

Last change on this file since 55290 was 52776, checked in by vboxsync, 10 years ago

fix OSE

  • 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 52776 2014-09-17 14:51:43Z vboxsync $
3# pylint: disable=C0302
4
5"""
6Test Manager Core - Base Class(es).
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2014 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: 52776 $"
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, fAllowUnicodeSymbols = False): # pylint: disable=C0301
641 """ Validates a string field. """
642 if sValue in aoNilValues:
643 return (sValue, None if fAllowNull else 'Mandatory.');
644
645 if cchMin is not None and len(sValue) < cchMin:
646 sError = 'Too short, min %s chars' % (cchMin,);
647 elif cchMax is not None and len(sValue) > cchMax:
648 sError = 'Too long, max %s chars' % (cchMax,);
649 elif fAllowUnicodeSymbols is False and utils.hasNonAsciiCharacters(sValue):
650 sError = 'Non-ascii characters not allowed'
651 else:
652 sError = None;
653 return (sValue, sError);
654
655 @staticmethod
656 def validateEmail(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
657 """ Validates a email field."""
658 if sValue in aoNilValues:
659 return (sValue, None if fAllowNull else 'Mandatory.');
660
661 if re.match(r'.+@.+\..+', sValue) is None:
662 return (sValue,'Invalid e-mail format.');
663 return (sValue, None);
664
665 @staticmethod
666 def validateListOfSomething(asValues, aoNilValues = tuple([[], None]), fAllowNull = True):
667 """ Validate a list of some uniform values. Returns a copy of the list (if list it is). """
668 if asValues in aoNilValues or (len(asValues) == 0 and not fAllowNull):
669 return (asValues, None if fAllowNull else 'Mandatory.')
670
671 if not isinstance(asValues, list):
672 return (asValues, 'Invalid data type (%s).' % (type(asValues),));
673
674 asValues = list(asValues); # copy the list.
675 if len(asValues) > 0:
676 oType = type(asValues[0]);
677 for i in range(1, len(asValues)):
678 if type(asValues[i]) is not oType:
679 return (asValues, 'Invalid entry data type ([0]=%s vs [%d]=%s).' % (oType, i, type(asValues[i])) );
680
681 return (asValues, None);
682
683 @staticmethod
684 def validateListOfStr(asValues, cchMin = None, cchMax = None, asValidValues = None,
685 aoNilValues = tuple([[], None]), fAllowNull = True):
686 """ Validates a list of text items."""
687 (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
688
689 if sError is None and asValues not in aoNilValues and len(asValues) > 0:
690 if not utils.isString(asValues[0]):
691 return (asValues, 'Invalid item data type.');
692
693 if not fAllowNull and cchMin is None:
694 cchMin = 1;
695
696 for sValue in asValues:
697 if asValidValues is not None and sValue not in asValidValues:
698 sThisErr = 'Invalid value "%s".' % (sValue,);
699 elif cchMin is not None and len(sValue) < cchMin:
700 sThisErr = 'Value "%s" is too short, min length is %u chars.' % (sValue, cchMin);
701 elif cchMax is not None and len(sValue) > cchMax:
702 sThisErr = 'Value "%s" is too long, max length is %u chars.' % (sValue, cchMax);
703 else:
704 continue;
705
706 if sError is None:
707 sError = sThisErr;
708 else:
709 sError += ' ' + sThisErr;
710
711 return (asValues, sError);
712
713 @staticmethod
714 def validateListOfInts(asValues, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([[], None]), fAllowNull = True):
715 """ Validates a list of integer items."""
716 (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
717
718 if sError is None and asValues not in aoNilValues and len(asValues) > 0:
719 for i in range(len(asValues)):
720 sValue = asValues[i];
721
722 sThisErr = '';
723 try:
724 iValue = int(sValue);
725 except:
726 sThisErr = 'Invalid integer value "%s".' % (sValue,);
727 else:
728 asValues[i] = iValue;
729 if iValue < iMin:
730 sThisErr = 'Value %d is too small (min %d)' % (iValue, iMin,);
731 elif iValue > iMax:
732 sThisErr = 'Value %d is too high (max %d)' % (iValue, iMax,);
733 else:
734 continue;
735
736 if sError is None:
737 sError = sThisErr;
738 else:
739 sError += ' ' + sThisErr;
740
741 return (asValues, sError);
742
743
744
745 #
746 # Old validation helpers.
747 #
748
749 @staticmethod
750 def _validateInt(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
751 """ Validates an integer field. """
752 (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = True);
753 if sError is not None:
754 dErrors[sName] = sError;
755 return sValue;
756
757 @staticmethod
758 def _validateIntNN(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
759 """ Validates an integer field, not null. """
760 (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = False);
761 if sError is not None:
762 dErrors[sName] = sError;
763 return sValue;
764
765 @staticmethod
766 def _validateLong(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
767 """ Validates an long integer field. """
768 (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = False);
769 if sError is not None:
770 dErrors[sName] = sError;
771 return sValue;
772
773 @staticmethod
774 def _validateLongNN(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
775 """ Validates an long integer field, not null. """
776 (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = True);
777 if sError is not None:
778 dErrors[sName] = sError;
779 return sValue;
780
781 @staticmethod
782 def _validateTs(dErrors, sName, sValue):
783 """ Validates a timestamp field. """
784 (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = True);
785 if sError is not None:
786 dErrors[sName] = sError;
787 return sValue;
788
789 @staticmethod
790 def _validateTsNN(dErrors, sName, sValue):
791 """ Validates a timestamp field, not null. """
792 (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = False);
793 if sError is not None:
794 dErrors[sName] = sError;
795 return sValue;
796
797 @staticmethod
798 def _validateIp(dErrors, sName, sValue):
799 """ Validates an IP address field. """
800 (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = True);
801 if sError is not None:
802 dErrors[sName] = sError;
803 return sValue;
804
805 @staticmethod
806 def _validateIpNN(dErrors, sName, sValue):
807 """ Validates an IP address field, not null. """
808 (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = False);
809 if sError is not None:
810 dErrors[sName] = sError;
811 return sValue;
812
813 @staticmethod
814 def _validateBool(dErrors, sName, sValue):
815 """ Validates a boolean field. """
816 (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = True);
817 if sError is not None:
818 dErrors[sName] = sError;
819 return sValue;
820
821 @staticmethod
822 def _validateBoolNN(dErrors, sName, sValue):
823 """ Validates a boolean field, not null. """
824 (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = False);
825 if sError is not None:
826 dErrors[sName] = sError;
827 return sValue;
828
829 @staticmethod
830 def _validateUuid(dErrors, sName, sValue):
831 """ Validates an UUID field. """
832 (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = True);
833 if sError is not None:
834 dErrors[sName] = sError;
835 return sValue;
836
837 @staticmethod
838 def _validateUuidNN(dErrors, sName, sValue):
839 """ Validates an UUID field, not null. """
840 (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = False);
841 if sError is not None:
842 dErrors[sName] = sError;
843 return sValue;
844
845 @staticmethod
846 def _validateWord(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
847 """ Validates a word field. """
848 (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = True);
849 if sError is not None:
850 dErrors[sName] = sError;
851 return sValue;
852
853 @staticmethod
854 def _validateWordNN(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
855 """ Validates a boolean field, not null. """
856 (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = False);
857 if sError is not None:
858 dErrors[sName] = sError;
859 return sValue;
860
861 @staticmethod
862 def _validateStr(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
863 """ Validates a string field. """
864 (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = True);
865 if sError is not None:
866 dErrors[sName] = sError;
867 return sValue;
868
869 @staticmethod
870 def _validateStrNN(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
871 """ Validates a string field, not null. """
872 (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = False);
873 if sError is not None:
874 dErrors[sName] = sError;
875 return sValue;
876
877 @staticmethod
878 def _validateEmail(dErrors, sName, sValue):
879 """ Validates a email field."""
880 (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = True);
881 if sError is not None:
882 dErrors[sName] = sError;
883 return sValue;
884
885 @staticmethod
886 def _validateEmailNN(dErrors, sName, sValue):
887 """ Validates a email field."""
888 (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = False);
889 if sError is not None:
890 dErrors[sName] = sError;
891 return sValue;
892
893 @staticmethod
894 def _validateListOfStr(dErrors, sName, asValues, asValidValues = None):
895 """ Validates a list of text items."""
896 (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = True);
897 if sError is not None:
898 dErrors[sName] = sError;
899 return sValue;
900
901 @staticmethod
902 def _validateListOfStrNN(dErrors, sName, asValues, asValidValues = None):
903 """ Validates a list of text items, not null and len >= 1."""
904 (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = False);
905 if sError is not None:
906 dErrors[sName] = sError;
907 return sValue;
908
909 #
910 # Various helpers.
911 #
912
913 @staticmethod
914 def formatSimpleNowAndPeriod(oDb, tsNow = None, sPeriodBack = None,
915 sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
916 """
917 Formats a set of tsNow and sPeriodBack arguments for a standard testmanager
918 table.
919
920 If sPeriodBack is given, the query is effective for the period
921 (tsNow - sPeriodBack) thru (tsNow).
922
923 If tsNow isn't given, it defaults to current time.
924
925 Returns the final portion of a WHERE query (start with AND) and maybe an
926 ORDER BY and LIMIT bit if sPeriodBack is given.
927 """
928 if tsNow is not None:
929 if sPeriodBack is not None:
930 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (%s::timestamp - %s::interval)\n'
931 ' AND tsEffective <= %s\n'
932 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
933 'LIMIT 1\n'
934 , ( tsNow, sPeriodBack, tsNow));
935 else:
936 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > %s\n'
937 ' AND ' + sTablePrefix + sEffCol + ' <= %s\n'
938 , ( tsNow, tsNow, ));
939 else:
940 if sPeriodBack is not None:
941 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (CURRENT_TIMESTAMP - %s::interval)\n'
942 ' AND ' + sTablePrefix + sEffCol + ' <= CURRENT_TIMESTAMP\n'
943 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
944 'LIMIT 1\n'
945 , ( sPeriodBack, ));
946 else:
947 sRet = ' AND ' + sTablePrefix + sExpCol + ' = \'infinity\'::timestamp\n';
948 return sRet;
949
950 @staticmethod
951 def formatSimpleNowAndPeriodQuery(oDb, sQuery, aBindArgs, tsNow = None, sPeriodBack = None,
952 sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
953 """
954 Formats a simple query for a standard testmanager table with optional
955 tsNow and sPeriodBack arguments.
956
957 The sQuery and sBindArgs are passed along to oDb.formatBindArgs to form
958 the first part of the query. Must end with an open WHERE statement as
959 we'll be adding the time part starting with 'AND something...'.
960
961 See formatSimpleNowAndPeriod for tsNow and sPeriodBack description.
962
963 Returns the final portion of a WHERE query (start with AND) and maybe an
964 ORDER BY and LIMIT bit if sPeriodBack is given.
965
966 """
967 return oDb.formatBindArgs(sQuery, aBindArgs) \
968 + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol);
969
970 #
971 # Sub-classes.
972 #
973
974 class DispWrapper(object):
975 """Proxy object."""
976 def __init__(self, oDisp, sAttrFmt):
977 self.oDisp = oDisp;
978 self.sAttrFmt = sAttrFmt;
979 def getStringParam(self, sName, asValidValues = None, sDefault = None):
980 """See WuiDispatcherBase.getStringParam."""
981 return self.oDisp.getStringParam(self.sAttrFmt % (sName,), asValidValues, sDefault);
982 def getListOfStrParams(self, sName, asDefaults = None):
983 """See WuiDispatcherBase.getListOfStrParams."""
984 return self.oDisp.getListOfStrParams(self.sAttrFmt % (sName,), asDefaults);
985 def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
986 """See WuiDispatcherBase.getListOfIntParams."""
987 return self.oDisp.getListOfIntParams(self.sAttrFmt % (sName,), iMin, iMax, aiDefaults);
988
989
990
991
992# pylint: disable=E1101,C0111,R0903
993class ModelDataBaseTestCase(unittest.TestCase):
994 """
995 Base testcase for ModelDataBase decendants.
996 Derive from this and override setUp.
997 """
998
999 def setUp(self):
1000 """
1001 Override this! Don't call super!
1002 The subclasses are expected to set aoSamples to an array of instance
1003 samples. The first entry must be a default object, the subsequent ones
1004 are optional and their contents freely choosen.
1005 """
1006 self.aoSamples = [ModelDataBase(),];
1007
1008 def testEquality(self):
1009 for oSample in self.aoSamples:
1010 self.assertEqual(oSample.isEqual(copy.copy(oSample)), True);
1011 self.assertIsNotNone(oSample.isEqual(self.aoSamples[0]));
1012
1013 def testNullConversion(self):
1014 if len(self.aoSamples[0].getDataAttributes()) == 0:
1015 return;
1016 for oSample in self.aoSamples:
1017 oCopy = copy.copy(oSample);
1018 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1019 self.assertEqual(oCopy.isEqual(oSample), False);
1020 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1021 self.assertEqual(oCopy.isEqual(oSample), True, '\ngot : %s\nexpected: %s' % (oCopy, oSample,));
1022
1023 oCopy = copy.copy(oSample);
1024 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1025 oCopy2 = copy.copy(oCopy);
1026 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1027 self.assertEqual(oCopy.isEqual(oCopy2), True);
1028 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1029 self.assertEqual(oCopy.isEqual(oCopy2), True);
1030
1031 oCopy = copy.copy(oSample);
1032 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1033 oCopy2 = copy.copy(oCopy);
1034 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1035 self.assertEqual(oCopy.isEqual(oCopy2), True);
1036 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1037 self.assertEqual(oCopy.isEqual(oCopy2), True);
1038
1039 def testReinitToNull(self):
1040 oFirst = copy.copy(self.aoSamples[0]);
1041 self.assertEqual(oFirst.reinitToNull(), oFirst);
1042 for oSample in self.aoSamples:
1043 oCopy = copy.copy(oSample);
1044 self.assertEqual(oCopy.reinitToNull(), oCopy);
1045 self.assertEqual(oCopy.isEqual(oFirst), True);
1046
1047 def testValidateAndConvert(self):
1048 for oSample in self.aoSamples:
1049 oCopy = copy.copy(oSample);
1050 oCopy.convertToParamNull();
1051 dError1 = oCopy.validateAndConvert(None);
1052
1053 oCopy2 = copy.copy(oCopy);
1054 self.assertEqual(oCopy.validateAndConvert(None), dError1);
1055 self.assertEqual(oCopy.isEqual(oCopy2), True);
1056
1057 def testInitFromParams(self):
1058 class DummyDisp(object):
1059 def getStringParam(self, sName, asValidValues = None, sDefault = None):
1060 _ = sName; _ = asValidValues;
1061 return sDefault;
1062 def getListOfStrParams(self, sName, asDefaults = None):
1063 _ = sName;
1064 return asDefaults;
1065 def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
1066 _ = sName; _ = iMin; _ = iMax;
1067 return aiDefaults;
1068
1069 for oSample in self.aoSamples:
1070 oCopy = copy.copy(oSample);
1071 self.assertEqual(oCopy.initFromParams(DummyDisp(), fStrict = False), oCopy);
1072
1073 def testToString(self):
1074 for oSample in self.aoSamples:
1075 self.assertIsNotNone(oSample.toString());
1076
1077# pylint: enable=E1101,C0111,R0903
1078
1079
1080class ModelLogicBase(ModelBase): # pylint: disable=R0903
1081 """
1082 Something all classes in the logic classes the logical model inherits from.
1083 """
1084
1085 def __init__(self, oDb):
1086 ModelBase.__init__(self);
1087
1088 #
1089 # Note! Do not create a connection here if None, we need to DB share
1090 # connection with all other logic objects so we can perform half
1091 # complex transactions involving several logic objects.
1092 #
1093 self._oDb = oDb;
1094
1095 def getDbConnection(self):
1096 """
1097 Gets the database connection.
1098 This should only be used for instantiating other ModelLogicBase children.
1099 """
1100 return self._oDb;
1101
1102
1103class AttributeChangeEntry(object): # pylint: disable=R0903
1104 """
1105 Data class representing the changes made to one attribute.
1106 """
1107
1108 def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText):
1109 self.sAttr = sAttr;
1110 self.oNewRaw = oNewRaw;
1111 self.oOldRaw = oOldRaw;
1112 self.sNewText = sNewText;
1113 self.sOldText = sOldText;
1114
1115class ChangeLogEntry(object): # pylint: disable=R0903
1116 """
1117 A change log entry returned by the fetchChangeLog method typically
1118 implemented by ModelLogicBase child classes.
1119 """
1120
1121 def __init__(self, uidAuthor, sAuthor, tsEffective, tsExpire, oNewRaw, oOldRaw, aoChanges):
1122 self.uidAuthor = uidAuthor;
1123 self.sAuthor = sAuthor;
1124 self.tsEffective = tsEffective;
1125 self.tsExpire = tsExpire;
1126 self.oNewRaw = oNewRaw;
1127 self.oOldRaw = oOldRaw; # Note! NULL for the last entry.
1128 self.aoChanges = aoChanges;
1129
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