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, STATUS
32 'type', 'required', 'description',
33 'default', 'constraints', 'entry_schema', 'status'
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)
90 return self.schema.get(self.STATUS, '')
93 def constraints(self):
94 if not self.constraints_list:
95 constraint_schemata = self.schema.get(self.CONSTRAINTS)
96 if constraint_schemata:
97 self.constraints_list = [Constraint(self.name,
100 for cschema in constraint_schemata]
101 return self.constraints_list
104 def entry_schema(self):
105 return self.schema.get(self.ENTRYSCHEMA)
107 def __getitem__(self, key):
108 return self.schema[key]
120 if self._len is None:
121 self._len = len(list(iter(self)))
125 class Constraint(object):
126 '''Parent class for constraints for a Property or Input.'''
128 CONSTRAINTS = (EQUAL, GREATER_THAN,
129 GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL, IN_RANGE,
130 VALID_VALUES, LENGTH, MIN_LENGTH, MAX_LENGTH, PATTERN) = \
131 ('equal', 'greater_than', 'greater_or_equal', 'less_than',
132 'less_or_equal', 'in_range', 'valid_values', 'length',
133 'min_length', 'max_length', 'pattern')
135 def __new__(cls, property_name, property_type, constraint):
136 if cls is not Constraint:
137 return super(Constraint, cls).__new__(cls)
139 if(not isinstance(constraint, collections.Mapping) or
140 len(constraint) != 1):
141 ExceptionCollector.appendException(
142 InvalidSchemaError(message=_('Invalid constraint schema.')))
144 for type in constraint.keys():
145 ConstraintClass = get_constraint_class(type)
146 if not ConstraintClass:
147 msg = _('Invalid property "%s".') % type
148 ExceptionCollector.appendException(
149 InvalidSchemaError(message=msg))
151 return ConstraintClass(property_name, property_type, constraint)
153 def __init__(self, property_name, property_type, constraint):
154 self.property_name = property_name
155 self.property_type = property_type
156 self.constraint_value = constraint[self.constraint_key]
157 self.constraint_value_msg = self.constraint_value
158 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
159 self.constraint_value = self._get_scalarunit_constraint_value()
160 # check if constraint is valid for property type
161 if property_type not in self.valid_prop_types:
162 msg = _('Property "%(ctype)s" is not valid for data type '
163 '"%(dtype)s".') % dict(
164 ctype=self.constraint_key,
166 ExceptionCollector.appendException(InvalidSchemaError(message=msg))
168 def _get_scalarunit_constraint_value(self):
169 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
170 ScalarUnit_Class = (scalarunit.
171 get_scalarunit_class(self.property_type))
172 if isinstance(self.constraint_value, list):
173 return [ScalarUnit_Class(v).get_num_from_scalar_unit()
174 for v in self.constraint_value]
176 return (ScalarUnit_Class(self.constraint_value).
177 get_num_from_scalar_unit())
179 def _err_msg(self, value):
180 return _('Property "%s" could not be validated.') % self.property_name
182 def validate(self, value):
183 self.value_msg = value
184 if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES:
185 value = scalarunit.get_scalarunit_value(self.property_type, value)
186 if not self._is_valid(value):
187 err_msg = self._err_msg(value)
188 ExceptionCollector.appendException(
189 ValidationError(message=err_msg))
192 class Equal(Constraint):
193 """Constraint class for "equal"
195 Constrains a property or parameter to a value equal to ('=')
199 constraint_key = Constraint.EQUAL
201 valid_prop_types = Schema.PROPERTY_TYPES
203 def _is_valid(self, value):
204 if value == self.constraint_value:
209 def _err_msg(self, value):
210 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
211 'equal to "%(cvalue)s".') %
212 dict(pname=self.property_name,
213 pvalue=self.value_msg,
214 cvalue=self.constraint_value_msg))
217 class GreaterThan(Constraint):
218 """Constraint class for "greater_than"
220 Constrains a property or parameter to a value greater than ('>')
224 constraint_key = Constraint.GREATER_THAN
226 valid_types = (int, float, datetime.date,
227 datetime.time, datetime.datetime)
229 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
230 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
231 Schema.SCALAR_UNIT_TIME)
233 def __init__(self, property_name, property_type, constraint):
234 super(GreaterThan, self).__init__(property_name, property_type,
236 if not isinstance(constraint[self.GREATER_THAN], self.valid_types):
237 ExceptionCollector.appendException(
238 InvalidSchemaError(message=_('The property "greater_than" '
239 'expects comparable values.')))
241 def _is_valid(self, value):
242 if value > self.constraint_value:
247 def _err_msg(self, value):
248 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
249 'greater than "%(cvalue)s".') %
250 dict(pname=self.property_name,
251 pvalue=self.value_msg,
252 cvalue=self.constraint_value_msg))
255 class GreaterOrEqual(Constraint):
256 """Constraint class for "greater_or_equal"
258 Constrains a property or parameter to a value greater than or equal
259 to ('>=') the value declared.
262 constraint_key = Constraint.GREATER_OR_EQUAL
264 valid_types = (int, float, datetime.date,
265 datetime.time, datetime.datetime)
267 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
268 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
269 Schema.SCALAR_UNIT_TIME)
271 def __init__(self, property_name, property_type, constraint):
272 super(GreaterOrEqual, self).__init__(property_name, property_type,
274 if not isinstance(self.constraint_value, self.valid_types):
275 ExceptionCollector.appendException(
276 InvalidSchemaError(message=_('The property '
277 '"greater_or_equal" expects '
278 'comparable values.')))
280 def _is_valid(self, value):
281 if toscaparser.functions.is_function(value) or \
282 value >= self.constraint_value:
286 def _err_msg(self, value):
287 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
288 'greater than or equal to "%(cvalue)s".') %
289 dict(pname=self.property_name,
290 pvalue=self.value_msg,
291 cvalue=self.constraint_value_msg))
294 class LessThan(Constraint):
295 """Constraint class for "less_than"
297 Constrains a property or parameter to a value less than ('<')
301 constraint_key = Constraint.LESS_THAN
303 valid_types = (int, float, datetime.date,
304 datetime.time, datetime.datetime)
306 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
307 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
308 Schema.SCALAR_UNIT_TIME)
310 def __init__(self, property_name, property_type, constraint):
311 super(LessThan, self).__init__(property_name, property_type,
313 if not isinstance(self.constraint_value, self.valid_types):
314 ExceptionCollector.appendException(
315 InvalidSchemaError(message=_('The property "less_than" '
316 'expects comparable values.')))
318 def _is_valid(self, value):
319 if value < self.constraint_value:
324 def _err_msg(self, value):
325 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
326 'less than "%(cvalue)s".') %
327 dict(pname=self.property_name,
328 pvalue=self.value_msg,
329 cvalue=self.constraint_value_msg))
332 class LessOrEqual(Constraint):
333 """Constraint class for "less_or_equal"
335 Constrains a property or parameter to a value less than or equal
336 to ('<=') the value declared.
339 constraint_key = Constraint.LESS_OR_EQUAL
341 valid_types = (int, float, datetime.date,
342 datetime.time, datetime.datetime)
344 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
345 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
346 Schema.SCALAR_UNIT_TIME)
348 def __init__(self, property_name, property_type, constraint):
349 super(LessOrEqual, self).__init__(property_name, property_type,
351 if not isinstance(self.constraint_value, self.valid_types):
352 ExceptionCollector.appendException(
353 InvalidSchemaError(message=_('The property "less_or_equal" '
354 'expects comparable values.')))
356 def _is_valid(self, value):
357 if value <= self.constraint_value:
362 def _err_msg(self, value):
363 return (_('The value "%(pvalue)s" of property "%(pname)s" must be '
364 'less than or equal to "%(cvalue)s".') %
365 dict(pname=self.property_name,
366 pvalue=self.value_msg,
367 cvalue=self.constraint_value_msg))
370 class InRange(Constraint):
371 """Constraint class for "in_range"
373 Constrains a property or parameter to a value in range of (inclusive)
374 the two values declared.
376 UNBOUNDED = 'UNBOUNDED'
378 constraint_key = Constraint.IN_RANGE
380 valid_types = (int, float, datetime.date,
381 datetime.time, datetime.datetime, str)
383 valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
384 Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
385 Schema.SCALAR_UNIT_TIME, Schema.RANGE)
387 def __init__(self, property_name, property_type, constraint):
388 super(InRange, self).__init__(property_name, property_type, constraint)
389 if(not isinstance(self.constraint_value, collections.Sequence) or
390 (len(constraint[self.IN_RANGE]) != 2)):
391 ExceptionCollector.appendException(
392 InvalidSchemaError(message=_('The property "in_range" '
395 msg = _('The property "in_range" expects comparable values.')
396 for value in self.constraint_value:
397 if not isinstance(value, self.valid_types):
398 ExceptionCollector.appendException(
399 InvalidSchemaError(message=msg))
400 # The only string we allow for range is the special value
402 if(isinstance(value, str) and value != self.UNBOUNDED):
403 ExceptionCollector.appendException(
404 InvalidSchemaError(message=msg))
406 self.min = self.constraint_value[0]
407 self.max = self.constraint_value[1]
409 def _is_valid(self, value):
410 if not isinstance(self.min, str):
413 elif self.min != self.UNBOUNDED:
415 if not isinstance(self.max, str):
418 elif self.max != self.UNBOUNDED:
422 def _err_msg(self, value):
423 return (_('The value "%(pvalue)s" of property "%(pname)s" is out of '
424 'range "(min:%(vmin)s, max:%(vmax)s)".') %
425 dict(pname=self.property_name,
426 pvalue=self.value_msg,
427 vmin=self.constraint_value_msg[0],
428 vmax=self.constraint_value_msg[1]))
431 class ValidValues(Constraint):
432 """Constraint class for "valid_values"
434 Constrains a property or parameter to a value that is in the list of
437 constraint_key = Constraint.VALID_VALUES
439 valid_prop_types = Schema.PROPERTY_TYPES
441 def __init__(self, property_name, property_type, constraint):
442 super(ValidValues, self).__init__(property_name, property_type,
444 if not isinstance(self.constraint_value, collections.Sequence):
445 ExceptionCollector.appendException(
446 InvalidSchemaError(message=_('The property "valid_values" '
449 def _is_valid(self, value):
450 if isinstance(value, list):
451 return all(v in self.constraint_value for v in value)
452 return value in self.constraint_value
454 def _err_msg(self, value):
455 allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value)
456 return (_('The value "%(pvalue)s" of property "%(pname)s" is not '
457 'valid. Expected a value from "%(cvalue)s".') %
458 dict(pname=self.property_name,
463 class Length(Constraint):
464 """Constraint class for "length"
466 Constrains the property or parameter to a value of a given length.
469 constraint_key = Constraint.LENGTH
471 valid_types = (int, )
473 valid_prop_types = (Schema.STRING, )
475 def __init__(self, property_name, property_type, constraint):
476 super(Length, self).__init__(property_name, property_type, constraint)
477 if not isinstance(self.constraint_value, self.valid_types):
478 ExceptionCollector.appendException(
479 InvalidSchemaError(message=_('The property "length" expects '
482 def _is_valid(self, value):
483 if isinstance(value, str) and len(value) == self.constraint_value:
488 def _err_msg(self, value):
489 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
490 'must be equal to "%(cvalue)s".') %
491 dict(pname=self.property_name,
493 cvalue=self.constraint_value))
496 class MinLength(Constraint):
497 """Constraint class for "min_length"
499 Constrains the property or parameter to a value to a minimum length.
502 constraint_key = Constraint.MIN_LENGTH
504 valid_types = (int, )
506 valid_prop_types = (Schema.STRING, Schema.MAP)
508 def __init__(self, property_name, property_type, constraint):
509 super(MinLength, self).__init__(property_name, property_type,
511 if not isinstance(self.constraint_value, self.valid_types):
512 ExceptionCollector.appendException(
513 InvalidSchemaError(message=_('The property "min_length" '
514 'expects an integer.')))
516 def _is_valid(self, value):
517 if ((isinstance(value, str) or isinstance(value, dict)) and
518 len(value) >= self.constraint_value):
523 def _err_msg(self, value):
524 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
525 'must be at least "%(cvalue)s".') %
526 dict(pname=self.property_name,
528 cvalue=self.constraint_value))
531 class MaxLength(Constraint):
532 """Constraint class for "max_length"
534 Constrains the property or parameter to a value to a maximum length.
537 constraint_key = Constraint.MAX_LENGTH
539 valid_types = (int, )
541 valid_prop_types = (Schema.STRING, Schema.MAP)
543 def __init__(self, property_name, property_type, constraint):
544 super(MaxLength, self).__init__(property_name, property_type,
546 if not isinstance(self.constraint_value, self.valid_types):
547 ExceptionCollector.appendException(
548 InvalidSchemaError(message=_('The property "max_length" '
549 'expects an integer.')))
551 def _is_valid(self, value):
552 if ((isinstance(value, str) or isinstance(value, dict)) and
553 len(value) <= self.constraint_value):
558 def _err_msg(self, value):
559 return (_('Length of value "%(pvalue)s" of property "%(pname)s" '
560 'must be no greater than "%(cvalue)s".') %
561 dict(pname=self.property_name,
563 cvalue=self.constraint_value))
566 class Pattern(Constraint):
567 """Constraint class for "pattern"
569 Constrains the property or parameter to a value that is allowed by
570 the provided regular expression.
573 constraint_key = Constraint.PATTERN
575 valid_types = (str, )
577 valid_prop_types = (Schema.STRING, )
579 def __init__(self, property_name, property_type, constraint):
580 super(Pattern, self).__init__(property_name, property_type, constraint)
581 if not isinstance(self.constraint_value, self.valid_types):
582 ExceptionCollector.appendException(
583 InvalidSchemaError(message=_('The property "pattern" '
584 'expects a string.')))
585 self.match = re.compile(self.constraint_value).match
587 def _is_valid(self, value):
588 match = self.match(value)
589 return match is not None and match.end() == len(value)
591 def _err_msg(self, value):
592 return (_('The value "%(pvalue)s" of property "%(pname)s" does not '
593 'match pattern "%(cvalue)s".') %
594 dict(pname=self.property_name,
596 cvalue=self.constraint_value))
599 constraint_mapping = {
600 Constraint.EQUAL: Equal,
601 Constraint.GREATER_THAN: GreaterThan,
602 Constraint.GREATER_OR_EQUAL: GreaterOrEqual,
603 Constraint.LESS_THAN: LessThan,
604 Constraint.LESS_OR_EQUAL: LessOrEqual,
605 Constraint.IN_RANGE: InRange,
606 Constraint.VALID_VALUES: ValidValues,
607 Constraint.LENGTH: Length,
608 Constraint.MIN_LENGTH: MinLength,
609 Constraint.MAX_LENGTH: MaxLength,
610 Constraint.PATTERN: Pattern
614 def get_constraint_class(type):
615 return constraint_mapping.get(type)