Statistics
| Branch: | Tag: | Revision:

root / snf-common / synnefo / util / keypath.py @ b253c438

History | View | Annotate | Download (6.7 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34

    
35
def lookup_path(container, path, sep='.', createpath=False):
36
    """
37
    return (['a','b'],
38
            [container['a'], container['a']['b']],
39
            'c')  where path=sep.join(['a','b','c'])
40

41
    """
42
    names = path.split(sep)
43
    dirnames = names[:-1]
44
    basename = names[-1]
45

    
46
    node = container
47
    name_path = []
48
    node_path = [node]
49
    for name in dirnames:
50
        name_path.append(name)
51
        if name not in node:
52
            if not createpath:
53
                m = "'{0}': path not found".format(sep.join(name_path))
54
                raise KeyError(m)
55
            node[name] = {}
56
        try:
57
            node = node[name]
58
        except TypeError as e:
59
            m = "'{0}': cannot traverse path beyond this node: {1}"
60
            m = m.format(sep.join(name_path), str(e))
61
            raise ValueError(m)
62
        node_path.append(node)
63

    
64
    return name_path, node_path, basename
65

    
66

    
67
def walk_paths(container):
68
    for name, node in container.iteritems():
69
        if not hasattr(node, 'items'):
70
            yield [name], [node]
71
        else:
72
            for names, nodes in walk_paths(node):
73
                yield [name] + names, [node] + nodes
74

    
75

    
76
def list_paths(container, sep='.'):
77
    """
78
    >>> sorted(list_paths({'a': {'b': {'c': 'd'}}}))
79
    [('a.b.c', 'd')]
80
    >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': 3}}))
81
    [('a.b.c', 'd'), ('a.e', 3)]
82
    >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': {'f': 3}}}))
83
    [('a.b.c', 'd'), ('a.e.f', 3)]
84
    >>> list_paths({})
85
    []
86

87
    """
88
    return [(sep.join(name_path), node_path[-1])
89
            for name_path, node_path in walk_paths(container)]
90

    
91

    
92
def del_path(container, path, sep='.', collect=True):
93
    """
94
    del container['a']['b']['c'] where path=sep.join(['a','b','c'])
95

96
    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c'); d
97
    {}
98
    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c', collect=False); d
99
    {'a': {'b': {}}}
100
    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c.d')
101
    Traceback (most recent call last):
102
    ValueError: 'a.b.c': cannot traverse path beyond this node:\
103
 'str' object does not support item deletion
104
    """
105

    
106
    name_path, node_path, basename = \
107
            lookup_path(container, path, sep=sep, createpath=False)
108

    
109
    lastnode = node_path.pop()
110
    lastname = basename
111
    try:
112
        if basename in lastnode:
113
            del lastnode[basename]
114
    except (TypeError, KeyError) as e:
115
        m = "'{0}': cannot traverse path beyond this node: {1}"
116
        m = m.format(sep.join(name_path), str(e))
117
        raise ValueError(m)
118

    
119
    if collect:
120
        while node_path and not lastnode:
121
            basename = name_path.pop()
122
            lastnode = node_path.pop()
123
            del lastnode[basename]
124

    
125

    
126
def get_path(container, path, sep='.'):
127
    """
128
    return container['a']['b']['c'] where path=sep.join(['a','b','c'])
129

130
    >>> get_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d')
131
    Traceback (most recent call last):
132
    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
133
 string indices must be integers, not str
134
    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c.d')
135
    Traceback (most recent call last):
136
    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
137
 'int' object is unsubscriptable
138
    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c')
139
    1
140
    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b')
141
    {'c': 1}
142

143
    """
144
    name_path, node_path, basename = \
145
            lookup_path(container, path, sep=sep, createpath=False)
146
    name_path.append(basename)
147
    node = node_path[-1]
148

    
149
    try:
150
        return node[basename]
151
    except TypeError as e:
152
        m = "'{0}': cannot traverse path beyond this node: {1}"
153
        m = m.format(sep.join(name_path), str(e))
154
        raise ValueError(m)
155
    except KeyError as e:
156
        m = "'{0}': path not found: {1}"
157
        m = m.format(sep.join(name_path), str(e))
158
        raise KeyError(m)
159

    
160

    
161
def set_path(container, path, value, sep='.',
162
             createpath=False, overwrite=True):
163
    """
164
    container['a']['b']['c'] = value where path=sep.join(['a','b','c'])
165

166
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d', 1)
167
    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
170
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1)
171
    Traceback (most recent call last):
172
    KeyError: "'a.b.x': path not found"
173
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1, createpath=True)
174
    
175
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1)
176
    
177
    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1, overwrite=False)
178
    Traceback (most recent call last):
179
    ValueError: will not overwrite path 'a.b.c'
180

181
    """
182
    name_path, node_path, basename = \
183
            lookup_path(container, path, sep=sep, createpath=createpath)
184
    name_path.append(basename)
185
    node = node_path[-1]
186

    
187
    if basename in node and not overwrite:
188
        m = "will not overwrite path '{0}'".format(path)
189
        raise ValueError(m)
190

    
191
    try:
192
        node[basename] = value
193
    except TypeError as e:
194
        m = "'{0}': cannot traverse path beyond this node: {1}"
195
        m = m.format(sep.join(name_path), str(e))
196
        raise ValueError(m)
197

    
198

    
199
if __name__ == '__main__':
200
    import doctest
201
    doctest.testmod()