summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/functions.py
blob: 554bbfbddc2df4f87759ec8bf77feba1caa89e38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Mechanism for evaluating intrinsic functions.
"""
from ..parser.exceptions import InvalidValueError
from ..parser.consumption import ConsumptionContext
from ..utils.collections import OrderedDict
from ..utils.type import full_type_name
from . import exceptions


class Function(object):
    """
    Base class for intrinsic functions. Serves as a placeholder for a value that should eventually
    be derived by "evaluating" (calling) the function.

    Note that this base class is provided as a convenience and you do not have to inherit it: any
    object with an ``__evaluate__`` method would be treated similarly.
    """

    @property
    def as_raw(self):
        raise NotImplementedError

    def __evaluate__(self, container_holder):
        """
        Evaluates the function if possible.

        :rtype: :class:`Evaluation` (or any object with ``value`` and ``final`` properties)
        :raises CannotEvaluateFunctionException: if cannot be evaluated at this time (do *not* just
         return ``None``)
        """

        raise NotImplementedError

    def __deepcopy__(self, memo):
        # Circumvent cloning in order to maintain our state
        return self


class Evaluation(object):
    """
    An evaluated :class:`Function` return value.

    :ivar value: evaluated value
    :ivar final: whether the value is final
    :vartype final: boolean
    """

    def __init__(self, value, final=False):
        self.value = value
        self.final = final


def evaluate(value, container_holder, report_issues=False): # pylint: disable=too-many-branches
    """
    Recursively attempts to call ``__evaluate__``. If an evaluation occurred will return an
    :class:`Evaluation`, otherwise it will be ``None``. If any evaluation is non-final, then the
    entire evaluation will also be non-final.

    The ``container_holder`` argument should have three properties: ``container`` should return
    the model that contains the value, ``service`` should return the containing
    :class:`~aria.modeling.models.Service` model or None, and ``service_template`` should return the
    containing :class:`~aria.modeling.models.ServiceTemplate` model or ``None``.
    """

    evaluated = False
    final = True

    if hasattr(value, '__evaluate__'):
        try:
            evaluation = value.__evaluate__(container_holder)

            # Verify evaluation structure
            if (evaluation is None) \
                or (not hasattr(evaluation, 'value')) \
                or (not hasattr(evaluation, 'final')):
                raise InvalidValueError('bad __evaluate__ implementation: {0}'
                                        .format(full_type_name(value)))

            evaluated = True
            value = evaluation.value
            final = evaluation.final

            # The evaluated value might itself be evaluable
            evaluation = evaluate(value, container_holder, report_issues)
            if evaluation is not None:
                value = evaluation.value
                if not evaluation.final:
                    final = False
        except exceptions.CannotEvaluateFunctionException:
            pass
        except InvalidValueError as e:
            if report_issues:
                context = ConsumptionContext.get_thread_local()
                context.validation.report(e.issue)

    elif isinstance(value, list):
        evaluated_list = []
        for v in value:
            evaluation = evaluate(v, container_holder, report_issues)
            if evaluation is not None:
                evaluated_list.append(evaluation.value)
                evaluated = True
                if not evaluation.final:
                    final = False
            else:
                evaluated_list.append(v)
        if evaluated:
            value = evaluated_list

    elif isinstance(value, dict):
        evaluated_dict = OrderedDict()
        for k, v in value.iteritems():
            evaluation = evaluate(v, container_holder, report_issues)
            if evaluation is not None:
                evaluated_dict[k] = evaluation.value
                evaluated = True
                if not evaluation.final:
                    final = False
            else:
                evaluated_dict[k] = v
        if evaluated:
            value = evaluated_dict

    return Evaluation(value, final) if evaluated else None