srinivas_tadepalli 4898140457 tosca data type validation for float and timestamp
included unittesting in toscalib

Co-Authored-By: Sahdev Zala <spzala@us.ibm.com>

Change-Id: I2bb5a6eded7835a17c9d1662c8f8805d0e09d1b4
Closes-Bug: #1349648
2015-03-31 19:52:22 -07:00

649 lines
22 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import datetime
import dateutil.parser
import math
import numbers
import re
import six
from translator.toscalib.common.exception import InvalidSchemaError
from translator.toscalib.common.exception import ValidationError
from translator.toscalib.functions import is_function
from translator.toscalib.utils.gettextutils import _
class Schema(collections.Mapping):
KEYS = (
TYPE, REQUIRED, DESCRIPTION,
DEFAULT, CONSTRAINTS, ENTRYSCHEMA
) = (
'type', 'required', 'description',
'default', 'constraints', 'entry_schema'
)
PROPERTY_TYPES = (
INTEGER, STRING, BOOLEAN, FLOAT,
NUMBER, TIMESTAMP, LIST, MAP, SCALAR_UNIT_SIZE
) = (
'integer', 'string', 'boolean', 'float',
'number', 'timestamp', 'list', 'map', 'scalar-unit.size'
)
SCALAR_UNIT_SIZE_DEFAULT = 'B'
SCALAR_UNIT_SIZE_DICT = {'B': 1, 'KB': 1000, 'KIB': 1024, 'MB': 1000000,
'MIB': 1048576, 'GB': 1000000000,
'GIB': 1073741824, 'TB': 1000000000000,
'TIB': 1099511627776}
def __init__(self, name, schema_dict):
self.name = name
if not isinstance(schema_dict, collections.Mapping):
msg = _("Schema %(pname)s must be a dict.") % dict(pname=name)
raise InvalidSchemaError(message=msg)
try:
schema_dict['type']
except KeyError:
msg = _("Schema %(pname)s must have type.") % dict(pname=name)
raise InvalidSchemaError(message=msg)
self.schema = schema_dict
self._len = None
self.constraints_list = []
@property
def type(self):
return self.schema[self.TYPE]
@property
def required(self):
return self.schema.get(self.REQUIRED, False)
@property
def description(self):
return self.schema.get(self.DESCRIPTION, '')
@property
def default(self):
return self.schema.get(self.DEFAULT)
@property
def constraints(self):
if not self.constraints_list:
constraint_schemata = self.schema.get(self.CONSTRAINTS)
if constraint_schemata:
self.constraints_list = [Constraint(self.name,
self.type,
cschema)
for cschema in constraint_schemata]
return self.constraints_list
@property
def entry_schema(self):
return self.schema.get(self.ENTRYSCHEMA)
def __getitem__(self, key):
return self.schema[key]
def __iter__(self):
for k in self.KEYS:
try:
self.schema[k]
except KeyError:
pass
else:
yield k
def __len__(self):
if self._len is None:
self._len = len(list(iter(self)))
return self._len
class Constraint(object):
'''Parent class for constraints for a Property or Input.'''
CONSTRAINTS = (EQUAL, GREATER_THAN,
GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL, IN_RANGE,
VALID_VALUES, LENGTH, MIN_LENGTH, MAX_LENGTH, PATTERN) = \
('equal', 'greater_than', 'greater_or_equal', 'less_than',
'less_or_equal', 'in_range', 'valid_values', 'length',
'min_length', 'max_length', 'pattern')
def __new__(cls, property_name, property_type, constraint):
if cls is not Constraint:
return super(Constraint, cls).__new__(cls)
if(not isinstance(constraint, collections.Mapping) or
len(constraint) != 1):
raise InvalidSchemaError(message=_('Invalid constraint schema.'))
for type in constraint.keys():
ConstraintClass = get_constraint_class(type)
if not ConstraintClass:
msg = _('Invalid constraint type "%s".') % type
raise InvalidSchemaError(message=msg)
return ConstraintClass(property_name, property_type, constraint)
def __init__(self, property_name, property_type, constraint):
self.property_name = property_name
self.property_type = property_type
self.constraint_value = constraint[self.constraint_key]
self.constraint_value_msg = self.constraint_value
if self.property_type == Schema.SCALAR_UNIT_SIZE:
if isinstance(self.constraint_value, list):
self.constraint_value = [Constraint.
get_num_from_scalar_unit_size(v)
for v in self.constraint_value]
else:
self.constraint_value = (Constraint.
get_num_from_scalar_unit_size
(self.constraint_value))
# check if constraint is valid for property type
if property_type not in self.valid_prop_types:
msg = _('Constraint type "%(ctype)s" is not valid '
'for data type "%(dtype)s".') % dict(
ctype=self.constraint_key,
dtype=property_type)
raise InvalidSchemaError(message=msg)
def _err_msg(self, value):
return _('Property %s could not be validated.') % self.property_name
def validate(self, value):
self.value_msg = value
if self.property_type == Schema.SCALAR_UNIT_SIZE:
value = self.get_num_from_scalar_unit_size(value)
if not self._is_valid(value):
err_msg = self._err_msg(value)
raise ValidationError(message=err_msg)
@staticmethod
def validate_integer(value):
if not isinstance(value, int):
raise ValueError(_('"%s" is not an integer') % value)
return Constraint.validate_number(value)
@staticmethod
def validate_float(value):
if not isinstance(value, float):
raise ValueError(_('"%s" is not a float') % value)
return Constraint.validate_number(value)
@staticmethod
def validate_number(value):
return Constraint.str_to_num(value)
@staticmethod
def validate_string(value):
if not isinstance(value, six.string_types):
raise ValueError(_('"%s" is not a string') % value)
return value
@staticmethod
def validate_list(value):
if not isinstance(value, list):
raise ValueError(_('"%s" is not a list') % value)
return value
@staticmethod
def validate_map(value):
if not isinstance(value, collections.Mapping):
raise ValueError(_('"%s" is not a map') % value)
return value
@staticmethod
def validate_boolean(value):
if isinstance(value, bool):
return value
if isinstance(value, str):
normalised = value.lower()
if normalised in ['true', 'false']:
return normalised == 'true'
raise ValueError(_('"%s" is not a boolean') % value)
@staticmethod
def validate_scalar_unit_size(value):
regex = re.compile('(\d*)\s*(\w*)')
result = regex.match(str(value)).groups()
if result[0] and ((not result[1]) or (result[1].upper() in
Schema.SCALAR_UNIT_SIZE_DICT.
keys())):
return value
raise ValueError(_('"%s" is not a valid scalar-unit') % value)
@staticmethod
def get_num_from_scalar_unit_size(value, unit=None):
if unit:
if unit.upper() not in Schema.SCALAR_UNIT_SIZE_DICT.keys():
raise ValueError(_('input unit "%s" is not a valid unit')
% unit)
else:
unit = Schema.SCALAR_UNIT_SIZE_DEFAULT
Constraint.validate_scalar_unit_size(value)
regex = re.compile('(\d*)\s*(\w*)')
result = regex.match(str(value)).groups()
if not result[1]:
converted = (Constraint.str_to_num(result[0]))
if result[1].upper() in Schema.SCALAR_UNIT_SIZE_DICT.keys():
converted = int(Constraint.str_to_num(result[0])
* Schema.SCALAR_UNIT_SIZE_DICT[result[1].upper()]
* math.pow(Schema.SCALAR_UNIT_SIZE_DICT
[unit.upper()], -1))
return converted
@staticmethod
def validate_timestamp(value):
return dateutil.parser.parse(value)
@staticmethod
def str_to_num(value):
'''Convert a string representation of a number into a numeric type.'''
if isinstance(value, numbers.Number):
return value
try:
return int(value)
except ValueError:
return float(value)
class Equal(Constraint):
"""Constraint class for "equal"
Constrains a property or parameter to a value equal to ('=')
the value declared.
"""
constraint_key = Constraint.EQUAL
valid_prop_types = Schema.PROPERTY_TYPES
def _is_valid(self, value):
if value == self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s is not equal to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=self.value_msg,
cvalue=self.constraint_value_msg))
class GreaterThan(Constraint):
"""Constraint class for "greater_than"
Constrains a property or parameter to a value greater than ('>')
the value declared.
"""
constraint_key = Constraint.GREATER_THAN
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Schema.INTEGER, Schema.FLOAT,
Schema.TIMESTAMP, Schema.SCALAR_UNIT_SIZE)
def __init__(self, property_name, property_type, constraint):
super(GreaterThan, self).__init__(property_name, property_type,
constraint)
if not isinstance(constraint[self.GREATER_THAN], self.valid_types):
raise InvalidSchemaError(message=_('greater_than must '
'be comparable.'))
def _is_valid(self, value):
if value > self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be greater than "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=self.value_msg,
cvalue=self.constraint_value_msg))
class GreaterOrEqual(Constraint):
"""Constraint class for "greater_or_equal"
Constrains a property or parameter to a value greater than or equal
to ('>=') the value declared.
"""
constraint_key = Constraint.GREATER_OR_EQUAL
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Schema.INTEGER, Schema.FLOAT,
Schema.TIMESTAMP, Schema.SCALAR_UNIT_SIZE)
def __init__(self, property_name, property_type, constraint):
super(GreaterOrEqual, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('greater_or_equal must '
'be comparable.'))
def _is_valid(self, value):
if is_function(value) or value >= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be greater or equal '
'to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=self.value_msg,
cvalue=self.constraint_value_msg))
class LessThan(Constraint):
"""Constraint class for "less_than"
Constrains a property or parameter to a value less than ('<')
the value declared.
"""
constraint_key = Constraint.LESS_THAN
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Schema.INTEGER, Schema.FLOAT,
Schema.TIMESTAMP, Schema.SCALAR_UNIT_SIZE)
def __init__(self, property_name, property_type, constraint):
super(LessThan, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('less_than must '
'be comparable.'))
def _is_valid(self, value):
if value < self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be less than "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=self.value_msg,
cvalue=self.constraint_value_msg))
class LessOrEqual(Constraint):
"""Constraint class for "less_or_equal"
Constrains a property or parameter to a value less than or equal
to ('<=') the value declared.
"""
constraint_key = Constraint.LESS_OR_EQUAL
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Schema.INTEGER, Schema.FLOAT,
Schema.TIMESTAMP, Schema.SCALAR_UNIT_SIZE)
def __init__(self, property_name, property_type, constraint):
super(LessOrEqual, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('less_or_equal must '
'be comparable.'))
def _is_valid(self, value):
if value <= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be less or '
'equal to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=self.value_msg,
cvalue=self.constraint_value_msg))
class InRange(Constraint):
"""Constraint class for "in_range"
Constrains a property or parameter to a value in range of (inclusive)
the two values declared.
"""
constraint_key = Constraint.IN_RANGE
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Schema.INTEGER, Schema.FLOAT,
Schema.TIMESTAMP, Schema.SCALAR_UNIT_SIZE)
def __init__(self, property_name, property_type, constraint):
super(InRange, self).__init__(property_name, property_type, constraint)
if(not isinstance(self.constraint_value, collections.Sequence) or
(len(constraint[self.IN_RANGE]) != 2)):
raise InvalidSchemaError(message=_('in_range must be a list.'))
for value in self.constraint_value:
if not isinstance(value, self.valid_types):
raise InvalidSchemaError(_('in_range value must '
'be comparable.'))
self.min = self.constraint_value[0]
self.max = self.constraint_value[1]
def _is_valid(self, value):
if value < self.min:
return False
if value > self.max:
return False
return True
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s is out of range '
'(min:%(vmin)s, max:%(vmax)s).') %
dict(pname=self.property_name,
pvalue=self.value_msg,
vmin=self.constraint_value_msg[0],
vmax=self.constraint_value_msg[1]))
class ValidValues(Constraint):
"""Constraint class for "valid_values"
Constrains a property or parameter to a value that is in the list of
declared values.
"""
constraint_key = Constraint.VALID_VALUES
valid_prop_types = Schema.PROPERTY_TYPES
def __init__(self, property_name, property_type, constraint):
super(ValidValues, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, collections.Sequence):
raise InvalidSchemaError(message=_('valid_values must be a list.'))
def _is_valid(self, value):
if isinstance(value, list):
return all(v in self.constraint_value for v in value)
return value in self.constraint_value
def _err_msg(self, value):
allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value)
return (_('%(pname)s: %(pvalue)s is not an valid '
'value "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=allowed))
class Length(Constraint):
"""Constraint class for "length"
Constrains the property or parameter to a value of a given length.
"""
constraint_key = Constraint.LENGTH
valid_types = (int, )
valid_prop_types = (Schema.STRING, )
def __init__(self, property_name, property_type, constraint):
super(Length, self).__init__(property_name, property_type, constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('length must be integer.'))
def _is_valid(self, value):
if isinstance(value, str) and len(value) == self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('length of %(pname)s: %(pvalue)s must be equal '
'to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class MinLength(Constraint):
"""Constraint class for "min_length"
Constrains the property or parameter to a value to a minimum length.
"""
constraint_key = Constraint.MIN_LENGTH
valid_types = (int, )
valid_prop_types = (Schema.STRING, )
def __init__(self, property_name, property_type, constraint):
super(MinLength, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('min_length must be integer.'))
def _is_valid(self, value):
if isinstance(value, str) and len(value) >= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('length of %(pname)s: %(pvalue)s must be '
'at least "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class MaxLength(Constraint):
"""Constraint class for "max_length"
Constrains the property or parameter to a value to a maximum length.
"""
constraint_key = Constraint.MAX_LENGTH
valid_types = (int, )
valid_prop_types = (Schema.STRING, )
def __init__(self, property_name, property_type, constraint):
super(MaxLength, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('max_length must be integer.'))
def _is_valid(self, value):
if isinstance(value, str) and len(value) <= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('length of %(pname)s: %(pvalue)s must be no greater '
'than "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class Pattern(Constraint):
"""Constraint class for "pattern"
Constrains the property or parameter to a value that is allowed by
the provided regular expression.
"""
constraint_key = Constraint.PATTERN
valid_types = (str, )
valid_prop_types = (Schema.STRING, )
def __init__(self, property_name, property_type, constraint):
super(Pattern, self).__init__(property_name, property_type, constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise InvalidSchemaError(message=_('pattern must be string.'))
self.match = re.compile(self.constraint_value).match
def _is_valid(self, value):
match = self.match(value)
return match is not None and match.end() == len(value)
def _err_msg(self, value):
return (_('%(pname)s: "%(pvalue)s" does not match '
'pattern "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
constraint_mapping = {
Constraint.EQUAL: Equal,
Constraint.GREATER_THAN: GreaterThan,
Constraint.GREATER_OR_EQUAL: GreaterOrEqual,
Constraint.LESS_THAN: LessThan,
Constraint.LESS_OR_EQUAL: LessOrEqual,
Constraint.IN_RANGE: InRange,
Constraint.VALID_VALUES: ValidValues,
Constraint.LENGTH: Length,
Constraint.MIN_LENGTH: MinLength,
Constraint.MAX_LENGTH: MaxLength,
Constraint.PATTERN: Pattern
}
def get_constraint_class(type):
return constraint_mapping.get(type)