docs: fix issues
[parser.git] / tosca2heat / tosca-parser-0.3.0 / toscaparser / elements / constraints.py
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
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
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
11 #    under the License.
12
13 import collections
14 import datetime
15 import re
16
17 import toscaparser
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 _
23
24
25 class Schema(collections.Mapping):
26
27     KEYS = (
28         TYPE, REQUIRED, DESCRIPTION,
29         DEFAULT, CONSTRAINTS, ENTRYSCHEMA
30     ) = (
31         'type', 'required', 'description',
32         'default', 'constraints', 'entry_schema'
33     )
34
35     PROPERTY_TYPES = (
36         INTEGER, STRING, BOOLEAN, FLOAT,
37         NUMBER, TIMESTAMP, LIST, MAP,
38         SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME,
39         PORTDEF, VERSION
40     ) = (
41         'integer', 'string', 'boolean', 'float',
42         'number', 'timestamp', 'list', 'map',
43         'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time',
44         'PortDef', 'version'
45     )
46
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,
51                              'TIB': 1099511627776}
52
53     def __init__(self, name, schema_dict):
54         self.name = name
55         if not isinstance(schema_dict, collections.Mapping):
56             msg = (_('Schema definition of "%(pname)s" must be a dict.')
57                    % dict(pname=name))
58             ExceptionCollector.appendException(InvalidSchemaError(message=msg))
59
60         try:
61             schema_dict['type']
62         except KeyError:
63             msg = (_('Schema definition of "%(pname)s" must have a "type" '
64                      'attribute.') % dict(pname=name))
65             ExceptionCollector.appendException(InvalidSchemaError(message=msg))
66
67         self.schema = schema_dict
68         self._len = None
69         self.constraints_list = []
70
71     @property
72     def type(self):
73         return self.schema[self.TYPE]
74
75     @property
76     def required(self):
77         return self.schema.get(self.REQUIRED, True)
78
79     @property
80     def description(self):
81         return self.schema.get(self.DESCRIPTION, '')
82
83     @property
84     def default(self):
85         return self.schema.get(self.DEFAULT)
86
87     @property
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,
93                                                     self.type,
94                                                     cschema)
95                                          for cschema in constraint_schemata]
96         return self.constraints_list
97
98     @property
99     def entry_schema(self):
100         return self.schema.get(self.ENTRYSCHEMA)
101
102     def __getitem__(self, key):
103         return self.schema[key]
104
105     def __iter__(self):
106         for k in self.KEYS:
107             try:
108                 self.schema[k]
109             except KeyError:
110                 pass
111             else:
112                 yield k
113
114     def __len__(self):
115         if self._len is None:
116             self._len = len(list(iter(self)))
117         return self._len
118
119
120 class Constraint(object):
121     '''Parent class for constraints for a Property or Input.'''
122
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')
129
130     def __new__(cls, property_name, property_type, constraint):
131         if cls is not Constraint:
132             return super(Constraint, cls).__new__(cls)
133
134         if(not isinstance(constraint, collections.Mapping) or
135            len(constraint) != 1):
136             ExceptionCollector.appendException(
137                 InvalidSchemaError(message=_('Invalid constraint schema.')))
138
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))
145
146         return ConstraintClass(property_name, property_type, constraint)
147
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,
160                         dtype=property_type)
161             ExceptionCollector.appendException(InvalidSchemaError(message=msg))
162
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]
170         else:
171             return (ScalarUnit_Class(self.constraint_value).
172                     get_num_from_scalar_unit())
173
174     def _err_msg(self, value):
175         return _('Property "%s" could not be validated.') % self.property_name
176
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))
185
186
187 class Equal(Constraint):
188     """Constraint class for "equal"
189
190     Constrains a property or parameter to a value equal to ('=')
191     the value declared.
192     """
193
194     constraint_key = Constraint.EQUAL
195
196     valid_prop_types = Schema.PROPERTY_TYPES
197
198     def _is_valid(self, value):
199         if value == self.constraint_value:
200             return True
201
202         return False
203
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))
210
211
212 class GreaterThan(Constraint):
213     """Constraint class for "greater_than"
214
215     Constrains a property or parameter to a value greater than ('>')
216     the value declared.
217     """
218
219     constraint_key = Constraint.GREATER_THAN
220
221     valid_types = (int, float, datetime.date,
222                    datetime.time, datetime.datetime)
223
224     valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
225                         Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
226                         Schema.SCALAR_UNIT_TIME)
227
228     def __init__(self, property_name, property_type, constraint):
229         super(GreaterThan, self).__init__(property_name, property_type,
230                                           constraint)
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.')))
235
236     def _is_valid(self, value):
237         if value > self.constraint_value:
238             return True
239
240         return False
241
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))
248
249
250 class GreaterOrEqual(Constraint):
251     """Constraint class for "greater_or_equal"
252
253     Constrains a property or parameter to a value greater than or equal
254     to ('>=') the value declared.
255     """
256
257     constraint_key = Constraint.GREATER_OR_EQUAL
258
259     valid_types = (int, float, datetime.date,
260                    datetime.time, datetime.datetime)
261
262     valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
263                         Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
264                         Schema.SCALAR_UNIT_TIME)
265
266     def __init__(self, property_name, property_type, constraint):
267         super(GreaterOrEqual, self).__init__(property_name, property_type,
268                                              constraint)
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.')))
274
275     def _is_valid(self, value):
276         if toscaparser.functions.is_function(value) or \
277            value >= self.constraint_value:
278             return True
279         return False
280
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))
287
288
289 class LessThan(Constraint):
290     """Constraint class for "less_than"
291
292     Constrains a property or parameter to a value less than ('<')
293     the value declared.
294     """
295
296     constraint_key = Constraint.LESS_THAN
297
298     valid_types = (int, float, datetime.date,
299                    datetime.time, datetime.datetime)
300
301     valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
302                         Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
303                         Schema.SCALAR_UNIT_TIME)
304
305     def __init__(self, property_name, property_type, constraint):
306         super(LessThan, self).__init__(property_name, property_type,
307                                        constraint)
308         if not isinstance(self.constraint_value, self.valid_types):
309             ExceptionCollector.appendException(
310                 InvalidSchemaError(message=_('The property "less_than" '
311                                              'expects comparable values.')))
312
313     def _is_valid(self, value):
314         if value < self.constraint_value:
315             return True
316
317         return False
318
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))
325
326
327 class LessOrEqual(Constraint):
328     """Constraint class for "less_or_equal"
329
330     Constrains a property or parameter to a value less than or equal
331     to ('<=') the value declared.
332     """
333
334     constraint_key = Constraint.LESS_OR_EQUAL
335
336     valid_types = (int, float, datetime.date,
337                    datetime.time, datetime.datetime)
338
339     valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
340                         Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
341                         Schema.SCALAR_UNIT_TIME)
342
343     def __init__(self, property_name, property_type, constraint):
344         super(LessOrEqual, self).__init__(property_name, property_type,
345                                           constraint)
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.')))
350
351     def _is_valid(self, value):
352         if value <= self.constraint_value:
353             return True
354
355         return False
356
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))
363
364
365 class InRange(Constraint):
366     """Constraint class for "in_range"
367
368     Constrains a property or parameter to a value in range of (inclusive)
369     the two values declared.
370     """
371
372     constraint_key = Constraint.IN_RANGE
373
374     valid_types = (int, float, datetime.date,
375                    datetime.time, datetime.datetime)
376
377     valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP,
378                         Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY,
379                         Schema.SCALAR_UNIT_TIME)
380
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" '
387                                              'expects a list.')))
388
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.')))
394
395         self.min = self.constraint_value[0]
396         self.max = self.constraint_value[1]
397
398     def _is_valid(self, value):
399         if value < self.min:
400             return False
401         if value > self.max:
402             return False
403
404         return True
405
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]))
413
414
415 class ValidValues(Constraint):
416     """Constraint class for "valid_values"
417
418     Constrains a property or parameter to a value that is in the list of
419     declared values.
420     """
421     constraint_key = Constraint.VALID_VALUES
422
423     valid_prop_types = Schema.PROPERTY_TYPES
424
425     def __init__(self, property_name, property_type, constraint):
426         super(ValidValues, self).__init__(property_name, property_type,
427                                           constraint)
428         if not isinstance(self.constraint_value, collections.Sequence):
429             ExceptionCollector.appendException(
430                 InvalidSchemaError(message=_('The property "valid_values" '
431                                              'expects a list.')))
432
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
437
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,
443                      pvalue=value,
444                      cvalue=allowed))
445
446
447 class Length(Constraint):
448     """Constraint class for "length"
449
450     Constrains the property or parameter to a value of a given length.
451     """
452
453     constraint_key = Constraint.LENGTH
454
455     valid_types = (int, )
456
457     valid_prop_types = (Schema.STRING, )
458
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 '
464                                              'an integer.')))
465
466     def _is_valid(self, value):
467         if isinstance(value, str) and len(value) == self.constraint_value:
468             return True
469
470         return False
471
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,
476                      pvalue=value,
477                      cvalue=self.constraint_value))
478
479
480 class MinLength(Constraint):
481     """Constraint class for "min_length"
482
483     Constrains the property or parameter to a value to a minimum length.
484     """
485
486     constraint_key = Constraint.MIN_LENGTH
487
488     valid_types = (int, )
489
490     valid_prop_types = (Schema.STRING, )
491
492     def __init__(self, property_name, property_type, constraint):
493         super(MinLength, self).__init__(property_name, property_type,
494                                         constraint)
495         if not isinstance(self.constraint_value, self.valid_types):
496             ExceptionCollector.appendException(
497                 InvalidSchemaError(message=_('The property "min_length" '
498                                              'expects an integer.')))
499
500     def _is_valid(self, value):
501         if isinstance(value, str) and len(value) >= self.constraint_value:
502             return True
503
504         return False
505
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,
510                      pvalue=value,
511                      cvalue=self.constraint_value))
512
513
514 class MaxLength(Constraint):
515     """Constraint class for "max_length"
516
517     Constrains the property or parameter to a value to a maximum length.
518     """
519
520     constraint_key = Constraint.MAX_LENGTH
521
522     valid_types = (int, )
523
524     valid_prop_types = (Schema.STRING, )
525
526     def __init__(self, property_name, property_type, constraint):
527         super(MaxLength, self).__init__(property_name, property_type,
528                                         constraint)
529         if not isinstance(self.constraint_value, self.valid_types):
530             ExceptionCollector.appendException(
531                 InvalidSchemaError(message=_('The property "max_length" '
532                                              'expects an integer.')))
533
534     def _is_valid(self, value):
535         if isinstance(value, str) and len(value) <= self.constraint_value:
536             return True
537
538         return False
539
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,
544                      pvalue=value,
545                      cvalue=self.constraint_value))
546
547
548 class Pattern(Constraint):
549     """Constraint class for "pattern"
550
551     Constrains the property or parameter to a value that is allowed by
552     the provided regular expression.
553     """
554
555     constraint_key = Constraint.PATTERN
556
557     valid_types = (str, )
558
559     valid_prop_types = (Schema.STRING, )
560
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
568
569     def _is_valid(self, value):
570         match = self.match(value)
571         return match is not None and match.end() == len(value)
572
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,
577                      pvalue=value,
578                      cvalue=self.constraint_value))
579
580
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
593     }
594
595
596 def get_constraint_class(type):
597     return constraint_mapping.get(type)