From 02e5e50d54a456b76f2b33053db28ece9011fed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Bouliane?= Date: Thu, 15 Dec 2016 19:21:52 -0500 Subject: [PATCH] Use python style enumerables in calls Fixes the behavior when list, tuples and dict are passed to a client method. Currently the enumerables are passed to requests and it encodes them in a weird manner. It will now form encode lists and dicts whenever one is passed in a nested manner, allowing complex arguments. --- tests/http_utils_test.py | 55 +++++++++++++++++++ tests/ubersmith_request_form_encoding_test.py | 43 +++++++++++++++ ubersmith_client/_http_utils.py | 28 ++++++++++ ubersmith_client/ubersmith_request_get.py | 4 +- ubersmith_client/ubersmith_request_post.py | 4 +- 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 tests/http_utils_test.py create mode 100644 tests/ubersmith_request_form_encoding_test.py create mode 100644 ubersmith_client/_http_utils.py diff --git a/tests/http_utils_test.py b/tests/http_utils_test.py new file mode 100644 index 0000000..52472e2 --- /dev/null +++ b/tests/http_utils_test.py @@ -0,0 +1,55 @@ +import unittest +from ubersmith_client import _http_utils + + +class HttpUtilsTest(unittest.TestCase): + def test_form_encode_with_list(self): + result = _http_utils.form_encode(dict(test=['a', 'b'])) + self.assertDictEqual({ + 'test[0]': 'a', + 'test[1]': 'b', + }, result) + + def test_with_tuples(self): + result = _http_utils.form_encode(dict(test=('a', 'b'))) + + self.assertDictEqual({ + 'test[0]': 'a', + 'test[1]': 'b', + }, result) + + def test_with_dict(self): + result = _http_utils.form_encode(dict(test={'a': '1', 'b': '2'})) + + self.assertDictEqual({ + 'test[a]': '1', + 'test[b]': '2' + }, result) + + def test_with_empty_dict(self): + result = _http_utils.form_encode(dict(test_dict={}, test_list=[])) + + self.assertDictEqual({ + 'test_dict': {}, + 'test_list': [] + }, result) + + def test_with_nested_lists_and_dicts(self): + result = _http_utils.form_encode(dict(test=[['a', 'b'], {'c': '1', 'd': '2'}])) + + self.assertDictEqual({ + 'test[0][0]': 'a', + 'test[0][1]': 'b', + 'test[1][c]': '1', + 'test[1][d]': '2' + }, result) + + def test_with_bools(self): + result = _http_utils.form_encode(dict(true=True, false=False)) + + self.assertDictEqual({ + 'true': True, + 'false': False + }, result) + + diff --git a/tests/ubersmith_request_form_encoding_test.py b/tests/ubersmith_request_form_encoding_test.py new file mode 100644 index 0000000..4572f6f --- /dev/null +++ b/tests/ubersmith_request_form_encoding_test.py @@ -0,0 +1,43 @@ +import unittest + +from mock import sentinel, patch, MagicMock + +from ubersmith_client.ubersmith_request_get import UbersmithRequestGet +from ubersmith_client.ubersmith_request_post import UbersmithRequestPost + + +class UbersmithRequestFormEncodingTest(unittest.TestCase): + def setUp(self): + self.ubersmith_constructor_params = (sentinel.url, sentinel.username, sentinel.password, + sentinel.module, sentinel.timeout) + self._standard_kwargs = dict(auth=(sentinel.username, sentinel.password), + timeout=sentinel.timeout, + url=sentinel.url) + + @patch('ubersmith_client.ubersmith_request_get.requests') + def test_get_with_list(self, request_mock): + request_mock.get.return_value = MagicMock(status_code=200) + + self.client = UbersmithRequestGet(*self.ubersmith_constructor_params) + self.client.call(test=['a']) + + expected_args = self._standard_kwargs + expected_args.update(dict(params={ + 'method': 'sentinel.module.call', + 'test[0]': 'a', + })) + request_mock.get.assert_called_with(**expected_args) + + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_post_with_list(self, request_mock): + request_mock.post.return_value = MagicMock(status_code=200) + + self.client = UbersmithRequestPost(*self.ubersmith_constructor_params) + self.client.call(test=['a']) + + expected_args = self._standard_kwargs + expected_args.update(dict(data={ + 'method': 'sentinel.module.call', + 'test[0]': 'a', + })) + request_mock.post.assert_called_with(**expected_args) diff --git a/ubersmith_client/_http_utils.py b/ubersmith_client/_http_utils.py new file mode 100644 index 0000000..a851b56 --- /dev/null +++ b/ubersmith_client/_http_utils.py @@ -0,0 +1,28 @@ +def form_encode(data): + exploded_data = {} + for k, v in data.items(): + items = _explode_enumerable(k, v) + for new_key, new_val in items: + exploded_data[new_key] = new_val + return exploded_data + + +def _explode_enumerable(k, v): + exploded_items = [] + if isinstance(v, list) or isinstance(v, tuple): + if len(v) == 0: + exploded_items.append((k, v)) + else: + for idx, item in enumerate(v): + current_key = '{}[{}]'.format(k, idx) + exploded_items.extend(_explode_enumerable(current_key, item)) + elif isinstance(v, dict): + if len(v) == 0: + exploded_items.append((k, v)) + else: + for idx, item in v.items(): + current_key = '{}[{}]'.format(k, idx) + exploded_items.extend(_explode_enumerable(current_key, item)) + else: + exploded_items.append((k, v)) + return exploded_items diff --git a/ubersmith_client/ubersmith_request_get.py b/ubersmith_client/ubersmith_request_get.py index 246278a..7435139 100644 --- a/ubersmith_client/ubersmith_request_get.py +++ b/ubersmith_client/ubersmith_request_get.py @@ -12,17 +12,19 @@ # limitations under the License. import requests +from ubersmith_client import _http_utils from ubersmith_client.ubersmith_request import UbersmithRequest class UbersmithRequestGet(UbersmithRequest): def __call__(self, **kwargs): self._build_request_params(kwargs) + params = _http_utils.form_encode(kwargs) response = self._process_request(method=requests.get, url=self.url, auth=(self.user, self.password), timeout=self.timeout, - params=kwargs) + params=params) return UbersmithRequest.process_ubersmith_response(response) diff --git a/ubersmith_client/ubersmith_request_post.py b/ubersmith_client/ubersmith_request_post.py index 5c7e74b..b57abe4 100644 --- a/ubersmith_client/ubersmith_request_post.py +++ b/ubersmith_client/ubersmith_request_post.py @@ -12,17 +12,19 @@ # limitations under the License. import requests +from ubersmith_client import _http_utils from ubersmith_client.ubersmith_request import UbersmithRequest class UbersmithRequestPost(UbersmithRequest): def __call__(self, **kwargs): self._build_request_params(kwargs) + params = _http_utils.form_encode(kwargs) response = self._process_request(method=requests.post, url=self.url, auth=(self.user, self.password), timeout=self.timeout, - data=kwargs) + data=params) return UbersmithRequest.process_ubersmith_response(response)