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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
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
26 from .functions import get_function
27 from ..presentation.types import get_type_by_name
34 def get_inherited_constraints(context, presentation):
36 If we don't have constraints, will return our parent's constraints (if we have one),
39 Implication: if we define even one constraint, the parent's constraints will not be inherited.
42 constraints = presentation.constraints
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
55 def coerce_data_type_value(context, presentation, data_type, entry_schema, constraints, value, # pylint: disable=unused-argument
58 Handles the ``_coerce_data()`` hook for complex data types.
60 There are two kinds of handling:
62 1. If we have a primitive type as our great ancestor, then we do primitive type coersion, and
63 just check for constraints.
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
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,
77 definitions = data_type._get_properties(context)
78 if isinstance(value, dict):
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,
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)
97 # Fill in defaults from the definitions, and check if required definitions have not been
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')
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)
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)
127 def validate_data_type_name(context, presentation):
129 Makes sure the complex data type's name is not that of a built-in type.
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'
136 locator=presentation._locator, level=Issue.BETWEEN_TYPES)
140 # PropertyDefinition, AttributeDefinition, EntrySchema, DataType
143 def get_data_type(context, presentation, field_name, allow_none=False):
145 Returns the type, whether it's a complex data type (a DataType instance) or a primitive (a
146 Python primitive type class).
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
154 type_name = getattr(presentation, field_name)
156 if type_name is None:
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):
167 # Try complex data type
168 data_type = get_type_by_name(context, type_name, 'data_types')
169 if data_type is not None:
172 # Try primitive data type
173 return get_primitive_data_type(type_name)
177 # PropertyDefinition, EntrySchema
180 def get_property_constraints(context, presentation):
182 If we don't have constraints, will return our type's constraints (if we have one), recursively.
184 Implication: if we define even one constraint, the type's constraints will not be inherited.
187 constraints = presentation.constraints
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
204 def apply_constraint_to_value(context, presentation, constraint_clause, value): # pylint: disable=too-many-statements,too-many-return-statements,too-many-branches
206 Returns false if the value does not conform to the constraint.
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)
214 def coerce_constraint(constraint):
215 return coerce_value(context, presentation, the_type, entry_schema, None, constraint,
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,
223 locator=presentation._locator, level=Issue.BETWEEN_FIELDS)
225 if constraint_key == 'equal':
226 constraint = coerce_constraint(constraint_clause.equal)
227 if value != constraint:
228 report('is not equal to', constraint)
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)
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)
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)
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)
255 elif constraint_key == 'in_range':
256 lower, upper = constraint_clause.in_range
257 lower, upper = coerce_constraint(lower), coerce_constraint(upper)
259 report('is not greater than or equal to lower bound', lower)
261 if (upper != 'UNBOUNDED') and (value > upper):
262 report('is not lesser than or equal to upper bound', upper)
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)
271 elif constraint_key == 'length':
272 constraint = constraint_clause.length
274 if len(value) != constraint:
275 report('is not of length', constraint)
278 pass # should be validated elsewhere
280 elif constraint_key == 'min_length':
281 constraint = constraint_clause.min_length
283 if len(value) < constraint:
284 report('has a length lesser than', constraint)
287 pass # should be validated elsewhere
289 elif constraint_key == 'max_length':
290 constraint = constraint_clause.max_length
292 if len(value) > constraint:
293 report('has a length greater than', constraint)
296 pass # should be validated elsewhere
298 elif constraint_key == 'pattern':
299 constraint = constraint_clause.pattern
301 # From TOSCA 1.0 3.5.2.1:
303 # "Note: Future drafts of this specification will detail the use of regular expressions
304 # and reference an appropriate standardized grammar."
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)
311 pass # should be validated elsewhere
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)
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)
337 PRIMITIVE_DATA_TYPES = {
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__,
350 'null': None.__class__}
353 @implements_specification('3.2.1-3', 'tosca-simple-1.0')
354 def get_primitive_data_type(type_name):
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].
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
364 return PRIMITIVE_DATA_TYPES.get(type_name)
367 def get_data_type_name(the_type):
369 Returns the name of the type, whether it's a DataType, a primitive type, or another class.
372 return the_type._name if hasattr(the_type, '_name') else full_type_name(the_type)
375 def coerce_value(context, presentation, the_type, entry_schema, constraints, value, aspect=None): # pylint: disable=too-many-return-statements
377 Returns the value after it's coerced to its type, reporting validation errors if it cannot be
380 Supports both complex data types and primitives.
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.
386 # TODO: should support models as well as presentations
388 is_function, func = get_function(context, presentation, value)
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)
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:
408 coerce_value_fn = import_fullname(coerce_value_fn_name)
409 return coerce_value_fn(context, presentation, the_type, entry_schema, constraints,
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,
417 # Coerce to primitive type
418 return coerce_to_primitive(context, presentation, the_type, constraints, value, aspect)
421 def coerce_to_primitive(context, presentation, primitive_type, constraints, value, aspect=None):
423 Returns the value after it's coerced to a primitive type, translating exceptions to validation
424 errors if it cannot be coerced.
432 value = validate_primitive(value, primitive_type,
433 context.validation.allow_primitive_coersion)
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)
444 def coerce_to_data_type_class(context, presentation, cls, entry_schema, constraints, value,
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.
450 Will either call a ``_create`` static function in the class, or instantiate it using a
451 constructor if ``_create`` is not available.
453 This will usually be called by a ``coerce_value`` extension hook in a :class:`DataType`.
457 if hasattr(cls, '_create'):
458 # Instantiate using creator function
459 value = cls._create(context, presentation, entry_schema, constraints, value, aspect)
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)
468 value = apply_constraints_to_value(context, presentation, constraints, value)
473 def apply_constraints_to_value(context, presentation, constraints, value):
475 Applies all constraints to the value. If the value conforms, returns the value. If it does not
476 conform, returns None.
479 if (value is not None) and (constraints is not None):
481 for constraint in constraints:
482 if not constraint._apply_to_value(context, presentation, value):
489 def get_container_data_type(presentation):
490 if presentation is None:
492 if type(presentation).__name__ == 'DataType':
494 return get_container_data_type(presentation._container)
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
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,
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,