VirtualBox

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

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

testmanager: Try allow unicode svn comments. (To try deal with r103985.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.0 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: base.py 61149 2016-05-24 01:54:00Z 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: 61149 $"
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 fAllowUnicodeSymbols = getattr(self, 'kfAllowUnicode_' + sAttr, False) );
278 ## @todo al.
279 elif sPrefix == 'aid':
280 (oNewValue, sError) = self.validateListOfInts(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
281 iMin = 1, iMax = 0x7ffffffe);
282 elif sPrefix == 'as':
283 (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
284 asValidValues = getattr(self, 'kasValidValues_' + sAttr, None),
285 cchMin = getattr(self, 'kcchMin_' + sAttr, 0 if fAllowNull else 1),
286 cchMax = getattr(self, 'kcchMax_' + sAttr, 4096));
287
288 elif sPrefix == 'bm':
289 ## @todo figure out bitfields.
290 (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull);
291 else:
292 raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix));
293
294 _ = sParam; _ = oDb;
295 return (oNewValue, sError);
296
297 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb):
298 """
299 Worker for implementing validateAndConvert().
300 """
301 dErrors = dict();
302 for sAttr in self.getDataAttributes():
303 oValue = getattr(self, sAttr);
304 sParam = getattr(self, 'ksParam_' + sAttr);
305 aoNilValues = self.getAttributeParamNullValues(sAttr);
306 aoNilValues.append(None);
307
308 (oNewValue, sError) = self._validateAndConvertAttribute(sAttr, sParam, oValue, aoNilValues,
309 sAttr in asAllowNullAttributes, oDb);
310 if oValue != oNewValue:
311 setattr(self, sAttr, oNewValue);
312 if sError is not None:
313 dErrors[sParam] = sError;
314 return dErrors;
315
316 def validateAndConvert(self, oDb):
317 """
318 Validates the input and converts valid fields to their right type.
319 Returns a dictionary with per field reports, only invalid fields will
320 be returned, so an empty dictionary means that the data is valid.
321
322 The dictionary keys are ksParam_*.
323
324 Child classes can override _validateAndConvertAttribute to handle
325 selected fields specially. There are also a few class variables that
326 can be used to advice the validation: kcchMin_sAttr, kcchMax_sAttr,
327 kiMin_iAttr, kiMax_iAttr, klMin_lAttr, klMax_lAttr,
328 kasValidValues_enmAttr, and kasAllowNullAttributes.
329 """
330 return self._validateAndConvertWorker(getattr(self, 'kasAllowNullAttributes', list()), oDb);
331
332 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
333 """
334 Calculate the attribute value when initialized from a parameter.
335
336 Returns the new value, with parameter NULL values. Raises exception on
337 invalid parameter value.
338
339 Child classes can override to do special parameter conversion jobs.
340 """
341 sPrefix = self.getHungarianPrefix(sAttr);
342 asValidValues = getattr(self, 'kasValidValues_' + sAttr, None);
343 if fStrict:
344 if sPrefix == 'f':
345 # HACK ALERT! Checkboxes are only present when checked, so we always have to provide a default.
346 oNewValue = oDisp.getStringParam(sParam, asValidValues, '0');
347 elif sPrefix[0] == 'a':
348 # HACK ALERT! List are not present if empty.
349 oNewValue = oDisp.getListOfStrParams(sParam, []);
350 else:
351 oNewValue = oDisp.getStringParam(sParam, asValidValues, None);
352 else:
353 if sPrefix[0] == 'a':
354 oNewValue = oDisp.getListOfStrParams(sParam, []);
355 else:
356 assert oValue is not None, 'sAttr=%s' % (sAttr,);
357 oNewValue = oDisp.getStringParam(sParam, asValidValues, oValue);
358 return oNewValue;
359
360 def initFromParams(self, oDisp, fStrict = True):
361 """
362 Initialize the object from parameters.
363 The input is not validated at all, except that all parameters must be
364 present when fStrict is True.
365
366 Returns self. Raises exception on invalid parameter value.
367
368 Note! The returned object has parameter NULL values, not database ones!
369 """
370
371 self.convertToParamNull()
372 for sAttr in self.getDataAttributes():
373 oValue = getattr(self, sAttr);
374 oNewValue = self.convertParamToAttribute(sAttr, getattr(self, 'ksParam_' + sAttr), oValue, oDisp, fStrict);
375 if oNewValue != oValue:
376 setattr(self, sAttr, oNewValue);
377 return self;
378
379 def areAttributeValuesEqual(self, sAttr, sPrefix, oValue1, oValue2):
380 """
381 Called to compare two attribute values and python thinks differs.
382
383 Returns True/False.
384
385 Child classes can override this to do special compares of things like arrays.
386 """
387 # Just in case someone uses it directly.
388 if oValue1 == oValue2:
389 return True;
390
391 #
392 # Timestamps can be both string (param) and object (db)
393 # depending on the data source. Compare string values to make
394 # sure we're doing the right thing here.
395 #
396 if sPrefix == 'ts':
397 return str(oValue1) == str(oValue2);
398
399 #
400 # Some generic code handling ModelDataBase children.
401 #
402 if isinstance(oValue1, list) and isinstance(oValue2, list):
403 if len(oValue1) == len(oValue2):
404 for i in range(len(oValue1)):
405 if not isinstance(oValue1[i], ModelDataBase) \
406 or type(oValue1) != type(oValue2):
407 return False;
408 if not oValue1[i].isEqual(oValue2[i]):
409 return False;
410 return True;
411
412 elif isinstance(oValue1, ModelDataBase) \
413 and type(oValue1) == type(oValue2):
414 return oValue1[i].isEqual(oValue2[i]);
415
416 _ = sAttr;
417 return False;
418
419 def isEqual(self, oOther):
420 """ Compares two instances. """
421 for sAttr in self.getDataAttributes():
422 if getattr(self, sAttr) != getattr(oOther, sAttr):
423 # Delegate the final decision to an overridable method.
424 if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
425 getattr(self, sAttr), getattr(oOther, sAttr)):
426 return False;
427 return True;
428
429 def isEqualEx(self, oOther, asExcludeAttrs):
430 """ Compares two instances, omitting the given attributes. """
431 for sAttr in self.getDataAttributes():
432 if sAttr not in asExcludeAttrs \
433 and getattr(self, sAttr) != getattr(oOther, sAttr):
434 # Delegate the final decision to an overridable method.
435 if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr),
436 getattr(self, sAttr), getattr(oOther, sAttr)):
437 return False;
438 return True;
439
440 def reinitToNull(self):
441 """
442 Reinitializes the object to (database) NULL values.
443 Returns self.
444 """
445 for sAttr in self.getDataAttributes():
446 setattr(self, sAttr, None);
447 return self;
448
449 def toString(self):
450 """
451 Stringifies the object.
452 Returns string representation.
453 """
454
455 sMembers = '';
456 for sAttr in self.getDataAttributes():
457 oValue = getattr(self, sAttr);
458 sMembers += ', %s=%s' % (sAttr, oValue);
459
460 oClass = type(self);
461 if sMembers == '':
462 return '<%s>' % (oClass.__name__);
463 return '<%s: %s>' % (oClass.__name__, sMembers[2:]);
464
465 def __str__(self):
466 return self.toString();
467
468
469
470 #
471 # New validation helpers.
472 #
473 # These all return (oValue, sError), where sError is None when the value
474 # is valid and an error message when not. On success and in case of
475 # range errors, oValue is converted into the requested type.
476 #
477
478 @staticmethod
479 def validateInt(sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, '']), fAllowNull = True):
480 """ Validates an integer field. """
481 if sValue in aoNilValues:
482 if fAllowNull:
483 return (None if sValue is None else aoNilValues[0], None);
484 return (sValue, 'Mandatory.');
485
486 try:
487 if utils.isString(sValue):
488 iValue = int(sValue, 0);
489 else:
490 iValue = int(sValue);
491 except:
492 return (sValue, 'Not an integer');
493
494 if iValue in aoNilValues:
495 return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
496
497 if iValue < iMin:
498 return (iValue, 'Value too small (min %d)' % (iMin,));
499 elif iValue > iMax:
500 return (iValue, 'Value too high (max %d)' % (iMax,));
501 return (iValue, None);
502
503 @staticmethod
504 def validateLong(sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, '']), fAllowNull = True):
505 """ Validates an long integer field. """
506 if sValue in aoNilValues:
507 if fAllowNull:
508 return (None if sValue is None else aoNilValues[0], None);
509 return (sValue, 'Mandatory.');
510 try:
511 if utils.isString(sValue):
512 lValue = long(sValue, 0);
513 else:
514 lValue = long(sValue);
515 except:
516 return (sValue, 'Not a long integer');
517
518 if lValue in aoNilValues:
519 return (aoNilValues[0], None if fAllowNull else 'Mandatory.');
520
521 if lMin is not None and lValue < lMin:
522 return (lValue, 'Value too small (min %d)' % (lMin,));
523 elif lMax is not None and lValue > lMax:
524 return (lValue, 'Value too high (max %d)' % (lMax,));
525 return (lValue, None);
526
527 @staticmethod
528 def validateTs(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
529 """ Validates a timestamp field. """
530 if sValue in aoNilValues:
531 return (sValue, None if fAllowNull else 'Mandatory.');
532 if not utils.isString(sValue):
533 return (sValue, None);
534
535 sError = None;
536 if len(sValue) == len('2012-10-08 01:54:06.364207+02:00'):
537 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);
538 if oRes is not None \
539 and ( int(oRes.group(6)) > 12 \
540 or int(oRes.group(7)) >= 60):
541 sError = 'Invalid timezone offset.';
542 elif len(sValue) == len('2012-10-08 01:54:06.00'):
543 oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{2}', sValue);
544 elif len(sValue) == len('9999-12-31 23:59:59.999999'):
545 oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{6}', sValue);
546 elif len(sValue) == len('999999-12-31 00:00:00.00'):
547 oRes = re.match(r'(\d{6})-([01]\d)-([0123])\d ([012]\d):[0-5]\d:([0-6]\d).\d{2}', sValue);
548 elif len(sValue) == len('9999-12-31T23:59:59.999999Z'):
549 oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d[Tt]([012]\d):[0-5]\d:([0-6]\d).\d{6}[Zz]', sValue);
550 elif len(sValue) == len('9999-12-31T23:59:59.999999999Z'):
551 oRes = re.match(r'(\d{4})-([01]\d)-([0123])\d[Tt]([012]\d):[0-5]\d:([0-6]\d).\d{9}[Zz]', sValue);
552 else:
553 return (sValue, 'Invalid timestamp length.');
554
555 if oRes is None:
556 sError = 'Invalid timestamp (format: 2012-10-08 01:54:06.364207+02:00).';
557 else:
558 iYear = int(oRes.group(1));
559 if iYear % 4 == 0 and (iYear % 100 != 0 or iYear % 400 == 0):
560 acDaysOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
561 else:
562 acDaysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
563 iMonth = int(oRes.group(2));
564 iDay = int(oRes.group(3));
565 iHour = int(oRes.group(4));
566 iSec = int(oRes.group(5));
567 if iMonth > 12:
568 sError = 'Invalid timestamp month.';
569 elif iDay > acDaysOfMonth[iMonth - 1]:
570 sError = 'Invalid timestamp day-of-month (%02d has %d days).' % (iMonth, acDaysOfMonth[iMonth - 1]);
571 elif iHour > 23:
572 sError = 'Invalid timestamp hour.'
573 elif iSec >= 61:
574 sError = 'Invalid timestamp second.'
575 elif iSec >= 60:
576 sError = 'Invalid timestamp: no leap seconds, please.'
577 return (sValue, sError);
578
579 @staticmethod
580 def validateIp(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
581 """ Validates an IP address field. """
582 if sValue in aoNilValues:
583 return (sValue, None if fAllowNull else 'Mandatory.');
584
585 if sValue == '::1':
586 return (sValue, None);
587
588 try:
589 socket.inet_pton(socket.AF_INET, sValue); # pylint: disable=E1101
590 except:
591 try:
592 socket.inet_pton(socket.AF_INET6, sValue); # pylint: disable=E1101
593 except:
594 return (sValue, 'Not a valid IP address.');
595
596 return (sValue, None);
597
598 @staticmethod
599 def validateBool(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
600 """ Validates a boolean field. """
601 if sValue in aoNilValues:
602 return (sValue, None if fAllowNull else 'Mandatory.');
603
604 if sValue in ('True', 'true', '1', True):
605 return (True, None);
606 if sValue in ('False', 'false', '0', False):
607 return (False, None);
608 return (sValue, 'Invalid boolean value.');
609
610 @staticmethod
611 def validateUuid(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
612 """ Validates an UUID field. """
613 if sValue in aoNilValues:
614 return (sValue, None if fAllowNull else 'Mandatory.');
615
616 try:
617 sValue = str(uuid.UUID(sValue));
618 except:
619 return (sValue, 'Invalid UUID value.');
620 return (sValue, None);
621
622 @staticmethod
623 def validateWord(sValue, cchMin = 1, cchMax = 64, asValid = None, aoNilValues = tuple([None, '']), fAllowNull = True):
624 """ Validates a word field. """
625 if sValue in aoNilValues:
626 return (sValue, None if fAllowNull else 'Mandatory.');
627
628 if re.search('[^a-zA-Z0-9_-]', sValue) is not None:
629 sError = 'Single word ([a-zA-Z0-9_-]), please.';
630 elif cchMin is not None and len(sValue) < cchMin:
631 sError = 'Too short, min %s chars' % (cchMin,);
632 elif cchMax is not None and len(sValue) > cchMax:
633 sError = 'Too long, max %s chars' % (cchMax,);
634 elif asValid is not None and sValue not in asValid:
635 sError = 'Invalid value "%s", must be one of: %s' % (sValue, asValid);
636 else:
637 sError = None;
638 return (sValue, sError);
639
640 @staticmethod
641 def validateStr(sValue, cchMin = 0, cchMax = 4096, aoNilValues = tuple([None, '']), fAllowNull = True,
642 fAllowUnicodeSymbols = False):
643 """ Validates a string field. """
644 if sValue in aoNilValues:
645 return (sValue, None if fAllowNull else 'Mandatory.');
646
647 if cchMin is not None and len(sValue) < cchMin:
648 sError = 'Too short, min %s chars' % (cchMin,);
649 elif cchMax is not None and len(sValue) > cchMax:
650 sError = 'Too long, max %s chars' % (cchMax,);
651 elif fAllowUnicodeSymbols is False and utils.hasNonAsciiCharacters(sValue):
652 sError = 'Non-ascii characters not allowed'
653 else:
654 sError = None;
655 return (sValue, sError);
656
657 @staticmethod
658 def validateEmail(sValue, aoNilValues = tuple([None, '']), fAllowNull = True):
659 """ Validates a email field."""
660 if sValue in aoNilValues:
661 return (sValue, None if fAllowNull else 'Mandatory.');
662
663 if re.match(r'.+@.+\..+', sValue) is None:
664 return (sValue,'Invalid e-mail format.');
665 return (sValue, None);
666
667 @staticmethod
668 def validateListOfSomething(asValues, aoNilValues = tuple([[], None]), fAllowNull = True):
669 """ Validate a list of some uniform values. Returns a copy of the list (if list it is). """
670 if asValues in aoNilValues or (len(asValues) == 0 and not fAllowNull):
671 return (asValues, None if fAllowNull else 'Mandatory.')
672
673 if not isinstance(asValues, list):
674 return (asValues, 'Invalid data type (%s).' % (type(asValues),));
675
676 asValues = list(asValues); # copy the list.
677 if len(asValues) > 0:
678 oType = type(asValues[0]);
679 for i in range(1, len(asValues)):
680 if type(asValues[i]) is not oType:
681 return (asValues, 'Invalid entry data type ([0]=%s vs [%d]=%s).' % (oType, i, type(asValues[i])) );
682
683 return (asValues, None);
684
685 @staticmethod
686 def validateListOfStr(asValues, cchMin = None, cchMax = None, asValidValues = None,
687 aoNilValues = tuple([[], None]), fAllowNull = True):
688 """ Validates a list of text items."""
689 (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
690
691 if sError is None and asValues not in aoNilValues and len(asValues) > 0:
692 if not utils.isString(asValues[0]):
693 return (asValues, 'Invalid item data type.');
694
695 if not fAllowNull and cchMin is None:
696 cchMin = 1;
697
698 for sValue in asValues:
699 if asValidValues is not None and sValue not in asValidValues:
700 sThisErr = 'Invalid value "%s".' % (sValue,);
701 elif cchMin is not None and len(sValue) < cchMin:
702 sThisErr = 'Value "%s" is too short, min length is %u chars.' % (sValue, cchMin);
703 elif cchMax is not None and len(sValue) > cchMax:
704 sThisErr = 'Value "%s" is too long, max length is %u chars.' % (sValue, cchMax);
705 else:
706 continue;
707
708 if sError is None:
709 sError = sThisErr;
710 else:
711 sError += ' ' + sThisErr;
712
713 return (asValues, sError);
714
715 @staticmethod
716 def validateListOfInts(asValues, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([[], None]), fAllowNull = True):
717 """ Validates a list of integer items."""
718 (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull);
719
720 if sError is None and asValues not in aoNilValues and len(asValues) > 0:
721 for i in range(len(asValues)):
722 sValue = asValues[i];
723
724 sThisErr = '';
725 try:
726 iValue = int(sValue);
727 except:
728 sThisErr = 'Invalid integer value "%s".' % (sValue,);
729 else:
730 asValues[i] = iValue;
731 if iValue < iMin:
732 sThisErr = 'Value %d is too small (min %d)' % (iValue, iMin,);
733 elif iValue > iMax:
734 sThisErr = 'Value %d is too high (max %d)' % (iValue, iMax,);
735 else:
736 continue;
737
738 if sError is None:
739 sError = sThisErr;
740 else:
741 sError += ' ' + sThisErr;
742
743 return (asValues, sError);
744
745
746
747 #
748 # Old validation helpers.
749 #
750
751 @staticmethod
752 def _validateInt(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
753 """ Validates an integer field. """
754 (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = True);
755 if sError is not None:
756 dErrors[sName] = sError;
757 return sValue;
758
759 @staticmethod
760 def _validateIntNN(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])):
761 """ Validates an integer field, not null. """
762 (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = False);
763 if sError is not None:
764 dErrors[sName] = sError;
765 return sValue;
766
767 @staticmethod
768 def _validateLong(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
769 """ Validates an long integer field. """
770 (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = False);
771 if sError is not None:
772 dErrors[sName] = sError;
773 return sValue;
774
775 @staticmethod
776 def _validateLongNN(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])):
777 """ Validates an long integer field, not null. """
778 (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = True);
779 if sError is not None:
780 dErrors[sName] = sError;
781 return sValue;
782
783 @staticmethod
784 def _validateTs(dErrors, sName, sValue):
785 """ Validates a timestamp field. """
786 (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = True);
787 if sError is not None:
788 dErrors[sName] = sError;
789 return sValue;
790
791 @staticmethod
792 def _validateTsNN(dErrors, sName, sValue):
793 """ Validates a timestamp field, not null. """
794 (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = False);
795 if sError is not None:
796 dErrors[sName] = sError;
797 return sValue;
798
799 @staticmethod
800 def _validateIp(dErrors, sName, sValue):
801 """ Validates an IP address field. """
802 (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = True);
803 if sError is not None:
804 dErrors[sName] = sError;
805 return sValue;
806
807 @staticmethod
808 def _validateIpNN(dErrors, sName, sValue):
809 """ Validates an IP address field, not null. """
810 (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = False);
811 if sError is not None:
812 dErrors[sName] = sError;
813 return sValue;
814
815 @staticmethod
816 def _validateBool(dErrors, sName, sValue):
817 """ Validates a boolean field. """
818 (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = True);
819 if sError is not None:
820 dErrors[sName] = sError;
821 return sValue;
822
823 @staticmethod
824 def _validateBoolNN(dErrors, sName, sValue):
825 """ Validates a boolean field, not null. """
826 (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = False);
827 if sError is not None:
828 dErrors[sName] = sError;
829 return sValue;
830
831 @staticmethod
832 def _validateUuid(dErrors, sName, sValue):
833 """ Validates an UUID field. """
834 (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = True);
835 if sError is not None:
836 dErrors[sName] = sError;
837 return sValue;
838
839 @staticmethod
840 def _validateUuidNN(dErrors, sName, sValue):
841 """ Validates an UUID field, not null. """
842 (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = False);
843 if sError is not None:
844 dErrors[sName] = sError;
845 return sValue;
846
847 @staticmethod
848 def _validateWord(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
849 """ Validates a word field. """
850 (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = True);
851 if sError is not None:
852 dErrors[sName] = sError;
853 return sValue;
854
855 @staticmethod
856 def _validateWordNN(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None):
857 """ Validates a boolean field, not null. """
858 (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = False);
859 if sError is not None:
860 dErrors[sName] = sError;
861 return sValue;
862
863 @staticmethod
864 def _validateStr(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
865 """ Validates a string field. """
866 (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = True);
867 if sError is not None:
868 dErrors[sName] = sError;
869 return sValue;
870
871 @staticmethod
872 def _validateStrNN(dErrors, sName, sValue, cchMin = 0, cchMax = 4096):
873 """ Validates a string field, not null. """
874 (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = False);
875 if sError is not None:
876 dErrors[sName] = sError;
877 return sValue;
878
879 @staticmethod
880 def _validateEmail(dErrors, sName, sValue):
881 """ Validates a email field."""
882 (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = True);
883 if sError is not None:
884 dErrors[sName] = sError;
885 return sValue;
886
887 @staticmethod
888 def _validateEmailNN(dErrors, sName, sValue):
889 """ Validates a email field."""
890 (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = False);
891 if sError is not None:
892 dErrors[sName] = sError;
893 return sValue;
894
895 @staticmethod
896 def _validateListOfStr(dErrors, sName, asValues, asValidValues = None):
897 """ Validates a list of text items."""
898 (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = True);
899 if sError is not None:
900 dErrors[sName] = sError;
901 return sValue;
902
903 @staticmethod
904 def _validateListOfStrNN(dErrors, sName, asValues, asValidValues = None):
905 """ Validates a list of text items, not null and len >= 1."""
906 (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = False);
907 if sError is not None:
908 dErrors[sName] = sError;
909 return sValue;
910
911 #
912 # Various helpers.
913 #
914
915 @staticmethod
916 def formatSimpleNowAndPeriod(oDb, tsNow = None, sPeriodBack = None,
917 sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
918 """
919 Formats a set of tsNow and sPeriodBack arguments for a standard testmanager
920 table.
921
922 If sPeriodBack is given, the query is effective for the period
923 (tsNow - sPeriodBack) thru (tsNow).
924
925 If tsNow isn't given, it defaults to current time.
926
927 Returns the final portion of a WHERE query (start with AND) and maybe an
928 ORDER BY and LIMIT bit if sPeriodBack is given.
929 """
930 if tsNow is not None:
931 if sPeriodBack is not None:
932 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (%s::timestamp - %s::interval)\n'
933 ' AND tsEffective <= %s\n'
934 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
935 'LIMIT 1\n'
936 , ( tsNow, sPeriodBack, tsNow));
937 else:
938 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > %s\n'
939 ' AND ' + sTablePrefix + sEffCol + ' <= %s\n'
940 , ( tsNow, tsNow, ));
941 else:
942 if sPeriodBack is not None:
943 sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (CURRENT_TIMESTAMP - %s::interval)\n'
944 ' AND ' + sTablePrefix + sEffCol + ' <= CURRENT_TIMESTAMP\n'
945 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n'
946 'LIMIT 1\n'
947 , ( sPeriodBack, ));
948 else:
949 sRet = ' AND ' + sTablePrefix + sExpCol + ' = \'infinity\'::timestamp\n';
950 return sRet;
951
952 @staticmethod
953 def formatSimpleNowAndPeriodQuery(oDb, sQuery, aBindArgs, tsNow = None, sPeriodBack = None,
954 sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'):
955 """
956 Formats a simple query for a standard testmanager table with optional
957 tsNow and sPeriodBack arguments.
958
959 The sQuery and sBindArgs are passed along to oDb.formatBindArgs to form
960 the first part of the query. Must end with an open WHERE statement as
961 we'll be adding the time part starting with 'AND something...'.
962
963 See formatSimpleNowAndPeriod for tsNow and sPeriodBack description.
964
965 Returns the final portion of a WHERE query (start with AND) and maybe an
966 ORDER BY and LIMIT bit if sPeriodBack is given.
967
968 """
969 return oDb.formatBindArgs(sQuery, aBindArgs) \
970 + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol);
971
972 #
973 # Sub-classes.
974 #
975
976 class DispWrapper(object):
977 """Proxy object."""
978 def __init__(self, oDisp, sAttrFmt):
979 self.oDisp = oDisp;
980 self.sAttrFmt = sAttrFmt;
981 def getStringParam(self, sName, asValidValues = None, sDefault = None):
982 """See WuiDispatcherBase.getStringParam."""
983 return self.oDisp.getStringParam(self.sAttrFmt % (sName,), asValidValues, sDefault);
984 def getListOfStrParams(self, sName, asDefaults = None):
985 """See WuiDispatcherBase.getListOfStrParams."""
986 return self.oDisp.getListOfStrParams(self.sAttrFmt % (sName,), asDefaults);
987 def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
988 """See WuiDispatcherBase.getListOfIntParams."""
989 return self.oDisp.getListOfIntParams(self.sAttrFmt % (sName,), iMin, iMax, aiDefaults);
990
991
992
993
994# pylint: disable=E1101,C0111,R0903
995class ModelDataBaseTestCase(unittest.TestCase):
996 """
997 Base testcase for ModelDataBase decendants.
998 Derive from this and override setUp.
999 """
1000
1001 def setUp(self):
1002 """
1003 Override this! Don't call super!
1004 The subclasses are expected to set aoSamples to an array of instance
1005 samples. The first entry must be a default object, the subsequent ones
1006 are optional and their contents freely choosen.
1007 """
1008 self.aoSamples = [ModelDataBase(),];
1009
1010 def testEquality(self):
1011 for oSample in self.aoSamples:
1012 self.assertEqual(oSample.isEqual(copy.copy(oSample)), True);
1013 self.assertIsNotNone(oSample.isEqual(self.aoSamples[0]));
1014
1015 def testNullConversion(self):
1016 if len(self.aoSamples[0].getDataAttributes()) == 0:
1017 return;
1018 for oSample in self.aoSamples:
1019 oCopy = copy.copy(oSample);
1020 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1021 self.assertEqual(oCopy.isEqual(oSample), False);
1022 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1023 self.assertEqual(oCopy.isEqual(oSample), True, '\ngot : %s\nexpected: %s' % (oCopy, oSample,));
1024
1025 oCopy = copy.copy(oSample);
1026 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1027 oCopy2 = copy.copy(oCopy);
1028 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1029 self.assertEqual(oCopy.isEqual(oCopy2), True);
1030 self.assertEqual(oCopy.convertToParamNull(), oCopy);
1031 self.assertEqual(oCopy.isEqual(oCopy2), True);
1032
1033 oCopy = copy.copy(oSample);
1034 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1035 oCopy2 = copy.copy(oCopy);
1036 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1037 self.assertEqual(oCopy.isEqual(oCopy2), True);
1038 self.assertEqual(oCopy.convertFromParamNull(), oCopy);
1039 self.assertEqual(oCopy.isEqual(oCopy2), True);
1040
1041 def testReinitToNull(self):
1042 oFirst = copy.copy(self.aoSamples[0]);
1043 self.assertEqual(oFirst.reinitToNull(), oFirst);
1044 for oSample in self.aoSamples:
1045 oCopy = copy.copy(oSample);
1046 self.assertEqual(oCopy.reinitToNull(), oCopy);
1047 self.assertEqual(oCopy.isEqual(oFirst), True);
1048
1049 def testValidateAndConvert(self):
1050 for oSample in self.aoSamples:
1051 oCopy = copy.copy(oSample);
1052 oCopy.convertToParamNull();
1053 dError1 = oCopy.validateAndConvert(None);
1054
1055 oCopy2 = copy.copy(oCopy);
1056 self.assertEqual(oCopy.validateAndConvert(None), dError1);
1057 self.assertEqual(oCopy.isEqual(oCopy2), True);
1058
1059 def testInitFromParams(self):
1060 class DummyDisp(object):
1061 def getStringParam(self, sName, asValidValues = None, sDefault = None):
1062 _ = sName; _ = asValidValues;
1063 return sDefault;
1064 def getListOfStrParams(self, sName, asDefaults = None):
1065 _ = sName;
1066 return asDefaults;
1067 def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None):
1068 _ = sName; _ = iMin; _ = iMax;
1069 return aiDefaults;
1070
1071 for oSample in self.aoSamples:
1072 oCopy = copy.copy(oSample);
1073 self.assertEqual(oCopy.initFromParams(DummyDisp(), fStrict = False), oCopy);
1074
1075 def testToString(self):
1076 for oSample in self.aoSamples:
1077 self.assertIsNotNone(oSample.toString());
1078
1079# pylint: enable=E1101,C0111,R0903
1080
1081
1082class ModelLogicBase(ModelBase): # pylint: disable=R0903
1083 """
1084 Something all classes in the logic classes the logical model inherits from.
1085 """
1086
1087 def __init__(self, oDb):
1088 ModelBase.__init__(self);
1089
1090 #
1091 # Note! Do not create a connection here if None, we need to DB share
1092 # connection with all other logic objects so we can perform half
1093 # complex transactions involving several logic objects.
1094 #
1095 self._oDb = oDb;
1096
1097 def getDbConnection(self):
1098 """
1099 Gets the database connection.
1100 This should only be used for instantiating other ModelLogicBase children.
1101 """
1102 return self._oDb;
1103
1104
1105class AttributeChangeEntry(object): # pylint: disable=R0903
1106 """
1107 Data class representing the changes made to one attribute.
1108 """
1109
1110 def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText):
1111 self.sAttr = sAttr;
1112 self.oNewRaw = oNewRaw;
1113 self.oOldRaw = oOldRaw;
1114 self.sNewText = sNewText;
1115 self.sOldText = sOldText;
1116
1117class ChangeLogEntry(object): # pylint: disable=R0903
1118 """
1119 A change log entry returned by the fetchChangeLog method typically
1120 implemented by ModelLogicBase child classes.
1121 """
1122
1123 def __init__(self, uidAuthor, sAuthor, tsEffective, tsExpire, oNewRaw, oOldRaw, aoChanges):
1124 self.uidAuthor = uidAuthor;
1125 self.sAuthor = sAuthor;
1126 self.tsEffective = tsEffective;
1127 self.tsExpire = tsExpire;
1128 self.oNewRaw = oNewRaw;
1129 self.oOldRaw = oOldRaw; # Note! NULL for the last entry.
1130 self.aoChanges = aoChanges;
1131
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