formencode.variabledecode
Covered: 66 lines
Missed: 67 lines
Skipped 15 lines
Percent: 49 %
  1
"""
  2
Takes GET/POST variable dictionary, as might be returned by ``cgi``,
  3
and turns them into lists and dictionaries.
  5
Keys (variable names) can have subkeys, with a ``.`` and
  6
can be numbered with ``-``, like ``a.b-3=something`` means that
  7
the value ``a`` is a dictionary with a key ``b``, and ``b``
  8
is a list, the third(-ish) element with the value ``something``.
  9
Numbers are used to sort, missing numbers are ignored.
 11
This doesn't deal with multiple keys, like in a query string of
 12
``id=10&id=20``, which returns something like ``{'id': ['10',
 13
'20']}``.  That's left to someplace else to interpret.  If you want to
 14
represent lists in this model, you use indexes, and the lists are
 15
explicitly ordered.
 17
If you want to change the character that determines when to split for
 18
a dict or list, both variable_decode and variable_encode take dict_char
 19
and list_char keyword args. For example, to have the GET/POST variables,
 20
``a_1=something`` as a list, you would use a list_char='_'.
 21
"""
 23
import api
 25
__all__ = ['variable_decode', 'variable_encode', 'NestedVariables']
 27
def variable_decode(d, dict_char='.', list_char='-'):
 28
    """
 29
    Decodes the flat dictionary d into a nested structure.
 30
    """
 31
    result = {}
 32
    dicts_to_sort = {}
 33
    known_lengths = {}
 34
    for key, value in d.items():
 35
        keys = key.split(dict_char)
 36
        new_keys = []
 37
        was_repetition_count = False
 38
        for key in keys:
 39
            if key.endswith('--repetitions'):
 40
                key = key[:-len('--repetitions')]
 41
                new_keys.append(key)
 42
                known_lengths[tuple(new_keys)] = int(value)
 43
                was_repetition_count = True
 44
                break
 45
            elif list_char in key:
 46
                key, index = key.split(list_char)
 47
                new_keys.append(key)
 48
                dicts_to_sort[tuple(new_keys)] = 1
 49
                new_keys.append(int(index))
 50
            else:
 51
                new_keys.append(key)
 52
        if was_repetition_count:
 53
            continue
 55
        place = result
 56
        for i in range(len(new_keys)-1):
 57
            try:
 58
                if not isinstance(place[new_keys[i]], dict):
 59
                    place[new_keys[i]] = {None: place[new_keys[i]]}
 60
                place = place[new_keys[i]]
 61
            except KeyError:
 62
                place[new_keys[i]] = {}
 63
                place = place[new_keys[i]]
 64
        if place.has_key(new_keys[-1]):
 65
            if isinstance(place[new_keys[-1]], dict):
 66
                place[new_keys[-1]][None] = value
 67
            elif isinstance(place[new_keys[-1]], list):
 68
                if isinstance(value, list):
 69
                    place[new_keys[-1]].extend(value)
 70
                else:
 71
                    place[new_keys[-1]].append(value)
 72
            else:
 73
                if isinstance(value, list):
 74
                    place[new_keys[-1]] = [place[new_keys[-1]]]
 75
                    place[new_keys[-1]].extend(value)
 76
                else:
 77
                    place[new_keys[-1]] = [place[new_keys[-1]], value]
 78
        else:
 79
            place[new_keys[-1]] = value
 81
    to_sort_keys = dicts_to_sort.keys()
 82
    to_sort_keys.sort(lambda a, b: -cmp(len(a), len(b)))
 83
    for key in to_sort_keys:
 84
        to_sort = result
 85
        source = None
 86
        last_key = None
 87
        for sub_key in key:
 88
            source = to_sort
 89
            last_key = sub_key
 90
            to_sort = to_sort[sub_key]
 91
        if to_sort.has_key(None):
 92
            noneVals = [(0, x) for x in to_sort[None]]
 93
            del to_sort[None]
 94
            noneVals.extend(to_sort.items())
 95
            to_sort = noneVals
 96
        else:
 97
            to_sort = to_sort.items()
 98
        to_sort.sort()
 99
        to_sort = [v for k, v in to_sort]
100
        if known_lengths.has_key(key):
101
            if len(to_sort) < known_lengths[key]:
102
                to_sort.extend(['']*(known_lengths[key] - len(to_sort)))
103
        source[last_key] = to_sort
105
    return result
107
def variable_encode(d, prepend='', result=None, add_repetitions=True,
108
                    dict_char='.', list_char='-'):
109
    """
110
    Encodes a nested structure into a flat dictionary.
111
    """
112
    if result is None:
113
        result = {}
114
    if isinstance(d, dict):
115
        for key, value in d.items():
116
            if key is None:
117
                name = prepend
118
            elif not prepend:
119
                name = key
120
            else:
121
                name = "%s%s%s" % (prepend, dict_char, key)
122
            variable_encode(value, name, result, add_repetitions,
123
                            dict_char=dict_char, list_char=list_char)
124
    elif isinstance(d, list):
125
        for i in range(len(d)):
126
            variable_encode(d[i], "%s%s%i" % (prepend, list_char, i), result,
127
                            add_repetitions, dict_char=dict_char, list_char=list_char)
128
        if add_repetitions:
129
            if prepend:
130
                repName = '%s--repetitions' % prepend
131
            else:
132
                repName = '__repetitions__'
133
            result[repName] = str(len(d))
134
    else:
135
        result[prepend] = d
136
    return result
138
class NestedVariables(api.FancyValidator):
140
    def _to_python(self, value, state):
141
        return variable_decode(value)
143
    def _from_python(self, value, state):
144
        return variable_encode(value)
146
    def empty_value(self, value):
147
        return {}