Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/FormEncode/formencode/variabledecode.py
0001"""
0002Takes GET/POST variable dictionary, as might be returned by ``cgi``,
0003and turns them into lists and dictionaries.
0004
0005Keys (variable names) can have subkeys, with a ``.`` and
0006can be numbered with ``-``, like ``a.b-3=something`` means that
0007the value ``a`` is a dictionary with a key ``b``, and ``b``
0008is a list, the third(-ish) element with the value ``something``.
0009Numbers are used to sort, missing numbers are ignored.
0010
0011This doesn't deal with multiple keys, like in a query string of
0012``id=10&id=20``, which returns something like ``{'id': ['10',
0013'20']}``.  That's left to someplace else to interpret.  If you want to
0014represent lists in this model, you use indexes, and the lists are
0015explicitly ordered.
0016
0017If you want to change the character that determines when to split for
0018a dict or list, both variable_decode and variable_encode take dict_char
0019and list_char keyword args. For example, to have the GET/POST variables,
0020``a_1=something`` as a list, you would use a list_char='_'.
0021"""
0022
0023import api
0024
0025__all__ = ['variable_decode', 'variable_encode', 'NestedVariables']
0026
0027def variable_decode(d, dict_char='.', list_char='-'):
0028    """
0029    Decodes the flat dictionary d into a nested structure.
0030    """
0031    result = {}
0032    dicts_to_sort = {}
0033    known_lengths = {}
0034    for key, value in d.items():
0035        keys = key.split(dict_char)
0036        new_keys = []
0037        was_repetition_count = False
0038        for key in keys:
0039            if key.endswith('--repetitions'):
0040                key = key[:-len('--repetitions')]
0041                new_keys.append(key)
0042                known_lengths[tuple(new_keys)] = int(value)
0043                was_repetition_count = True
0044                break
0045            elif list_char in key:
0046                key, index = key.split(list_char)
0047                new_keys.append(key)
0048                dicts_to_sort[tuple(new_keys)] = 1
0049                new_keys.append(int(index))
0050            else:
0051                new_keys.append(key)
0052        if was_repetition_count:
0053            continue
0054
0055        place = result
0056        for i in range(len(new_keys)-1):
0057            try:
0058                if not isinstance(place[new_keys[i]], dict):
0059                    place[new_keys[i]] = {None: place[new_keys[i]]}
0060                place = place[new_keys[i]]
0061            except KeyError:
0062                place[new_keys[i]] = {}
0063                place = place[new_keys[i]]
0064        if place.has_key(new_keys[-1]):
0065            if isinstance(place[new_keys[-1]], dict):
0066                place[new_keys[-1]][None] = value
0067            elif isinstance(place[new_keys[-1]], list):
0068                if isinstance(value, list):
0069                    place[new_keys[-1]].extend(value)
0070                else:
0071                    place[new_keys[-1]].append(value)
0072            else:
0073                if isinstance(value, list):
0074                    place[new_keys[-1]] = [place[new_keys[-1]]]
0075                    place[new_keys[-1]].extend(value)
0076                else:
0077                    place[new_keys[-1]] = [place[new_keys[-1]], value]
0078        else:
0079            place[new_keys[-1]] = value
0080
0081    to_sort_keys = dicts_to_sort.keys()
0082    to_sort_keys.sort(lambda a, b: -cmp(len(a), len(b)))
0083    for key in to_sort_keys:
0084        to_sort = result
0085        source = None
0086        last_key = None
0087        for sub_key in key:
0088            source = to_sort
0089            last_key = sub_key
0090            to_sort = to_sort[sub_key]
0091        if to_sort.has_key(None):
0092            noneVals = [(0, x) for x in to_sort[None]]
0093            del to_sort[None]
0094            noneVals.extend(to_sort.items())
0095            to_sort = noneVals
0096        else:
0097            to_sort = to_sort.items()
0098        to_sort.sort()
0099        to_sort = [v for k, v in to_sort]
0100        if known_lengths.has_key(key):
0101            if len(to_sort) < known_lengths[key]:
0102                to_sort.extend(['']*(known_lengths[key] - len(to_sort)))
0103        source[last_key] = to_sort
0104
0105    return result
0106
0107def variable_encode(d, prepend='', result=None, add_repetitions=True,
0108                    dict_char='.', list_char='-'):
0109    """
0110    Encodes a nested structure into a flat dictionary.
0111    """
0112    if result is None:
0113        result = {}
0114    if isinstance(d, dict):
0115        for key, value in d.items():
0116            if key is None:
0117                name = prepend
0118            elif not prepend:
0119                name = key
0120            else:
0121                name = "%s%s%s" % (prepend, dict_char, key)
0122            variable_encode(value, name, result, add_repetitions,
0123                            dict_char=dict_char, list_char=list_char)
0124    elif isinstance(d, list):
0125        for i in range(len(d)):
0126            variable_encode(d[i], "%s%s%i" % (prepend, list_char, i), result,
0127                            add_repetitions, dict_char=dict_char, list_char=list_char)
0128        if add_repetitions:
0129            if prepend:
0130                repName = '%s--repetitions' % prepend
0131            else:
0132                repName = '__repetitions__'
0133            result[repName] = str(len(d))
0134    else:
0135        result[prepend] = d
0136    return result
0137
0138class NestedVariables(api.FancyValidator):
0139
0140    def _to_python(self, value, state):
0141        return variable_decode(value)
0142
0143    def _from_python(self, value, state):
0144        return variable_encode(value)

Top