1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
5 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
18 from toscaparser.common.exception import ExceptionCollector
19 from toscaparser.common.exception import InvalidSchemaError
20 from toscaparser.common.exception import ValidationError
21 from toscaparser.elements import scalarunit
22 from toscaparser.utils.gettextutils import _
25 class Schema(collections.Mapping):
28 TYPE, REQUIRED, DESCRIPTION,
29 DEFAULT, CONSTRAINTS, ENTRYSCHEMA
31 'type', 'required', 'description',
32 'default', 'constraints', 'entry_schema'
36 INTEGER, STRING, BOOLEAN, FLOAT,
37 NUMBER, TIMESTAMP, LIST, MAP,
38 SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
41 'integer', 'string', 'boolean', 'float',
42 'number', 'timestamp', 'list', 'map',
43 'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time',
47 SCALAR_UNIT_SIZE_DEFAULT = 'B'
48 SCALAR_UNIT_SIZE_DICT = {'B': 1, 'KB': 1000, 'KIB': 1024, 'MB': 1000000,
49 'MIB': 1048576, 'GB': 1000000000,
50 'GIB': 1073741824, 'TB': 1000000000000,
53 def __init__(self, name, schema_dict):
55 if not isinstance(schema_dict, collections.Mapping):
56 msg = (_('Schema definition of "%(pname)s" must be a dict.')
58 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
63 msg = (_('Schema definition of "%(pname)s" must have a "type" '
64 'attribute.') % dict(pname=name))
65 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
67 self.schema = schema_dict
69 self.constraints_list = []
73 return self.schema[self.TYPE]
77 return self.schema.get(self.REQUIRED, True)
80 def description(self):
81 return self.schema.get(self.DESCRIPTION, '')
85 return self.schema.get(self.DEFAULT)
88 def constraints(self):
89 if not self.constraints_list:
90 constraint_schemata = self.schema.get(self.CONSTRAINTS)
91 if constraint_schemata:
92 self.constraints_list = [Constraint(self.name,
95 for cschema in constraint_schemata]
96 return self.constraints_list
99 def entry_schema(self):
100 return self.schema.get(self.ENTRYSCHEMA)
102 def __getitem__(self, key):
103 return self.schema[key]
115 if self._len is None:
116 self._len = len(list(iter(self)))
120 class Constraint(object):
121 '''Parent class for constraints for a Property or Input.'''
123 CONSTRAINTS = (EQUAL, GREATER_THAN,
124 GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL, IN_RANGE,
125 VALID_VALUES, LENGTH, MIN_LENGTH, MAX_LENGTH, PATTERN) = \
126 ('equal', 'greater_than', 'greater_or_equal', 'less_than',
127 'less_or_equal', 'in_range', 'valid_values', 'length',
128 'min_length', 'max_length', 'pattern')
130 def __new__(cls, property_name, property_type, constraint):
131 if cls is not Constraint:
132 return super(Constraint, cls).__new__(cls)
134 if(not isinstance(constraint, collections.Mapping) or
135 len(constraint) != 1):
136 ExceptionCollector.appendException(
137 InvalidSchemaError(message=_('Invalid constraint schema.')))
139 for type in constraint.keys():
140 ConstraintClass = get_constraint_class(type)
141 if not ConstraintClass:
142 msg = _('Invalid property "%s".') % type
143 ExceptionCollector.appendException(
144 InvalidSchemaError(message=msg))
146 return ConstraintClass(property_name, property_type, constraint)
148 def __init__(self, property_name, property_type, constraint):
149 self.property_name = property_name
150 self.property_type = property_type
151 self.constraint_value = constraint[self.constraint_key]
152 self.constraint_value_msg = self.constraint_value
153 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
154 self.constraint_value = self._get_scalarunit_constraint_value()
155 # check if constraint is valid for property type
156 if property_type not in self.valid_prop_types:
157 msg = _('Property "%(ctype)s" is not valid for data type '
158 '"%(dtype)s".') % dict(
159 ctype=self.constraint_key,
161 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
163 def _get_scalarunit_constraint_value(self):
164 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
165 ScalarUnit_Class = (scalarunit.
166 get_scalarunit_class(self.property_type))
167 if isinstance(self.constraint_value, list):
168 return [ScalarUnit_Class(v).get_num_from_scalar_unit()
169 for v in self.constraint_value]
171 return (ScalarUnit_Class(self.constraint_value).
172 get_num_from_scalar_unit())
174 def _err_msg(self, value):
175 return _('Property "%s" could not be validated.') % self.property_name
177 def validate(self, value):
178 self.value_msg = value
179 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
180 value = scalarunit.get_scalarunit_value(self.property_type, value)
181 if not self._is_valid(value):
182 err_msg = self._err_msg(value)
183 ExceptionCollector.appendException(
184 ValidationError(message=err_msg))
187 class Equal(Constraint):
188 """Constraint class for "equal"
190 Constrains a property or parameter to a value equal to ('=')
194 constraint_key = Constraint.EQUAL
196 valid_prop_types = Schema.PROPERTY_TYPES
198 def _is_valid(self, value):
199 if value == self.constraint_value:
204 def _err_msg(self, value):
205 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
206 'equal to "%(cvalue)s".') %
207 dict(pname=self.property_name,
208 pvalue=self.value_msg,
209 cvalue=self.constraint_value_msg))
212 class GreaterThan(Constraint):
213 """Constraint class for "greater_than"
215 Constrains a property or parameter to a value greater than ('>')
219 constraint_key = Constraint.GREATER_THAN
221 valid_types = (int, float, datetime.date,
222 datetime.time, datetime.datetime)
224 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
225 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
226 Schema.SCALAR_UNIT_TIME)
228 def __init__(self, property_name, property_type, constraint):
229 super(GreaterThan, self).__init__(property_name, property_type,
231 if not isinstance(constraint[self.GREATER_THAN], self.valid_types):
232 ExceptionCollector.appendException(
233 InvalidSchemaError(message=_('The property "greater_than" '
234 'expects comparable values.')))
236 def _is_valid(self, value):
237 if value > self.constraint_value:
242 def _err_msg(self, value):
243 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
244 'greater than "%(cvalue)s".') %
245 dict(pname=self.property_name,
246 pvalue=self.value_msg,
247 cvalue=self.constraint_value_msg))
250 class GreaterOrEqual(Constraint):
251 """Constraint class for "greater_or_equal"
253 Constrains a property or parameter to a value greater than or equal
254 to ('>=') the value declared.
257 constraint_key = Constraint.GREATER_OR_EQUAL
259 valid_types = (int, float, datetime.date,
260 datetime.time, datetime.datetime)
262 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
263 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
264 Schema.SCALAR_UNIT_TIME)
266 def __init__(self, property_name, property_type, constraint):
267 super(GreaterOrEqual, self).__init__(property_name, property_type,
269 if not isinstance(self.constraint_value, self.valid_types):
270 ExceptionCollector.appendException(
271 InvalidSchemaError(message=_('The property '
272 '"greater_or_equal" expects '
273 'comparable values.')))
275 def _is_valid(self, value):
276 if toscaparser.functions.is_function(value) or \
277 value >= self.constraint_value:
281 def _err_msg(self, value):
282 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
283 'greater than or equal to "%(cvalue)s".') %
284 dict(pname=self.property_name,
285 pvalue=self.value_msg,
286 cvalue=self.constraint_value_msg))
289 class LessThan(Constraint):
290 """Constraint class for "less_than"
292 Constrains a property or parameter to a value less than ('<')
296 constraint_key = Constraint.LESS_THAN
298 valid_types = (int, float, datetime.date,
299 datetime.time, datetime.datetime)
301 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
302 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
303 Schema.SCALAR_UNIT_TIME)
305 def __init__(self, property_name, property_type, constraint):
306 super(LessThan, self).__init__(property_name, property_type,
308 if not isinstance(self.constraint_value, self.valid_types):
309 ExceptionCollector.appendException(
310 InvalidSchemaError(message=_('The property "less_than" '
311 'expects comparable values.')))
313 def _is_valid(self, value):
314 if value < self.constraint_value:
319 def _err_msg(self, value):
320 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
321 'less than "%(cvalue)s".') %
322 dict(pname=self.property_name,
323 pvalue=self.value_msg,
324 cvalue=self.constraint_value_msg))
327 class LessOrEqual(Constraint):
328 """Constraint class for "less_or_equal"
330 Constrains a property or parameter to a value less than or equal
331 to ('<=') the value declared.
334 constraint_key = Constraint.LESS_OR_EQUAL
336 valid_types = (int, float, datetime.date,
337 datetime.time, datetime.datetime)
339 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
340 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
341 Schema.SCALAR_UNIT_TIME)
343 def __init__(self, property_name, property_type, constraint):
344 super(LessOrEqual, self).__init__(property_name, property_type,
346 if not isinstance(self.constraint_value, self.valid_types):
347 ExceptionCollector.appendException(
348 InvalidSchemaError(message=_('The property "less_or_equal" '
349 'expects comparable values.')))
351 def _is_valid(self, value):
352 if value <= self.constraint_value:
357 def _err_msg(self, value):
358 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
359 'less than or equal to "%(cvalue)s".') %
360 dict(pname=self.property_name,
361 pvalue=self.value_msg,
362 cvalue=self.constraint_value_msg))
365 class InRange(Constraint):
366 """Constraint class for "in_range"
368 Constrains a property or parameter to a value in range of (inclusive)
369 the two values declared.
372 constraint_key = Constraint.IN_RANGE
374 valid_types = (int, float, datetime.date,
375 datetime.time, datetime.datetime)
377 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
378 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
379 Schema.SCALAR_UNIT_TIME)
381 def __init__(self, property_name, property_type, constraint):
382 super(InRange, self).__init__(property_name, property_type, constraint)
383 if(not isinstance(self.constraint_value, collections.Sequence) or
384 (len(constraint[self.IN_RANGE]) != 2)):
385 ExceptionCollector.appendException(
386 InvalidSchemaError(message=_('The property "in_range" '
389 for value in self.constraint_value:
390 if not isinstance(value, self.valid_types):
391 ExceptionCollector.appendException(
392 InvalidSchemaError(_('The property "in_range" expects '
393 'comparable values.')))
395 self.min = self.constraint_value[0]
396 self.max = self.constraint_value[1]
398 def _is_valid(self, value):
406 def _err_msg(self, value):
407 return (_('The value "%(pvalue)s" of property "%(pname)s" is out of '
408 'range "(min:%(vmin)s, max:%(vmax)s)".') %
409 dict(pname=self.property_name,
410 pvalue=self.value_msg,
411 vmin=self.constraint_value_msg[0],
412 vmax=self.constraint_value_msg[1]))
415 class ValidValues(Constraint):
416 """Constraint class for "valid_values"
418 Constrains a property or parameter to a value that is in the list of
421 constraint_key = Constraint.VALID_VALUES
423 valid_prop_types = Schema.PROPERTY_TYPES
425 def __init__(self, property_name, property_type, constraint):
426 super(ValidValues, self).__init__(property_name, property_type,
428 if not isinstance(self.constraint_value, collections.Sequence):
429 ExceptionCollector.appendException(
430 InvalidSchemaError(message=_('The property "valid_values" '
433 def _is_valid(self, value):
434 if isinstance(value, list):
435 return all(v in self.constraint_value for v in value)
436 return value in self.constraint_value
438 def _err_msg(self, value):
439 allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value)
440 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
441 'valid. Expected a value from "%(cvalue)s".') %
442 dict(pname=self.property_name,
447 class Length(Constraint):
448 """Constraint class for "length"
450 Constrains the property or parameter to a value of a given length.
453 constraint_key = Constraint.LENGTH
455 valid_types = (int, )
457 valid_prop_types = (Schema.STRING, )
459 def __init__(self, property_name, property_type, constraint):
460 super(Length, self).__init__(property_name, property_type, constraint)
461 if not isinstance(self.constraint_value, self.valid_types):
462 ExceptionCollector.appendException(
463 InvalidSchemaError(message=_('The property "length" expects '
466 def _is_valid(self, value):
467 if isinstance(value, str) and len(value) == self.constraint_value:
472 def _err_msg(self, value):
473 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
474 'must be equal to "%(cvalue)s".') %
475 dict(pname=self.property_name,
477 cvalue=self.constraint_value))
480 class MinLength(Constraint):
481 """Constraint class for "min_length"
483 Constrains the property or parameter to a value to a minimum length.
486 constraint_key = Constraint.MIN_LENGTH
488 valid_types = (int, )
490 valid_prop_types = (Schema.STRING, )
492 def __init__(self, property_name, property_type, constraint):
493 super(MinLength, self).__init__(property_name, property_type,
495 if not isinstance(self.constraint_value, self.valid_types):
496 ExceptionCollector.appendException(
497 InvalidSchemaError(message=_('The property "min_length" '
498 'expects an integer.')))
500 def _is_valid(self, value):
501 if isinstance(value, str) and len(value) >= self.constraint_value:
506 def _err_msg(self, value):
507 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
508 'must be at least "%(cvalue)s".') %
509 dict(pname=self.property_name,
511 cvalue=self.constraint_value))
514 class MaxLength(Constraint):
515 """Constraint class for "max_length"
517 Constrains the property or parameter to a value to a maximum length.
520 constraint_key = Constraint.MAX_LENGTH
522 valid_types = (int, )
524 valid_prop_types = (Schema.STRING, )
526 def __init__(self, property_name, property_type, constraint):
527 super(MaxLength, self).__init__(property_name, property_type,
529 if not isinstance(self.constraint_value, self.valid_types):
530 ExceptionCollector.appendException(
531 InvalidSchemaError(message=_('The property "max_length" '
532 'expects an integer.')))
534 def _is_valid(self, value):
535 if isinstance(value, str) and len(value) <= self.constraint_value:
540 def _err_msg(self, value):
541 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
542 'must be no greater than "%(cvalue)s".') %
543 dict(pname=self.property_name,
545 cvalue=self.constraint_value))
548 class Pattern(Constraint):
549 """Constraint class for "pattern"
551 Constrains the property or parameter to a value that is allowed by
552 the provided regular expression.
555 constraint_key = Constraint.PATTERN
557 valid_types = (str, )
559 valid_prop_types = (Schema.STRING, )
561 def __init__(self, property_name, property_type, constraint):
562 super(Pattern, self).__init__(property_name, property_type, constraint)
563 if not isinstance(self.constraint_value, self.valid_types):
564 ExceptionCollector.appendException(
565 InvalidSchemaError(message=_('The property "pattern" '
566 'expects a string.')))
567 self.match = re.compile(self.constraint_value).match
569 def _is_valid(self, value):
570 match = self.match(value)
571 return match is not None and match.end() == len(value)
573 def _err_msg(self, value):
574 return (_('The value "%(pvalue)s" of property "%(pname)s" does not '
575 'match pattern "%(cvalue)s".') %
576 dict(pname=self.property_name,
578 cvalue=self.constraint_value))
581 constraint_mapping = {
582 Constraint.EQUAL: Equal,
583 Constraint.GREATER_THAN: GreaterThan,
584 Constraint.GREATER_OR_EQUAL: GreaterOrEqual,
585 Constraint.LESS_THAN: LessThan,
586 Constraint.LESS_OR_EQUAL: LessOrEqual,
587 Constraint.IN_RANGE: InRange,
588 Constraint.VALID_VALUES: ValidValues,
589 Constraint.LENGTH: Length,
590 Constraint.MIN_LENGTH: MinLength,
591 Constraint.MAX_LENGTH: MaxLength,
592 Constraint.PATTERN: Pattern
596 def get_constraint_class(type):
597 return constraint_mapping.get(type)