commit d2cfb7a17411337c81100826206c5207c955ff89
Author: Giorgos Korfiatis <gkorf@grnet.gr>
Date:   Wed Oct 9 13:33:48 2013 +0300

    common: Do not encode key paths as string
    
    Handle explicitly key paths as lists of keys. This allows the key
    to be any python object, as is the case in python dicts.
    
    Provide utility unpack() to convert from a string representation to
    a list of keys. It assumes that a key is either a string or an integer
    (if it looks like one).
    
    Conflicts:
    	snf-cyclades-app/synnefo/cyclades_settings.py
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

diff --git a/snf-common/synnefo/util/keypath.py b/snf-common/synnefo/util/keypath.py
index 02b8939..fc4671e 100644
--- a/snf-common/synnefo/util/keypath.py
+++ b/snf-common/synnefo/util/keypath.py
@@ -36,52 +36,49 @@ import re
 integer_re = re.compile('-?[0-9]+')
 
 
-def join_path(sep, path):
-    iterable = ((str(n) if isinstance(n, (int, long)) else n) for n in path)
-    return sep.join(iterable)
+def unpack(pathstr, sep='.'):
+    """
+    >>> unpack('a.-2.x')
+    ['a', -2, 'x']
+    """
+    names = pathstr.split(sep)
+    parse = lambda x: int(x) if integer_re.match(x) else x
+    return [parse(x) for x in names]
 
 
-def lookup_path(container, path, sep='.', createpath=False):
+def lookup_path(container, path, createpath=False):
     """
     return (['a','b'],
             [container['a'], container['a']['b']],
-            'c')  where path=sep.join(['a','b','c'])
+            'c')  where path=['a','b','c']
 
     """
-    names = path.split(sep)
-    dirnames = names[:-1]
-    basename = names[-1]
-    if integer_re.match(basename):
-        basename = int(basename)
+    dirnames = path[:-1]
+    basename = path[-1]
 
     node = container
     name_path = []
     node_path = [node]
     for name in dirnames:
         name_path.append(name)
-
-        if integer_re.match(name):
-            name = int(name)
-
         try:
             node = node[name]
         except KeyError as e:
             if not createpath:
-                m = "'{0}': path not found".format(join_path(sep, name_path))
+                m = "{0}: path not found".format(name_path)
                 raise KeyError(m)
             node[name] = {}
             node = node[name]
         except IndexError as e:
             if not createpath:
-                m = "'{0}': path not found: {1}".format(
-                    join_path(sep, name_path), e)
+                m = "{0}: path not found: {1}".format(name_path, e)
                 raise KeyError(m)
             size = name if name > 0 else -name
             node += (dict() for _ in xrange(len(node), size))
             node = node[name]
         except TypeError as e:
-            m = "'{0}': cannot traverse path beyond this node: {1}"
-            m = m.format(join_path(sep, name_path), str(e))
+            m = "{0}: cannot traverse path beyond this node: {1}"
+            m = m.format(name_path, str(e))
             raise ValueError(m)
         node_path.append(node)
 
@@ -97,49 +94,49 @@ def walk_paths(container):
                 yield [name] + names, [node] + nodes
 
 
-def list_paths(container, sep='.'):
+def list_paths(container):
     """
     >>> sorted(list_paths({'a': {'b': {'c': 'd'}}}))
-    [('a.b.c', 'd')]
+    [(['a', 'b', 'c'], 'd')]
     >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': 3}}))
-    [('a.b.c', 'd'), ('a.e', 3)]
+    [(['a', 'b', 'c'], 'd'), (['a', 'e'], 3)]
     >>> sorted(list_paths({'a': {'b': {'c': 'd'}, 'e': {'f': 3}}}))
-    [('a.b.c', 'd'), ('a.e.f', 3)]
+    [(['a', 'b', 'c'], 'd'), (['a', 'e', 'f'], 3)]
     >>> sorted(list_paths({'a': [{'b': 3}, 2]}))
-    [('a', [{'b': 3}, 2])]
+    [(['a'], [{'b': 3}, 2])]
     >>> list_paths({})
     []
 
     """
