vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / aria / utils / collections.py
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
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 """
17 Additional collection classes and collection utilities.
18 """
19
20 from __future__ import absolute_import  # so we can import standard 'collections'
21
22 from copy import deepcopy
23 try:
24     from collections import OrderedDict
25 except ImportError:
26     from ordereddict import OrderedDict
27
28
29 def cls_name(cls):
30     module = str(cls.__module__)
31     name = str(cls.__name__)
32     return name if module == '__builtin__' else '%s.%s' % (module, name)
33
34
35 class FrozenList(list):
36     """
37     An immutable list.
38
39     After initialization it will raise :class:`~exceptions.TypeError` exceptions if modification is
40     attempted.
41
42     Note that objects stored in the list may not be immutable.
43     """
44     def __init__(self, *args, **kwargs):
45         self.locked = False
46         super(FrozenList, self).__init__(*args, **kwargs)
47         self.locked = True
48
49     def __setitem__(self, index, value):
50         if self.locked:
51             raise TypeError('frozen list')
52         return super(FrozenList, self).__setitem__(index, value)
53
54     def __delitem__(self, index):
55         if self.locked:
56             raise TypeError('frozen list')
57         return super(FrozenList, self).__delitem__(index)
58
59     def __iadd__(self, values):
60         if self.locked:
61             raise TypeError('frozen list')
62         return super(FrozenList, self).__iadd__(values)
63
64     def __deepcopy__(self, memo):
65         res = [deepcopy(v, memo) for v in self]
66         return FrozenList(res)
67
68     def append(self, value):
69         if self.locked:
70             raise TypeError('frozen list')
71         return super(FrozenList, self).append(value)
72
73     def extend(self, values):
74         if self.locked:
75             raise TypeError('frozen list')
76         return super(FrozenList, self).append(values)
77
78     def insert(self, index, value):
79         if self.locked:
80             raise TypeError('frozen list')
81         return super(FrozenList, self).insert(index, value)
82
83 EMPTY_READ_ONLY_LIST = FrozenList()
84
85
86 class FrozenDict(OrderedDict):
87     """
88     An immutable ordered dict.
89
90     After initialization it will raise :class:`~exceptions.TypeError` exceptions if modification is
91     attempted.
92
93     Note that objects stored in the dict may not be immutable.
94     """
95
96     def __init__(self, *args, **kwargs):
97         self.locked = False
98         super(FrozenDict, self).__init__(*args, **kwargs)
99         self.locked = True
100
101     def __setitem__(self, key, value, **_):
102         if self.locked:
103             raise TypeError('frozen dict')
104         return super(FrozenDict, self).__setitem__(key, value)
105
106     def __delitem__(self, key, **_):
107         if self.locked:
108             raise TypeError('frozen dict')
109         return super(FrozenDict, self).__delitem__(key)
110
111     def __deepcopy__(self, memo):
112         res = [(deepcopy(k, memo), deepcopy(v, memo)) for k, v in self.iteritems()]
113         return FrozenDict(res)
114
115 EMPTY_READ_ONLY_DICT = FrozenDict()
116
117
118 class StrictList(list):
119     """
120     A list that raises :class:`~exceptions.TypeError` exceptions when objects of the wrong type are
121     inserted.
122     """
123
124     def __init__(self,
125                  items=None,
126                  value_class=None,
127                  wrapper_function=None,
128                  unwrapper_function=None):
129         super(StrictList, self).__init__()
130         if isinstance(items, StrictList):
131             self.value_class = items.value_class
132             self.wrapper_function = items.wrapper_function
133             self.unwrapper_function = items.unwrapper_function
134         self.value_class = value_class
135         self.wrapper_function = wrapper_function
136         self.unwrapper_function = unwrapper_function
137         if items:
138             for item in items:
139                 self.append(item)
140
141     def _wrap(self, value):
142         if (self.value_class is not None) and (not isinstance(value, self.value_class)):
143             raise TypeError('value must be a "%s": %s' % (cls_name(self.value_class), repr(value)))
144         if self.wrapper_function is not None:
145             value = self.wrapper_function(value)
146         return value
147
148     def _unwrap(self, value):
149         if self.unwrapper_function is not None:
150             value = self.unwrapper_function(value)
151         return value
152
153     def __getitem__(self, index):
154         value = super(StrictList, self).__getitem__(index)
155         value = self._unwrap(value)
156         return value
157
158     def __setitem__(self, index, value):
159         value = self._wrap(value)
160         return super(StrictList, self).__setitem__(index, value)
161
162     def __iadd__(self, values):
163         values = [self._wrap(v) for v in values]
164         return super(StrictList, self).__iadd__(values)
165
166     def append(self, value):
167         value = self._wrap(value)
168         return super(StrictList, self).append(value)
169
170     def extend(self, values):
171         values = [self._wrap(v) for v in values]
172         return super(StrictList, self).extend(values)
173
174     def insert(self, index, value):
175         value = self._wrap(value)
176         return super(StrictList, self).insert(index, value)
177
178
179 class StrictDict(OrderedDict):
180     """
181     An ordered dict that raises :class:`~exceptions.TypeError` exceptions when keys or values of the
182     wrong type are used.
183     """
184
185     def __init__(self,
186                  items=None,
187                  key_class=None,
188                  value_class=None,
189                  wrapper_function=None,
190                  unwrapper_function=None):
191         super(StrictDict, self).__init__()
192         if isinstance(items, StrictDict):
193             self.key_class = items.key_class
194             self.value_class = items.value_class
195             self.wrapper_function = items.wrapper_function
196             self.unwrapper_function = items.unwrapper_function
197         self.key_class = key_class
198         self.value_class = value_class
199         self.wrapper_function = wrapper_function
200         self.unwrapper_function = unwrapper_function
201         if items:
202             for k, v in items:
203                 self[k] = v
204
205     def __getitem__(self, key):
206         if (self.key_class is not None) and (not isinstance(key, self.key_class)):
207             raise TypeError('key must be a "%s": %s' % (cls_name(self.key_class), repr(key)))
208         value = super(StrictDict, self).__getitem__(key)
209         if self.unwrapper_function is not None:
210             value = self.unwrapper_function(value)
211         return value
212
213     def __setitem__(self, key, value, **_):
214         if (self.key_class is not None) and (not isinstance(key, self.key_class)):
215             raise TypeError('key must be a "%s": %s' % (cls_name(self.key_class), repr(key)))
216         if (self.value_class is not None) and (not isinstance(value, self.value_class)):
217             raise TypeError('value must be a "%s": %s' % (cls_name(self.value_class), repr(value)))
218         if self.wrapper_function is not None:
219             value = self.wrapper_function(value)
220         return super(StrictDict, self).__setitem__(key, value)
221
222
223 def merge(dict_a, dict_b, path=None, strict=False):
224     """
225     Merges dicts, recursively.
226     """
227
228     # TODO: a.add_yaml_merge(b), see https://bitbucket.org/ruamel/yaml/src/
229     # TODO: 86622a1408e0f171a12e140d53c4ffac4b6caaa3/comments.py?fileviewer=file-view-default
230
231     path = path or []
232     for key, value_b in dict_b.iteritems():
233         if key in dict_a:
234             value_a = dict_a[key]
235             if isinstance(value_a, dict) and isinstance(value_b, dict):
236                 merge(value_a, value_b, path + [str(key)], strict)
237             elif value_a != value_b:
238                 if strict:
239                     raise ValueError('dict merge conflict at %s' % '.'.join(path + [str(key)]))
240                 else:
241                     dict_a[key] = value_b
242         else:
243             dict_a[key] = value_b
244     return dict_a
245
246
247 def is_removable(_container, _key, v):
248     return (v is None) or ((isinstance(v, dict) or isinstance(v, list)) and (len(v) == 0))
249
250
251 def prune(value, is_removable_function=is_removable):
252     """
253     Deletes ``None`` and empty lists and dicts, recursively.
254     """
255
256     if isinstance(value, list):
257         for i, v in enumerate(value):
258             if is_removable_function(value, i, v):
259                 del value[i]
260             else:
261                 prune(v, is_removable_function)
262     elif isinstance(value, dict):
263         for k, v in value.items():
264             if is_removable_function(value, k, v):
265                 del value[k]
266             else:
267                 prune(v, is_removable_function)
268
269     return value
270
271
272 # TODO: Move following two methods to some place parser specific
273
274 def deepcopy_with_locators(value):
275     """
276     Like :func:`~copy.deepcopy`, but also copies over locators.
277     """
278
279     res = deepcopy(value)
280     copy_locators(res, value)
281     return res
282
283
284 def copy_locators(target, source):
285     """
286     Copies over ``_locator`` for all elements, recursively.
287
288     Assumes that target and source have exactly the same list/dict structure.
289     """
290
291     locator = getattr(source, '_locator', None)
292     if locator is not None:
293         try:
294             setattr(target, '_locator', locator)
295         except AttributeError:
296             pass
297
298     if isinstance(target, list) and isinstance(source, list):
299         for i, _ in enumerate(target):
300             copy_locators(target[i], source[i])
301     elif isinstance(target, dict) and isinstance(source, dict):
302         for k, v in target.iteritems():
303             copy_locators(v, source[k])