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