-    return [(join_path(sep, name_path), node_path[-1])
+    return [(name_path, node_path[-1])
             for name_path, node_path in walk_paths(container)]
 
 
-def del_path(container, path, sep='.', collect=True):
+def del_path(container, path, collect=True):
     """
-    del container['a']['b']['c'] where path=sep.join(['a','b','c'])
+    del container['a']['b']['c'] where path=['a','b','c']
 
-    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c'); d
+    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, ['a', 'b', 'c']); d
     {}
-    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c', collect=False); d
+    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, ['a', 'b', 'c'],\
+ collect=False); d
     {'a': {'b': {}}}
-    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, 'a.b.c.d')
+    >>> d = {'a': {'b': {'c': 'd'}}}; del_path(d, ['a', 'b', 'c', 'd'])
     Traceback (most recent call last):
-    ValueError: 'a.b.c': cannot traverse path beyond this node:\
+    ValueError: ['a', 'b', 'c']: cannot traverse path beyond this node:\
  'str' object does not support item deletion
     """
 
     name_path, node_path, basename = \
-            lookup_path(container, path, sep=sep, createpath=False)
+        lookup_path(container, path, createpath=False)
 
     lastnode = node_path.pop()
-    lastname = basename
     try:
         if basename in lastnode:
             del lastnode[basename]
     except (TypeError, KeyError) as e:
-        m = "'{0}': cannot traverse path beyond this node: {1}"
-        m = m.format(join_path(sep, name_path), str(e))
+        m = "{0}: cannot traverse path beyond this node: {1}"
+        m = m.format(name_path, str(e))
         raise ValueError(m)
 
     if collect:
@@ -149,100 +146,111 @@ def del_path(container, path, sep='.', collect=True):
             del lastnode[basename]
 
 
-def get_path(container, path, sep='.'):
+def get_path(container, path):
     """
-    return container['a']['b']['c'] where path=sep.join(['a','b','c'])
+    return container['a']['b']['c'] where path=['a','b','c']
 
-    >>> get_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d')
+    >>> get_path({'a': {'b': {'c': 'd'}}}, ['a', 'b', 'c', 'd'])
     Traceback (most recent call last):
-    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
+    ValueError: ['a', 'b', 'c', 'd']: cannot traverse path beyond this node:\
  string indices must be integers, not str
-    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c.d')
+    >>> get_path({'a': {'b': {'c': 1}}}, ['a', 'b', 'c', 'd'])
     Traceback (most recent call last):
-    ValueError: 'a.b.c.d': cannot traverse path beyond this node:\
- 'int' object is unsubscriptable
-    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b.c')
+    ValueError: ['a', 'b', 'c', 'd']: cannot traverse path beyond this node:\
+ 'int' object has no attribute '__getitem__'
+    >>> get_path({'a': {'b': {'c': 1}}}, ['a', 'b', 'c'])
     1
-    >>> get_path({'a': {'b': {'c': 1}}}, 'a.b')
+    >>> get_path({'a': {'b': {'c': 1}}}, ['a', 'b'])
     {'c': 1}
-    >>> get_path({'a': [{'z': 1}]}, 'a.0')
+    >>> get_path({'a': [{'z': 1}]}, ['a', 'b'])
+    Traceback (most recent call last):
+    ValueError: ['a', 'b']: cannot traverse path beyond this node:\
+ list indices must be integers, not str
+    >>> get_path({'a': [{'z': 1}]}, ['a', 0])
     {'z': 1}
-    >>> get_path({'a': [{'z': 1}]}, 'a.0.z')
+    >>> get_path({'a': [{'z': 1}]}, ['a', 1])
+    Traceback (most recent call last):
+    KeyError: "['a', 1]: path not found: list index out of range"
+    >>> get_path({'a': [{'z': 1}]}, ['a', 0, 'z'])
     1
-    >>> get_path({'a': [{'z': 1}]}, 'a.-1.z')
+    >>> get_path({'a': [{'z': 1}]}, ['a', -1, 'z'])
     1
 
     """
     name_path, node_path, basename = \
-            lookup_path(container, path, sep=sep, createpath=False)
+        lookup_path(container, path, createpath=False)
     name_path.append(basename)
     node = node_path[-1]
 
     try:
         return node[basename]
     except TypeError as e:
-        m = "'{0}': cannot traverse path beyond this node: {1}"
-        m = m.format(join_path(sep, name_path), str(e))
+        m = "{0}: cannot traverse path beyond this node: {1}"
+        m = m.format(name_path, str(e))
         raise ValueError(m)
     except KeyError as e:
-        m = "'{0}': path not found: {1}"
-        m = m.format(join_path(sep, name_path), str(e))
+        m = "{0}: path not found: {1}"
+        m = m.format(name_path, str(e))
+        raise KeyError(m)
+    except IndexError as e:
+        m = "{0}: path not found: {1}"
+        m = m.format(name_path, str(e))
         raise KeyError(m)
 
 
-def set_path(container, path, value, sep='.',
-             createpath=False, overwrite=True):
+def set_path(container, path, value, createpath=False, overwrite=True):
     """
-    container['a']['b']['c'] = value where path=sep.join(['a','b','c'])
+    container['a']['b']['c'] = value where path=['a','b','c']
 
-    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c.d', 1)
-    Traceback (most recent call last):
-    ValueError: 'a.b.c.d': cannot index non-object node with string
-    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1)
+    >>> set_path({'a': {'b': {'c': 'd'}}}, ['a', 'b', 'c', 'd'], 1)
     Traceback (most recent call last):
-    KeyError: "'a.b.x': path not found"
-    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.x.d', 1, createpath=True)
-    
-    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1)
-    
-    >>> set_path({'a': {'b': {'c': 'd'}}}, 'a.b.c', 1, overwrite=False)
+    ValueError: ['a', 'b', 'c', 'd']: cannot index, node is neither dict nor\
+ list
+    >>> set_path({'a': {'b': {'c': 'd'}}}, ['a', 'b', 'x', 'd'], 1)
     Traceback (most recent call last):
-    ValueError: will not overwrite path 'a.b.c'
-    >>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.1', 2, createpath=False)
+    KeyError: "['a', 'b', 'x']: path not found"
+    >>> set_path({'a': {'b': {'c': 'd'}}}, ['a', 'b', 'x', 'd'], 1,\
+ createpath=True)
+
+    >>> set_path({'a': {'b': {'c': 'd'}}}, ['a', 'b', 'c'], 1)
+
+    >>> set_path({'a': {'b': {'c': 'd'}}}, ['a', 'b', 'c'], 1, overwrite=False)
     Traceback (most recent call last):
-    KeyError: "'a.-2': path not found: list index out of range"
-    >>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.1', 2, createpath=True)
+    ValueError: will not overwrite path ['a', 'b', 'c']
+    >>> d = {'a': [{'z': 1}]}; set_path(d, ['a', -2, 1], 2, createpath=False)
     Traceback (most recent call last):
-    ValueError: 'a.-2.1': will not index object node with integer
-    >>> d = {'a': [{'z': 1}]}; set_path(d, 'a.-2.z', 2, createpath=True); \
- d['a'][-2]['z']
+    KeyError: "['a', -2]: path not found: list index out of range"
+    >>> d = {'a': [{'z': 1}]}; set_path(d, ['a', -2, 1], 2, createpath=True); \
+ d['a'][-2][1]
     2
 
     """
     name_path, node_path, basename = \
-            lookup_path(container, path, sep=sep, createpath=createpath)
+        lookup_path(container, path, createpath=createpath)
     name_path.append(basename)
     node = node_path[-1]
 
     if basename in node and not overwrite:
-        m = "will not overwrite path '{0}'".format(path)
+        m = "will not overwrite path {0}".format(path)
         raise ValueError(m)
 
     is_object_node = hasattr(node, 'keys')
-    is_string_name = isinstance(basename, basestring)
-    if not is_string_name and is_object_node:
-        m = "'{0}': will not index object node with integer"
-        m = m.format(join_path(sep, name_path))
+    is_list_node = isinstance(node, list)
+    if not is_object_node and not is_list_node:
+        m = "{0}: cannot index, node is neither dict nor list"
+        m = m.format(name_path)
         raise ValueError(m)
-    if is_string_name and not is_object_node:
-        m = "'{0}': cannot index non-object node with string"
-        m = m.format(join_path(sep, name_path))
+
+    is_integer = isinstance(basename, (int, long))
+    if is_list_node and not is_integer:
+        m = "{0}: cannot index list node without an integer"
+        m = m.format(name_path)
         raise ValueError(m)
     try:
         node[basename] = value
     except TypeError as e:
-        m = "'{0}': cannot traverse path beyond this node: {1}"
-        m = m.format(join_path(sep, name_path), str(e))
+        m = "{0}: cannot traverse path beyond this node: {1}"
+        m = m.format(name_path, str(e))
         raise ValueError(m)
 
 
