Implement PortSpec datatype and all spec. requirements

Support the built-in PortSpec datatype beyond basic schema by
checking for all sub properties and additional requirements
during validation.

Change-Id: Idb7ecbe7a8587badee59eae96c8f2b487e9dc0e6
This commit is contained in:
Matt Rutkowski 2016-03-28 23:00:20 -05:00
parent ceba80e85e
commit 0a91862a05
13 changed files with 347 additions and 41 deletions

View File

@ -88,6 +88,15 @@ class InvalidTypeError(TOSCAException):
msg_fmt = _('Type "%(what)s" is not a valid type.') msg_fmt = _('Type "%(what)s" is not a valid type.')
class InvalidTypeAdditionalRequirementsError(TOSCAException):
msg_fmt = _('Additional requirements for type "%(type)s" not met.')
class RangeValueError(TOSCAException):
msg_fmt = _('The value "%(pvalue)s" of property "%(pname)s" is out of '
'range "(min:%(vmin)s, max:%(vmax)s)".')
class InvalidSchemaError(TOSCAException): class InvalidSchemaError(TOSCAException):
msg_fmt = _('%(message)s') msg_fmt = _('%(message)s')

View File

@ -16,10 +16,10 @@ from toscaparser.common.exception import TypeMismatchError
from toscaparser.common.exception import UnknownFieldError from toscaparser.common.exception import UnknownFieldError
from toscaparser.elements.constraints import Schema from toscaparser.elements.constraints import Schema
from toscaparser.elements.datatype import DataType from toscaparser.elements.datatype import DataType
from toscaparser.elements.portspectype import PortSpec
from toscaparser.elements.scalarunit import ScalarUnit_Frequency from toscaparser.elements.scalarunit import ScalarUnit_Frequency
from toscaparser.elements.scalarunit import ScalarUnit_Size from toscaparser.elements.scalarunit import ScalarUnit_Size
from toscaparser.elements.scalarunit import ScalarUnit_Time from toscaparser.elements.scalarunit import ScalarUnit_Time
from toscaparser.utils.gettextutils import _ from toscaparser.utils.gettextutils import _
from toscaparser.utils import validateutils from toscaparser.utils import validateutils
@ -27,11 +27,13 @@ from toscaparser.utils import validateutils
class DataEntity(object): class DataEntity(object):
'''A complex data value entity.''' '''A complex data value entity.'''
def __init__(self, datatypename, value_dict, custom_def=None): def __init__(self, datatypename, value_dict, custom_def=None,
prop_name=None):
self.custom_def = custom_def self.custom_def = custom_def
self.datatype = DataType(datatypename, custom_def) self.datatype = DataType(datatypename, custom_def)
self.schema = self.datatype.get_all_properties() self.schema = self.datatype.get_all_properties()
self.value = value_dict self.value = value_dict
self.property_name = prop_name
def validate(self): def validate(self):
'''Validate the value by the definition of the datatype.''' '''Validate the value by the definition of the datatype.'''
@ -43,7 +45,7 @@ class DataEntity(object):
self.value, self.value,
None, None,
self.custom_def) self.custom_def)
schema = Schema(None, self.datatype.defs) schema = Schema(self.property_name, self.datatype.defs)
for constraint in schema.constraints: for constraint in schema.constraints:
constraint.validate(self.value) constraint.validate(self.value)
# If the datatype has 'properties' definition # If the datatype has 'properties' definition
@ -110,7 +112,8 @@ class DataEntity(object):
return self.schema[name].schema return self.schema[name].schema
@staticmethod @staticmethod
def validate_datatype(type, value, entry_schema=None, custom_def=None): def validate_datatype(type, value, entry_schema=None, custom_def=None,
prop_name=None):
'''Validate value with given type. '''Validate value with given type.
If type is list or map, validate its entry by entry_schema(if defined) If type is list or map, validate its entry by entry_schema(if defined)
@ -123,7 +126,7 @@ class DataEntity(object):
elif type == Schema.FLOAT: elif type == Schema.FLOAT:
return validateutils.validate_float(value) return validateutils.validate_float(value)
elif type == Schema.NUMBER: elif type == Schema.NUMBER:
return validateutils.validate_number(value) return validateutils.validate_numeric(value)
elif type == Schema.BOOLEAN: elif type == Schema.BOOLEAN:
return validateutils.validate_boolean(value) return validateutils.validate_boolean(value)
elif type == Schema.RANGE: elif type == Schema.RANGE:
@ -149,6 +152,10 @@ class DataEntity(object):
if entry_schema: if entry_schema:
DataEntity.validate_entry(value, entry_schema, custom_def) DataEntity.validate_entry(value, entry_schema, custom_def)
return value return value
elif type == Schema.PORTSPEC:
# TODO(TBD) bug 1567063, validate source & target as PortDef type
# as complex types not just as integers
PortSpec.validate_additional_req(value, prop_name, custom_def)
else: else:
data = DataEntity(type, value, custom_def) data = DataEntity(type, value, custom_def)
return data.validate() return data.validate()

