1try:
2    from urllib import urlencode
3except ImportError:
4    from urllib.parse import urlencode
5
6import requests
7import six
8
9import soundcloud
10
11from . import hashconversions
12
13
14def is_file_like(f):
15    """Check to see if ```f``` has a ```read()``` method."""
16    return hasattr(f, 'read') and callable(f.read)
17
18
19def extract_files_from_dict(d):
20    """Return any file objects from the provided dict.
21
22    >>> extract_files_from_dict({
23    ... 'oauth_token': 'foo',
24    ... 'track': {
25    ...   'title': 'bar',
26    ...   'asset_data': open('setup.py', 'rb')
27    ...  }})  # doctest:+ELLIPSIS
28    {'track': {'asset_data': <...}}
29    """
30    files = {}
31    for key, value in six.iteritems(d):
32        if isinstance(value, dict):
33            files[key] = extract_files_from_dict(value)
34        elif is_file_like(value):
35            files[key] = value
36    return files
37
38
39def remove_files_from_dict(d):
40    """Return the provided dict with any file objects removed.
41
42    >>> remove_files_from_dict({
43    ...   'oauth_token': 'foo',
44    ...   'track': {
45    ...       'title': 'bar',
46    ...       'asset_data': open('setup.py', 'rb')
47    ...   }
48    ... }) == {'track': {'title': 'bar'}, 'oauth_token': 'foo'}
49    ... # doctest:+ELLIPSIS
50    True
51    """
52    file_free = {}
53    for key, value in six.iteritems(d):
54        if isinstance(value, dict):
55            file_free[key] = remove_files_from_dict(value)
56        elif not is_file_like(value):
57            if hasattr(value, '__iter__'):
58                file_free[key] = value
59            else:
60                if hasattr(value, 'encode'):
61                    file_free[key] = value.encode('utf-8')
62                else:
63                    file_free[key] = str(value)
64    return file_free
65
66
67def namespaced_query_string(d, prefix=""):
68    """Transform a nested dict into a string with namespaced query params.
69
70    >>> namespaced_query_string({
71    ...  'oauth_token': 'foo',
72    ...  'track': {'title': 'bar', 'sharing': 'private'}}) == {
73    ...      'track[sharing]': 'private',
74    ...      'oauth_token': 'foo',
75    ...      'track[title]': 'bar'}  # doctest:+ELLIPSIS
76    True
77    """
78    qs = {}
79    prefixed = lambda k: prefix and "%s[%s]" % (prefix, k) or k
80    for key, value in six.iteritems(d):
81        if isinstance(value, dict):
82            qs.update(namespaced_query_string(value, prefix=key))
83        else:
84            qs[prefixed(key)] = value
85    return qs
86
87
88def make_request(method, url, params):
89    """Make an HTTP request, formatting params as required."""
90    empty = []
91
92    # TODO
93    # del params[key]
94    # without list
95    for key, value in six.iteritems(params):
96        if value is None:
97            empty.append(key)
98    for key in empty:
99        del params[key]
100
101    # allow caller to disable automatic following of redirects
102    allow_redirects = params.get('allow_redirects', True)
103
104    kwargs = {
105        'allow_redirects': allow_redirects,
106        'headers': {
107            'User-Agent': soundcloud.USER_AGENT
108        }
109    }
110    # options, not params
111    if 'verify_ssl' in params:
112        if params['verify_ssl'] is False:
113            kwargs['verify'] = params['verify_ssl']
114        del params['verify_ssl']
115    if 'proxies' in params:
116        kwargs['proxies'] = params['proxies']
117        del params['proxies']
118    if 'allow_redirects' in params:
119        del params['allow_redirects']
120
121    params = hashconversions.to_params(params)
122    files = namespaced_query_string(extract_files_from_dict(params))
123    data = namespaced_query_string(remove_files_from_dict(params))
124
125    request_func = getattr(requests, method, None)
126    if request_func is None:
127        raise TypeError('Unknown method: %s' % (method,))
128
129    if method == 'get':
130        kwargs['headers']['Accept'] = 'application/json'
131        qs = urlencode(data)
132        if '?' in url:
133            url_qs = '%s&%s' % (url, qs)
134        else:
135            url_qs = '%s?%s' % (url, qs)
136        result = request_func(url_qs, **kwargs)
137    else:
138        kwargs['data'] = data
139        if files:
140            kwargs['files'] = files
141        result = request_func(url, **kwargs)
142
143    # if redirects are disabled, don't raise for 301 / 302
144    if result.status_code in (301, 302):
145        if allow_redirects:
146            result.raise_for_status()
147    else:
148        result.raise_for_status()
149    return result
150