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.portspectype import PortSpec
22 from toscaparser.elements import scalarunit
23 from toscaparser.utils.gettextutils import _
26 class Schema(collections.Mapping):
29 TYPE, REQUIRED, DESCRIPTION,
30 DEFAULT, CONSTRAINTS, ENTRYSCHEMA
32 'type', 'required', 'description',
33 'default', 'constraints', 'entry_schema'
37 INTEGER, STRING, BOOLEAN, FLOAT, RANGE,
38 NUMBER, TIMESTAMP, LIST, MAP,
39 SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
40 VERSION, PORTDEF, PORTSPEC
42 'integer', 'string', 'boolean', 'float', 'range',
43 'number', 'timestamp', 'list', 'map',
44 'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time',
45 'version', 'PortDef', PortSpec.SHORTNAME
48 SCALAR_UNIT_SIZE_DEFAULT = 'B'
49 SCALAR_UNIT_SIZE_DICT = {'B': 1, 'KB': 1000, 'KIB': 1024, 'MB': 1000000,
50 'MIB': 1048576, 'GB': 1000000000,
51 'GIB': 1073741824, 'TB': 1000000000000,
54 def __init__(self, name, schema_dict):
56 if not isinstance(schema_dict, collections.Mapping):
57 msg = (_('Schema definition of "%(pname)s" must be a dict.')
59 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
64 msg = (_('Schema definition of "%(pname)s" must have a "type" '
65 'attribute.') % dict(pname=name))
66 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
68 self.schema = schema_dict
70 self.constraints_list = []
74 return self.schema[self.TYPE]
78 return self.schema.get(self.REQUIRED, True)
81 def description(self):
82 return self.schema.get(self.DESCRIPTION, '')
86 return self.schema.get(self.DEFAULT)
89 def constraints(self):
90 if not self.constraints_list:
91 constraint_schemata = self.schema.get(self.CONSTRAINTS)
92 if constraint_schemata:
93 self.constraints_list = [Constraint(self.name,
96 for cschema in constraint_schemata]
97 return self.constraints_list
100 def entry_schema(self):
101 return self.schema.get(self.ENTRYSCHEMA)
103 def __getitem__(self, key):
104 return self.schema[key]
116 if self._len is None:
117 self._len = len(list(iter(self)))
121 class Constraint(object):
122 '''Parent class for constraints for a Property or Input.'''
124 CONSTRAINTS = (EQUAL, GREATER_THAN,
125 GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL, IN_RANGE,
126 VALID_VALUES, LENGTH, MIN_LENGTH, MAX_LENGTH, PATTERN) = \
127 ('equal', 'greater_than', 'greater_or_equal', 'less_than',
128 'less_or_equal', 'in_range', 'valid_values', 'length',
129 'min_length', 'max_length', 'pattern')
131 def __new__(cls, property_name, property_type, constraint):
132 if cls is not Constraint:
133 return super(Constraint, cls).__new__(cls)
135 if(not isinstance(constraint, collections.Mapping) or
136 len(constraint) != 1):
137 ExceptionCollector.appendException(
138 InvalidSchemaError(message=_('Invalid constraint schema.')))
140 for type in constraint.keys():
141 ConstraintClass = get_constraint_class(type)
142 if not ConstraintClass:
143 msg = _('Invalid property "%s".') % type
144 ExceptionCollector.appendException(
145 InvalidSchemaError(message=msg))
147 return ConstraintClass(property_name, property_type, constraint)
149 def __init__(self, property_name, property_type, constraint):
150 self.property_name = property_name
151 self.property_type = property_type
152 self.constraint_value = constraint[self.constraint_key]
153 self.constraint_value_msg = self.constraint_value
154 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
155 self.constraint_value = self._get_scalarunit_constraint_value()
156 # check if constraint is valid for property type
157 if property_type not in self.valid_prop_types:
158 msg = _('Property "%(ctype)s" is not valid for data type '
159 '"%(dtype)s".') % dict(
160 ctype=self.constraint_key,
162 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
164 def _get_scalarunit_constraint_value(self):
165 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
166 ScalarUnit_Class = (scalarunit.
167 get_scalarunit_class(self.property_type))
168 if isinstance(self.constraint_value, list):
169 return [ScalarUnit_Class(v).get_num_from_scalar_unit()
170 for v in self.constraint_value]
172 return (ScalarUnit_Class(self.constraint_value).
173 get_num_from_scalar_unit())
175 def _err_msg(self, value):
176 return _('Property "%s" could not be validated.') % self.property_name
178 def validate(self, value):
179 self.value_msg = value
180 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
181 value = scalarunit.get_scalarunit_value(self.property_type, value)
182 if not self._is_valid(value):
183 err_msg = self._err_msg(value)
184 ExceptionCollector.appendException(
185 ValidationError(message=err_msg))
188 class Equal(Constraint):
189 """Constraint class for "equal"
191 Constrains a property or parameter to a value equal to ('=')
195 constraint_key = Constraint.EQUAL
197 valid_prop_types = Schema.PROPERTY_TYPES
199 def _is_valid(self, value):
200 if value == self.constraint_value:
205 def _err_msg(self, value):
206 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
207 'equal to "%(cvalue)s".') %
208 dict(pname=self.property_name,
209 pvalue=self.value_msg,
210 cvalue=self.constraint_value_msg))
213 class GreaterThan(Constraint):
214 """Constraint class for "greater_than"
216 Constrains a property or parameter to a value greater than ('>')
220 constraint_key = Constraint.GREATER_THAN
222 valid_types = (int, float, datetime.date,
223 datetime.time, datetime.datetime)
225 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
226 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
227 Schema.SCALAR_UNIT_TIME)
229 def __init__(self, property_name, property_type, constraint):
230 super(GreaterThan, self).__init__(property_name, property_type,
232 if not isinstance(constraint[self.GREATER_THAN], self.valid_types):
233 ExceptionCollector.appendException(
234 InvalidSchemaError(message=_('The property "greater_than" '
235 'expects comparable values.')))
237 def _is_valid(self, value):
238 if value > self.constraint_value:
243 def _err_msg(self, value):
244 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
245 'greater than "%(cvalue)s".') %
246 dict(pname=self.property_name,
247 pvalue=self.value_msg,
248 cvalue=self.constraint_value_msg))
251 class GreaterOrEqual(Constraint):
252 """Constraint class for "greater_or_equal"
254 Constrains a property or parameter to a value greater than or equal
255 to ('>=') the value declared.
258 constraint_key = Constraint.GREATER_OR_EQUAL
260 valid_types = (int, float, datetime.date,
261 datetime.time, datetime.datetime)
263 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
264 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
265 Schema.SCALAR_UNIT_TIME)
267 def __init__(self, property_name, property_type, constraint):
268 super(GreaterOrEqual, self).__init__(property_name, property_type,
270 if not isinstance(self.constraint_value, self.valid_types):
271 ExceptionCollector.appendException(
272 InvalidSchemaError(message=_('The property '
273 '"greater_or_equal" expects '
274 'comparable values.')))
276 def _is_valid(self, value):
277 if toscaparser.functions.is_function(value) or \
278 value >= self.constraint_value:
282 def _err_msg(self, value):
283 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
284 'greater than or equal to "%(cvalue)s".') %
285 dict(pname=self.property_name,
286 pvalue=self.value_msg,
287 cvalue=self.constraint_value_msg))
290 class LessThan(Constraint):
291 """Constraint class for "less_than"
293 Constrains a property or parameter to a value less than ('<')
297 constraint_key = Constraint.LESS_THAN
299 valid_types = (int, float, datetime.date,
300 datetime.time, datetime.datetime)
302 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
303 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
304 Schema.SCALAR_UNIT_TIME)
306 def __init__(self, property_name, property_type, constraint):
307 super(LessThan, self).__init__(property_name, property_type,
309 if not isinstance(self.constraint_value, self.valid_types):
310 ExceptionCollector.appendException(
311 InvalidSchemaError(message=_('The property "less_than" '
312 'expects comparable values.')))
314 def _is_valid(self, value):
315 if value < self.constraint_value:
320 def _err_msg(self, value):
321 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
322 'less than "%(cvalue)s".') %
323 dict(pname=self.property_name,
324 pvalue=self.value_msg,
325 cvalue=self.constraint_value_msg))
328 class LessOrEqual(Constraint):
329 """Constraint class for "less_or_equal"
331 Constrains a property or parameter to a value less than or equal
332 to ('<=') the value declared.
335 constraint_key = Constraint.LESS_OR_EQUAL
337 valid_types = (int, float, datetime.date,
338 datetime.time, datetime.datetime)
340 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
341 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
342 Schema.SCALAR_UNIT_TIME)
344 def __init__(self, property_name, property_type, constraint):
345 super(LessOrEqual, self).__init__(property_name, property_type,
347 if not isinstance(self.constraint_value, self.valid_types):
348 ExceptionCollector.appendException(
349 InvalidSchemaError(message=_('The property "less_or_equal" '
350 'expects comparable values.')))
352 def _is_valid(self, value):
353 if value <= self.constraint_value:
358 def _err_msg(self, value):
359 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
360 'less than or equal to "%(cvalue)s".') %
361 dict(pname=self.property_name,
362 pvalue=self.value_msg,
363 cvalue=self.constraint_value_msg))
366 class InRange(Constraint):
367 """Constraint class for "in_range"
369 Constrains a property or parameter to a value in range of (inclusive)
370 the two values declared.
372 UNBOUNDED = 'UNBOUNDED'
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)