View File

@ -16,6 +16,7 @@ from toscaparser.elements.statefulentitytype import StatefulEntityType
class CapabilityTypeDef(StatefulEntityType): class CapabilityTypeDef(StatefulEntityType):
'''TOSCA built-in capabilities type.''' '''TOSCA built-in capabilities type.'''
TOSCA_TYPEURI_CAPABILITY_ROOT = 'tosca.capabilities.Root'
def __init__(self, name, ctype, ntype, custom_def=None): def __init__(self, name, ctype, ntype, custom_def=None):
self.name = name self.name = name
@ -61,7 +62,7 @@ class CapabilityTypeDef(StatefulEntityType):
capabilities = {} capabilities = {}
parent_cap = self.parent_type parent_cap = self.parent_type
if parent_cap: if parent_cap:
while parent_cap != 'tosca.capabilities.Root': while parent_cap != self.TOSCA_TYPEURI_CAPABILITY_ROOT:
if parent_cap in self.TOSCA_DEF.keys(): if parent_cap in self.TOSCA_DEF.keys():
capabilities[parent_cap] = self.TOSCA_DEF[parent_cap] capabilities[parent_cap] = self.TOSCA_DEF[parent_cap]
elif custom_def and parent_cap in custom_def.keys(): elif custom_def and parent_cap in custom_def.keys():

View File

@ -18,6 +18,7 @@ import toscaparser
from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import ExceptionCollector
from toscaparser.common.exception import InvalidSchemaError from toscaparser.common.exception import InvalidSchemaError
from toscaparser.common.exception import ValidationError from toscaparser.common.exception import ValidationError
from toscaparser.elements.portspectype import PortSpec
from toscaparser.elements import scalarunit from toscaparser.elements import scalarunit
from toscaparser.utils.gettextutils import _ from toscaparser.utils.gettextutils import _
@ -36,12 +37,12 @@ class Schema(collections.Mapping):
INTEGER, STRING, BOOLEAN, FLOAT, RANGE, INTEGER, STRING, BOOLEAN, FLOAT, RANGE,
NUMBER, TIMESTAMP, LIST, MAP, NUMBER, TIMESTAMP, LIST, MAP,
SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME, SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
PORTDEF, VERSION VERSION, PORTDEF, PORTSPEC
) = ( ) = (
'integer', 'string', 'boolean', 'float', 'range', 'integer', 'string', 'boolean', 'float', 'range',
'number', 'timestamp', 'list', 'map', 'number', 'timestamp', 'list', 'map',
'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time', 'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time',
'PortDef', 'version' 'version', 'PortDef', PortSpec.SHORTNAME
) )
SCALAR_UNIT_SIZE_DEFAULT = 'B' SCALAR_UNIT_SIZE_DEFAULT = 'B'
@ -127,8 +128,6 @@ class Constraint(object):
'less_or_equal', 'in_range', 'valid_values', 'length', 'less_or_equal', 'in_range', 'valid_values', 'length',
'min_length', 'max_length', 'pattern') 'min_length', 'max_length', 'pattern')
UNBOUNDED = 'UNBOUNDED'
def __new__(cls, property_name, property_type, constraint): def __new__(cls, property_name, property_type, constraint):
if cls is not Constraint: if cls is not Constraint:
return super(Constraint, cls).__new__(cls) return super(Constraint, cls).__new__(cls)
@ -370,6 +369,7 @@ class InRange(Constraint):
Constrains a property or parameter to a value in range of (inclusive) Constrains a property or parameter to a value in range of (inclusive)
the two values declared. the two values declared.
""" """
UNBOUNDED = 'UNBOUNDED'
constraint_key = Constraint.IN_RANGE constraint_key = Constraint.IN_RANGE

View File

