1import re
2import collections
3try:
4    from urllib import quote_plus
5except ImportError:
6    from urllib.parse import quote_plus
7
8import six
9
10
11def to_params(hash):
12    normalized = [normalize_param(k, v) for (k, v) in six.iteritems(hash)]
13    return dict((k, v) for d in normalized for (k, v) in d.items())
14
15
16def normalize_param(key, value):
17    """Convert a set of key, value parameters into a dictionary suitable for
18    passing into requests. This will convert lists into the syntax required
19    by SoundCloud. Heavily lifted from HTTParty.
20
21    >>> normalize_param('playlist', {
22    ...  'title': 'foo',
23    ...  'sharing': 'private',
24    ...  'tracks': [
25    ...    {id: 1234}, {id: 4567}
26    ...  ]}) == {
27    ...     u'playlist[tracks][][<built-in function id>]': [1234, 4567],
28    ...     u'playlist[sharing]': 'private',
29    ...     u'playlist[title]': 'foo'}  # doctest:+ELLIPSIS
30    True
31
32    >>> normalize_param('oauth_token', 'foo')
33    {'oauth_token': 'foo'}
34
35    >>> normalize_param('playlist[tracks]', [1234, 4567]) == {
36    ...     u'playlist[tracks][]': [1234, 4567]}
37    True
38    """
39    params = {}
40    stack = []
41    if isinstance(value, list):
42        normalized = [normalize_param(u"{0[key]}[]".format(dict(key=key)), e) for e in value]
43        keys = [item for sublist in tuple(h.keys() for h in normalized) for item in sublist]
44
45        lists = {}
46        if len(keys) != len(set(keys)):
47            duplicates = [x for x, y in collections.Counter(keys).items() if y > 1]
48            for dup in duplicates:
49                lists[dup] = [h[dup] for h in normalized]
50                for h in normalized:
51                    del h[dup]
52
53        params.update(dict((k, v) for d in normalized for (k, v) in d.items()))
54        params.update(lists)
55    elif isinstance(value, dict):
56        stack.append([key, value])
57    else:
58        params.update({key: value})
59
60    for (parent, hash) in stack:
61        for (key, value) in six.iteritems(hash):
62            if isinstance(value, dict):
63                stack.append([u"{0[parent]}[{0[key]}]".format(dict(parent=parent, key=key)), value])
64            else:
65                params.update(normalize_param(u"{0[parent]}[{0[key]}]".format(dict(parent=parent, key=key)), value))
66
67    return params
68