root / snf-common / synnefo / util / keypath.py @ d509e6da
History | View | Annotate | Download (8.5 kB)
1 | b253c438 | Georgios D. Tsoukalas | # Copyright 2013 GRNET S.A. All rights reserved.
|
---|---|---|---|
2 | b253c438 | Georgios D. Tsoukalas | #
|
3 | b253c438 | Georgios D. Tsoukalas | # Redistribution and use in source and binary forms, with or
|
4 | b253c438 | Georgios D. Tsoukalas | # without modification, are permitted provided that the following
|
5 | b253c438 | Georgios D. Tsoukalas | # conditions are met:
|
6 | b253c438 | Georgios D. Tsoukalas | #
|
7 | b253c438 | Georgios D. Tsoukalas | # 1. Redistributions of source code must retain the above
|
8 | b253c438 | Georgios D. Tsoukalas | # copyright notice, this list of conditions and the following
|
9 | b253c438 | Georgios D. Tsoukalas | # disclaimer.
|
10 | b253c438 | Georgios D. Tsoukalas | #
|
11 | b253c438 | Georgios D. Tsoukalas | # 2. Redistributions in binary form must reproduce the above
|
12 | b253c438 | Georgios D. Tsoukalas | # copyright notice, this list of conditions and the following
|
13 | b253c438 | Georgios D. Tsoukalas | # disclaimer in the documentation and/or other materials
|
14 | b253c438 | Georgios D. Tsoukalas | # provided with the distribution.
|
15 | b253c438 | Georgios D. Tsoukalas | #
|
16 | b253c438 | Georgios D. Tsoukalas | # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 | b253c438 | Georgios D. Tsoukalas | # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 | b253c438 | Georgios D. Tsoukalas | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 | b253c438 | Georgios D. Tsoukalas | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 | b253c438 | Georgios D. Tsoukalas | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 | b253c438 | Georgios D. Tsoukalas | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 | b253c438 | Georgios D. Tsoukalas | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 | b253c438 | Georgios D. Tsoukalas | # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 | b253c438 | Georgios D. Tsoukalas | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 | b253c438 | Georgios D. Tsoukalas | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 | b253c438 | Georgios D. Tsoukalas | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 | b253c438 | Georgios D. Tsoukalas | # POSSIBILITY OF SUCH DAMAGE.
|
28 | b253c438 | Georgios D. Tsoukalas | #
|
29 | b253c438 | Georgios D. Tsoukalas | # The views and conclusions contained in the software and
|
30 | b253c438 | Georgios D. Tsoukalas | # documentation are those of the authors and should not be
|
31 | b253c438 | Georgios D. Tsoukalas | # interpreted as representing official policies, either expressed
|
32 | b253c438 | Georgios D. Tsoukalas | # or implied, of GRNET S.A.
|
33 | b253c438 | Georgios D. Tsoukalas | |
34 | b253c438 | Georgios D. Tsoukalas | |
35 | 1c1c2b9e | Georgios D. Tsoukalas | import re |
36 | 1c1c2b9e | Georgios D. Tsoukalas | integer_re = re.compile('-?[0-9]+')
|
37 | 1c1c2b9e | Georgios D. Tsoukalas | |
38 | 1c1c2b9e | Georgios D. Tsoukalas | |
39 | 1c1c2b9e | Georgios D. Tsoukalas | def join_path(sep, path): |
40 | 1c1c2b9e | Georgios D. Tsoukalas | iterable = ((str(n) if isinstance(n, (int, long)) else n) for n in path) |
41 | 1c1c2b9e | Georgios D. Tsoukalas | return sep.join(iterable)
|
42 | 1c1c2b9e | Georgios D. Tsoukalas | |
43 | 1c1c2b9e | Georgios D. Tsoukalas | |
44 | b253c438 | Georgios D. Tsoukalas | def lookup_path(container, path, sep='.', createpath=False): |
45 | b253c438 | Georgios D. Tsoukalas | """
|
46 | b253c438 | Georgios D. Tsoukalas | return (['a','b'],
|
47 | b253c438 | Georgios D. Tsoukalas | [container['a'], container['a']['b']],
|
48 | b253c438 | Georgios D. Tsoukalas | 'c') where path=sep.join(['a','b','c'])
|
49 | b253c438 | Georgios D. Tsoukalas |
|
50 | b253c438 | Georgios D. Tsoukalas | """
|
51 | b253c438 | Georgios D. Tsoukalas | names = path.split(sep) |
52 | b253c438 | Georgios D. Tsoukalas | dirnames = names[:-1]
|
53 | b253c438 | Georgios D. Tsoukalas | basename = names[-1]
|
54 | 1c1c2b9e | Georgios D. Tsoukalas | if integer_re.match(basename):
|
55 | 1c1c2b9e | Georgios D. Tsoukalas | basename = int(basename)
|
56 | b253c438 | Georgios D. Tsoukalas | |
57 | b253c438 | Georgios D. Tsoukalas | node = container |
58 | b253c438 | Georgios D. Tsoukalas | name_path = [] |
59 | b253c438 | Georgios D. Tsoukalas | node_path = [node] |
60 | b253c438 | Georgios D. Tsoukalas | for name in dirnames: |
61 | b253c438 | Georgios D. Tsoukalas | name_path.append(name) |
62 | 1c1c2b9e | Georgios D. Tsoukalas | |
63 | 1c1c2b9e | Georgios D. Tsoukalas | if integer_re.match(name):
|
64 | 1c1c2b9e | Georgios D. Tsoukalas | name = int(name)
|
65 | 1c1c2b9e | Georgios D. Tsoukalas | |
66 | 1c1c2b9e | Georgios D. Tsoukalas | try:
|
67 | 1c1c2b9e | Georgios D. Tsoukalas | node = node[name] |
68 | 1c1c2b9e | Georgios D. Tsoukalas | except KeyError as e: |
69 | b253c438 | Georgios D. Tsoukalas | if not createpath: |
70 | 1c1c2b9e | Georgios D. Tsoukalas | m = "'{0}': path not found".format(join_path(sep, name_path))
|
71 | b253c438 | Georgios D. Tsoukalas | raise KeyError(m) |
72 | b253c438 | Georgios D. Tsoukalas | node[name] = {} |
73 | 1c1c2b9e | Georgios D. Tsoukalas | node = node[name] |
74 | 1c1c2b9e | Georgios D. Tsoukalas | except IndexError as e: |
75 | 1c1c2b9e | Georgios D. Tsoukalas | if not createpath: |
76 | 1c1c2b9e | Georgios D. Tsoukalas | m = "'{0}': path not found: {1}".format(
|
77 | 1c1c2b9e | Georgios D. Tsoukalas | join_path(sep, name_path), e) |
78 | 1c1c2b9e | Georgios D. Tsoukalas | raise KeyError(m) |
79 | 1c1c2b9e | Georgios D. Tsoukalas | size = name if name > 0 else -name |
80 | 1c1c2b9e | Georgios D. Tsoukalas | node += (dict() for _ in xrange(len(node), size)) |
81 | b253c438 | Georgios D. Tsoukalas | node = node[name] |
82 | b253c438 | Georgios D. Tsoukalas | except TypeError as e: |
83 | b253c438 | Georgios D. Tsoukalas | m = "'{0}': cannot traverse path beyond this node: {1}"
|
84 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path), str(e))
|
85 | b253c438 | Georgios D. Tsoukalas | raise ValueError(m) |
86 | b253c438 | Georgios D. Tsoukalas | node_path.append(node) |
87 | b253c438 | Georgios D. Tsoukalas | |
88 | b253c438 | Georgios D. Tsoukalas | return name_path, node_path, basename
|
89 | b253c438 | Georgios D. Tsoukalas | |
90 | b253c438 | Georgios D. Tsoukalas | |
91 | b253c438 | Georgios D. Tsoukalas | def walk_paths(container): |
92 | b253c438 | Georgios D. Tsoukalas | for name, node in container.iteritems(): |
93 | b253c438 | Georgios D. Tsoukalas | if not hasattr(node, 'items'): |
94 | b253c438 | Georgios D. Tsoukalas | yield [name], [node]
|
95 | b253c438 | Georgios D. Tsoukalas | else:
|
96 | b253c438 | Georgios D. Tsoukalas | for names, nodes in walk_paths(node): |
97 | b253c438 | Georgios D. Tsoukalas | yield [name] + names, [node] + nodes
|
98 | b253c438 | Georgios D. Tsoukalas | |
99 | b253c438 | Georgios D. Tsoukalas | |
100 | b253c438 | Georgios D. Tsoukalas | def list_paths(container, sep='.'): |
101 | b253c438 | Georgios D. Tsoukalas | """
|
102 | b253c438 | Georgios D. Tsoukalas | >>> sorted(list_paths({'a': {'b': {'c': 'd'}}}))
|
103 | b253c438 | Georgios D. Tsoukalas | [('a.b.c', 'd')]
|
104 | b253c438 | Georgios D. Tsoukalas | >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': 3}}))
|
105 | b253c438 | Georgios D. Tsoukalas | [('a.b.c', 'd'), ('a.e', 3)]
|
106 | b253c438 | Georgios D. Tsoukalas | >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': {'f': 3}}}))
|
107 | b253c438 | Georgios D. Tsoukalas | [('a.b.c', 'd'), ('a.e.f', 3)]
|
108 | 1c1c2b9e | Georgios D. Tsoukalas | >>> sorted(list_paths({'a': [{'b': 3}, 2]}))
|
109 | 1c1c2b9e | Georgios D. Tsoukalas | [('a', [{'b': 3}, 2])]
|
110 | b253c438 | Georgios D. Tsoukalas | >>> list_paths({})
|
111 | b253c438 | Georgios D. Tsoukalas | []
|
112 | b253c438 | Georgios D. Tsoukalas |
|
113 | b253c438 | Georgios D. Tsoukalas | """
|
114 | 1c1c2b9e | Georgios D. Tsoukalas | return [(join_path(sep, name_path), node_path[-1]) |
115 | b253c438 | Georgios D. Tsoukalas | for name_path, node_path in walk_paths(container)] |
116 | b253c438 | Georgios D. Tsoukalas | |
117 | b253c438 | Georgios D. Tsoukalas | |
118 | b253c438 | Georgios D. Tsoukalas | def del_path(container, path, sep='.', collect=True): |
119 | b253c438 | Georgios D. Tsoukalas | """
|
120 | b253c438 | Georgios D. Tsoukalas | del container['a']['b']['c'] where path=sep.join(['a','b','c'])
|
121 | b253c438 | Georgios D. Tsoukalas |
|
122 | b253c438 | Georgios D. Tsoukalas | >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c'); d
|
123 | b253c438 | Georgios D. Tsoukalas | {}
|
124 | b253c438 | Georgios D. Tsoukalas | >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c', collect=False); d
|
125 | b253c438 | Georgios D. Tsoukalas | {'a': {'b': {}}}
|
126 | b253c438 | Georgios D. Tsoukalas | >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c.d')
|
127 | b253c438 | Georgios D. Tsoukalas | Traceback (most recent call last):
|
128 | b253c438 | Georgios D. Tsoukalas | ValueError: 'a.b.c': cannot traverse path beyond this node:\
|
129 | b253c438 | Georgios D. Tsoukalas | 'str' object does not support item deletion
|
130 | b253c438 | Georgios D. Tsoukalas | """
|
131 | b253c438 | Georgios D. Tsoukalas | |
132 | b253c438 | Georgios D. Tsoukalas | name_path, node_path, basename = \ |
133 | b253c438 | Georgios D. Tsoukalas | lookup_path(container, path, sep=sep, createpath=False)
|
134 | b253c438 | Georgios D. Tsoukalas | |
135 | b253c438 | Georgios D. Tsoukalas | lastnode = node_path.pop() |
136 | b253c438 | Georgios D. Tsoukalas | lastname = basename |
137 | b253c438 | Georgios D. Tsoukalas | try:
|
138 | b253c438 | Georgios D. Tsoukalas | if basename in lastnode: |
139 | b253c438 | Georgios D. Tsoukalas | del lastnode[basename]
|
140 | b253c438 | Georgios D. Tsoukalas | except (TypeError, KeyError) as e: |
141 | b253c438 | Georgios D. Tsoukalas | m = "'{0}': cannot traverse path beyond this node: {1}"
|
142 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path), str(e))
|
143 | b253c438 | Georgios D. Tsoukalas | raise ValueError(m) |
144 | b253c438 | Georgios D. Tsoukalas | |
145 | b253c438 | Georgios D. Tsoukalas | if collect:
|
146 | b253c438 | Georgios D. Tsoukalas | while node_path and not lastnode: |
147 | b253c438 | Georgios D. Tsoukalas | basename = name_path.pop() |
148 | b253c438 | Georgios D. Tsoukalas | lastnode = node_path.pop() |
149 | b253c438 | Georgios D. Tsoukalas | del lastnode[basename]
|
150 | b253c438 | Georgios D. Tsoukalas | |
151 | b253c438 | Georgios D. Tsoukalas | |
152 | b253c438 | Georgios D. Tsoukalas | def get_path(container, path, sep='.'): |
153 | b253c438 | Georgios D. Tsoukalas | """
|
154 | b253c438 | Georgios D. Tsoukalas | return container['a']['b']['c'] where path=sep.join(['a','b','c'])
|
155 | b253c438 | Georgios D. Tsoukalas |
|
156 | b253c438 | Georgios D. Tsoukalas | >>> get_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d')
|
157 | b253c438 | Georgios D. Tsoukalas | Traceback (most recent call last):
|
158 | b253c438 | Georgios D. Tsoukalas | ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
|
159 | b253c438 | Georgios D. Tsoukalas | string indices must be integers, not str
|
160 | b253c438 | Georgios D. Tsoukalas | >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c.d')
|
161 | b253c438 | Georgios D. Tsoukalas | Traceback (most recent call last):
|
162 | b253c438 | Georgios D. Tsoukalas | ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
|
163 | b253c438 | Georgios D. Tsoukalas | 'int' object is unsubscriptable
|
164 | b253c438 | Georgios D. Tsoukalas | >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c')
|
165 | b253c438 | Georgios D. Tsoukalas | 1
|
166 | b253c438 | Georgios D. Tsoukalas | >>> get_path({'a': {'b': {'c': 1}}}, 'a.b')
|
167 | b253c438 | Georgios D. Tsoukalas | {'c': 1}
|
168 | 1c1c2b9e | Georgios D. Tsoukalas | >>> get_path({'a': [{'z': 1}]}, 'a.0')
|
169 | 1c1c2b9e | Georgios D. Tsoukalas | {'z': 1}
|
170 | 1c1c2b9e | Georgios D. Tsoukalas | >>> get_path({'a': [{'z': 1}]}, 'a.0.z')
|
171 | 1c1c2b9e | Georgios D. Tsoukalas | 1
|
172 | 1c1c2b9e | Georgios D. Tsoukalas | >>> get_path({'a': [{'z': 1}]}, 'a.-1.z')
|
173 | 1c1c2b9e | Georgios D. Tsoukalas | 1
|
174 | b253c438 | Georgios D. Tsoukalas |
|
175 | b253c438 | Georgios D. Tsoukalas | """
|
176 | b253c438 | Georgios D. Tsoukalas | name_path, node_path, basename = \ |
177 | b253c438 | Georgios D. Tsoukalas | lookup_path(container, path, sep=sep, createpath=False)
|
178 | b253c438 | Georgios D. Tsoukalas | name_path.append(basename) |
179 | b253c438 | Georgios D. Tsoukalas | node = node_path[-1]
|
180 | b253c438 | Georgios D. Tsoukalas | |
181 | b253c438 | Georgios D. Tsoukalas | try:
|
182 | b253c438 | Georgios D. Tsoukalas | return node[basename]
|
183 | b253c438 | Georgios D. Tsoukalas | except TypeError as e: |
184 | b253c438 | Georgios D. Tsoukalas | m = "'{0}': cannot traverse path beyond this node: {1}"
|
185 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path), str(e))
|
186 | b253c438 | Georgios D. Tsoukalas | raise ValueError(m) |
187 | b253c438 | Georgios D. Tsoukalas | except KeyError as e: |
188 | b253c438 | Georgios D. Tsoukalas | m = "'{0}': path not found: {1}"
|
189 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path), str(e))
|
190 | b253c438 | Georgios D. Tsoukalas | raise KeyError(m) |
191 | b253c438 | Georgios D. Tsoukalas | |
192 | b253c438 | Georgios D. Tsoukalas | |
193 | b253c438 | Georgios D. Tsoukalas | def set_path(container, path, value, sep='.', |
194 | b253c438 | Georgios D. Tsoukalas | createpath=False, overwrite=True): |
195 | b253c438 | Georgios D. Tsoukalas | """
|
196 | b253c438 | Georgios D. Tsoukalas | container['a']['b']['c'] = value where path=sep.join(['a','b','c'])
|
197 | b253c438 | Georgios D. Tsoukalas |
|
198 | b253c438 | Georgios D. Tsoukalas | >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d', 1)
|
199 | b253c438 | Georgios D. Tsoukalas | Traceback (most recent call last):
|
200 | 1c1c2b9e | Georgios D. Tsoukalas | ValueError: 'a.b.c.d': cannot index non-object node with string
|
201 | b253c438 | Georgios D. Tsoukalas | >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1)
|
202 | b253c438 | Georgios D. Tsoukalas | Traceback (most recent call last):
|
203 | b253c438 | Georgios D. Tsoukalas | KeyError: "'a.b.x': path not found"
|
204 | b253c438 | Georgios D. Tsoukalas | >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1, createpath=True)
|
205 | b253c438 | Georgios D. Tsoukalas |
|
206 | b253c438 | Georgios D. Tsoukalas | >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1)
|
207 | b253c438 | Georgios D. Tsoukalas |
|
208 | b253c438 | Georgios D. Tsoukalas | >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1, overwrite=False)
|
209 | b253c438 | Georgios D. Tsoukalas | Traceback (most recent call last):
|
210 | b253c438 | Georgios D. Tsoukalas | ValueError: will not overwrite path 'a.b.c'
|
211 | 1c1c2b9e | Georgios D. Tsoukalas | >>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.1', 2, createpath=False)
|
212 | 1c1c2b9e | Georgios D. Tsoukalas | Traceback (most recent call last):
|
213 | 1c1c2b9e | Georgios D. Tsoukalas | KeyError: "'a.-2': path not found: list index out of range"
|
214 | 1c1c2b9e | Georgios D. Tsoukalas | >>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.1', 2, createpath=True)
|
215 | 1c1c2b9e | Georgios D. Tsoukalas | Traceback (most recent call last):
|
216 | 1c1c2b9e | Georgios D. Tsoukalas | ValueError: 'a.-2.1': will not index object node with integer
|
217 | 1c1c2b9e | Georgios D. Tsoukalas | >>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.z', 2, createpath=True); \
|
218 | 1c1c2b9e | Georgios D. Tsoukalas | d['a'][-2]['z']
|
219 | 1c1c2b9e | Georgios D. Tsoukalas | 2
|
220 | b253c438 | Georgios D. Tsoukalas |
|
221 | b253c438 | Georgios D. Tsoukalas | """
|
222 | b253c438 | Georgios D. Tsoukalas | name_path, node_path, basename = \ |
223 | b253c438 | Georgios D. Tsoukalas | lookup_path(container, path, sep=sep, createpath=createpath) |
224 | b253c438 | Georgios D. Tsoukalas | name_path.append(basename) |
225 | b253c438 | Georgios D. Tsoukalas | node = node_path[-1]
|
226 | b253c438 | Georgios D. Tsoukalas | |
227 | b253c438 | Georgios D. Tsoukalas | if basename in node and not overwrite: |
228 | b253c438 | Georgios D. Tsoukalas | m = "will not overwrite path '{0}'".format(path)
|
229 | b253c438 | Georgios D. Tsoukalas | raise ValueError(m) |
230 | b253c438 | Georgios D. Tsoukalas | |
231 | 1c1c2b9e | Georgios D. Tsoukalas | is_object_node = hasattr(node, 'keys') |
232 | 1c1c2b9e | Georgios D. Tsoukalas | is_string_name = isinstance(basename, basestring) |
233 | 1c1c2b9e | Georgios D. Tsoukalas | if not is_string_name and is_object_node: |
234 | 1c1c2b9e | Georgios D. Tsoukalas | m = "'{0}': will not index object node with integer"
|
235 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path)) |
236 | 1c1c2b9e | Georgios D. Tsoukalas | raise ValueError(m) |
237 | 1c1c2b9e | Georgios D. Tsoukalas | if is_string_name and not is_object_node: |
238 | 1c1c2b9e | Georgios D. Tsoukalas | m = "'{0}': cannot index non-object node with string"
|
239 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path)) |
240 | 1c1c2b9e | Georgios D. Tsoukalas | raise ValueError(m) |
241 | b253c438 | Georgios D. Tsoukalas | try:
|
242 | b253c438 | Georgios D. Tsoukalas | node[basename] = value |
243 | b253c438 | Georgios D. Tsoukalas | except TypeError as e: |
244 | b253c438 | Georgios D. Tsoukalas | m = "'{0}': cannot traverse path beyond this node: {1}"
|
245 | 1c1c2b9e | Georgios D. Tsoukalas | m = m.format(join_path(sep, name_path), str(e))
|
246 | b253c438 | Georgios D. Tsoukalas | raise ValueError(m) |
247 | b253c438 | Georgios D. Tsoukalas | |
248 | b253c438 | Georgios D. Tsoukalas | |
249 | b253c438 | Georgios D. Tsoukalas | if __name__ == '__main__': |
250 | b253c438 | Georgios D. Tsoukalas | import doctest |
251 | b253c438 | Georgios D. Tsoukalas | doctest.testmod() |