@ -18,7 +18,8 @@ class DataType(StatefulEntityType):
'''TOSCA built-in and user defined complex data type.''' '''TOSCA built-in and user defined complex data type.'''
def __init__(self, datatypename, custom_def=None): def __init__(self, datatypename, custom_def=None):
super(DataType, self).__init__(datatypename, self.DATATYPE_PREFIX, super(DataType, self).__init__(datatypename,
self.DATATYPE_NETWORK_PREFIX,
custom_def) custom_def)
self.custom_def = custom_def self.custom_def = custom_def

View File

@ -56,7 +56,8 @@ class EntityType(object):
GROUP_PREFIX = 'tosca.groups.' GROUP_PREFIX = 'tosca.groups.'
# currently the data types are defined only for network # currently the data types are defined only for network
# but may have changes in the future. # but may have changes in the future.
DATATYPE_PREFIX = 'tosca.datatypes.network.' DATATYPE_PREFIX = 'tosca.datatypes.'
DATATYPE_NETWORK_PREFIX = DATATYPE_PREFIX + 'network.'
TOSCA = 'tosca' TOSCA = 'tosca'
def derived_from(self, defs): def derived_from(self, defs):

View File

@ -0,0 +1,86 @@
# 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 logging
from toscaparser.common.exception import ExceptionCollector
from toscaparser.common.exception import InvalidTypeAdditionalRequirementsError
from toscaparser.utils.gettextutils import _
import toscaparser.utils.validateutils as validateutils
log = logging.getLogger('tosca')
class PortSpec(object):
'''Parent class for tosca.datatypes.network.PortSpec type.'''
SHORTNAME = 'PortSpec'
TYPE_URI = 'tosca.datatypes.network.' + SHORTNAME
PROPERTY_NAMES = (
PROTOCOL, SOURCE, SOURCE_RANGE,
TARGET, TARGET_RANGE
) = (
'protocol', 'source', 'source_range',
'target', 'target_range'
)
# TODO(TBD) May want to make this a subclass of DataType
# and change init method to set PortSpec's properties
def __init__(self):
pass
# The following additional requirements MUST be tested:
# 1) A valid PortSpec MUST have at least one of the following properties:
# target, target_range, source or source_range.
# 2) A valid PortSpec MUST have a value for the source property that
# is within the numeric range specified by the property source_range
# when source_range is specified.
# 3) A valid PortSpec MUST have a value for the target property that is
# within the numeric range specified by the property target_range
# when target_range is specified.
@staticmethod
def validate_additional_req(properties, prop_name, custom_def=None, ):
try:
source = properties.get(PortSpec.SOURCE)
source_range = properties.get(PortSpec.SOURCE_RANGE)
target = properties.get(PortSpec.TARGET)
target_range = properties.get(PortSpec.TARGET_RANGE)
# verify one of the specified values is set
if source is None and source_range is None and \
target is None and target_range is None:
ExceptionCollector.appendException(
InvalidTypeAdditionalRequirementsError(
type=PortSpec.TYPE_URI))
# Validate source value is in specified range
if source and source_range:
validateutils.validate_value_in_range(source, source_range,
PortSpec.SOURCE)
else:
from toscaparser.dataentity import DataEntity
portdef = DataEntity('PortDef', source, None, PortSpec.SOURCE)
portdef.validate()
# Validate target value is in specified range
if target and target_range:
validateutils.validate_value_in_range(target, target_range,
PortSpec.TARGET)
else:
from toscaparser.dataentity import DataEntity
portdef = DataEntity('PortDef', source, None, PortSpec.TARGET)
portdef.validate()
except Exception:
msg = _('"%(value)s" do not meet requirements '
'for type "%(type)s".') \
% {'value': properties, 'type': PortSpec.SHORTNAME}
ExceptionCollector.appendException(
ValueError(msg))

View File

