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, RANGE,
37 NUMBER, TIMESTAMP, LIST, MAP,
38 SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
41 'integer', 'string', 'boolean', 'float', 'range',
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 UNBOUNDED = 'UNBOUNDED'
132 def __new__(cls, property_name, property_type, constraint):
133 if cls is not Constraint:
134 return super(Constraint, cls).__new__(cls)
136 if(not isinstance(constraint, collections.Mapping) or
137 len(constraint) != 1):
138 ExceptionCollector.appendException(
139 InvalidSchemaError(message=_('Invalid constraint schema.')))
141 for type in constraint.keys():
142 ConstraintClass = get_constraint_class(type)
143 if not ConstraintClass:
144 msg = _('Invalid property "%s".') % type
145 ExceptionCollector.appendException(
146 InvalidSchemaError(message=msg))
148 return ConstraintClass(property_name, property_type, constraint)
150 def __init__(self, property_name, property_type, constraint):
151 self.property_name = property_name
152 self.property_type = property_type
153 self.constraint_value = constraint[self.constraint_key]
154 self.constraint_value_msg = self.constraint_value
155 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
156 self.constraint_value = self._get_scalarunit_constraint_value()
157 # check if constraint is valid for property type
158 if property_type not in self.valid_prop_types:
159 msg = _('Property "%(ctype)s" is not valid for data type '
160 '"%(dtype)s".') % dict(
161 ctype=self.constraint_key,
163 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
165 def _get_scalarunit_constraint_value(self):
166 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
167 ScalarUnit_Class = (scalarunit.
168 get_scalarunit_class(self.property_type))
169 if isinstance(self.constraint_value, list):
170 return [ScalarUnit_Class(v).get_num_from_scalar_unit()
171 for v in self.constraint_value]
173 return (ScalarUnit_Class(self.constraint_value).
174 get_num_from_scalar_unit())
176 def _err_msg(self, value):
177 return _('Property "%s" could not be validated.') % self.property_name
179 def validate(self, value):
180 self.value_msg = value
181 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
182 value = scalarunit.get_scalarunit_value(self.property_type, value)
183 if not self._is_valid(value):
184 err_msg = self._err_msg(value)
185 ExceptionCollector.appendException(
186 ValidationError(message=err_msg))
189 class Equal(Constraint):
190 """Constraint class for "equal"
192 Constrains a property or parameter to a value equal to ('=')
196 constraint_key = Constraint.EQUAL
198 valid_prop_types = Schema.PROPERTY_TYPES
200 def _is_valid(self, value):
201 if value == self.constraint_value:
206 def _err_msg(self, value):
207 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
208 'equal to "%(cvalue)s".') %
209 dict(pname=self.property_name,
210 pvalue=self.value_msg,
211 cvalue=self.constraint_value_msg))
214 class GreaterThan(Constraint):
215 """Constraint class for "greater_than"
217 Constrains a property or parameter to a value greater than ('>')
221 constraint_key = Constraint.GREATER_THAN
223 valid_types = (int, float, datetime.date,
224 datetime.time, datetime.datetime)
226 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
227 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
228 Schema.SCALAR_UNIT_TIME)
230 def __init__(self, property_name, property_type, constraint):
231 super(GreaterThan, self).__init__(property_name, property_type,
233 if not isinstance(constraint[self.GREATER_THAN], self.valid_types):
234 ExceptionCollector.appendException(
235 InvalidSchemaError(message=_('The property "greater_than" '
236 'expects comparable values.')))
238 def _is_valid(self, value):
239 if value > self.constraint_value:
244 def _err_msg(self, value):
245 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
246 'greater than "%(cvalue)s".') %
247 dict(pname=self.property_name,
248 pvalue=self.value_msg,
249 cvalue=self.constraint_value_msg))
252 class GreaterOrEqual(Constraint):
253 """Constraint class for "greater_or_equal"
255 Constrains a property or parameter to a value greater than or equal
256 to ('>=') the value declared.
259 constraint_key = Constraint.GREATER_OR_EQUAL
261 valid_types = (int, float, datetime.date,
262 datetime.time, datetime.datetime)
264 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
265 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
266 Schema.SCALAR_UNIT_TIME)
268 def __init__(self, property_name, property_type, constraint):
269 super(GreaterOrEqual, self).__init__(property_name, property_type,
271 if not isinstance(self.constraint_value, self.valid_types):
272 ExceptionCollector.appendException(
273 InvalidSchemaError(message=_('The property '
274 '"greater_or_equal" expects '
275 'comparable values.')))
277 def _is_valid(self, value):
278 if toscaparser.functions.is_function(value) or \
279 value >= self.constraint_value:
283 def _err_msg(self, value):
284 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
285 'greater than or equal to "%(cvalue)s".') %
286 dict(pname=self.property_name,
287 pvalue=self.value_msg,
288 cvalue=self.constraint_value_msg))
291 class LessThan(Constraint):
292 """Constraint class for "less_than"
294 Constrains a property or parameter to a value less than ('<')
298 constraint_key = Constraint.LESS_THAN
300 valid_types = (int, float, datetime.date,
301 datetime.time, datetime.datetime)
303 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
304 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
305 Schema.SCALAR_UNIT_TIME)
307 def __init__(self, property_name, property_type, constraint):
308 super(LessThan, self).__init__(property_name, property_type,
310 if not isinstance(self.constraint_value, self.valid_types):
311 ExceptionCollector.appendException(
312 InvalidSchemaError(message=_('The property "less_than" '
313 'expects comparable values.')))
315 def _is_valid(self, value):
316 if value < self.constraint_value:
321 def _err_msg(self, value):
322 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
323 'less than "%(cvalue)s".') %
324 dict(pname=self.property_name,
325 pvalue=self.value_msg,
326 cvalue=self.constraint_value_msg))
329 class LessOrEqual(Constraint):
330 """Constraint class for "less_or_equal"
332 Constrains a property or parameter to a value less than or equal
333 to ('<=') the value declared.
336 constraint_key = Constraint.LESS_OR_EQUAL
338 valid_types = (int, float, datetime.date,
339 datetime.time, datetime.datetime)
341 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
342 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
343 Schema.SCALAR_UNIT_TIME)
345 def __init__(self, property_name, property_type, constraint):
346 super(LessOrEqual, self).__init__(property_name, property_type,
348 if not isinstance(self.constraint_value, self.valid_types):
349 ExceptionCollector.appendException(
350 InvalidSchemaError(message=_('The property "less_or_equal" '
351 'expects comparable values.')))
353 def _is_valid(self, value):
354 if value <= self.constraint_value:
359 def _err_msg(self, value):
360 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
361 'less than or equal to "%(cvalue)s".') %
362 dict(pname=self.property_name,
363 pvalue=self.value_msg,
364 cvalue=self.constraint_value_msg))
367 class InRange(Constraint):
368 """Constraint class for "in_range"
370 Constrains a property or parameter to a value in range of (inclusive)
371 the two values declared.
374 constraint_key = Constraint.IN_RANGE
376 valid_types = (int, float, datetime.date,
377 datetime.time, datetime.datetime, str)
379 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
380 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
381 Schema.SCALAR_UNIT_TIME, Schema.RANGE)
383 def __init__(self, property_name, property_type, constraint):
384 super(InRange, self).__init__(property_name, property_type, constraint)
385 if(not isinstance(self.constraint_value, collections.Sequence) or
386 (len(constraint[self.IN_RANGE]) != 2)):
387 ExceptionCollector.appendException(
388 InvalidSchemaError(message=_('The property "in_range" '
391 msg = _('The property "in_range" expects comparable values.')
392 for value in self.constraint_value:
393 if not isinstance(value, self.valid_types):
394 ExceptionCollector.appendException(
395 InvalidSchemaError(message=msg))
396 # The only string we allow for range is the special value
398 if(isinstance(value, str) and value != self.UNBOUNDED):
399 ExceptionCollector.appendException(
400 InvalidSchemaError(message=msg))
402 self.min = self.constraint_value[0]
403 self.max = self.constraint_value[1]
405 def _is_valid(self, value):
406 if not isinstance(self.min, str):
409 elif self.min != self.UNBOUNDED:
411 if not isinstance(self.max, str):
414 elif self.max != self.UNBOUNDED:
418 def _err_msg(self, value):
419 return (_('The value "%(pvalue)s" of property "%(pname)s" is out of '
420 'range "(min:%(vmin)s, max:%(vmax)s)".') %
421 dict(pname=self.property_name,
422 pvalue=self.value_msg,
423 vmin=self.constraint_value_msg[0],
424 vmax=self.constraint_value_msg[1]))
427 class ValidValues(Constraint):
428 """Constraint class for "valid_values"
430 Constrains a property or parameter to a value that is in the list of
433 constraint_key = Constraint.VALID_VALUES
435 valid_prop_types = Schema.PROPERTY_TYPES
437 def __init__(self, property_name, property_type, constraint):
438 super(ValidValues, self).__init__(property_name, property_type,
440 if not isinstance(self.constraint_value, collections.Sequence):
441 ExceptionCollector.appendException(
442 InvalidSchemaError(message=_('The property "valid_values" '
445 def _is_valid(self, value):
446 if isinstance(value, list):
447 return all(v in self.constraint_value for v in value)
448 return value in self.constraint_value
450 def _err_msg(self, value):
451 allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value)
452 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
453 'valid. Expected a value from "%(cvalue)s".') %
454 dict(pname=self.property_name,
459 class Length(Constraint):
460 """Constraint class for "length"
462 Constrains the property or parameter to a value of a given length.
465 constraint_key = Constraint.LENGTH
467 valid_types = (int, )
469 valid_prop_types = (Schema.STRING, )
471 def __init__(self, property_name, property_type, constraint):
472 super(Length, self).__init__(property_name, property_type, constraint)
473 if not isinstance(self.constraint_value, self.valid_types):
474 ExceptionCollector.appendException(
475 InvalidSchemaError(message=_('The property "length" expects '
478 def _is_valid(self, value):
479 if isinstance(value, str) and len(value) == self.constraint_value:
484 def _err_msg(self, value):
485 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
486 'must be equal to "%(cvalue)s".') %
487 dict(pname=self.property_name,
489 cvalue=self.constraint_value))
492 class MinLength(Constraint):
493 """Constraint class for "min_length"
495 Constrains the property or parameter to a value to a minimum length.
498 constraint_key = Constraint.MIN_LENGTH
500 valid_types = (int, )
502 valid_prop_types = (Schema.STRING, Schema.MAP)
504 def __init__(self, property_name, property_type, constraint):
505 super(MinLength, self).__init__(property_name, property_type,
507 if not isinstance(self.constraint_value, self.valid_types):
508 ExceptionCollector.appendException(
509 InvalidSchemaError(message=_('The property "min_length" '
510 'expects an integer.')))
512 def _is_valid(self, value):
513 if ((isinstance(value, str) or isinstance(value, dict)) and
514 len(value) >= self.constraint_value):
519 def _err_msg(self, value):
520 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
521 'must be at least "%(cvalue)s".') %
522 dict(pname=self.property_name,
524 cvalue=self.constraint_value))
527 class MaxLength(Constraint):
528 """Constraint class for "max_length"
530 Constrains the property or parameter to a value to a maximum length.
533 constraint_key = Constraint.MAX_LENGTH
535 valid_types = (int, )
537 valid_prop_types = (Schema.STRING, Schema.MAP)
539 def __init__(self, property_name, property_type, constraint):
540 super(MaxLength, self).__init__(property_name, property_type,
542 if not isinstance(self.constraint_value, self.valid_types):
543 ExceptionCollector.appendException(
544 InvalidSchemaError(message=_('The property "max_length" '
545 'expects an integer.')))
547 def _is_valid(self, value):
548 if ((isinstance(value, str) or isinstance(value, dict)) and
549 len(value) <= self.constraint_value):
554 def _err_msg(self, value):
555 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
556 'must be no greater than "%(cvalue)s".') %
557 dict(pname=self.property_name,
559 cvalue=self.constraint_value))
562 class Pattern(Constraint):
563 """Constraint class for "pattern"
565 Constrains the property or parameter to a value that is allowed by
566 the provided regular expression.
569 constraint_key = Constraint.PATTERN
571 valid_types = (str, )
573 valid_prop_types = (Schema.STRING, )
575 def __init__(self, property_name, property_type, constraint):
576 super(Pattern, self).__init__(property_name, property_type, constraint)
577 if not isinstance(self.constraint_value, self.valid_types):
578 ExceptionCollector.appendException(
579 InvalidSchemaError(message=_('The property "pattern" '
580 'expects a string.')))
581 self.match = re.compile(self.constraint_value).match
583 def _is_valid(self, value):
584 match = self.match(value)
585 return match is not None and match.end() == len(value)
587 def _err_msg(self, value):
588 return (_('The value "%(pvalue)s" of property "%(pname)s" does not '
589 'match pattern "%(cvalue)s".') %
590 dict(pname=self.property_name,
592 cvalue=self.constraint_value))
595 constraint_mapping = {
596 Constraint.EQUAL: Equal,
597 Constraint.GREATER_THAN: GreaterThan,
598 Constraint.GREATER_OR_EQUAL: GreaterOrEqual,
599 Constraint.LESS_THAN: LessThan,
600 Constraint.LESS_OR_EQUAL: LessOrEqual,
601 Constraint.IN_RANGE: InRange,
602 Constraint.VALID_VALUES: ValidValues,
603 Constraint.LENGTH: Length,
604 Constraint.MIN_LENGTH: MinLength,
605 Constraint.MAX_LENGTH: MaxLength,
606 Constraint.PATTERN: Pattern
610 def get_constraint_class(type):
611 return constraint_mapping.get(type)