vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / extensions / aria_extension_tosca / simple_v1_0 / modeling / data_types.py
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import re
17
18 from aria.utils.collections import OrderedDict
19 from aria.utils.formatting import safe_repr
20 from aria.utils.type import full_type_name
21 from aria.utils.imports import import_fullname
22 from aria.parser import implements_specification
23 from aria.parser.presentation import (get_locator, validate_primitive)
24 from aria.parser.validation import Issue
25
26 from .functions import get_function
27 from ..presentation.types import get_type_by_name
28
29
30 #
31 # DataType
32 #
33
34 def get_inherited_constraints(context, presentation):
35     """
36     If we don't have constraints, will return our parent's constraints (if we have one),
37     recursively.
38
39     Implication: if we define even one constraint, the parent's constraints will not be inherited.
40     """
41
42     constraints = presentation.constraints
43
44     if constraints is None:
45         # If we don't have any, use our parent's
46         parent = presentation._get_parent(context)
47         parent_constraints = get_inherited_constraints(context, parent) \
48             if parent is not None else None
49         if parent_constraints is not None:
50             constraints = parent_constraints
51
52     return constraints
53
54
55 def coerce_data_type_value(context, presentation, data_type, entry_schema, constraints, value, # pylint: disable=unused-argument
56                            aspect):
57     """
58     Handles the ``_coerce_data()`` hook for complex data types.
59
60     There are two kinds of handling:
61
62     1. If we have a primitive type as our great ancestor, then we do primitive type coersion, and
63        just check for constraints.
64
65     2. Otherwise, for normal complex data types we return the assigned property values while making
66        sure they are defined in our type. The property definition's default value, if available,
67        will be used if we did not assign it. We also make sure that required definitions indeed end
68        up with a value.
69     """
70
71     primitive_type = data_type._get_primitive_ancestor(context)
72     if primitive_type is not None:
73         # Must be coercible to primitive ancestor
74         value = coerce_to_primitive(context, presentation, primitive_type, constraints, value,
75                                     aspect)
76     else:
77         definitions = data_type._get_properties(context)
78         if isinstance(value, dict):
79             temp = OrderedDict()
80
81             # Fill in our values, but make sure they are defined
82             for name, v in value.iteritems():
83                 if name in definitions:
84                     definition = definitions[name]
85                     definition_type = definition._get_type(context)
86                     definition_entry_schema = definition.entry_schema
87                     definition_constraints = definition._get_constraints(context)
88                     temp[name] = coerce_value(context, presentation, definition_type,
89                                               definition_entry_schema, definition_constraints, v,
90                                               aspect)
91                 else:
92                     context.validation.report(
93                         'assignment to undefined property "%s" in type "%s" in "%s"'
94                         % (name, data_type._fullname, presentation._fullname),
95                         locator=get_locator(v, value, presentation), level=Issue.BETWEEN_TYPES)
96
97             # Fill in defaults from the definitions, and check if required definitions have not been
98             # assigned
99             for name, definition in definitions.iteritems():
100                 if (temp.get(name) is None) and hasattr(definition, 'default') \
101                     and (definition.default is not None):
102                     definition_type = definition._get_type(context)
103                     definition_entry_schema = definition.entry_schema
104                     definition_constraints = definition._get_constraints(context)
105                     temp[name] = coerce_value(context, presentation, definition_type,
106                                               definition_entry_schema, definition_constraints,
107                                               definition.default, 'default')
108
109                 if getattr(definition, 'required', False) and (temp.get(name) is None):
110                     context.validation.report(
111                         'required property "%s" in type "%s" is not assigned a value in "%s"'
112                         % (name, data_type._fullname, presentation._fullname),
113                         locator=presentation._get_child_locator('definitions'),
114                         level=Issue.BETWEEN_TYPES)
115
116             value = temp
117         elif value is not None:
118             context.validation.report('value of type "%s" is not a dict in "%s"'
119                                       % (data_type._fullname, presentation._fullname),
120                                       locator=get_locator(value, presentation),
121                                       level=Issue.BETWEEN_TYPES)
122             value = None
123
124     return value
125
126
127 def validate_data_type_name(context, presentation):
128     """
129     Makes sure the complex data type's name is not that of a built-in type.
130     """
131
132     name = presentation._name
133     if get_primitive_data_type(name) is not None:
134         context.validation.report('data type name is that of a built-in type: %s'
135                                   % safe_repr(name),
136                                   locator=presentation._locator, level=Issue.BETWEEN_TYPES)
137
138
139 #
140 # PropertyDefinition, AttributeDefinition, EntrySchema, DataType
141 #
142
143 def get_data_type(context, presentation, field_name, allow_none=False):
144     """
145     Returns the type, whether it's a complex data type (a DataType instance) or a primitive (a
146     Python primitive type class).
147
148     If the type is not specified, defaults to :class:`str`, per note in section 3.2.1.1 of the
149     `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
150     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
151     #_Toc379455072>`__
152     """
153
154     type_name = getattr(presentation, field_name)
155
156     if type_name is None:
157         if allow_none:
158             return None
159         else:
160             return str
161
162     # Avoid circular definitions
163     container_data_type = get_container_data_type(presentation)
164     if (container_data_type is not None) and (container_data_type._name == type_name):
165         return None
166
167     # Try complex data type
168     data_type = get_type_by_name(context, type_name, 'data_types')
169     if data_type is not None:
170         return data_type
171
172     # Try primitive data type
173     return get_primitive_data_type(type_name)
174
175
176 #
177 # PropertyDefinition, EntrySchema
178 #
179
180 def get_property_constraints(context, presentation):
181     """
182     If we don't have constraints, will return our type's constraints (if we have one), recursively.
183
184     Implication: if we define even one constraint, the type's constraints will not be inherited.
185     """
186
187     constraints = presentation.constraints
188
189     if constraints is None:
190         # If we don't have any, use our type's
191         the_type = presentation._get_type(context)
192         type_constraints = the_type._get_constraints(context) \
193             if hasattr(the_type, '_get_constraints') else None
194         if type_constraints is not None:
195             constraints = type_constraints
196
197     return constraints
198
199
200 #
201 # ConstraintClause
202 #
203
204 def apply_constraint_to_value(context, presentation, constraint_clause, value): # pylint: disable=too-many-statements,too-many-return-statements,too-many-branches
205     """
206     Returns false if the value does not conform to the constraint.
207     """
208
209     constraint_key = constraint_clause._raw.keys()[0]
210     the_type = constraint_clause._get_type(context)
211     # PropertyAssignment does not have this:
212     entry_schema = getattr(presentation, 'entry_schema', None)
213
214     def coerce_constraint(constraint):
215         return coerce_value(context, presentation, the_type, entry_schema, None, constraint,
216                             constraint_key)
217
218     def report(message, constraint):
219         context.validation.report('value %s %s per constraint in "%s": %s'
220                                   % (message, safe_repr(constraint),
221                                      presentation._name or presentation._container._name,
222                                      safe_repr(value)),
223                                   locator=presentation._locator, level=Issue.BETWEEN_FIELDS)
224
225     if constraint_key == 'equal':
226         constraint = coerce_constraint(constraint_clause.equal)
227         if value != constraint:
228             report('is not equal to', constraint)
229             return False
230
231     elif constraint_key == 'greater_than':
232         constraint = coerce_constraint(constraint_clause.greater_than)
233         if value <= constraint:
234             report('is not greater than', constraint)
235             return False
236
237     elif constraint_key == 'greater_or_equal':
238         constraint = coerce_constraint(constraint_clause.greater_or_equal)
239         if value < constraint:
240             report('is not greater than or equal to', constraint)
241             return False
242
243     elif constraint_key == 'less_than':
244         constraint = coerce_constraint(constraint_clause.less_than)
245         if value >= constraint:
246             report('is not less than', constraint)
247             return False
248
249     elif constraint_key == 'less_or_equal':
250         constraint = coerce_constraint(constraint_clause.less_or_equal)
251         if value > constraint:
252             report('is not less than or equal to', constraint)
253             return False
254
255     elif constraint_key == 'in_range':
256         lower, upper = constraint_clause.in_range
257         lower, upper = coerce_constraint(lower), coerce_constraint(upper)
258         if value < lower:
259             report('is not greater than or equal to lower bound', lower)
260             return False
261         if (upper != 'UNBOUNDED') and (value > upper):
262             report('is not lesser than or equal to upper bound', upper)
263             return False
264
265     elif constraint_key == 'valid_values':
266         constraint = tuple(coerce_constraint(v) for v in constraint_clause.valid_values)
267         if value not in constraint:
268             report('is not one of', constraint)
269             return False
270
271     elif constraint_key == 'length':
272         constraint = constraint_clause.length
273         try:
274             if len(value) != constraint:
275                 report('is not of length', constraint)
276                 return False
277         except TypeError:
278             pass # should be validated elsewhere
279
280     elif constraint_key == 'min_length':
281         constraint = constraint_clause.min_length
282         try:
283             if len(value) < constraint:
284                 report('has a length lesser than', constraint)
285                 return False
286         except TypeError:
287             pass # should be validated elsewhere
288
289     elif constraint_key == 'max_length':
290         constraint = constraint_clause.max_length
291         try:
292             if len(value) > constraint:
293                 report('has a length greater than', constraint)
294                 return False
295         except TypeError:
296             pass # should be validated elsewhere
297
298     elif constraint_key == 'pattern':
299         constraint = constraint_clause.pattern
300         try:
301             # From TOSCA 1.0 3.5.2.1:
302             #
303             # "Note: Future drafts of this specification will detail the use of regular expressions
304             # and reference an appropriate standardized grammar."
305             #
306             # So we will just use Python's.
307             if re.match(constraint, str(value)) is None:
308                 report('does not match regular expression', constraint)
309                 return False
310         except re.error:
311             pass # should be validated elsewhere
312
313     return True
314
315
316 #
317 # Repository
318 #
319
320 def get_data_type_value(context, presentation, field_name, type_name):
321     the_type = get_type_by_name(context, type_name, 'data_types')
322     if the_type is not None:
323         value = getattr(presentation, field_name)
324         if value is not None:
325             return coerce_data_type_value(context, presentation, the_type, None, None, value, None)
326     else:
327         context.validation.report('field "%s" in "%s" refers to unknown data type "%s"'
328                                   % (field_name, presentation._fullname, type_name),
329                                   locator=presentation._locator, level=Issue.BETWEEN_TYPES)
330     return None
331
332
333 #
334 # Utils
335 #
336
337 PRIMITIVE_DATA_TYPES = {
338     # YAML 1.2:
339     'tag:yaml.org,2002:str': unicode,
340     'tag:yaml.org,2002:integer': int,
341     'tag:yaml.org,2002:float': float,
342     'tag:yaml.org,2002:bool': bool,
343     'tag:yaml.org,2002:null': None.__class__,
344
345     # TOSCA aliases:
346     'string': unicode,
347     'integer': int,
348     'float': float,
349     'boolean': bool,
350     'null': None.__class__}
351
352
353 @implements_specification('3.2.1-3', 'tosca-simple-1.0')
354 def get_primitive_data_type(type_name):
355     """
356     Many of the types we use in this profile are built-in types from the YAML 1.2 specification
357     (i.e., those identified by the "tag:yaml.org,2002" version tag) [YAML-1.2].
358
359     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
360     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html
361     #_Toc373867862>`__
362     """
363
364     return PRIMITIVE_DATA_TYPES.get(type_name)
365
366
367 def get_data_type_name(the_type):
368     """
369     Returns the name of the type, whether it's a DataType, a primitive type, or another class.
370     """
371
372     return the_type._name if hasattr(the_type, '_name') else full_type_name(the_type)
373
374
375 def coerce_value(context, presentation, the_type, entry_schema, constraints, value, aspect=None): # pylint: disable=too-many-return-statements
376     """
377     Returns the value after it's coerced to its type, reporting validation errors if it cannot be
378     coerced.
379
380     Supports both complex data types and primitives.
381
382     Data types can use the ``coerce_value`` extension to hook their own specialized function.
383     If the extension is present, we will delegate to that hook.
384     """
385
386     # TODO: should support models as well as presentations
387
388     is_function, func = get_function(context, presentation, value)
389     if is_function:
390         return func
391
392     if the_type is None:
393         return value
394
395     if the_type == None.__class__:
396         if value is not None:
397             context.validation.report('field "%s" is of type "null" but has a non-null value: %s'
398                                       % (presentation._name, safe_repr(value)),
399                                       locator=presentation._locator, level=Issue.BETWEEN_FIELDS)
400             return None
401
402     # Delegate to 'coerce_value' extension
403     if hasattr(the_type, '_get_extension'):
404         coerce_value_fn_name = the_type._get_extension('coerce_value')
405         if coerce_value_fn_name is not None:
406             if value is None:
407                 return None
408             coerce_value_fn = import_fullname(coerce_value_fn_name)
409             return coerce_value_fn(context, presentation, the_type, entry_schema, constraints,
410                                    value, aspect)
411
412     if hasattr(the_type, '_coerce_value'):
413         # Delegate to '_coerce_value' (likely a DataType instance)
414         return the_type._coerce_value(context, presentation, entry_schema, constraints, value,
415                                       aspect)
416
417     # Coerce to primitive type
418     return coerce_to_primitive(context, presentation, the_type, constraints, value, aspect)
419
420
421 def coerce_to_primitive(context, presentation, primitive_type, constraints, value, aspect=None):
422     """
423     Returns the value after it's coerced to a primitive type, translating exceptions to validation
424     errors if it cannot be coerced.
425     """
426
427     if value is None:
428         return None
429
430     try:
431         # Coerce
432         value = validate_primitive(value, primitive_type,
433                                    context.validation.allow_primitive_coersion)
434
435         # Check constraints
436         apply_constraints_to_value(context, presentation, constraints, value)
437     except (ValueError, TypeError) as e:
438         report_issue_for_bad_format(context, presentation, primitive_type, value, aspect, e)
439         value = None
440
441     return value
442
443
444 def coerce_to_data_type_class(context, presentation, cls, entry_schema, constraints, value,
445                               aspect=None):
446     """
447     Returns the value after it's coerced to a data type class, reporting validation errors if it
448     cannot be coerced. Constraints will be applied after coersion.
449
450     Will either call a ``_create`` static function in the class, or instantiate it using a
451     constructor if ``_create`` is not available.
452
453     This will usually be called by a ``coerce_value`` extension hook in a :class:`DataType`.
454     """
455
456     try:
457         if hasattr(cls, '_create'):
458             # Instantiate using creator function
459             value = cls._create(context, presentation, entry_schema, constraints, value, aspect)
460         else:
461             # Normal instantiation
462             value = cls(entry_schema, constraints, value, aspect)
463     except ValueError as e:
464         report_issue_for_bad_format(context, presentation, cls, value, aspect, e)
465         value = None
466
467     # Check constraints
468     value = apply_constraints_to_value(context, presentation, constraints, value)
469
470     return value
471
472
473 def apply_constraints_to_value(context, presentation, constraints, value):
474     """
475     Applies all constraints to the value. If the value conforms, returns the value. If it does not
476     conform, returns None.
477     """
478
479     if (value is not None) and (constraints is not None):
480         valid = True
481         for constraint in constraints:
482             if not constraint._apply_to_value(context, presentation, value):
483                 valid = False
484         if not valid:
485             value = None
486     return value
487
488
489 def get_container_data_type(presentation):
490     if presentation is None:
491         return None
492     if type(presentation).__name__ == 'DataType':
493         return presentation
494     return get_container_data_type(presentation._container)
495
496
497 def report_issue_for_bad_format(context, presentation, the_type, value, aspect, e):
498     if aspect == 'default':
499         aspect = '"default" value'
500     elif aspect is not None:
501         aspect = '"%s" aspect' % aspect
502
503     if aspect is not None:
504         context.validation.report('%s for field "%s" is not a valid "%s": %s'
505                                   % (aspect, presentation._name or presentation._container._name,
506                                      get_data_type_name(the_type), safe_repr(value)),
507                                   locator=presentation._locator, level=Issue.BETWEEN_FIELDS,
508                                   exception=e)
509     else:
510         context.validation.report('field "%s" is not a valid "%s": %s'
511                                   % (presentation._name or presentation._container._name,
512                                      get_data_type_name(the_type), safe_repr(value)),
513                                   locator=presentation._locator, level=Issue.BETWEEN_FIELDS,
514                                   exception=e)