@ -68,13 +68,16 @@ class Input(object):
ExceptionCollector.appendException( ExceptionCollector.appendException(
ValueError(_('Invalid type "%s".') % type)) ValueError(_('Invalid type "%s".') % type))
# TODO(anyone) Need to test for any built-in datatype not just network
# that is, tosca.datatypes.* and not assume tosca.datatypes.network.*
# TODO(anyone) Add support for tosca.datatypes.Credential
def _validate_value(self, value): def _validate_value(self, value):
tosca = EntityType.TOSCA_DEF tosca = EntityType.TOSCA_DEF
datatype = None datatype = None
if self.type in tosca: if self.type in tosca:
datatype = tosca[self.type] datatype = tosca[self.type]
elif EntityType.DATATYPE_PREFIX + self.type in tosca: elif EntityType.DATATYPE_NETWORK_PREFIX + self.type in tosca:
datatype = tosca[EntityType.DATATYPE_PREFIX + self.type] datatype = tosca[EntityType.DATATYPE_NETWORK_PREFIX + self.type]
DataEntity.validate_datatype(self.type, value, None, datatype) DataEntity.validate_datatype(self.type, value, None, datatype)

View File

@ -67,7 +67,8 @@ class Property(object):
self.value = str(self.value) self.value = str(self.value)
self.value = DataEntity.validate_datatype(self.type, self.value, self.value = DataEntity.validate_datatype(self.type, self.value,
self.entry_schema, self.entry_schema,
self.custom_def) self.custom_def,
self.name)
self._validate_constraints() self._validate_constraints()
def _validate_constraints(self): def _validate_constraints(self):

View File

@ -0,0 +1,41 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: TOSCA test PortSpec Additional Requirement clauses
node_types:
MyNodeType:
derived_from: Root
properties:
test_port:
type: PortSpec
topology_template:
node_templates:
# Test invalid source value below (default) specified range constraint
test_node2:
type: MyNodeType
properties:
test_port:
protocol: tcp
source: 0
# Test invalid source value over specified range
test_node3:
type: MyNodeType
properties:
test_port:
protocol: tcp
source: 65535
source_range: [ 2, 65534 ]
# Test invalid source value under specified range
test_node4:
type: MyNodeType
properties:
test_port:
protocol: tcp
source: 1
source_range: [ 2, 65534 ]

View File

