Statistics
| Branch: | Tag: | Revision:

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()