1# -*- coding: utf-8 -*- 2"""Implementation of nested form-data encoding function(s).""" 3from .._compat import basestring 4from .._compat import urlencode as _urlencode 5 6 7__all__ = ('urlencode',) 8 9 10def urlencode(query, *args, **kwargs): 11 """Handle nested form-data queries and serialize them appropriately. 12 13 There are times when a website expects a nested form data query to be sent 14 but, the standard library's urlencode function does not appropriately 15 handle the nested structures. In that case, you need this function which 16 will flatten the structure first and then properly encode it for you. 17 18 When using this to send data in the body of a request, make sure you 19 specify the appropriate Content-Type header for the request. 20 21 .. code-block:: python 22 23 import requests 24 from requests_toolbelt.utils import formdata 25 26 query = { 27 'my_dict': { 28 'foo': 'bar', 29 'biz': 'baz", 30 }, 31 'a': 'b', 32 } 33 34 resp = requests.get(url, params=formdata.urlencode(query)) 35 # or 36 resp = requests.post( 37 url, 38 data=formdata.urlencode(query), 39 headers={ 40 'Content-Type': 'application/x-www-form-urlencoded' 41 }, 42 ) 43 44 Similarly, you can specify a list of nested tuples, e.g., 45 46 .. code-block:: python 47 48 import requests 49 from requests_toolbelt.utils import formdata 50 51 query = [ 52 ('my_list', [ 53 ('foo', 'bar'), 54 ('biz', 'baz'), 55 ]), 56 ('a', 'b'), 57 ] 58 59 resp = requests.get(url, params=formdata.urlencode(query)) 60 # or 61 resp = requests.post( 62 url, 63 data=formdata.urlencode(query), 64 headers={ 65 'Content-Type': 'application/x-www-form-urlencoded' 66 }, 67 ) 68 69 For additional parameter and return information, see the official 70 `urlencode`_ documentation. 71 72 .. _urlencode: 73 https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode 74 """ 75 expand_classes = (dict, list, tuple) 76 original_query_list = _to_kv_list(query) 77 78 if not all(_is_two_tuple(i) for i in original_query_list): 79 raise ValueError("Expected query to be able to be converted to a " 80 "list comprised of length 2 tuples.") 81 82 query_list = original_query_list 83 while any(isinstance(v, expand_classes) for _, v in query_list): 84 query_list = _expand_query_values(query_list) 85 86 return _urlencode(query_list, *args, **kwargs) 87 88 89def _to_kv_list(dict_or_list): 90 if hasattr(dict_or_list, 'items'): 91 return list(dict_or_list.items()) 92 return dict_or_list 93 94 95def _is_two_tuple(item): 96 return isinstance(item, (list, tuple)) and len(item) == 2 97 98 99def _expand_query_values(original_query_list): 100 query_list = [] 101 for key, value in original_query_list: 102 if isinstance(value, basestring): 103 query_list.append((key, value)) 104 else: 105 key_fmt = key + '[%s]' 106 value_list = _to_kv_list(value) 107 query_list.extend((key_fmt % k, v) for k, v in value_list) 108 return query_list 109