@ -66,16 +66,21 @@ class DataTypeTest(TestCase):
tosca.my.datatypes.TestLab: tosca.my.datatypes.TestLab:
properties: properties:
temperature:
type: range
required: false
constraints:
- in_range: [-256, UNBOUNDED]
humidity: humidity:
type: range type: range
required: false required: false
constraints: constraints:
- in_range: [-256, INFINITY] - in_range: [-256, INFINITY]
temperature1:
type: range
required: false
constraints:
- in_range: [-256, UNBOUNDED]
temperature2:
type: range
required: false
constraints:
- in_range: [UNBOUNDED, 256]
''' '''
custom_type_def = yamlparser.simple_parse(custom_type_schema) custom_type_def = yamlparser.simple_parse(custom_type_schema)
@ -84,15 +89,6 @@ class DataTypeTest(TestCase):
value = yamlparser.simple_parse(value_snippet) value = yamlparser.simple_parse(value_snippet)
self.assertEqual(value, {}) self.assertEqual(value, {})
# TODO(Matt) - opened as bug 1555300
# Need a test for PortSpec normative data type
# that tests the spec. requirement: "A valid PortSpec
# must have at least one of the following properties:
# target, target_range, source or source_range."
# TODO(Matt) - opened as bug 1555310
# test PortSpec value for source and target
# against the source_range and target_range
# when specified.
def test_built_in_datatype(self): def test_built_in_datatype(self):
value_snippet = ''' value_snippet = '''
private_network: private_network:
@ -140,6 +136,31 @@ class DataTypeTest(TestCase):
data = DataEntity('PortInfo', value.get('ethernet_port')) data = DataEntity('PortInfo', value.get('ethernet_port'))
self.assertIsNotNone(data.validate()) self.assertIsNotNone(data.validate())
# Test normative PortSpec datatype's additional requirements
# TODO(Matt) - opened as bug 1555300
# Need a test for PortSpec normative data type
# that tests the spec. requirement: "A valid PortSpec
# must have at least one of the following properties:
# target, target_range, source or source_range."
# TODO(Matt) - opened as bug 1555310
# test PortSpec value for source and target
# against the source_range and target_range
# when specified.
def test_port_spec_addl_reqs(self):
value_snippet = '''
test_port:
protocol: tcp
target: 65535
target_range: [ 1, 65535 ]
source: 1
source_range: [ 1, 65535 ]
'''
value = yamlparser.simple_parse(value_snippet)
data = DataEntity('tosca.datatypes.network.PortSpec',
value.get('test_port'))
self.assertIsNotNone(data.validate())
def test_built_in_datatype_without_properties(self): def test_built_in_datatype_without_properties(self):
value_snippet = ''' value_snippet = '''
2 2
@ -365,6 +386,7 @@ class DataTypeTest(TestCase):
value_snippet = ''' value_snippet = '''
user_port: user_port:
protocol: tcp protocol: tcp
target: 1
target_range: [20000] target_range: [20000]
''' '''
value = yamlparser.simple_parse(value_snippet) value = yamlparser.simple_parse(value_snippet)
@ -377,6 +399,7 @@ class DataTypeTest(TestCase):
value_snippet = ''' value_snippet = '''
user_port: user_port:
protocol: tcp protocol: tcp
target: 1
target_range: [20000, 3000] target_range: [20000, 3000]
''' '''
value = yamlparser.simple_parse(value_snippet) value = yamlparser.simple_parse(value_snippet)
@ -400,7 +423,55 @@ class DataTypeTest(TestCase):
def test_range_unbounded(self): def test_range_unbounded(self):
value_snippet = ''' value_snippet = '''
temperature: [-100, 999999] humidity: [-100, 100]
'''
value = yamlparser.simple_parse(value_snippet)
data = DataEntity('tosca.my.datatypes.TestLab',
value, DataTypeTest.custom_type_def)
err = self.assertRaises(exception.InvalidSchemaError,
lambda: data.validate())
self.assertEqual(_('The property "in_range" expects comparable values.'
),
err.__str__())
def test_invalid_ranges_against_constraints(self):
# The TestLab range type has min=-256, max=UNBOUNDED
value_snippet = '''
temperature1: [-257, 999999]
'''
value = yamlparser.simple_parse(value_snippet)
data = DataEntity('tosca.my.datatypes.TestLab', value,
DataTypeTest.custom_type_def)
err = self.assertRaises(exception.ValidationError, data.validate)
self.assertEqual(_('The value "-257" of property "temperature1" is '
'out of range "(min:-256, max:UNBOUNDED)".'),
err.__str__())
value_snippet = '''
temperature2: [-999999, 257]
'''
value = yamlparser.simple_parse(value_snippet)
data = DataEntity('tosca.my.datatypes.TestLab', value,
DataTypeTest.custom_type_def)
err = self.assertRaises(exception.ValidationError, data.validate)
self.assertEqual(_('The value "257" of property "temperature2" is '
'out of range "(min:UNBOUNDED, max:256)".'),
err.__str__())
def test_valid_ranges_against_constraints(self):
# The TestLab range type has max=UNBOUNDED
value_snippet = '''
temperature1: [-255, 999999]
'''
value = yamlparser.simple_parse(value_snippet)
data = DataEntity('tosca.my.datatypes.TestLab', value,
DataTypeTest.custom_type_def)
self.assertIsNotNone(data.validate())
# The TestLab range type has min=UNBOUNDED
value_snippet = '''
temperature2: [-999999, 255]
''' '''
value = yamlparser.simple_parse(value_snippet) value = yamlparser.simple_parse(value_snippet)
data = DataEntity('tosca.my.datatypes.TestLab', value, data = DataEntity('tosca.my.datatypes.TestLab', value,

View File

@ -16,6 +16,7 @@ import six
from toscaparser.common import exception from toscaparser.common import exception
import toscaparser.elements.interfaces as ifaces import toscaparser.elements.interfaces as ifaces
from toscaparser.elements.nodetype import NodeType from toscaparser.elements.nodetype import NodeType
from toscaparser.elements.portspectype import PortSpec
from toscaparser.functions import GetInput from toscaparser.functions import GetInput
from toscaparser.functions import GetProperty from toscaparser.functions import GetProperty
from toscaparser.nodetemplate import NodeTemplate from toscaparser.nodetemplate import NodeTemplate
@ -730,3 +731,40 @@ class ToscaTemplateTest(TestCase):
rel = tosca.relationship_templates[0] rel = tosca.relationship_templates[0]
self.assertEqual(len(rel.interfaces), 1) self.assertEqual(len(rel.interfaces), 1)
self.assertEqual(rel.interfaces[0].type, "Configure") self.assertEqual(rel.interfaces[0].type, "Configure")
def test_various_portspec_errors(self):
tosca_tpl = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/datatypes/test_datatype_portspec_add_req.yaml")
self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl,
None)
# TODO(TBD) find way to reuse error messages from constraints.py
msg = (_('The value "%(pvalue)s" of property "%(pname)s" is out of '
'range "(min:%(vmin)s, max:%(vmax)s)".') %
dict(pname=PortSpec.SOURCE,
pvalue='0',
vmin='1',
vmax='65535'))
exception.ExceptionCollector.assertExceptionMessage(
exception.ValidationError, msg)
# Test value below range min.
msg = (_('The value "%(pvalue)s" of property "%(pname)s" is out of '
'range "(min:%(vmin)s, max:%(vmax)s)".') %
dict(pname=PortSpec.SOURCE,
pvalue='1',
vmin='2',
vmax='65534'))
exception.ExceptionCollector.assertExceptionMessage(
exception.RangeValueError, msg)
# Test value above range max.
msg = (_('The value "%(pvalue)s" of property "%(pname)s" is out of '
'range "(min:%(vmin)s, max:%(vmax)s)".') %
dict(pname=PortSpec.SOURCE,
pvalue='65535',
vmin='2',
vmax='65534'))
exception.ExceptionCollector.assertExceptionMessage(
exception.RangeValueError, msg)

View File

@ -17,14 +17,20 @@ import numbers
import re import re
import six import six
# from toscaparser.elements import constraints
from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import ExceptionCollector
from toscaparser.common.exception import InvalidTOSCAVersionPropertyException from toscaparser.common.exception import InvalidTOSCAVersionPropertyException
from toscaparser.common.exception import RangeValueError
from toscaparser.utils.gettextutils import _ from toscaparser.utils.gettextutils import _
log = logging.getLogger('tosca') log = logging.getLogger('tosca')
RANGE_UNBOUNDED = 'UNBOUNDED'
def str_to_num(value): def str_to_num(value):
'''Convert a string representation of a number into a numeric type.''' '''Convert a string representation of a number into a numeric type.'''
# TODO(TBD) we should not allow numeric values in, input should be str
if isinstance(value, numbers.Number): if isinstance(value, numbers.Number):
return value return value
try: try:
@ -33,8 +39,11 @@ def str_to_num(value):
return float(value) return float(value)
def validate_number(value): def validate_numeric(value):
return str_to_num(value) if not isinstance(value, numbers.Number):
ExceptionCollector.appendException(
ValueError(_('"%s" is not a numeric.') % value))
return value
def validate_integer(value): def validate_integer(value):
@ -51,7 +60,7 @@ def validate_float(value):
if not isinstance(value, float): if not isinstance(value, float):
ExceptionCollector.appendException( ExceptionCollector.appendException(
ValueError(_('"%s" is not a float.') % value)) ValueError(_('"%s" is not a float.') % value))
return validate_number(value) return value
def validate_string(value): def validate_string(value):
@ -68,15 +77,53 @@ def validate_list(value):
return value return value
def validate_range(value): def validate_range(range):
validate_list(value) # list class check
if isinstance(value, list): validate_list(range)
if len(value) != 2 or not (value[0] <= value[1]): # validate range list has a min and max
if len(range) != 2:
ExceptionCollector.appendException(
ValueError(_('"%s" is not a valid range.') % range))
# validate min and max are numerics or the keyword UNBOUNDED
min_test = max_test = False
if not range[0] == RANGE_UNBOUNDED:
min = validate_numeric(range[0])
else:
min_test = True
if not range[1] == RANGE_UNBOUNDED:
max = validate_numeric(range[1])
else:
max_test = True
# validate the max > min (account for UNBOUNDED)
if not min_test and not max_test:
# Note: min == max is allowed
if min > max:
ExceptionCollector.appendException( ExceptionCollector.appendException(
ValueError(_('"%s" is not a valid range.') % value)) ValueError(_('"%s" is not a valid range.') % range))
validate_integer(value[0])
if not value[1] == "UNBOUNDED": return range
validate_integer(value[1])
def validate_value_in_range(value, range, prop_name):
validate_numeric(value)
validate_range(range)
# Note: value is valid if equal to min
if range[0] != RANGE_UNBOUNDED:
if value < range[0]:
ExceptionCollector.appendException(
RangeValueError(pname=prop_name,
pvalue=value,
vmin=range[0],
vmax=range[1]))
# Note: value is valid if equal to max
if range[1] != RANGE_UNBOUNDED:
if value > range[1]:
ExceptionCollector.appendException(
RangeValueError(pname=prop_name,
pvalue=value,
vmin=range[0],
vmax=range[1]))
return value return value