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.
17 Miscellaneous modeling utilities.
21 from json import JSONEncoder
22 from StringIO import StringIO
24 from . import exceptions
25 from ..utils.type import validate_value_type
26 from ..utils.collections import OrderedDict
27 from ..utils.formatting import string_list_as_string
30 class ModelJSONEncoder(JSONEncoder):
32 JSON encoder that automatically unwraps ``value`` attributes.
34 def __init__(self, *args, **kwargs):
35 # Just here to make sure Sphinx doesn't grab the base constructor's docstring
36 super(ModelJSONEncoder, self).__init__(*args, **kwargs)
38 def default(self, o): # pylint: disable=method-hidden
39 from .mixins import ModelMixin
40 if isinstance(o, ModelMixin):
41 if hasattr(o, 'value'):
42 dict_to_return = o.to_dict(fields=('value',))
43 return dict_to_return['value']
47 return JSONEncoder.default(self, o)
50 class NodeTemplateContainerHolder(object):
52 Wrapper that allows using a :class:`~aria.modeling.models.NodeTemplate` model directly as the
53 ``container_holder`` input for :func:`~aria.modeling.functions.evaluate`.
56 def __init__(self, node_template):
57 self.container = node_template
61 def service_template(self):
62 return self.container.service_template
65 # def validate_no_undeclared_inputs(declared_inputs, supplied_inputs):
67 # undeclared_inputs = [input for input in supplied_inputs if input not in declared_inputs]
68 # if undeclared_inputs:
69 # raise exceptions.UndeclaredInputsException(
70 # 'Undeclared inputs have been provided: {0}; Declared inputs: {1}'
71 # .format(string_list_as_string(undeclared_inputs),
72 # string_list_as_string(declared_inputs.keys())))
75 def validate_required_inputs_are_supplied(declared_inputs, supplied_inputs):
76 required_inputs = [input for input in declared_inputs.values() if input.required]
77 missing_required_inputs = [input for input in required_inputs
78 if input.name not in supplied_inputs and not str(input.value)]
79 if missing_required_inputs:
80 raise exceptions.MissingRequiredInputsException(
81 'Required inputs {0} have not been provided values'
82 .format(string_list_as_string(missing_required_inputs)))
85 def merge_parameter_values(provided_values, declared_parameters, model_cls=None):
87 Merges parameter values according to those declared by a type.
89 Exceptions will be raised for validation errors.
91 :param provided_values: provided parameter values or None
92 :type provided_values: {:obj:`basestring`: object}
93 :param declared_parameters: declared parameters
94 :type declared_parameters: {:obj:`basestring`: :class:`~aria.modeling.models.Parameter`}
95 :param model_cls: the model class that should be created from a provided value
96 :type model_cls: :class:`~aria.modeling.models.Input` or :class:`~aria.modeling.models.Argument`
97 :return: the merged parameters
98 :rtype: {:obj:`basestring`: :class:`~aria.modeling.models.Parameter`}
99 :raises ~aria.modeling.exceptions.UndeclaredInputsException: if a key in
100 ``parameter_values`` does not exist in ``declared_parameters``
101 :raises ~aria.modeling.exceptions.MissingRequiredInputsException: if a key in
102 ``declared_parameters`` does not exist in ``parameter_values`` and also has no default value
103 :raises ~aria.modeling.exceptions.ParametersOfWrongTypeException: if a value in
104 ``parameter_values`` does not match its type in ``declared_parameters``
107 provided_values = provided_values or {}
108 provided_values_of_wrong_type = OrderedDict()
109 model_parameters = OrderedDict()
110 model_cls = model_cls or _get_class_from_sql_relationship(declared_parameters)
112 for declared_parameter_name, declared_parameter in declared_parameters.iteritems():
113 if declared_parameter_name in provided_values:
114 # a value has been provided
115 value = provided_values[declared_parameter_name]
118 type_name = declared_parameter.type_name
120 validate_value_type(value, type_name)
122 provided_values_of_wrong_type[declared_parameter_name] = type_name
124 # TODO This error shouldn't be raised (or caught), but right now we lack support
125 # for custom data_types, which will raise this error. Skipping their validation.
127 model_parameters[declared_parameter_name] = model_cls( # pylint: disable=unexpected-keyword-arg
128 name=declared_parameter_name,
130 description=declared_parameter.description,
133 # Copy default value from declaration
134 model_parameters[declared_parameter_name] = model_cls(
135 value=declared_parameter._value,
136 name=declared_parameter.name,
137 type_name=declared_parameter.type_name,
138 description=declared_parameter.description)
140 if provided_values_of_wrong_type:
141 error_message = StringIO()
142 for param_name, param_type in provided_values_of_wrong_type.iteritems():
143 error_message.write('Parameter "{0}" is not of declared type "{1}"{2}'
144 .format(param_name, param_type, os.linesep))
145 raise exceptions.ParametersOfWrongTypeException(error_message.getvalue())
147 return model_parameters
150 def parameters_as_values(the_dict):
151 return dict((k, v.value) for k, v in the_dict.iteritems())
154 def dict_as_arguments(the_dict):
155 return OrderedDict((name, value.as_argument()) for name, value in the_dict.iteritems())
158 class classproperty(object): # pylint: disable=invalid-name
159 def __init__(self, f):
161 self.__doct__ = f.__doc__
163 def __get__(self, instance, owner):
164 return self._func(owner)
169 Class decorator to use the last base class's docstring and make sure Sphinx doesn't grab the
170 base constructor's docstring.
172 original_init = cls.__init__
173 def init(*args, **kwargs):
174 original_init(*args, **kwargs)
177 cls.__doc__ = cls.__bases__[-1].__doc__
182 def _get_class_from_sql_relationship(field):
183 class_ = field._sa_adapter.owner_state.class_
184 prop_name = field._sa_adapter.attr.key
185 return getattr(class_, prop_name).property.mapper.class_