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 Additional collection classes and collection utilities.
20 from __future__ import absolute_import # so we can import standard 'collections'
22 from copy import deepcopy
24 from collections import OrderedDict
26 from ordereddict import OrderedDict
30 module = str(cls.__module__)
31 name = str(cls.__name__)
32 return name if module == '__builtin__' else '%s.%s' % (module, name)
35 class FrozenList(list):
39 After initialization it will raise :class:`~exceptions.TypeError` exceptions if modification is
42 Note that objects stored in the list may not be immutable.
44 def __init__(self, *args, **kwargs):
46 super(FrozenList, self).__init__(*args, **kwargs)
49 def __setitem__(self, index, value):
51 raise TypeError('frozen list')
52 return super(FrozenList, self).__setitem__(index, value)
54 def __delitem__(self, index):
56 raise TypeError('frozen list')
57 return super(FrozenList, self).__delitem__(index)
59 def __iadd__(self, values):
61 raise TypeError('frozen list')
62 return super(FrozenList, self).__iadd__(values)
64 def __deepcopy__(self, memo):
65 res = [deepcopy(v, memo) for v in self]
66 return FrozenList(res)
68 def append(self, value):
70 raise TypeError('frozen list')
71 return super(FrozenList, self).append(value)
73 def extend(self, values):
75 raise TypeError('frozen list')
76 return super(FrozenList, self).append(values)
78 def insert(self, index, value):
80 raise TypeError('frozen list')
81 return super(FrozenList, self).insert(index, value)
83 EMPTY_READ_ONLY_LIST = FrozenList()
86 class FrozenDict(OrderedDict):
88 An immutable ordered dict.
90 After initialization it will raise :class:`~exceptions.TypeError` exceptions if modification is
93 Note that objects stored in the dict may not be immutable.
96 def __init__(self, *args, **kwargs):
98 super(FrozenDict, self).__init__(*args, **kwargs)
101 def __setitem__(self, key, value, **_):
103 raise TypeError('frozen dict')
104 return super(FrozenDict, self).__setitem__(key, value)
106 def __delitem__(self, key, **_):
108 raise TypeError('frozen dict')
109 return super(FrozenDict, self).__delitem__(key)
111 def __deepcopy__(self, memo):
112 res = [(deepcopy(k, memo), deepcopy(v, memo)) for k, v in self.iteritems()]
113 return FrozenDict(res)
115 EMPTY_READ_ONLY_DICT = FrozenDict()
118 class StrictList(list):
120 A list that raises :class:`~exceptions.TypeError` exceptions when objects of the wrong type are
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
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)
148 def _unwrap(self, value):
149 if self.unwrapper_function is not None:
150 value = self.unwrapper_function(value)
153 def __getitem__(self, index):
154 value = super(StrictList, self).__getitem__(index)
155 value = self._unwrap(value)
158 def __setitem__(self, index, value):
159 value = self._wrap(value)
160 return super(StrictList, self).__setitem__(index, value)
162 def __iadd__(self, values):
163 values = [self._wrap(v) for v in values]
164 return super(StrictList, self).__iadd__(values)
166 def append(self, value):
167 value = self._wrap(value)
168 return super(StrictList, self).append(value)
170 def extend(self, values):
171 values = [self._wrap(v) for v in values]
172 return super(StrictList, self).extend(values)
174 def insert(self, index, value):
175 value = self._wrap(value)
176 return super(StrictList, self).insert(index, value)
179 class StrictDict(OrderedDict):
181 An ordered dict that raises :class:`~exceptions.TypeError` exceptions when keys or values of the
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
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)
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)
223 def merge(dict_a, dict_b, path=None, strict=False):
225 Merges dicts, recursively.
228 # TODO: a.add_yaml_merge(b), see https://bitbucket.org/ruamel/yaml/src/
229 # TODO: 86622a1408e0f171a12e140d53c4ffac4b6caaa3/comments.py?fileviewer=file-view-default
232 for key, value_b in dict_b.iteritems():
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:
239 raise ValueError('dict merge conflict at %s' % '.'.join(path + [str(key)]))
241 dict_a[key] = value_b
243 dict_a[key] = value_b
247 def is_removable(_container, _key, v):
248 return (v is None) or ((isinstance(v, dict) or isinstance(v, list)) and (len(v) == 0))
251 def prune(value, is_removable_function=is_removable):
253 Deletes ``None`` and empty lists and dicts, recursively.
256 if isinstance(value, list):
257 for i, v in enumerate(value):
258 if is_removable_function(value, i, v):
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):
267 prune(v, is_removable_function)
272 # TODO: Move following two methods to some place parser specific
274 def deepcopy_with_locators(value):
276 Like :func:`~copy.deepcopy`, but also copies over locators.
279 res = deepcopy(value)
280 copy_locators(res, value)
284 def copy_locators(target, source):
286 Copies over ``_locator`` for all elements, recursively.
288 Assumes that target and source have exactly the same list/dict structure.
291 locator = getattr(source, '_locator', None)
292 if locator is not None:
294 setattr(target, '_locator', locator)
295 except AttributeError:
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])