summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/parser/presentation/presentation.py
blob: 3f9f86d8bfd0ee72bafba720418adb63fa7f3b6c (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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# 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.

from ...utils.caching import HasCachedMethods
from ...utils.collections import deepcopy_with_locators
from ...utils.formatting import safe_repr
from ...utils.type import full_type_name
from ...utils.console import puts
from ..validation import Issue
from .null import none_to_null
from .utils import (get_locator, validate_no_short_form, validate_no_unknown_fields,
                    validate_known_fields, validate_primitive)


class Value(object):
    """
    Encapsulates a typed value assignment.
    """

    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:
            puts(context.style.type_style(self.type))
        if self.value is not None:
            puts(context.style.literal_style(self.value))
        if self.description is not None:
            puts(context.style.meta_style(self.description))
        if self.required is not None:
            puts(context.style.required_style(self.required))


class PresentationBase(HasCachedMethods):
    """
    Base class for ARIA presentation classes.
    """

    def __init__(self, name=None, raw=None, container=None):
        self._name = name
        self._raw = raw
        self._container = container
        super(PresentationBase, self).__init__()

    @property
    def as_raw(self):
        return self._raw

    def _validate(self, context):
        """
        Validates the presentation while reporting errors in the validation context but *not*
        raising exceptions.

        The base class does not thing, but subclasses may override this for specialized validation.
        """

    @property
    def _fullname(self):
        """
        Always returns a usable full name of the presentation, whether it itself is named, or
        recursing to its container, and finally defaulting to the class name.
        """

        if self._name is not None:
            return self._name
        elif self._container is not None:
            return self._container._fullname
        return full_type_name(self)

    @property
    def _locator(self):
        """
        Attempts to return the most relevant locator, whether we have one, or recursing to our
        container.

        :rtype: :class:`aria.parser.reading.Locator`
        """

        return get_locator(self._raw, self._container)

    def _get(self, *names):
        """
        Gets attributes recursively.
        """

        obj = self
        if (obj is not None) and names:
            for name in names:
                obj = getattr(obj, name, None)
                if obj is None:
                    break
        return obj

    def _get_from_dict(self, *names):
        """
        Gets attributes recursively, except for the last name which is used to get a value from the
        last dict.
        """

        if names:
            obj = self._get(*names[:-1])
            if isinstance(obj, dict):
                return obj.get(names[-1])  # pylint: disable=no-member
        return None

    def _get_child_locator(self, *names):
        """
        Attempts to return the locator of one our children. Will default to our locator if not
        found.

        :rtype: :class:`aria.parser.reading.Locator`
        """

        if hasattr(self._raw, '_locator'):
            locator = self._raw._locator
            if locator is not None:
                return locator.get_child(*names)
        return self._locator

    def _dump(self, context):
        """
        Emits a colorized representation.

        The base class will emit a sensible default representation of the fields, (by calling
        ``_dump_content``), but subclasses may override this for specialized dumping.
        """

        if self._name:
            puts(context.style.node(self._name))
            with context.style.indent():
                self._dump_content(context)
        else:
            self._dump_content(context)

    def _dump_content(self, context, field_names=None):
        """
        Emits a colorized representation of the contents.

        The base class will call ``_dump_field`` on all the fields, but subclasses may override
        this for specialized dumping.
        """

        if field_names:
            for field_name in field_names:
                self._dump_field(context, field_name)
        elif hasattr(self, '_iter_field_names'):
            for field_name in self._iter_field_names():  # pylint: disable=no-member
                self._dump_field(context, field_name)
        else:
            puts(context.style.literal_style(self._raw))

    def _dump_field(self, context, field_name):
        """
        Emits a colorized representation of the field.

        According to the field type, this may trigger nested recursion. The nested types will
        delegate to their ``_dump`` methods.
        """

        field = self.FIELDS[field_name]  # pylint: disable=no-member
        field.dump(self, context)

    def _clone(self, container=None):
        """
        Creates a clone of this presentation, optionally allowing for a new container.
        """

        raw = deepcopy_with_locators(self._raw)
        if container is None:
            container = self._container
        return self.__class__(name=self._name, raw=raw, container=container)


class Presentation(PresentationBase):
    """
    Base class for ARIA presentations. A presentation is a Pythonic wrapper around agnostic raw
    data, adding the ability to read and modify the data with proper validation.

    ARIA presentation classes will often be decorated with :func:`has_fields`, as that mechanism
    automates a lot of field-specific validation. However, that is not a requirement.

    Make sure that your utility property and method names begin with a ``_``, because those names
    without a ``_`` prefix are normally reserved for fields.
    """

    def _validate(self, context):
        validate_no_short_form(context, self)
        validate_no_unknown_fields(context, self)
        validate_known_fields(context, self)


class AsIsPresentation(PresentationBase):
    """
    Base class for trivial ARIA presentations that provide the raw value as is.
    """

    def __init__(self, name=None, raw=None, container=None, cls=None):
        super(AsIsPresentation, self).__init__(name, raw, container)
        self.cls = cls

    @property
    def value(self):
        return none_to_null(self._raw)

    @value.setter
    def value(self, value):
        self._raw = value

    @property
    def _full_cls_name(self):
        name = full_type_name(self.cls) if self.cls is not None else None
        if name == 'unicode':
            # For simplicity, display "unicode" as "str"
            name = 'str'
        return name

    def _validate(self, context):
        try:
            validate_primitive(self._raw, self.cls, context.validation.allow_primitive_coersion)
        except ValueError as e:
            context.validation.report('"%s" is not a valid "%s": %s'
                                      % (self._fullname, self._full_cls_name, safe_repr(self._raw)),
                                      locator=self._locator,
                                      level=Issue.FIELD,
                                      exception=e)

    def _dump(self, context):
        if hasattr(self._raw, '_dump'):
            puts(context.style.node(self._name))
            with context.style.indent():
                self._raw._dump(context)
        else:
            super(AsIsPresentation, self)._dump(context)