Revision 1c1c2b9e snf-common/synnefo/util/keypath.py
b/snf-common/synnefo/util/keypath.py | ||
---|---|---|
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 | 34 |
|
35 |
import re |
|
36 |
integer_re = re.compile('-?[0-9]+') |
|
37 |
|
|
38 |
|
|
39 |
def join_path(sep, path): |
|
40 |
iterable = ((str(n) if isinstance(n, (int, long)) else n) for n in path) |
|
41 |
return sep.join(iterable) |
|
42 |
|
|
43 |
|
|
35 | 44 |
def lookup_path(container, path, sep='.', createpath=False): |
36 | 45 |
""" |
37 | 46 |
return (['a','b'], |
... | ... | |
42 | 51 |
names = path.split(sep) |
43 | 52 |
dirnames = names[:-1] |
44 | 53 |
basename = names[-1] |
54 |
if integer_re.match(basename): |
|
55 |
basename = int(basename) |
|
45 | 56 |
|
46 | 57 |
node = container |
47 | 58 |
name_path = [] |
48 | 59 |
node_path = [node] |
49 | 60 |
for name in dirnames: |
50 | 61 |
name_path.append(name) |
51 |
if name not in node: |
|
62 |
|
|
63 |
if integer_re.match(name): |
|
64 |
name = int(name) |
|
65 |
|
|
66 |
try: |
|
67 |
node = node[name] |
|
68 |
except KeyError as e: |
|
52 | 69 |
if not createpath: |
53 |
m = "'{0}': path not found".format(sep.join(name_path))
|
|
70 |
m = "'{0}': path not found".format(join_path(sep, name_path))
|
|
54 | 71 |
raise KeyError(m) |
55 | 72 |
node[name] = {} |
56 |
try: |
|
73 |
node = node[name] |
|
74 |
except IndexError as e: |
|
75 |
if not createpath: |
|
76 |
m = "'{0}': path not found: {1}".format( |
|
77 |
join_path(sep, name_path), e) |
|
78 |
raise KeyError(m) |
|
79 |
size = name if name > 0 else -name |
|
80 |
node += (dict() for _ in xrange(len(node), size)) |
|
57 | 81 |
node = node[name] |
58 | 82 |
except TypeError as e: |
59 | 83 |
m = "'{0}': cannot traverse path beyond this node: {1}" |
60 |
m = m.format(sep.join(name_path), str(e))
|
|
84 |
m = m.format(join_path(sep, name_path), str(e))
|
|
61 | 85 |
raise ValueError(m) |
62 | 86 |
node_path.append(node) |
63 | 87 |
|
... | ... | |
81 | 105 |
[('a.b.c', 'd'), ('a.e', 3)] |
82 | 106 |
>>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': {'f': 3}}})) |
83 | 107 |
[('a.b.c', 'd'), ('a.e.f', 3)] |
108 |
>>> sorted(list_paths({'a': [{'b': 3}, 2]})) |
|
109 |
[('a', [{'b': 3}, 2])] |
|
84 | 110 |
>>> list_paths({}) |
85 | 111 |
[] |
86 | 112 |
|
87 | 113 |
""" |
88 |
return [(sep.join(name_path), node_path[-1])
|
|
114 |
return [(join_path(sep, name_path), node_path[-1])
|
|
89 | 115 |
for name_path, node_path in walk_paths(container)] |
90 | 116 |
|
91 | 117 |
|
... | ... | |
113 | 139 |
del lastnode[basename] |
114 | 140 |
except (TypeError, KeyError) as e: |
115 | 141 |
m = "'{0}': cannot traverse path beyond this node: {1}" |
116 |
m = m.format(sep.join(name_path), str(e))
|
|
142 |
m = m.format(join_path(sep, name_path), str(e))
|
|
117 | 143 |
raise ValueError(m) |
118 | 144 |
|
119 | 145 |
if collect: |
... | ... | |
139 | 165 |
1 |
140 | 166 |
>>> get_path({'a': {'b': {'c': 1}}}, 'a.b') |
141 | 167 |
{'c': 1} |
168 |
>>> get_path({'a': [{'z': 1}]}, 'a.0') |
|
169 |
{'z': 1} |
|
170 |
>>> get_path({'a': [{'z': 1}]}, 'a.0.z') |
|
171 |
1 |
|
172 |
>>> get_path({'a': [{'z': 1}]}, 'a.-1.z') |
|
173 |
1 |
|
142 | 174 |
|
143 | 175 |
""" |
144 | 176 |
name_path, node_path, basename = \ |
... | ... | |
150 | 182 |
return node[basename] |
151 | 183 |
except TypeError as e: |
152 | 184 |
m = "'{0}': cannot traverse path beyond this node: {1}" |
153 |
m = m.format(sep.join(name_path), str(e))
|
|
185 |
m = m.format(join_path(sep, name_path), str(e))
|
|
154 | 186 |
raise ValueError(m) |
155 | 187 |
except KeyError as e: |
156 | 188 |
m = "'{0}': path not found: {1}" |
157 |
m = m.format(sep.join(name_path), str(e))
|
|
189 |
m = m.format(join_path(sep, name_path), str(e))
|
|
158 | 190 |
raise KeyError(m) |
159 | 191 |
|
160 | 192 |
|
... | ... | |
165 | 197 |
|
166 | 198 |
>>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d', 1) |
167 | 199 |
Traceback (most recent call last): |
168 |
ValueError: 'a.b.c.d': cannot traverse path beyond this node:\ |
|
169 |
'str' object does not support item assignment |
|
200 |
ValueError: 'a.b.c.d': cannot index non-object node with string |
|
170 | 201 |
>>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1) |
171 | 202 |
Traceback (most recent call last): |
172 | 203 |
KeyError: "'a.b.x': path not found" |
... | ... | |
177 | 208 |
>>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1, overwrite=False) |
178 | 209 |
Traceback (most recent call last): |
179 | 210 |
ValueError: will not overwrite path 'a.b.c' |
211 |
>>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.1', 2, createpath=False) |
|
212 |
Traceback (most recent call last): |
|
213 |
KeyError: "'a.-2': path not found: list index out of range" |
|
214 |
>>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.1', 2, createpath=True) |
|
215 |
Traceback (most recent call last): |
|
216 |
ValueError: 'a.-2.1': will not index object node with integer |
|
217 |
>>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.z', 2, createpath=True); \ |
|
218 |
d['a'][-2]['z'] |
|
219 |
2 |
|
180 | 220 |
|
181 | 221 |
""" |
182 | 222 |
name_path, node_path, basename = \ |
... | ... | |
188 | 228 |
m = "will not overwrite path '{0}'".format(path) |
189 | 229 |
raise ValueError(m) |
190 | 230 |
|
231 |
is_object_node = hasattr(node, 'keys') |
|
232 |
is_string_name = isinstance(basename, basestring) |
|
233 |
if not is_string_name and is_object_node: |
|
234 |
m = "'{0}': will not index object node with integer" |
|
235 |
m = m.format(join_path(sep, name_path)) |
|
236 |
raise ValueError(m) |
|
237 |
if is_string_name and not is_object_node: |
|
238 |
m = "'{0}': cannot index non-object node with string" |
|
239 |
m = m.format(join_path(sep, name_path)) |
|
240 |
raise ValueError(m) |
|
191 | 241 |
try: |
192 | 242 |
node[basename] = value |
193 | 243 |
except TypeError as e: |
194 | 244 |
m = "'{0}': cannot traverse path beyond this node: {1}" |
195 |
m = m.format(sep.join(name_path), str(e))
|
|
245 |
m = m.format(join_path(sep, name_path), str(e))
|
|
196 | 246 |
raise ValueError(m) |
197 | 247 |
|
198 | 248 |
|
Also available in: Unified diff