diff --git a/multidimensional_urlencode/tests/test_urlencode.py b/multidimensional_urlencode/tests/test_urlencode.py index 58e56c7..d73f9c2 100644 --- a/multidimensional_urlencode/tests/test_urlencode.py +++ b/multidimensional_urlencode/tests/test_urlencode.py @@ -41,3 +41,17 @@ def test_with_non_dict(): """Verify that we raise an exception when passing a non-dict.""" with pytest.raises(TypeError): urlencode("e") + + +def test_no_array_braces(): + """Verify that array braces can be left off.""" + d = {'a': {"b": [1, 2, 3]}} + expected = "a[b]=1&a[b]=2&a[b]=3" + assert unquote(urlencode(d, array_braces=False)) == expected + + +def test_encode_list_key(): + """Verify that list indexes are explicitly added.""" + d = {'a': {"b": [1, 2, 3]}} + expected = "a[b][0]=1&a[b][1]=2&a[b][2]=3" + assert unquote(urlencode(d, encode_list_key=True)) == expected diff --git a/multidimensional_urlencode/urlencoder.py b/multidimensional_urlencode/urlencoder.py index a003451..5d61f2e 100644 --- a/multidimensional_urlencode/urlencoder.py +++ b/multidimensional_urlencode/urlencoder.py @@ -6,7 +6,7 @@ from collections import OrderedDict -def flatten(d): +def flatten(d, encode_list_key=False): """Return a dict as a list of lists. >>> flatten({"a": "b"}) @@ -23,19 +23,25 @@ def flatten(d): [['a', 'b', 'c'], ['a', 'd', 'e'], ['b', 'c', 'd']] """ - if not isinstance(d, dict): - return [[d]] + if isinstance(d, dict) or (encode_list_key and isinstance(d, list)): + returned = [] - returned = [] - for key, value in sorted(d.items()): - # Each key, value is treated as a row. - nested = flatten(value) - for nest in nested: - current_row = [key] - current_row.extend(nest) - returned.append(current_row) + if isinstance(d, list) and encode_list_key: + inner = [(x, d[x]) for x in range(len(d))] + else: + inner = sorted(d.items()) - return returned + for key, value in inner: + # Each key, value is treated as a row. + nested = flatten(value, encode_list_key) + for nest in nested: + current_row = [key] + current_row.extend(nest) + returned.append(current_row) + + return returned + else: + return [[d]] def parametrize(params): @@ -54,21 +60,35 @@ def parametrize(params): return returned -def urlencode(params): - """Urlencode a multidimensional dict.""" +def urlencode(params, encode_list_key=False, array_braces=True): + """Urlencode a multidimensional dict. + + >>> try: + ... from urllib.parse import quote, unquote + ... except ImportError: + ... from urllib import quote, unquote + ... + >>> unquote(urlencode({'a': {"b": [1, 2, 3]}})) + 'a[b][]=1&a[b][]=2&a[b][]=3' + >>> unquote(urlencode({'a': {"b": [1, 2, 3]}}, array_braces=False)) + 'a[b]=1&a[b]=2&a[b]=3' + >>> unquote(urlencode({'a': {"b": [1, 2, 3]}}, encode_list_key=True)) + 'a[b][0]=1&a[b][1]=2&a[b][2]=3' + + """ # Not doing duck typing here. Will make debugging easier. if not isinstance(params, dict): raise TypeError("Only dicts are supported.") - params = flatten(params) + params = flatten(params, encode_list_key) url_params = OrderedDict() for param in params: value = param.pop() name = parametrize(param) - if isinstance(value, (list, tuple)): + if isinstance(value, (list, tuple)) and array_braces: name += "[]" url_params[name] = value