Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
ARIA-313 Fix handling the required field of inputs
The required field is handled in the following way:

topology_template inputs:
-------------------------
Every input that is declared as required must be supplied a value while
creating the service.
In addition, supplying inputs that were not declared in the topology
template is forbidden.

workflow inputs:*
----------------
Every input that is declared as required must be supplied a value while
creating/starting the execution.
In addition, supplying inputs that were not declared in the policy_type
is forbidden.
* workflow inputs are defined as properties of policy_types that are
derived from aria.Workflow

operation and interface inputs:
-------------------------------
The validation of the required field of inputs that belong to
operations and interfaces is done only in the parsing stage.
This reasoning follows the TOSCA spirit, where anything that is declared
as required in the type, must be assigned in the corresponding template

I split the logic of merging provided and declared input values into
three steps:

1. Validate that no undeclared inputs were provided.
2. Validate that all required inputs were provided with a value.
3. The actual merging process, which includes type checking.
  • Loading branch information
aviaefrat committed Jul 30, 2017
1 parent c2b8e65 commit 51e4ed0041c3221723eee27a81ebbc49156048b6
Showing 14 changed files with 159 additions and 111 deletions.
@@ -45,7 +45,7 @@ class CannotEvaluateFunctionException(ModelingException):
"""


class MissingRequiredParametersException(ParameterException):
class MissingRequiredInputsException(ParameterException):
"""
ARIA modeling exception: Required parameters have been omitted.
"""
@@ -57,7 +57,7 @@ class ParametersOfWrongTypeException(ParameterException):
"""


class UndeclaredParametersException(ParameterException):
class UndeclaredInputsException(ParameterException):
"""
ARIA modeling exception: Undeclared parameters have been provided.
"""
@@ -161,8 +161,6 @@ class ParameterMixin(TemplateModelMixin, caching.HasCachedMethods):
:func:`~aria.modeling.functions.evaluate`.
"""

__tablename__ = 'parameter'

type_name = Column(Text, doc="""
Type name.
@@ -22,6 +22,7 @@
from sqlalchemy import (
Column,
Text,
Boolean
)
from sqlalchemy.ext.declarative import declared_attr

@@ -84,6 +85,18 @@ class InputBase(ParameterMixin):

__tablename__ = 'input'

required = Column(Boolean, doc="""
Is the input mandatory.
:type: :obj:`bool`
""")

@classmethod
def wrap(cls, name, value, description=None, required=True): # pylint: disable=arguments-differ
input = super(InputBase, cls).wrap(name, value, description)
input.required = required
return input

# region many_to_one relationships

@declared_attr
@@ -1939,7 +1939,7 @@ def operation_template_fk(cls):
""")

retry_interval = Column(Integer, doc="""
Interval between task retry attemps (in seconds).
Interval between task retry attempts (in seconds).
:type: :obj:`float`
""")
@@ -343,6 +343,11 @@ def instantiate(self, container, model_storage, inputs=None): # pylint: disable
context = ConsumptionContext.get_thread_local()
context.modeling.instance = service

utils.validate_no_undeclared_inputs(declared_inputs=self.inputs,
supplied_inputs=inputs or {})
utils.validate_required_inputs_are_supplied(declared_inputs=self.inputs,
supplied_inputs=inputs or {})

service.inputs = utils.merge_parameter_values(inputs, self.inputs, model_cls=models.Input)
# TODO: now that we have inputs, we should scan properties and inputs and evaluate functions

@@ -64,81 +64,84 @@ def service_template(self):
return self.container.service_template


def merge_parameter_values(parameter_values, declared_parameters, model_cls):
def validate_no_undeclared_inputs(declared_inputs, supplied_inputs):

undeclared_inputs = [input for input in supplied_inputs if input not in declared_inputs]
if undeclared_inputs:
raise exceptions.UndeclaredInputsException(
'Undeclared inputs have been provided: {0}; Declared inputs: {1}'
.format(string_list_as_string(undeclared_inputs),
string_list_as_string(declared_inputs.keys())))


def validate_required_inputs_are_supplied(declared_inputs, supplied_inputs):
required_inputs = [input for input in declared_inputs.values() if input.required]
missing_required_inputs = [input for input in required_inputs
if input.name not in supplied_inputs and not input.value]
if missing_required_inputs:
raise exceptions.MissingRequiredInputsException(
'Required inputs {0} have not been provided values'
.format(string_list_as_string(missing_required_inputs)))


def merge_parameter_values(provided_values, declared_parameters, model_cls):
"""
Merges parameter values according to those declared by a type.
Exceptions will be raised for validation errors.
:param parameter_values: provided parameter values or None
:type parameter_values: {:obj:`basestring`: object}
:param provided_values: provided parameter values or None
:type provided_values: {:obj:`basestring`: object}
:param declared_parameters: declared parameters
:type declared_parameters: {:obj:`basestring`: :class:`~aria.modeling.models.Parameter`}
:param model_cls: the model class that should be created from a provided value
:type model_cls: :class:`~aria.modeling.models.Input` or :class:`~aria.modeling.models.Argument`
:return: the merged parameters
:rtype: {:obj:`basestring`: :class:`~aria.modeling.models.Parameter`}
:raises ~aria.modeling.exceptions.UndeclaredParametersException: if a key in
:raises ~aria.modeling.exceptions.UndeclaredInputsException: if a key in
``parameter_values`` does not exist in ``declared_parameters``
:raises ~aria.modeling.exceptions.MissingRequiredParametersException: if a key in
:raises ~aria.modeling.exceptions.MissingRequiredInputsException: if a key in
``declared_parameters`` does not exist in ``parameter_values`` and also has no default value
:raises ~aria.modeling.exceptions.ParametersOfWrongTypeException: if a value in
``parameter_values`` does not match its type in ``declared_parameters``
"""

parameter_values = parameter_values or {}

undeclared_names = list(set(parameter_values.keys()).difference(declared_parameters.keys()))
if undeclared_names:
raise exceptions.UndeclaredParametersException(
'Undeclared parameters have been provided: {0}; Declared: {1}'
.format(string_list_as_string(undeclared_names),
string_list_as_string(declared_parameters.keys())))
provided_values = provided_values or {}
provided_values_of_wrong_type = OrderedDict()
model_parameters = OrderedDict()

parameters = OrderedDict()

missing_names = []
wrong_type_values = OrderedDict()
for declared_parameter_name, declared_parameter in declared_parameters.iteritems():
if declared_parameter_name in parameter_values:
# Value has been provided
value = parameter_values[declared_parameter_name]
if declared_parameter_name in provided_values:
# a value has been provided
value = provided_values[declared_parameter_name]

# Validate type
type_name = declared_parameter.type_name
try:
validate_value_type(value, type_name)
except ValueError:
wrong_type_values[declared_parameter_name] = type_name
provided_values_of_wrong_type[declared_parameter_name] = type_name
except RuntimeError:
# TODO: This error shouldn't be raised (or caught), but right now we lack support
# TODO This error shouldn't be raised (or caught), but right now we lack support
# for custom data_types, which will raise this error. Skipping their validation.
pass

# Wrap in Parameter model
parameters[declared_parameter_name] = model_cls( # pylint: disable=unexpected-keyword-arg
model_parameters[declared_parameter_name] = model_cls( # pylint: disable=unexpected-keyword-arg
name=declared_parameter_name,
type_name=type_name,
description=declared_parameter.description,
value=value)
elif declared_parameter.value is not None:
# Copy default value from declaration
parameters[declared_parameter_name] = declared_parameter.instantiate(None)
else:
# Required value has not been provided
missing_names.append(declared_parameter_name)

if missing_names:
raise exceptions.MissingRequiredParametersException(
'Declared parameters {0} have not been provided values'
.format(string_list_as_string(missing_names)))
# Copy default value from declaration
model_parameters[declared_parameter_name] = declared_parameter.instantiate(None)

if wrong_type_values:
if provided_values_of_wrong_type:
error_message = StringIO()
for param_name, param_type in wrong_type_values.iteritems():
for param_name, param_type in provided_values_of_wrong_type.iteritems():
error_message.write('Parameter "{0}" is not of declared type "{1}"{2}'
.format(param_name, param_type, os.linesep))
raise exceptions.ParametersOfWrongTypeException(error_message.getvalue())

return parameters
return model_parameters


def coerce_dict_values(the_dict, report_issues=False):
@@ -137,6 +137,10 @@ def _create_execution_model(self, inputs):
else:
workflow_inputs = self.service.workflows[self._workflow_name].inputs

modeling_utils.validate_no_undeclared_inputs(declared_inputs=workflow_inputs,
supplied_inputs=inputs or {})
modeling_utils.validate_required_inputs_are_supplied(declared_inputs=workflow_inputs,
supplied_inputs=inputs or {})
execution.inputs = modeling_utils.merge_parameter_values(inputs,
workflow_inputs,
model_cls=models.Input)
@@ -48,3 +48,7 @@ def literal(value):
@staticmethod
def meta(value):
return Colored.green(value)

@staticmethod
def required(value):
return Colored.white(value)
@@ -29,10 +29,11 @@ class Value(object):
Encapsulates a typed value assignment.
"""

def __init__(self, type_name, value, description):
def __init__(self, type_name, value, description, required):
self.type = deepcopy_with_locators(type_name)
self.value = deepcopy_with_locators(value)
self.description = deepcopy_with_locators(description)
self.required = deepcopy_with_locators(required)

def _dump(self, context):
if self.type is not None:
@@ -41,6 +42,8 @@ def _dump(self, context):
puts(context.style.literal(self.value))
if self.description is not None:
puts(context.style.meta(self.description))
if self.required is not None:
puts(context.style.required(self.required))


class PresentationBase(HasCachedMethods):

0 comments on commit 51e4ed0

Please sign in to comment.