From 57af3351fc2f68c183808163937aef50530a8fe2 Mon Sep 17 00:00:00 2001 From: Marc Aubry Date: Tue, 16 Feb 2016 23:57:58 -0500 Subject: [PATCH 01/22] Exception string test --- tests/exceptions_test.py | 9 +++++++++ ubersmith_client/exceptions.py | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 tests/exceptions_test.py diff --git a/tests/exceptions_test.py b/tests/exceptions_test.py new file mode 100644 index 0000000..affb3f1 --- /dev/null +++ b/tests/exceptions_test.py @@ -0,0 +1,9 @@ +import unittest +from ubersmith_client.exceptions import UbersmithException + + +class ExceptionTestTest(unittest.TestCase): + + def test_UbersmithException_has_string_representation(self): + ex = UbersmithException(code=42, message='Wubba Lubba Dub Dub') + self.assertEquals("{0}".format(ex), "Error code 42 - message: Wubba Lubba Dub Dub") diff --git a/ubersmith_client/exceptions.py b/ubersmith_client/exceptions.py index 090ee43..bfb0fd5 100644 --- a/ubersmith_client/exceptions.py +++ b/ubersmith_client/exceptions.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + + class UbersmithException(Exception): code = None message = None From bbe71ac6f75e4f902e0ecc5f32204363ebf7483e Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Wed, 9 Mar 2016 15:41:50 -0500 Subject: [PATCH 02/22] Add timeout to http requests --- test-requirements.txt | 3 ++- tests/api_test.py | 27 +++++++++++++++++++++++++++ ubersmith_client/api.py | 20 ++++++++++++++------ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index ab4c471..11f684f 100755 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,4 @@ nose==1.2.1 requests-mock==0.7.0 -pyhamcrest==1.8.1 \ No newline at end of file +pyhamcrest==1.8.1 +flexmock \ No newline at end of file diff --git a/tests/api_test.py b/tests/api_test.py index a54281c..7836f14 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -13,7 +13,9 @@ # limitations under the License. import base64 import unittest +import requests +from flexmock import flexmock, flexmock_teardown from hamcrest import assert_that, equal_to, raises, calling import requests_mock from requests_mock.exceptions import NoMockAddress @@ -28,6 +30,9 @@ def setUp(self): self.username = 'admin' self.password = 'test' + def tearDown(self): + flexmock_teardown() + @requests_mock.mock() def test_api_method_returns_without_arguments(self, request_mock): json_data = [ @@ -173,6 +178,28 @@ def test_api_http_post_method_raises_on_result_414(self, request_mock): assert_that(calling(ubersmith_api.support.ticket_submit.http_post), raises(UnknownError)) + def test_api_http_timeout(self): + payload = dict(status=True, data="plop") + response = flexmock(status_code=200, json=lambda: payload) + ubersmith_api = api.init(self.url, self.username, self.password, 666) + + flexmock(requests).should_receive("get").with_args( + url=self.url, auth=(self.username, self.password), timeout=666, params={'method': 'uber.method_list'} + ).and_return(response) + + ubersmith_api.uber.method_list() + + def test_api_http_default_timeout(self): + payload = dict(status=True, data="plop") + response = flexmock(status_code=200, json=lambda: payload) + ubersmith_api = api.init(self.url, self.username, self.password) + + flexmock(requests).should_receive("get").with_args( + url=self.url, auth=(self.username, self.password), timeout=60, params={'method': 'uber.method_list'} + ).and_return(response) + + ubersmith_api.uber.method_list() + @requests_mock.mock() def test_api_http_post_method_raises_on_result_500(self, request_mock): json_data = { diff --git a/ubersmith_client/api.py b/ubersmith_client/api.py index 5796838..2caf125 100644 --- a/ubersmith_client/api.py +++ b/ubersmith_client/api.py @@ -16,28 +16,30 @@ from ubersmith_client.exceptions import UbersmithException, get_exception_for -def init(url, user, password): - return UbersmithApi(url, user, password) +def init(url, user, password, timeout=60): + return UbersmithApi(url, user, password, timeout) class UbersmithApi(object): - def __init__(self, url, user, password): + def __init__(self, url, user, password, timeout): self.url = url self.user = user self.password = password + self.timeout = timeout def __getattr__(self, module): - return UbersmithRequest(self.url, self.user, self.password, module) + return UbersmithRequest(self.url, self.user, self.password, module, self.timeout) class UbersmithRequest(object): - def __init__(self, url, user, password, module): + def __init__(self, url, user, password, module, timeout): self.url = url self.user = user self.password = password self.module = module self.methods = [] self.http_methods = {'GET': 'get', 'POST': 'post'} + self.timeout = timeout def __getattr__(self, function): self.methods.append(function) @@ -48,7 +50,13 @@ def __call__(self, **kwargs): def process_request(self, http_method, **kwargs): callable_http_method = getattr(requests, http_method) - response = callable_http_method(self.url, auth=(self.user, self.password), **kwargs) + + response = callable_http_method( + self.url, + auth=(self.user, self.password), + timeout=self.timeout, + **kwargs + ) if response.status_code < 200 or response.status_code >= 400: raise get_exception_for(status_code=response.status_code) From 3836062b03609f7e83faf5de8bc4310206b2fa25 Mon Sep 17 00:00:00 2001 From: Olivier C Date: Thu, 10 Mar 2016 14:29:05 -0500 Subject: [PATCH 03/22] Update readme --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0987682..1e91e61 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ubersmith_client -url = https://github.com/Marx314/python-ubersmithclient +url = https://github.com/internap/python-ubersmithclient author = Internap author-email = opensource@internap.com summary = Another ubersmith lib From 8fe8e96aa134b4a53f46aa6c7f15d5d3c5d1ff86 Mon Sep 17 00:00:00 2001 From: Olivier C Date: Thu, 10 Mar 2016 14:35:34 -0500 Subject: [PATCH 04/22] Changed package name Changed package name to python-ubersmithclient --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1e91e61..83a5503 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = ubersmith_client +name = python-ubersmithclient url = https://github.com/internap/python-ubersmithclient author = Internap author-email = opensource@internap.com From ca7cfe2f10b0d2b7c1250ed419ff31f9209f92ef Mon Sep 17 00:00:00 2001 From: Corentin Ardeois Date: Tue, 22 Mar 2016 14:28:35 -0400 Subject: [PATCH 05/22] Ability to use `POST` as default request method POST request `body` is now verified in tests Also fixes code so the example in the README works --- requirements.txt | 1 + tests/api_test.py | 84 ++++++++++++++++++++++++++---------- ubersmith_client/__init__.py | 4 +- ubersmith_client/api.py | 17 +++++--- 4 files changed, 77 insertions(+), 29 deletions(-) diff --git a/requirements.txt b/requirements.txt index c7317e0..bbe3b59 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ requests<=2.9.1 +six>=1.10.0 diff --git a/tests/api_test.py b/tests/api_test.py index 7836f14..b48cecd 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -13,14 +13,16 @@ # limitations under the License. import base64 import unittest +import six.moves.urllib.parse as urllib_parse + import requests from flexmock import flexmock, flexmock_teardown from hamcrest import assert_that, equal_to, raises, calling import requests_mock from requests_mock.exceptions import NoMockAddress -from ubersmith_client import api -from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized +import ubersmith_client +from ubersmith_client.exceptions import BadRequest, UnknownError, Forbidden, NotFound, Unauthorized from tests.ubersmith_json.response_data_structure import a_response_data @@ -46,7 +48,7 @@ def test_api_method_returns_without_arguments(self, request_mock): data = a_response_data(data=json_data) self.expect_a_ubersmith_call(request_mock, "client.list", data=data) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) response = ubersmith_api.client.list() assert_that(response, equal_to(json_data)) @@ -62,7 +64,7 @@ def test_api_method_returns_with_arguments(self, request_mock): self.expect_a_ubersmith_call(request_mock, method="device.ip_group_list", fac_id='1', client_id='30001', data=data) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) assert_that(response, equal_to(json_data)) @@ -71,15 +73,15 @@ def test_api_method_returns_with_arguments(self, request_mock): def test_api_raises_exception_with_if_data_status_is_false(self, request_mock): data = a_response_data(status=False, error_code=1, error_message="invalid method specified: client.miss", data=None) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) self.expect_a_ubersmith_call(request_mock, method="client.miss", data=data) - assert_that(calling(ubersmith_api.client.miss), raises(UbersmithException)) + assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) @requests_mock.mock() def test_api_raises_exception_for_invalid_status_code(self, request_mock): method = "client.list" - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) self.expect_a_ubersmith_call(request_mock, method=method, status_code=400) @@ -101,7 +103,7 @@ def test_api_raises_exception_for_invalid_status_code(self, request_mock): def test_api_with_a_false_identifier(self, request_mock): method = "client.list" self.expect_a_ubersmith_call(request_mock, method=method) - ubersmith_api = api.init(self.url, 'not_hapi', 'lol') + ubersmith_api = ubersmith_client.api.init(self.url, 'not_hapi', 'lol') with self.assertRaises(NoMockAddress) as ube: ubersmith_api.client.list() @@ -119,7 +121,7 @@ def test_api_http_get_method(self, request_mock): self.expect_a_ubersmith_call(request_mock, method="device.ip_group_list", fac_id='666', client_id='30666', data=data) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) response = ubersmith_api.device.ip_group_list.http_get(fac_id=666, client_id=30666) assert_that(response, equal_to(json_data)) @@ -135,7 +137,23 @@ def test_api_http_get_method_default(self, request_mock): self.expect_a_ubersmith_call(request_mock, method="device.ip_group_list", fac_id='666', client_id='30666', data=data) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + response = ubersmith_api.device.ip_group_list(fac_id=666, client_id=30666) + + assert_that(response, equal_to(json_data)) + + @requests_mock.mock() + def test_api_http_post_method_default(self, request_mock): + json_data = { + 'group_id': '666', + 'client_id': '30666', + 'assignment_count': '1' + } + data = a_response_data(data=json_data) + self.expect_a_ubersmith_call_post(request_mock, method='device.ip_group_list', fac_id='666', client_id='30666', + response_body=data) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_post=True) response = ubersmith_api.device.ip_group_list(fac_id=666, client_id=30666) assert_that(response, equal_to(json_data)) @@ -151,10 +169,13 @@ def test_api_http_post_method_result_200(self, request_mock): self.expect_a_ubersmith_call_post( request_mock, + method='support.ticket_submit', + body='ticket body', + subject='ticket subject', response_body=json_data, ) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) response = ubersmith_api.support.ticket_submit.http_post(body='ticket body', subject='ticket subject') assert_that(response, equal_to(json_data.get('data'))) @@ -170,18 +191,19 @@ def test_api_http_post_method_raises_on_result_414(self, request_mock): self.expect_a_ubersmith_call_post( request_mock, + method='support.ticket_submit', response_body=json_data, status_code=414 ) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) assert_that(calling(ubersmith_api.support.ticket_submit.http_post), raises(UnknownError)) def test_api_http_timeout(self): payload = dict(status=True, data="plop") response = flexmock(status_code=200, json=lambda: payload) - ubersmith_api = api.init(self.url, self.username, self.password, 666) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, 666) flexmock(requests).should_receive("get").with_args( url=self.url, auth=(self.username, self.password), timeout=666, params={'method': 'uber.method_list'} @@ -192,7 +214,7 @@ def test_api_http_timeout(self): def test_api_http_default_timeout(self): payload = dict(status=True, data="plop") response = flexmock(status_code=200, json=lambda: payload) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) flexmock(requests).should_receive("get").with_args( url=self.url, auth=(self.username, self.password), timeout=60, params={'method': 'uber.method_list'} @@ -211,18 +233,17 @@ def test_api_http_post_method_raises_on_result_500(self, request_mock): self.expect_a_ubersmith_call_post( request_mock, + method='support.ticket_submit', response_body=json_data, ) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - assert_that(calling(ubersmith_api.support.ticket_submit.http_post), raises(UbersmithException)) + assert_that(calling(ubersmith_api.support.ticket_submit.http_post), + raises(ubersmith_client.exceptions.UbersmithException)) def expect_a_ubersmith_call(self, request_mock, method, data=None, status_code=200, **kwargs): - url = self.url + '?method=' + method - if kwargs: - for key, value in kwargs.items(): - url += '&' + key + '=' + value + url = self.url + '?' + get_url_params(method, **kwargs) headers = { 'Content-Type': 'application/json', } @@ -230,9 +251,28 @@ def expect_a_ubersmith_call(self, request_mock, method, data=None, status_code=2 'Authorization': self.get_auth_header() }, status_code=status_code) - def expect_a_ubersmith_call_post(self, request_mock, response_body=None, status_code=200): - request_mock.post(self.url, json=response_body, status_code=status_code) + def expect_a_ubersmith_call_post(self, request_mock, method, response_body=None, status_code=200, **kwargs): + headers = { + 'Content-Type': 'application/json', + } + expected_text = get_url_params(method, **kwargs) + + def response_callback(request, context): + expected_params = urllib_parse.parse_qs(expected_text) + parsed_params = urllib_parse.parse_qs(request.text) + assert_that(parsed_params, equal_to(expected_params)) + return response_body + + request_mock.post(self.url, json=response_callback, headers=headers, request_headers={ + 'Authorization': self.get_auth_header(), + 'Content-Type': 'application/x-www-form-urlencoded' + }, status_code=status_code) def get_auth_header(self): auth = base64.b64encode((self.username + ':' + self.password).encode('utf-8')) return 'Basic ' + auth.decode('utf-8') + + +def get_url_params(method, **kwargs): + kwargs['method'] = method + return urllib_parse.urlencode(kwargs) diff --git a/ubersmith_client/__init__.py b/ubersmith_client/__init__.py index f44d680..6daacf5 100644 --- a/ubersmith_client/__init__.py +++ b/ubersmith_client/__init__.py @@ -10,4 +10,6 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file + +from . import api +from . import exceptions diff --git a/ubersmith_client/api.py b/ubersmith_client/api.py index 2caf125..14d41bf 100644 --- a/ubersmith_client/api.py +++ b/ubersmith_client/api.py @@ -16,23 +16,24 @@ from ubersmith_client.exceptions import UbersmithException, get_exception_for -def init(url, user, password, timeout=60): - return UbersmithApi(url, user, password, timeout) +def init(url, user, password, timeout=60, use_http_post=False): + return UbersmithApi(url, user, password, timeout, use_http_post) class UbersmithApi(object): - def __init__(self, url, user, password, timeout): + def __init__(self, url, user, password, timeout, use_http_post): self.url = url self.user = user self.password = password self.timeout = timeout + self.use_http_post = use_http_post def __getattr__(self, module): - return UbersmithRequest(self.url, self.user, self.password, module, self.timeout) + return UbersmithRequest(self.url, self.user, self.password, module, self.timeout, self.use_http_post) class UbersmithRequest(object): - def __init__(self, url, user, password, module, timeout): + def __init__(self, url, user, password, module, timeout, use_http_post): self.url = url self.user = user self.password = password @@ -40,13 +41,17 @@ def __init__(self, url, user, password, module, timeout): self.methods = [] self.http_methods = {'GET': 'get', 'POST': 'post'} self.timeout = timeout + self.use_http_post = use_http_post def __getattr__(self, function): self.methods.append(function) return self def __call__(self, **kwargs): - return self.http_get(**kwargs) + if self.use_http_post: + return self.http_post(**kwargs) + else: + return self.http_get(**kwargs) def process_request(self, http_method, **kwargs): callable_http_method = getattr(requests, http_method) From 400c49c7603505181bed30a85e2c497bdab59735 Mon Sep 17 00:00:00 2001 From: Corentin Ardeois Date: Tue, 22 Mar 2016 18:31:58 -0400 Subject: [PATCH 06/22] Add test that verifies old import still works. Improve Readme file --- README.rst | 39 +++++++++++++++++++++++++++++++-------- tests/api_test.py | 3 ++- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 987c9bf..b8bbada 100755 --- a/README.rst +++ b/README.rst @@ -1,4 +1,5 @@ -# Ubersmith API Client for Python +Ubersmith API Client for Python +=============================== .. image:: https://travis-ci.org/internap/python-ubersmithclient.svg?branch=master :target: https://travis-ci.org/internap/python-ubersmithclient @@ -6,11 +7,33 @@ .. image:: https://img.shields.io/pypi/v/ubersmith_client.svg?style=flat :target: https://pypi.python.org/pypi/ubersmith_client -# Usage +Usage +----- +.. code:: python - >>> import ubersmith_client - >>> api = ubersmith_client.api.init('http://ubersmith.com/api/2.0/', 'username', 'password') - >>> api.client.count() - u'264' - >>> api.client.latest_client() - 1265 + import ubersmith_client + + api = ubersmith_client.api.init('http://ubersmith.com/api/2.0/', 'username', 'password') + api.client.count() + >>> u'264' + api.client.latest_client() + >>> 1265 + +API +--------- + +**ubersmith_client.api.init(url, user, password, timeout, use_http_post)** + :url: + URL of your API + + *Example:* ``http://ubersmith.example.org/api/2.0/`` + + :user: API username + :password: API Password or token + :timeout: api timeout given to requests + + *Default:* ``60`` + :use_http_post: + Use `POST` requests instead of `GET` + + *Default:* ``False`` diff --git a/tests/api_test.py b/tests/api_test.py index b48cecd..c508b41 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -22,6 +22,7 @@ import requests_mock from requests_mock.exceptions import NoMockAddress import ubersmith_client +from ubersmith_client import api from ubersmith_client.exceptions import BadRequest, UnknownError, Forbidden, NotFound, Unauthorized from tests.ubersmith_json.response_data_structure import a_response_data @@ -48,7 +49,7 @@ def test_api_method_returns_without_arguments(self, request_mock): data = a_response_data(data=json_data) self.expect_a_ubersmith_call(request_mock, "client.list", data=data) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + ubersmith_api = api.init(self.url, self.username, self.password) response = ubersmith_api.client.list() assert_that(response, equal_to(json_data)) From 8517a843980c33d4a8431ad33814ded7ff9fd091 Mon Sep 17 00:00:00 2001 From: Corentin Ardeois Date: Tue, 22 Mar 2016 19:15:53 -0400 Subject: [PATCH 07/22] UbersmithException returns the original `code` and `message` given by the API --- tests/api_test.py | 8 +++++++- ubersmith_client/api.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/api_test.py b/tests/api_test.py index c508b41..2442523 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -77,7 +77,13 @@ def test_api_raises_exception_with_if_data_status_is_false(self, request_mock): ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) self.expect_a_ubersmith_call(request_mock, method="client.miss", data=data) - assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) + + with self.assertRaises(ubersmith_client.exceptions.UbersmithException) as cm: + ubersmith_api.client.miss() + + catched_exception = cm.exception + assert_that(catched_exception.code, equal_to(1)) + assert_that(catched_exception.message, equal_to('invalid method specified: client.miss')) @requests_mock.mock() def test_api_raises_exception_for_invalid_status_code(self, request_mock): diff --git a/ubersmith_client/api.py b/ubersmith_client/api.py index 14d41bf..be7c66d 100644 --- a/ubersmith_client/api.py +++ b/ubersmith_client/api.py @@ -69,8 +69,8 @@ def process_request(self, http_method, **kwargs): response_json = response.json() if not response_json['status']: raise UbersmithException( - 500, - "error {0}, {1}".format(response_json['error_code'], response_json['error_message']) + response_json['error_code'], + response_json['error_message'] ) return response.json()["data"] From 6e8fece8fd56281eb13cde5659797350a8f842ca Mon Sep 17 00:00:00 2001 From: Marc Aubry Date: Wed, 23 Mar 2016 10:18:22 -0400 Subject: [PATCH 08/22] Rework test without flexmock, request_mock Make post as default behavior - breaking change Fixe readme for new pypi lib name --- README.rst | 12 +- requirements.txt | 1 - test-requirements.txt | 3 +- tests/__init__.py | 7 - tests/api_test.py | 296 +++++------------- tests/exceptions_test.py | 9 - tests/ubersmith_json/__init__.py | 2 +- .../ubersmith_json/response_data_structure.py | 11 +- tests/ubersmith_request_test.py | 59 ++++ ubersmith_client/__init__.py | 1 + ubersmith_client/api.py | 81 +---- ubersmith_client/ubersmith_api.py | 26 ++ ubersmith_client/ubersmith_request.py | 51 +++ ubersmith_client/ubersmith_request_get.py | 31 ++ ubersmith_client/ubersmith_request_post.py | 27 ++ 15 files changed, 289 insertions(+), 328 deletions(-) delete mode 100644 tests/exceptions_test.py create mode 100644 tests/ubersmith_request_test.py create mode 100644 ubersmith_client/ubersmith_api.py create mode 100644 ubersmith_client/ubersmith_request.py create mode 100644 ubersmith_client/ubersmith_request_get.py create mode 100644 ubersmith_client/ubersmith_request_post.py diff --git a/README.rst b/README.rst index b8bbada..e104bac 100755 --- a/README.rst +++ b/README.rst @@ -4,8 +4,8 @@ Ubersmith API Client for Python .. image:: https://travis-ci.org/internap/python-ubersmithclient.svg?branch=master :target: https://travis-ci.org/internap/python-ubersmithclient -.. image:: https://img.shields.io/pypi/v/ubersmith_client.svg?style=flat - :target: https://pypi.python.org/pypi/ubersmith_client +.. image:: https://img.shields.io/pypi/v/python-ubersmithclient.svg?style=flat + :target: https://pypi.python.org/pypi/python-ubersmithclient Usage ----- @@ -13,7 +13,7 @@ Usage import ubersmith_client - api = ubersmith_client.api.init('http://ubersmith.com/api/2.0/', 'username', 'password') + api = ubersmith_client.api.init(url='http://ubersmith.com/api/2.0/', user='username', password='password') api.client.count() >>> u'264' api.client.latest_client() @@ -22,7 +22,7 @@ Usage API --------- -**ubersmith_client.api.init(url, user, password, timeout, use_http_post)** +**ubersmith_client.api.init(url, user, password, timeout, use_http_get)** :url: URL of your API @@ -33,7 +33,7 @@ API :timeout: api timeout given to requests *Default:* ``60`` - :use_http_post: - Use `POST` requests instead of `GET` + :use_http_get: + Use `GET` requests instead of `POST` *Default:* ``False`` diff --git a/requirements.txt b/requirements.txt index bbe3b59..c7317e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ requests<=2.9.1 -six>=1.10.0 diff --git a/test-requirements.txt b/test-requirements.txt index 11f684f..daf105d 100755 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,3 @@ nose==1.2.1 -requests-mock==0.7.0 pyhamcrest==1.8.1 -flexmock \ No newline at end of file +mock==1.3.0 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 2f12658..a5b5ca9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,10 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -def apply_kwargs(kwargs, default_kwargs): - for k, v in kwargs.items(): - if isinstance(v, dict): - default_kwargs[k] = apply_kwargs(v, default_kwargs[k]) - else: - default_kwargs[k] = v - return default_kwargs diff --git a/tests/api_test.py b/tests/api_test.py index 2442523..5bd2bb9 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -11,275 +11,125 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import base64 import unittest -import six.moves.urllib.parse as urllib_parse +from mock import patch, MagicMock -import requests - -from flexmock import flexmock, flexmock_teardown from hamcrest import assert_that, equal_to, raises, calling -import requests_mock -from requests_mock.exceptions import NoMockAddress -import ubersmith_client -from ubersmith_client import api -from ubersmith_client.exceptions import BadRequest, UnknownError, Forbidden, NotFound, Unauthorized + from tests.ubersmith_json.response_data_structure import a_response_data +import ubersmith_client -class UbersmithIWebTest(unittest.TestCase): +class ApiTest(unittest.TestCase): def setUp(self): - self.url = 'http://ubersmith.example.org/' + self.url = 'http://ubersmith.example.com/' self.username = 'admin' self.password = 'test' - def tearDown(self): - flexmock_teardown() + self.auth = (self.username, self.password) + self.timeout = 60 - @requests_mock.mock() - def test_api_method_returns_without_arguments(self, request_mock): - json_data = [ - { - 'client_id': '1', - 'first': 'John', - 'last': 'Snow', - 'company': 'The Night Watch' - } - ] - data = a_response_data(data=json_data) - self.expect_a_ubersmith_call(request_mock, "client.list", data=data) + @patch('ubersmith_client.ubersmith_request_get.requests') + def test_api_get_method_returns_without_arguments(self, requests_mock): + json_data = { + 'company': 'council of ricks' + } + expected_call = self.expect_a_ubersmith_call(requests_mock=requests_mock, + method='client.list', + returning=a_response_data(data=json_data)) - ubersmith_api = api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) response = ubersmith_api.client.list() assert_that(response, equal_to(json_data)) - @requests_mock.mock() - def test_api_method_returns_with_arguments(self, request_mock): + expected_call() + + @patch('ubersmith_client.ubersmith_request_get.requests') + def test_api_get_method_returns_with_arguments(self, request_mock): json_data = { 'group_id': '1', 'client_id': '30001', 'assignment_count': '1' } - data = a_response_data(data=json_data) - self.expect_a_ubersmith_call(request_mock, method="device.ip_group_list", fac_id='1', client_id='30001', - data=data) + expected_call = self.expect_a_ubersmith_call(requests_mock=request_mock, + method='device.ip_group_list', + fac_id=1, + client_id=30001, + returning=a_response_data(data=json_data)) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) assert_that(response, equal_to(json_data)) - @requests_mock.mock() - def test_api_raises_exception_with_if_data_status_is_false(self, request_mock): - data = a_response_data(status=False, error_code=1, error_message="invalid method specified: client.miss", - data=None) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - - self.expect_a_ubersmith_call(request_mock, method="client.miss", data=data) - - with self.assertRaises(ubersmith_client.exceptions.UbersmithException) as cm: - ubersmith_api.client.miss() - - catched_exception = cm.exception - assert_that(catched_exception.code, equal_to(1)) - assert_that(catched_exception.message, equal_to('invalid method specified: client.miss')) - - @requests_mock.mock() - def test_api_raises_exception_for_invalid_status_code(self, request_mock): - method = "client.list" - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - - self.expect_a_ubersmith_call(request_mock, method=method, status_code=400) - - assert_that(calling(ubersmith_api.client.list), raises(BadRequest)) - - self.expect_a_ubersmith_call(request_mock, method=method, status_code=401) - assert_that(calling(ubersmith_api.client.list), raises(Unauthorized)) + expected_call() - self.expect_a_ubersmith_call(request_mock, method=method, status_code=403) - assert_that(calling(ubersmith_api.client.list), raises(Forbidden)) - - self.expect_a_ubersmith_call(request_mock, method=method, status_code=404) - assert_that(calling(ubersmith_api.client.list), raises(NotFound)) - - self.expect_a_ubersmith_call(request_mock, method=method, status_code=500) - assert_that(calling(ubersmith_api.client.list), raises(UnknownError)) - - @requests_mock.mock() - def test_api_with_a_false_identifier(self, request_mock): - method = "client.list" - self.expect_a_ubersmith_call(request_mock, method=method) - ubersmith_api = ubersmith_client.api.init(self.url, 'not_hapi', 'lol') - - with self.assertRaises(NoMockAddress) as ube: - ubersmith_api.client.list() - - assert_that(str(ube.exception), equal_to("No mock address: GET " + self.url + "?method=" + method)) - - @requests_mock.mock() - def test_api_http_get_method(self, request_mock): + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_post_method_returns_with_arguments(self, request_mock): json_data = { - 'group_id': '666', - 'client_id': '30666', + 'group_id': '1', + 'client_id': '30001', 'assignment_count': '1' } - data = a_response_data(data=json_data) - self.expect_a_ubersmith_call(request_mock, method="device.ip_group_list", fac_id='666', client_id='30666', - data=data) + expected_call = self.expect_a_ubersmith_call_post(requests_mock=request_mock, + method='device.ip_group_list', + fac_id=1, + client_id=30001, + returning=a_response_data(data=json_data)) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - response = ubersmith_api.device.ip_group_list.http_get(fac_id=666, client_id=30666) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=False) + response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) assert_that(response, equal_to(json_data)) - @requests_mock.mock() - def test_api_http_get_method_default(self, request_mock): - json_data = { - 'group_id': '666', - 'client_id': '30666', - 'assignment_count': '1' - } - data = a_response_data(data=json_data) - self.expect_a_ubersmith_call(request_mock, method="device.ip_group_list", fac_id='666', client_id='30666', - data=data) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - response = ubersmith_api.device.ip_group_list(fac_id=666, client_id=30666) + expected_call() - assert_that(response, equal_to(json_data)) - - @requests_mock.mock() - def test_api_http_post_method_default(self, request_mock): + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_post_method_returns_without_arguments(self, requests_mock): json_data = { - 'group_id': '666', - 'client_id': '30666', - 'assignment_count': '1' + 'company': 'schwifty' } - data = a_response_data(data=json_data) - self.expect_a_ubersmith_call_post(request_mock, method='device.ip_group_list', fac_id='666', client_id='30666', - response_body=data) + expected_call = self.expect_a_ubersmith_call_post(requests_mock=requests_mock, + method='client.list', + returning=a_response_data(data=json_data)) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_post=True) - response = ubersmith_api.device.ip_group_list(fac_id=666, client_id=30666) + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=False) + response = ubersmith_api.client.list() assert_that(response, equal_to(json_data)) - @requests_mock.mock() - def test_api_http_post_method_result_200(self, request_mock): - json_data = { - 'data': '778', - 'error_code': None, - 'error_message': '', - 'status': True - } - - self.expect_a_ubersmith_call_post( - request_mock, - method='support.ticket_submit', - body='ticket body', - subject='ticket subject', - response_body=json_data, - ) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - response = ubersmith_api.support.ticket_submit.http_post(body='ticket body', subject='ticket subject') - - assert_that(response, equal_to(json_data.get('data'))) - - @requests_mock.mock() - def test_api_http_post_method_raises_on_result_414(self, request_mock): - json_data = { - 'data': '778', - 'error_code': None, - 'error_message': '', - 'status': True - } - - self.expect_a_ubersmith_call_post( - request_mock, - method='support.ticket_submit', - response_body=json_data, - status_code=414 - ) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - - assert_that(calling(ubersmith_api.support.ticket_submit.http_post), raises(UnknownError)) + expected_call() - def test_api_http_timeout(self): - payload = dict(status=True, data="plop") - response = flexmock(status_code=200, json=lambda: payload) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, 666) + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_raises_exception_with_if_data_status_is_false(self, requests_mock): + data = a_response_data(status=False, + error_code=1, + error_message='invalid method specified: client.miss', + data="schwifty") + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=False) - flexmock(requests).should_receive("get").with_args( - url=self.url, auth=(self.username, self.password), timeout=666, params={'method': 'uber.method_list'} - ).and_return(response) + self.expect_a_ubersmith_call_post(requests_mock, method='client.miss', returning=data) + assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) - ubersmith_api.uber.method_list() - - def test_api_http_default_timeout(self): - payload = dict(status=True, data="plop") - response = flexmock(status_code=200, json=lambda: payload) - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - - flexmock(requests).should_receive("get").with_args( - url=self.url, auth=(self.username, self.password), timeout=60, params={'method': 'uber.method_list'} - ).and_return(response) - - ubersmith_api.uber.method_list() - - @requests_mock.mock() - def test_api_http_post_method_raises_on_result_500(self, request_mock): - json_data = { - 'data': '778', - 'error_code': None, - 'error_message': '', - 'status': False - } - - self.expect_a_ubersmith_call_post( - request_mock, - method='support.ticket_submit', - response_body=json_data, - ) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - - assert_that(calling(ubersmith_api.support.ticket_submit.http_post), - raises(ubersmith_client.exceptions.UbersmithException)) - - def expect_a_ubersmith_call(self, request_mock, method, data=None, status_code=200, **kwargs): - url = self.url + '?' + get_url_params(method, **kwargs) - headers = { - 'Content-Type': 'application/json', - } - request_mock.get(url, json=data, headers=headers, request_headers={ - 'Authorization': self.get_auth_header() - }, status_code=status_code) - - def expect_a_ubersmith_call_post(self, request_mock, method, response_body=None, status_code=200, **kwargs): - headers = { - 'Content-Type': 'application/json', - } - expected_text = get_url_params(method, **kwargs) + def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): + response = MagicMock(status_code=200) + requests_mock.get = MagicMock(return_value=response) + response.json = MagicMock(return_value=returning) - def response_callback(request, context): - expected_params = urllib_parse.parse_qs(expected_text) - parsed_params = urllib_parse.parse_qs(request.text) - assert_that(parsed_params, equal_to(expected_params)) - return response_body + def assert_called_with(): + requests_mock.get.assert_called_with(auth=self.auth, params=kwargs, timeout=self.timeout, url=self.url) + response.json.assert_called_with() - request_mock.post(self.url, json=response_callback, headers=headers, request_headers={ - 'Authorization': self.get_auth_header(), - 'Content-Type': 'application/x-www-form-urlencoded' - }, status_code=status_code) + return assert_called_with - def get_auth_header(self): - auth = base64.b64encode((self.username + ':' + self.password).encode('utf-8')) - return 'Basic ' + auth.decode('utf-8') + def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_code=200, **kwargs): + response = MagicMock(status_code=status_code) + requests_mock.post = MagicMock(return_value=response) + response.json = MagicMock(return_value=returning) + def assert_called_with(): + requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs) + response.json.assert_called_with() -def get_url_params(method, **kwargs): - kwargs['method'] = method - return urllib_parse.urlencode(kwargs) + return assert_called_with diff --git a/tests/exceptions_test.py b/tests/exceptions_test.py deleted file mode 100644 index affb3f1..0000000 --- a/tests/exceptions_test.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest -from ubersmith_client.exceptions import UbersmithException - - -class ExceptionTestTest(unittest.TestCase): - - def test_UbersmithException_has_string_representation(self): - ex = UbersmithException(code=42, message='Wubba Lubba Dub Dub') - self.assertEquals("{0}".format(ex), "Error code 42 - message: Wubba Lubba Dub Dub") diff --git a/tests/ubersmith_json/__init__.py b/tests/ubersmith_json/__init__.py index f44d680..a5b5ca9 100644 --- a/tests/ubersmith_json/__init__.py +++ b/tests/ubersmith_json/__init__.py @@ -10,4 +10,4 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file +# limitations under the License. diff --git a/tests/ubersmith_json/response_data_structure.py b/tests/ubersmith_json/response_data_structure.py index 56ea561..216fe13 100644 --- a/tests/ubersmith_json/response_data_structure.py +++ b/tests/ubersmith_json/response_data_structure.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from tests import apply_kwargs + def a_response_data(**overrides): return apply_kwargs(overrides, { @@ -20,3 +20,12 @@ def a_response_data(**overrides): "error_message": "", "data": {}, }) + + +def apply_kwargs(kwargs, default_kwargs): + for k, v in kwargs.items(): + if isinstance(v, dict): + default_kwargs[k] = apply_kwargs(v, default_kwargs[k]) + else: + default_kwargs[k] = v + return default_kwargs diff --git a/tests/ubersmith_request_test.py b/tests/ubersmith_request_test.py new file mode 100644 index 0000000..e3bf5a2 --- /dev/null +++ b/tests/ubersmith_request_test.py @@ -0,0 +1,59 @@ +# Copyright 2016 Internap. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +from mock import Mock + +from hamcrest import assert_that, raises, calling + +from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized +from tests.ubersmith_json.response_data_structure import a_response_data +from ubersmith_client.ubersmith_request import UbersmithRequest + + +class UbersmithRequestTest(unittest.TestCase): + def test_process_ubersmith_response(self): + response = Mock() + response.status_code = 200 + json_data = { + 'client_id': '1', + 'first': 'Rick', + 'last': 'Sanchez', + 'company': 'Wubba lubba dub dub!' + } + + response.json = Mock(return_value=a_response_data(data=json_data)) + + self.assertDictEqual(json_data, UbersmithRequest.process_ubersmith_response(response)) + + def test_process_ubersmith_response_raise_exception(self): + response = Mock() + response.status_code = 400 + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(BadRequest)) + + response.status_code = 401 + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(Unauthorized)) + + response.status_code = 403 + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(Forbidden)) + + response.status_code = 404 + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(NotFound)) + + response.status_code = 500 + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(UnknownError)) + + response.status_code = 200 + response.json = Mock(return_value={'status': False, 'error_code': 42, 'error_message': 'come and watch tv'}) + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), + raises(UbersmithException, "Error code 42 - message: come and watch tv")) diff --git a/ubersmith_client/__init__.py b/ubersmith_client/__init__.py index 6daacf5..5921267 100644 --- a/ubersmith_client/__init__.py +++ b/ubersmith_client/__init__.py @@ -10,6 +10,7 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and +# limitations under the License. from . import api from . import exceptions diff --git a/ubersmith_client/api.py b/ubersmith_client/api.py index be7c66d..c6c39ba 100644 --- a/ubersmith_client/api.py +++ b/ubersmith_client/api.py @@ -11,84 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import requests -from ubersmith_client.exceptions import UbersmithException, get_exception_for +from ubersmith_client.ubersmith_api import UbersmithApi -def init(url, user, password, timeout=60, use_http_post=False): - return UbersmithApi(url, user, password, timeout, use_http_post) - - -class UbersmithApi(object): - def __init__(self, url, user, password, timeout, use_http_post): - self.url = url - self.user = user - self.password = password - self.timeout = timeout - self.use_http_post = use_http_post - - def __getattr__(self, module): - return UbersmithRequest(self.url, self.user, self.password, module, self.timeout, self.use_http_post) - - -class UbersmithRequest(object): - def __init__(self, url, user, password, module, timeout, use_http_post): - self.url = url - self.user = user - self.password = password - self.module = module - self.methods = [] - self.http_methods = {'GET': 'get', 'POST': 'post'} - self.timeout = timeout - self.use_http_post = use_http_post - - def __getattr__(self, function): - self.methods.append(function) - return self - - def __call__(self, **kwargs): - if self.use_http_post: - return self.http_post(**kwargs) - else: - return self.http_get(**kwargs) - - def process_request(self, http_method, **kwargs): - callable_http_method = getattr(requests, http_method) - - response = callable_http_method( - self.url, - auth=(self.user, self.password), - timeout=self.timeout, - **kwargs - ) - - if response.status_code < 200 or response.status_code >= 400: - raise get_exception_for(status_code=response.status_code) - - response_json = response.json() - if not response_json['status']: - raise UbersmithException( - response_json['error_code'], - response_json['error_message'] - ) - - return response.json()["data"] - - def http_get(self, **kwargs): - self._build_request_params(kwargs) - - response = self.process_request(self.http_methods.get('GET'), params=kwargs) - - return response - - def http_post(self, **kwargs): - self._build_request_params(kwargs) - - response = self.process_request(self.http_methods.get('POST'), data=kwargs) - - return response - - def _build_request_params(self, kwargs): - _methods = ".".join(self.methods) - kwargs['method'] = "{0}.{1}".format(self.module, _methods) +def init(url, user, password, timeout=60, use_http_get=False): + return UbersmithApi(url, user, password, timeout, use_http_get) diff --git a/ubersmith_client/ubersmith_api.py b/ubersmith_client/ubersmith_api.py new file mode 100644 index 0000000..4731bc5 --- /dev/null +++ b/ubersmith_client/ubersmith_api.py @@ -0,0 +1,26 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from ubersmith_client.ubersmith_request_get import UbersmithRequestGet +from ubersmith_client.ubersmith_request_post import UbersmithRequestPost + + +class UbersmithApi(object): + def __init__(self, url, user, password, timeout, use_http_get): + self.url = url + self.user = user + self.password = password + self.timeout = timeout + self.ubersmith_request = UbersmithRequestGet if use_http_get else UbersmithRequestPost + + def __getattr__(self, module): + return self.ubersmith_request(self.url, self.user, self.password, module, self.timeout) diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py new file mode 100644 index 0000000..d02785d --- /dev/null +++ b/ubersmith_client/ubersmith_request.py @@ -0,0 +1,51 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from abc import abstractmethod + +from ubersmith_client.exceptions import get_exception_for, UbersmithException + + +class UbersmithRequest(object): + def __init__(self, url, user, password, module, timeout): + self.url = url + self.user = user + self.password = password + self.module = module + self.methods = [] + self.timeout = timeout + + def __getattr__(self, function): + self.methods.append(function) + return self + + @abstractmethod + def __call__(self, **kwargs): + raise + + def _build_request_params(self, kwargs): + _methods = ".".join(self.methods) + kwargs['method'] = "{0}.{1}".format(self.module, _methods) + + @staticmethod + def process_ubersmith_response(response): + if response.status_code < 200 or response.status_code >= 400: + raise get_exception_for(status_code=response.status_code) + + response_json = response.json() + if not response_json['status']: + raise UbersmithException( + response_json['error_code'], + response_json['error_message'] + ) + + return response.json()["data"] diff --git a/ubersmith_client/ubersmith_request_get.py b/ubersmith_client/ubersmith_request_get.py new file mode 100644 index 0000000..c7d48e0 --- /dev/null +++ b/ubersmith_client/ubersmith_request_get.py @@ -0,0 +1,31 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import requests + +from ubersmith_client.ubersmith_request import UbersmithRequest + + +class UbersmithRequestGet(UbersmithRequest): + def __getattr__(self, function): + self.methods.append(function) + return self + + def __call__(self, **kwargs): + self._build_request_params(kwargs) + + response = requests.get(url=self.url, + auth=(self.user, self.password), + timeout=self.timeout, + params=kwargs) + + return UbersmithRequest.process_ubersmith_response(response) diff --git a/ubersmith_client/ubersmith_request_post.py b/ubersmith_client/ubersmith_request_post.py new file mode 100644 index 0000000..73ad2a8 --- /dev/null +++ b/ubersmith_client/ubersmith_request_post.py @@ -0,0 +1,27 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import requests + +from ubersmith_client.ubersmith_request import UbersmithRequest + + +class UbersmithRequestPost(UbersmithRequest): + def __call__(self, **kwargs): + self._build_request_params(kwargs) + + response = requests.post(url=self.url, + auth=(self.user, self.password), + timeout=self.timeout, + data=kwargs) + + return UbersmithRequest.process_ubersmith_response(response) From 6bf8f0a2284e4b9be477be7ebb497f9d2381d393 Mon Sep 17 00:00:00 2001 From: Maxime Belanger Date: Thu, 31 Mar 2016 14:13:34 -0400 Subject: [PATCH 09/22] Added more Ubersmith exceptions: - Timeout Exceptions - Connection Errors --- tests/api_test.py | 120 +++------------------------ tests/ubersmith_request_get_test.py | 76 +++++++++++++++++ tests/ubersmith_request_post_test.py | 87 +++++++++++++++++++ ubersmith_client/exceptions.py | 11 +++ ubersmith_client/ubersmith_api.py | 10 ++- 5 files changed, 195 insertions(+), 109 deletions(-) create mode 100644 tests/ubersmith_request_get_test.py create mode 100644 tests/ubersmith_request_post_test.py diff --git a/tests/api_test.py b/tests/api_test.py index 5bd2bb9..82e11f8 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import unittest -from mock import patch, MagicMock +from hamcrest import assert_that, raises, calling +from mock import Mock +from requests.exceptions import ConnectionError, Timeout -from hamcrest import assert_that, equal_to, raises, calling - -from tests.ubersmith_json.response_data_structure import a_response_data import ubersmith_client +from ubersmith_client.exceptions import UbersmithConnectionError, UbersmithTimeout class ApiTest(unittest.TestCase): @@ -26,110 +26,14 @@ def setUp(self): self.username = 'admin' self.password = 'test' - self.auth = (self.username, self.password) - self.timeout = 60 - - @patch('ubersmith_client.ubersmith_request_get.requests') - def test_api_get_method_returns_without_arguments(self, requests_mock): - json_data = { - 'company': 'council of ricks' - } - expected_call = self.expect_a_ubersmith_call(requests_mock=requests_mock, - method='client.list', - returning=a_response_data(data=json_data)) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) - response = ubersmith_api.client.list() - - assert_that(response, equal_to(json_data)) - - expected_call() - - @patch('ubersmith_client.ubersmith_request_get.requests') - def test_api_get_method_returns_with_arguments(self, request_mock): - json_data = { - 'group_id': '1', - 'client_id': '30001', - 'assignment_count': '1' - } - expected_call = self.expect_a_ubersmith_call(requests_mock=request_mock, - method='device.ip_group_list', - fac_id=1, - client_id=30001, - returning=a_response_data(data=json_data)) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) - response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) - - assert_that(response, equal_to(json_data)) - - expected_call() - - @patch('ubersmith_client.ubersmith_request_post.requests') - def test_api_post_method_returns_with_arguments(self, request_mock): - json_data = { - 'group_id': '1', - 'client_id': '30001', - 'assignment_count': '1' - } - expected_call = self.expect_a_ubersmith_call_post(requests_mock=request_mock, - method='device.ip_group_list', - fac_id=1, - client_id=30001, - returning=a_response_data(data=json_data)) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=False) - response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) - - assert_that(response, equal_to(json_data)) - - expected_call() - - @patch('ubersmith_client.ubersmith_request_post.requests') - def test_api_post_method_returns_without_arguments(self, requests_mock): - json_data = { - 'company': 'schwifty' - } - expected_call = self.expect_a_ubersmith_call_post(requests_mock=requests_mock, - method='client.list', - returning=a_response_data(data=json_data)) - - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=False) - response = ubersmith_api.client.list() - - assert_that(response, equal_to(json_data)) - - expected_call() - - @patch('ubersmith_client.ubersmith_request_post.requests') - def test_api_raises_exception_with_if_data_status_is_false(self, requests_mock): - data = a_response_data(status=False, - error_code=1, - error_message='invalid method specified: client.miss', - data="schwifty") - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=False) - - self.expect_a_ubersmith_call_post(requests_mock, method='client.miss', returning=data) - assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) - - def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): - response = MagicMock(status_code=200) - requests_mock.get = MagicMock(return_value=response) - response.json = MagicMock(return_value=returning) - - def assert_called_with(): - requests_mock.get.assert_called_with(auth=self.auth, params=kwargs, timeout=self.timeout, url=self.url) - response.json.assert_called_with() - - return assert_called_with + def test_api_method_returns_handle_connection_error_exception(self): + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + ubersmith_api.ubersmith_request = Mock(side_effect=ConnectionError()) - def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_code=200, **kwargs): - response = MagicMock(status_code=status_code) - requests_mock.post = MagicMock(return_value=response) - response.json = MagicMock(return_value=returning) + assert_that(calling(ubersmith_api.__getattr__).with_args("client"), raises(UbersmithConnectionError)) - def assert_called_with(): - requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs) - response.json.assert_called_with() + def test_api_method_returns_handle_timeout_exception(self): + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + ubersmith_api.ubersmith_request = Mock(side_effect=Timeout()) - return assert_called_with + assert_that(calling(ubersmith_api.__getattr__).with_args("client"), raises(UbersmithTimeout)) diff --git a/tests/ubersmith_request_get_test.py b/tests/ubersmith_request_get_test.py new file mode 100644 index 0000000..155a976 --- /dev/null +++ b/tests/ubersmith_request_get_test.py @@ -0,0 +1,76 @@ +# Copyright 2016 Internap. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +from hamcrest import assert_that, equal_to +from mock import patch, MagicMock + +import ubersmith_client +from tests.ubersmith_json.response_data_structure import a_response_data + + +class UbersmithRequestGetTest(unittest.TestCase): + def setUp(self): + self.url = 'http://ubersmith.example.com/' + self.username = 'admin' + self.password = 'test' + + self.auth = (self.username, self.password) + self.timeout = 60 + + @patch('ubersmith_client.ubersmith_request_get.requests') + def test_api_get_method_returns_without_arguments(self, requests_mock): + json_data = { + 'company': 'council of ricks' + } + expected_call = self.expect_a_ubersmith_call(requests_mock=requests_mock, + method='client.list', + returning=a_response_data(data=json_data)) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) + response = ubersmith_api.client.list() + + assert_that(response, equal_to(json_data)) + + expected_call() + + @patch('ubersmith_client.ubersmith_request_get.requests') + def test_api_get_method_returns_with_arguments(self, request_mock): + json_data = { + 'group_id': '1', + 'client_id': '30001', + 'assignment_count': '1' + } + expected_call = self.expect_a_ubersmith_call(requests_mock=request_mock, + method='device.ip_group_list', + fac_id=1, + client_id=30001, + returning=a_response_data(data=json_data)) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) + response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) + + assert_that(response, equal_to(json_data)) + + expected_call() + + def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): + response = MagicMock(status_code=200) + requests_mock.get = MagicMock(return_value=response) + response.json = MagicMock(return_value=returning) + + def assert_called_with(): + requests_mock.get.assert_called_with(auth=self.auth, params=kwargs, timeout=self.timeout, url=self.url) + response.json.assert_called_with() + + return assert_called_with diff --git a/tests/ubersmith_request_post_test.py b/tests/ubersmith_request_post_test.py new file mode 100644 index 0000000..81417db --- /dev/null +++ b/tests/ubersmith_request_post_test.py @@ -0,0 +1,87 @@ +# Copyright 2016 Internap. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +from hamcrest import assert_that, equal_to, calling, raises +from mock import patch, MagicMock + +import ubersmith_client +from tests.ubersmith_json.response_data_structure import a_response_data + + +class UbersmithRequestPostTest(unittest.TestCase): + def setUp(self): + self.url = 'http://ubersmith.example.com/' + self.username = 'admin' + self.password = 'test' + + self.auth = (self.username, self.password) + self.timeout = 60 + + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_post_method_returns_with_arguments(self, request_mock): + json_data = { + 'group_id': '1', + 'client_id': '30001', + 'assignment_count': '1' + } + expected_call = self.expect_a_ubersmith_call_post(requests_mock=request_mock, + method='device.ip_group_list', + fac_id=1, + client_id=30001, + returning=a_response_data(data=json_data)) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + response = ubersmith_api.device.ip_group_list(fac_id=1, client_id=30001) + + assert_that(response, equal_to(json_data)) + + expected_call() + + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_post_method_returns_without_arguments(self, requests_mock): + json_data = { + 'company': 'schwifty' + } + expected_call = self.expect_a_ubersmith_call_post(requests_mock=requests_mock, + method='client.list', + returning=a_response_data(data=json_data)) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + response = ubersmith_api.client.list() + + assert_that(response, equal_to(json_data)) + + expected_call() + + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_raises_exception_with_if_data_status_is_false(self, requests_mock): + data = a_response_data(status=False, + error_code=1, + error_message='invalid method specified: client.miss', + data="schwifty") + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + + self.expect_a_ubersmith_call_post(requests_mock, method='client.miss', returning=data) + assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) + + def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_code=200, **kwargs): + response = MagicMock(status_code=status_code) + requests_mock.post = MagicMock(return_value=response) + response.json = MagicMock(return_value=returning) + + def assert_called_with(): + requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs) + response.json.assert_called_with() + + return assert_called_with diff --git a/ubersmith_client/exceptions.py b/ubersmith_client/exceptions.py index bfb0fd5..08b075d 100644 --- a/ubersmith_client/exceptions.py +++ b/ubersmith_client/exceptions.py @@ -60,3 +60,14 @@ def __init__(self): class UnknownError(UbersmithException): def __init__(self, code): super(UnknownError, self).__init__(code=code, message='An unknown error occurred') + + +class UbersmithConnectionError(UbersmithException): + def __init__(self, url): + super(UbersmithConnectionError, self).__init__(message="Could not connect to {0}".format(url)) + + +class UbersmithTimeout(UbersmithException): + def __init__(self, url, timeout): + super(UbersmithTimeout, self)\ + .__init__(message='Trying to connect to {url} times out after {timeout}'.format(url=url, timeout=timeout)) diff --git a/ubersmith_client/ubersmith_api.py b/ubersmith_client/ubersmith_api.py index 4731bc5..aaacf46 100644 --- a/ubersmith_client/ubersmith_api.py +++ b/ubersmith_client/ubersmith_api.py @@ -10,6 +10,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from requests.exceptions import ConnectionError, Timeout + +from ubersmith_client.exceptions import UbersmithConnectionError, UbersmithTimeout from ubersmith_client.ubersmith_request_get import UbersmithRequestGet from ubersmith_client.ubersmith_request_post import UbersmithRequestPost @@ -23,4 +26,9 @@ def __init__(self, url, user, password, timeout, use_http_get): self.ubersmith_request = UbersmithRequestGet if use_http_get else UbersmithRequestPost def __getattr__(self, module): - return self.ubersmith_request(self.url, self.user, self.password, module, self.timeout) + try: + return self.ubersmith_request(self.url, self.user, self.password, module, self.timeout) + except ConnectionError: + raise UbersmithConnectionError(self.url) + except Timeout: + raise UbersmithTimeout(self.url, self.timeout) From 693383e88a96d71829fe0b79421558a30d1472ad Mon Sep 17 00:00:00 2001 From: Maxime Belanger Date: Thu, 31 Mar 2016 15:52:02 -0400 Subject: [PATCH 10/22] Refactored broken exception handling --- tests/api_test.py | 39 ---------------------- tests/ubersmith_request_test.py | 27 +++++++++++++-- ubersmith_client/ubersmith_api.py | 10 +----- ubersmith_client/ubersmith_request.py | 13 +++++++- ubersmith_client/ubersmith_request_get.py | 13 +++----- ubersmith_client/ubersmith_request_post.py | 9 ++--- 6 files changed, 48 insertions(+), 63 deletions(-) delete mode 100644 tests/api_test.py diff --git a/tests/api_test.py b/tests/api_test.py deleted file mode 100644 index 82e11f8..0000000 --- a/tests/api_test.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2016 Internap. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import unittest -from hamcrest import assert_that, raises, calling -from mock import Mock -from requests.exceptions import ConnectionError, Timeout - -import ubersmith_client -from ubersmith_client.exceptions import UbersmithConnectionError, UbersmithTimeout - - -class ApiTest(unittest.TestCase): - def setUp(self): - self.url = 'http://ubersmith.example.com/' - self.username = 'admin' - self.password = 'test' - - def test_api_method_returns_handle_connection_error_exception(self): - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - ubersmith_api.ubersmith_request = Mock(side_effect=ConnectionError()) - - assert_that(calling(ubersmith_api.__getattr__).with_args("client"), raises(UbersmithConnectionError)) - - def test_api_method_returns_handle_timeout_exception(self): - ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) - ubersmith_api.ubersmith_request = Mock(side_effect=Timeout()) - - assert_that(calling(ubersmith_api.__getattr__).with_args("client"), raises(UbersmithTimeout)) diff --git a/tests/ubersmith_request_test.py b/tests/ubersmith_request_test.py index e3bf5a2..ef3d0ee 100644 --- a/tests/ubersmith_request_test.py +++ b/tests/ubersmith_request_test.py @@ -12,16 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. import unittest -from mock import Mock + +import ubersmith_client +from mock import Mock, patch from hamcrest import assert_that, raises, calling +from requests.exceptions import ConnectionError, Timeout -from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized +from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized, UbersmithConnectionError, \ + UbersmithTimeout from tests.ubersmith_json.response_data_structure import a_response_data from ubersmith_client.ubersmith_request import UbersmithRequest class UbersmithRequestTest(unittest.TestCase): + def setUp(self): + self.url = 'http://ubersmith.example.com/' + self.username = 'admin' + self.password = 'test' + def test_process_ubersmith_response(self): response = Mock() response.status_code = 200 @@ -57,3 +66,17 @@ def test_process_ubersmith_response_raise_exception(self): response.json = Mock(return_value={'status': False, 'error_code': 42, 'error_message': 'come and watch tv'}) assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(UbersmithException, "Error code 42 - message: come and watch tv")) + + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_method_returns_handle_connection_error_exception(self, requests_mock): + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + requests_mock.post = Mock(side_effect=ConnectionError()) + + assert_that(calling(ubersmith_api.client.list), raises(UbersmithConnectionError)) + + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_method_returns_handle_timeout_exception(self, requests_mock): + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + requests_mock.post = Mock(side_effect=Timeout()) + + assert_that(calling(ubersmith_api.client.list), raises(UbersmithTimeout)) diff --git a/ubersmith_client/ubersmith_api.py b/ubersmith_client/ubersmith_api.py index aaacf46..4731bc5 100644 --- a/ubersmith_client/ubersmith_api.py +++ b/ubersmith_client/ubersmith_api.py @@ -10,9 +10,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from requests.exceptions import ConnectionError, Timeout - -from ubersmith_client.exceptions import UbersmithConnectionError, UbersmithTimeout from ubersmith_client.ubersmith_request_get import UbersmithRequestGet from ubersmith_client.ubersmith_request_post import UbersmithRequestPost @@ -26,9 +23,4 @@ def __init__(self, url, user, password, timeout, use_http_get): self.ubersmith_request = UbersmithRequestGet if use_http_get else UbersmithRequestPost def __getattr__(self, module): - try: - return self.ubersmith_request(self.url, self.user, self.password, module, self.timeout) - except ConnectionError: - raise UbersmithConnectionError(self.url) - except Timeout: - raise UbersmithTimeout(self.url, self.timeout) + return self.ubersmith_request(self.url, self.user, self.password, module, self.timeout) diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py index d02785d..905ccfc 100644 --- a/ubersmith_client/ubersmith_request.py +++ b/ubersmith_client/ubersmith_request.py @@ -11,8 +11,10 @@ # See the License for the specific language governing permissions and # limitations under the License. from abc import abstractmethod +from requests import Timeout, ConnectionError -from ubersmith_client.exceptions import get_exception_for, UbersmithException +from ubersmith_client.exceptions import get_exception_for, UbersmithException, UbersmithConnectionError, \ + UbersmithTimeout class UbersmithRequest(object): @@ -32,6 +34,15 @@ def __getattr__(self, function): def __call__(self, **kwargs): raise + def _process_request(self, method, **kwargs): + try: + return method(**kwargs) + + except ConnectionError: + raise UbersmithConnectionError(self.url) + except Timeout: + raise UbersmithTimeout(self.url, self.timeout) + def _build_request_params(self, kwargs): _methods = ".".join(self.methods) kwargs['method'] = "{0}.{1}".format(self.module, _methods) diff --git a/ubersmith_client/ubersmith_request_get.py b/ubersmith_client/ubersmith_request_get.py index c7d48e0..246278a 100644 --- a/ubersmith_client/ubersmith_request_get.py +++ b/ubersmith_client/ubersmith_request_get.py @@ -16,16 +16,13 @@ class UbersmithRequestGet(UbersmithRequest): - def __getattr__(self, function): - self.methods.append(function) - return self - def __call__(self, **kwargs): self._build_request_params(kwargs) - response = requests.get(url=self.url, - auth=(self.user, self.password), - timeout=self.timeout, - params=kwargs) + response = self._process_request(method=requests.get, + url=self.url, + auth=(self.user, self.password), + timeout=self.timeout, + params=kwargs) return UbersmithRequest.process_ubersmith_response(response) diff --git a/ubersmith_client/ubersmith_request_post.py b/ubersmith_client/ubersmith_request_post.py index 73ad2a8..5c7e74b 100644 --- a/ubersmith_client/ubersmith_request_post.py +++ b/ubersmith_client/ubersmith_request_post.py @@ -19,9 +19,10 @@ class UbersmithRequestPost(UbersmithRequest): def __call__(self, **kwargs): self._build_request_params(kwargs) - response = requests.post(url=self.url, - auth=(self.user, self.password), - timeout=self.timeout, - data=kwargs) + response = self._process_request(method=requests.post, + url=self.url, + auth=(self.user, self.password), + timeout=self.timeout, + data=kwargs) return UbersmithRequest.process_ubersmith_response(response) From 98a338e3259e84ccf6fd8e6c2813acc48fa4dded Mon Sep 17 00:00:00 2001 From: Maxime Belanger Date: Thu, 31 Mar 2016 16:51:31 -0400 Subject: [PATCH 11/22] Handle different content types --- tests/ubersmith_request_get_test.py | 2 +- tests/ubersmith_request_post_test.py | 2 +- tests/ubersmith_request_test.py | 16 ++++++++++------ ubersmith_client/exceptions.py | 2 +- ubersmith_client/ubersmith_request.py | 19 +++++++++++-------- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/tests/ubersmith_request_get_test.py b/tests/ubersmith_request_get_test.py index 155a976..ef0a19e 100644 --- a/tests/ubersmith_request_get_test.py +++ b/tests/ubersmith_request_get_test.py @@ -65,7 +65,7 @@ def test_api_get_method_returns_with_arguments(self, request_mock): expected_call() def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): - response = MagicMock(status_code=200) + response = MagicMock(status_code=200, headers={"content-type": "application/json"}) requests_mock.get = MagicMock(return_value=response) response.json = MagicMock(return_value=returning) diff --git a/tests/ubersmith_request_post_test.py b/tests/ubersmith_request_post_test.py index 81417db..d03bb4b 100644 --- a/tests/ubersmith_request_post_test.py +++ b/tests/ubersmith_request_post_test.py @@ -76,7 +76,7 @@ def test_api_raises_exception_with_if_data_status_is_false(self, requests_mock): assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_code=200, **kwargs): - response = MagicMock(status_code=status_code) + response = MagicMock(status_code=status_code, headers={"content-type": "application/json"}) requests_mock.post = MagicMock(return_value=response) response.json = MagicMock(return_value=returning) diff --git a/tests/ubersmith_request_test.py b/tests/ubersmith_request_test.py index ef3d0ee..2bef6bc 100644 --- a/tests/ubersmith_request_test.py +++ b/tests/ubersmith_request_test.py @@ -16,10 +16,11 @@ import ubersmith_client from mock import Mock, patch -from hamcrest import assert_that, raises, calling +from hamcrest import assert_that, raises, calling, equal_to from requests.exceptions import ConnectionError, Timeout -from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized, UbersmithConnectionError, \ +from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized, \ + UbersmithConnectionError, \ UbersmithTimeout from tests.ubersmith_json.response_data_structure import a_response_data from ubersmith_client.ubersmith_request import UbersmithRequest @@ -32,8 +33,8 @@ def setUp(self): self.password = 'test' def test_process_ubersmith_response(self): - response = Mock() - response.status_code = 200 + response = Mock(status_code=200, headers={"content-type": "application/json"}) + json_data = { 'client_id': '1', 'first': 'Rick', @@ -45,9 +46,12 @@ def test_process_ubersmith_response(self): self.assertDictEqual(json_data, UbersmithRequest.process_ubersmith_response(response)) + def test_process_ubersmith_response_not_application_json(self): + response = Mock(status_code=200, headers={"content-type": "text/html"}, content="42") + assert_that(response.content, equal_to(UbersmithRequest.process_ubersmith_response(response))) + def test_process_ubersmith_response_raise_exception(self): - response = Mock() - response.status_code = 400 + response = Mock(status_code=400, headers={"content-type": "application/json"}) assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(BadRequest)) response.status_code = 401 diff --git a/ubersmith_client/exceptions.py b/ubersmith_client/exceptions.py index 08b075d..ca3159b 100644 --- a/ubersmith_client/exceptions.py +++ b/ubersmith_client/exceptions.py @@ -70,4 +70,4 @@ def __init__(self, url): class UbersmithTimeout(UbersmithException): def __init__(self, url, timeout): super(UbersmithTimeout, self)\ - .__init__(message='Trying to connect to {url} times out after {timeout}'.format(url=url, timeout=timeout)) + .__init__(message='Trying to connect to {url} timed out after {timeout} seconds'.format(url=url, timeout=timeout)) diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py index 905ccfc..3324fbf 100644 --- a/ubersmith_client/ubersmith_request.py +++ b/ubersmith_client/ubersmith_request.py @@ -32,7 +32,7 @@ def __getattr__(self, function): @abstractmethod def __call__(self, **kwargs): - raise + raise AttributeError def _process_request(self, method, **kwargs): try: @@ -52,11 +52,14 @@ def process_ubersmith_response(response): if response.status_code < 200 or response.status_code >= 400: raise get_exception_for(status_code=response.status_code) - response_json = response.json() - if not response_json['status']: - raise UbersmithException( - response_json['error_code'], - response_json['error_message'] - ) + if response.headers['content-type'] == 'application/json': + response_json = response.json() + if not response_json['status']: + raise UbersmithException( + response_json['error_code'], + response_json['error_message'] + ) - return response.json()["data"] + return response.json()["data"] + + return response.content From 239201d3a2f297369b4e88f0c4ba419a4135a28d Mon Sep 17 00:00:00 2001 From: Marc Aubry Date: Thu, 31 Mar 2016 17:12:01 -0400 Subject: [PATCH 12/22] Single quote to be standard. pep8 --- tests/ubersmith_request_get_test.py | 4 ++-- tests/ubersmith_request_post_test.py | 6 +++--- tests/ubersmith_request_test.py | 10 ++++------ ubersmith_client/exceptions.py | 7 ++++--- ubersmith_client/ubersmith_request.py | 6 +++--- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/ubersmith_request_get_test.py b/tests/ubersmith_request_get_test.py index ef0a19e..e14d9db 100644 --- a/tests/ubersmith_request_get_test.py +++ b/tests/ubersmith_request_get_test.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. import unittest + from hamcrest import assert_that, equal_to from mock import patch, MagicMock - import ubersmith_client from tests.ubersmith_json.response_data_structure import a_response_data @@ -65,7 +65,7 @@ def test_api_get_method_returns_with_arguments(self, request_mock): expected_call() def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): - response = MagicMock(status_code=200, headers={"content-type": "application/json"}) + response = MagicMock(status_code=200, headers={'content-type': 'application/json'}) requests_mock.get = MagicMock(return_value=response) response.json = MagicMock(return_value=returning) diff --git a/tests/ubersmith_request_post_test.py b/tests/ubersmith_request_post_test.py index d03bb4b..44d407b 100644 --- a/tests/ubersmith_request_post_test.py +++ b/tests/ubersmith_request_post_test.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. import unittest + from hamcrest import assert_that, equal_to, calling, raises from mock import patch, MagicMock - import ubersmith_client from tests.ubersmith_json.response_data_structure import a_response_data @@ -69,14 +69,14 @@ def test_api_raises_exception_with_if_data_status_is_false(self, requests_mock): data = a_response_data(status=False, error_code=1, error_message='invalid method specified: client.miss', - data="schwifty") + data='schwifty') ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) self.expect_a_ubersmith_call_post(requests_mock, method='client.miss', returning=data) assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_code=200, **kwargs): - response = MagicMock(status_code=status_code, headers={"content-type": "application/json"}) + response = MagicMock(status_code=status_code, headers={'content-type': 'application/json'}) requests_mock.post = MagicMock(return_value=response) response.json = MagicMock(return_value=returning) diff --git a/tests/ubersmith_request_test.py b/tests/ubersmith_request_test.py index 2bef6bc..7220aff 100644 --- a/tests/ubersmith_request_test.py +++ b/tests/ubersmith_request_test.py @@ -15,10 +15,8 @@ import ubersmith_client from mock import Mock, patch - from hamcrest import assert_that, raises, calling, equal_to from requests.exceptions import ConnectionError, Timeout - from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized, \ UbersmithConnectionError, \ UbersmithTimeout @@ -33,7 +31,7 @@ def setUp(self): self.password = 'test' def test_process_ubersmith_response(self): - response = Mock(status_code=200, headers={"content-type": "application/json"}) + response = Mock(status_code=200, headers={'content-type': 'application/json'}) json_data = { 'client_id': '1', @@ -47,11 +45,11 @@ def test_process_ubersmith_response(self): self.assertDictEqual(json_data, UbersmithRequest.process_ubersmith_response(response)) def test_process_ubersmith_response_not_application_json(self): - response = Mock(status_code=200, headers={"content-type": "text/html"}, content="42") + response = Mock(status_code=200, headers={'content-type': 'text/html'}, content='42') assert_that(response.content, equal_to(UbersmithRequest.process_ubersmith_response(response))) def test_process_ubersmith_response_raise_exception(self): - response = Mock(status_code=400, headers={"content-type": "application/json"}) + response = Mock(status_code=400, headers={'content-type': 'application/json'}) assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(BadRequest)) response.status_code = 401 @@ -69,7 +67,7 @@ def test_process_ubersmith_response_raise_exception(self): response.status_code = 200 response.json = Mock(return_value={'status': False, 'error_code': 42, 'error_message': 'come and watch tv'}) assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), - raises(UbersmithException, "Error code 42 - message: come and watch tv")) + raises(UbersmithException, 'Error code 42 - message: come and watch tv')) @patch('ubersmith_client.ubersmith_request_post.requests') def test_api_method_returns_handle_connection_error_exception(self, requests_mock): diff --git a/ubersmith_client/exceptions.py b/ubersmith_client/exceptions.py index ca3159b..1724c44 100644 --- a/ubersmith_client/exceptions.py +++ b/ubersmith_client/exceptions.py @@ -64,10 +64,11 @@ def __init__(self, code): class UbersmithConnectionError(UbersmithException): def __init__(self, url): - super(UbersmithConnectionError, self).__init__(message="Could not connect to {0}".format(url)) + super(UbersmithConnectionError, self).__init__(message='Could not connect to {0}'.format(url)) class UbersmithTimeout(UbersmithException): def __init__(self, url, timeout): - super(UbersmithTimeout, self)\ - .__init__(message='Trying to connect to {url} timed out after {timeout} seconds'.format(url=url, timeout=timeout)) + super(UbersmithTimeout, self) \ + .__init__( + message='Trying to connect to {url} timed out after {timeout} seconds'.format(url=url, timeout=timeout)) diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py index 3324fbf..6c65aad 100644 --- a/ubersmith_client/ubersmith_request.py +++ b/ubersmith_client/ubersmith_request.py @@ -44,8 +44,8 @@ def _process_request(self, method, **kwargs): raise UbersmithTimeout(self.url, self.timeout) def _build_request_params(self, kwargs): - _methods = ".".join(self.methods) - kwargs['method'] = "{0}.{1}".format(self.module, _methods) + _methods = '.'.join(self.methods) + kwargs['method'] = '{0}.{1}'.format(self.module, _methods) @staticmethod def process_ubersmith_response(response): @@ -60,6 +60,6 @@ def process_ubersmith_response(response): response_json['error_message'] ) - return response.json()["data"] + return response.json()['data'] return response.content From b9a1206fabbaed881c565c595a72a713eb016503 Mon Sep 17 00:00:00 2001 From: Marc Aubry Date: Thu, 31 Mar 2016 17:16:49 -0400 Subject: [PATCH 13/22] Now is better than never. --- ubersmith_client/ubersmith_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py index 6c65aad..d77966e 100644 --- a/ubersmith_client/ubersmith_request.py +++ b/ubersmith_client/ubersmith_request.py @@ -60,6 +60,6 @@ def process_ubersmith_response(response): response_json['error_message'] ) - return response.json()['data'] + return response_json['data'] return response.content From 6d99a63e97d8ed8deea672ee82839a1f54bd8124 Mon Sep 17 00:00:00 2001 From: "R. Daneel Olivaw" Date: Thu, 7 Apr 2016 18:59:10 -0400 Subject: [PATCH 14/22] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e104bac..a9b3e73 100755 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ API :user: API username :password: API Password or token - :timeout: api timeout given to requests + :timeout: api timeout given to requests (type: float) *Default:* ``60`` :use_http_get: From 8867c3b2f37f9bdbe0bac21a0f14f8d5865b5260 Mon Sep 17 00:00:00 2001 From: "R. Daneel Olivaw" Date: Sat, 9 Apr 2016 09:21:05 -0400 Subject: [PATCH 15/22] cast 'timeout' to float The 'timeout' value is often given as an integer, but the lib expects a float. This casts the 'timeout' parameter as a float so callers do not have to bother. --- ubersmith_client/ubersmith_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ubersmith_client/ubersmith_api.py b/ubersmith_client/ubersmith_api.py index 4731bc5..8595b31 100644 --- a/ubersmith_client/ubersmith_api.py +++ b/ubersmith_client/ubersmith_api.py @@ -19,7 +19,7 @@ def __init__(self, url, user, password, timeout, use_http_get): self.url = url self.user = user self.password = password - self.timeout = timeout + self.timeout = float(timeout) self.ubersmith_request = UbersmithRequestGet if use_http_get else UbersmithRequestPost def __getattr__(self, module): From 96dceed6cfccb470f08a251087a4e3959ab02758 Mon Sep 17 00:00:00 2001 From: Martin Roy Date: Thu, 26 May 2016 15:26:52 -0400 Subject: [PATCH 16/22] Remove requests requirements Was conflicting with other up-to-date packages --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c7317e0..f229360 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests<=2.9.1 +requests 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 17/22] 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) From 6f50b1bcc639d670859e6d891571b18cb97261a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Tue, 18 Apr 2017 13:17:42 -0400 Subject: [PATCH 18/22] Improve setup.py and requirements.txt --- requirements.txt | 1 + setup.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f229360..39d4f77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ +pbr>=1.8 requests diff --git a/setup.py b/setup.py index b2e43a5..919bcf9 100755 --- a/setup.py +++ b/setup.py @@ -13,10 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/env python from setuptools import setup setup( - setup_requires=["pbr>=1.8"], + setup_requires=['pbr'], pbr=True, ) From 87ecc9dacceb5b4a5ec6bb83e70d845b644f2295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Tue, 18 Apr 2017 13:38:14 -0400 Subject: [PATCH 19/22] Add PEP8 validation and missing copyright headers --- setup.py | 3 +- test-requirements.txt | 3 +- tests/http_utils_test.py | 17 +++++++++-- tests/ubersmith_request_form_encoding_test.py | 14 +++++++++ tests/ubersmith_request_get_test.py | 2 ++ tests/ubersmith_request_post_test.py | 2 ++ tests/ubersmith_request_test.py | 30 +++++++++++-------- tox.ini | 11 ++++++- ubersmith_client/_http_utils.py | 15 ++++++++++ ubersmith_client/ubersmith_api.py | 2 ++ ubersmith_client/ubersmith_request.py | 18 +++++------ ubersmith_client/ubersmith_request_get.py | 2 ++ ubersmith_client/ubersmith_request_post.py | 2 ++ 13 files changed, 93 insertions(+), 28 deletions(-) diff --git a/setup.py b/setup.py index 919bcf9..823b9f8 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ - -# Copyright 2016 Internap +# Copyright 2017 Internap. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/test-requirements.txt b/test-requirements.txt index daf105d..bb0dfc5 100755 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,4 @@ nose==1.2.1 pyhamcrest==1.8.1 -mock==1.3.0 \ No newline at end of file +mock==1.3.0 +flake8 diff --git a/tests/http_utils_test.py b/tests/http_utils_test.py index 52472e2..2c01c50 100644 --- a/tests/http_utils_test.py +++ b/tests/http_utils_test.py @@ -1,4 +1,19 @@ +# Copyright 2017 Internap. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import unittest + from ubersmith_client import _http_utils @@ -51,5 +66,3 @@ def test_with_bools(self): 'true': True, 'false': False }, result) - - diff --git a/tests/ubersmith_request_form_encoding_test.py b/tests/ubersmith_request_form_encoding_test.py index 4572f6f..1337ccc 100644 --- a/tests/ubersmith_request_form_encoding_test.py +++ b/tests/ubersmith_request_form_encoding_test.py @@ -1,3 +1,17 @@ +# Copyright 2017 Internap. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import unittest from mock import sentinel, patch, MagicMock diff --git a/tests/ubersmith_request_get_test.py b/tests/ubersmith_request_get_test.py index e14d9db..3717e8c 100644 --- a/tests/ubersmith_request_get_test.py +++ b/tests/ubersmith_request_get_test.py @@ -11,11 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import unittest from hamcrest import assert_that, equal_to from mock import patch, MagicMock import ubersmith_client + from tests.ubersmith_json.response_data_structure import a_response_data diff --git a/tests/ubersmith_request_post_test.py b/tests/ubersmith_request_post_test.py index 44d407b..1149c0d 100644 --- a/tests/ubersmith_request_post_test.py +++ b/tests/ubersmith_request_post_test.py @@ -11,11 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import unittest from hamcrest import assert_that, equal_to, calling, raises from mock import patch, MagicMock import ubersmith_client + from tests.ubersmith_json.response_data_structure import a_response_data diff --git a/tests/ubersmith_request_test.py b/tests/ubersmith_request_test.py index 7220aff..6d026af 100644 --- a/tests/ubersmith_request_test.py +++ b/tests/ubersmith_request_test.py @@ -11,18 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import unittest import ubersmith_client from mock import Mock, patch from hamcrest import assert_that, raises, calling, equal_to from requests.exceptions import ConnectionError, Timeout -from ubersmith_client.exceptions import UbersmithException, BadRequest, UnknownError, Forbidden, NotFound, Unauthorized, \ - UbersmithConnectionError, \ - UbersmithTimeout -from tests.ubersmith_json.response_data_structure import a_response_data + +from ubersmith_client import exceptions from ubersmith_client.ubersmith_request import UbersmithRequest +from tests.ubersmith_json.response_data_structure import a_response_data + class UbersmithRequestTest(unittest.TestCase): def setUp(self): @@ -50,35 +51,40 @@ def test_process_ubersmith_response_not_application_json(self): def test_process_ubersmith_response_raise_exception(self): response = Mock(status_code=400, headers={'content-type': 'application/json'}) - assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(BadRequest)) + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), + raises(exceptions.BadRequest)) response.status_code = 401 - assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(Unauthorized)) + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), + raises(exceptions.Unauthorized)) response.status_code = 403 - assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(Forbidden)) + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), + raises(exceptions.Forbidden)) response.status_code = 404 - assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(NotFound)) + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), + raises(exceptions.NotFound)) response.status_code = 500 - assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), raises(UnknownError)) + assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), + raises(exceptions.UnknownError)) response.status_code = 200 response.json = Mock(return_value={'status': False, 'error_code': 42, 'error_message': 'come and watch tv'}) assert_that(calling(UbersmithRequest.process_ubersmith_response).with_args(response), - raises(UbersmithException, 'Error code 42 - message: come and watch tv')) + raises(exceptions.UbersmithException, 'Error code 42 - message: come and watch tv')) @patch('ubersmith_client.ubersmith_request_post.requests') def test_api_method_returns_handle_connection_error_exception(self, requests_mock): ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) requests_mock.post = Mock(side_effect=ConnectionError()) - assert_that(calling(ubersmith_api.client.list), raises(UbersmithConnectionError)) + assert_that(calling(ubersmith_api.client.list), raises(exceptions.UbersmithConnectionError)) @patch('ubersmith_client.ubersmith_request_post.requests') def test_api_method_returns_handle_timeout_exception(self, requests_mock): ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) requests_mock.post = Mock(side_effect=Timeout()) - assert_that(calling(ubersmith_api.client.list), raises(UbersmithTimeout)) + assert_that(calling(ubersmith_api.client.list), raises(exceptions.UbersmithTimeout)) diff --git a/tox.ini b/tox.ini index 6f9d47d..ef011f1 100755 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,16 @@ [tox] -envlist = py34,py27 +envlist = py34,py27,pep8 [testenv] deps = -r{toxinidir}/test-requirements.txt commands = nosetests + +[testenv:pep8] +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} + +[flake8] +show-source = True +max-line-length = 120 +exclude = .venv,.git,.tox,dist,doc,*egg,build,ubersmith_client/__init__.py diff --git a/ubersmith_client/_http_utils.py b/ubersmith_client/_http_utils.py index a851b56..df15971 100644 --- a/ubersmith_client/_http_utils.py +++ b/ubersmith_client/_http_utils.py @@ -1,3 +1,18 @@ +# Copyright 2017 Internap. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + def form_encode(data): exploded_data = {} for k, v in data.items(): diff --git a/ubersmith_client/ubersmith_api.py b/ubersmith_client/ubersmith_api.py index 8595b31..2431281 100644 --- a/ubersmith_client/ubersmith_api.py +++ b/ubersmith_client/ubersmith_api.py @@ -1,3 +1,4 @@ +# Copyright 2017 Internap. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -10,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + from ubersmith_client.ubersmith_request_get import UbersmithRequestGet from ubersmith_client.ubersmith_request_post import UbersmithRequestPost diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py index d77966e..4a807fc 100644 --- a/ubersmith_client/ubersmith_request.py +++ b/ubersmith_client/ubersmith_request.py @@ -1,3 +1,4 @@ +# Copyright 2017 Internap. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -10,11 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + from abc import abstractmethod from requests import Timeout, ConnectionError -from ubersmith_client.exceptions import get_exception_for, UbersmithException, UbersmithConnectionError, \ - UbersmithTimeout +from ubersmith_client import exceptions class UbersmithRequest(object): @@ -39,9 +40,9 @@ def _process_request(self, method, **kwargs): return method(**kwargs) except ConnectionError: - raise UbersmithConnectionError(self.url) + raise exceptions.UbersmithConnectionError(self.url) except Timeout: - raise UbersmithTimeout(self.url, self.timeout) + raise exceptions.UbersmithTimeout(self.url, self.timeout) def _build_request_params(self, kwargs): _methods = '.'.join(self.methods) @@ -50,16 +51,13 @@ def _build_request_params(self, kwargs): @staticmethod def process_ubersmith_response(response): if response.status_code < 200 or response.status_code >= 400: - raise get_exception_for(status_code=response.status_code) + raise exceptions.get_exception_for(status_code=response.status_code) if response.headers['content-type'] == 'application/json': response_json = response.json() if not response_json['status']: - raise UbersmithException( - response_json['error_code'], - response_json['error_message'] - ) - + raise exceptions.UbersmithException(response_json['error_code'], + response_json['error_message']) return response_json['data'] return response.content diff --git a/ubersmith_client/ubersmith_request_get.py b/ubersmith_client/ubersmith_request_get.py index 7435139..37d4cce 100644 --- a/ubersmith_client/ubersmith_request_get.py +++ b/ubersmith_client/ubersmith_request_get.py @@ -1,3 +1,4 @@ +# Copyright 2017 Internap. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -10,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import requests from ubersmith_client import _http_utils diff --git a/ubersmith_client/ubersmith_request_post.py b/ubersmith_client/ubersmith_request_post.py index b57abe4..9cc7742 100644 --- a/ubersmith_client/ubersmith_request_post.py +++ b/ubersmith_client/ubersmith_request_post.py @@ -1,3 +1,4 @@ +# Copyright 2017 Internap. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -10,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import requests from ubersmith_client import _http_utils From 9ffe346248698839f294babb8aa3d79614b9c5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Tue, 18 Apr 2017 13:55:51 -0400 Subject: [PATCH 20/22] Send custom user agent (issue #14) --- README.rst | 3 ++- tests/ubersmith_request_form_encoding_test.py | 3 ++- tests/ubersmith_request_get_test.py | 3 ++- tests/ubersmith_request_post_test.py | 3 ++- ubersmith_client/ubersmith_request.py | 1 - ubersmith_client/ubersmith_request_get.py | 1 + ubersmith_client/ubersmith_request_post.py | 1 + 7 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index a9b3e73..b9bb651 100755 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ Ubersmith API Client for Python Usage ----- + .. code:: python import ubersmith_client @@ -20,7 +21,7 @@ Usage >>> 1265 API ---------- +--- **ubersmith_client.api.init(url, user, password, timeout, use_http_get)** :url: diff --git a/tests/ubersmith_request_form_encoding_test.py b/tests/ubersmith_request_form_encoding_test.py index 1337ccc..1f5d747 100644 --- a/tests/ubersmith_request_form_encoding_test.py +++ b/tests/ubersmith_request_form_encoding_test.py @@ -26,7 +26,8 @@ def setUp(self): sentinel.module, sentinel.timeout) self._standard_kwargs = dict(auth=(sentinel.username, sentinel.password), timeout=sentinel.timeout, - url=sentinel.url) + url=sentinel.url, + headers={'user-agent': 'python-ubersmithclient'}) @patch('ubersmith_client.ubersmith_request_get.requests') def test_get_with_list(self, request_mock): diff --git a/tests/ubersmith_request_get_test.py b/tests/ubersmith_request_get_test.py index 3717e8c..aad5f47 100644 --- a/tests/ubersmith_request_get_test.py +++ b/tests/ubersmith_request_get_test.py @@ -72,7 +72,8 @@ def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): response.json = MagicMock(return_value=returning) def assert_called_with(): - requests_mock.get.assert_called_with(auth=self.auth, params=kwargs, timeout=self.timeout, url=self.url) + requests_mock.get.assert_called_with(auth=self.auth, params=kwargs, timeout=self.timeout, url=self.url, + headers={'user-agent': 'python-ubersmithclient'}) response.json.assert_called_with() return assert_called_with diff --git a/tests/ubersmith_request_post_test.py b/tests/ubersmith_request_post_test.py index 1149c0d..edab750 100644 --- a/tests/ubersmith_request_post_test.py +++ b/tests/ubersmith_request_post_test.py @@ -83,7 +83,8 @@ def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_cod response.json = MagicMock(return_value=returning) def assert_called_with(): - requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs) + requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs, + headers={'user-agent': 'python-ubersmithclient'}) response.json.assert_called_with() return assert_called_with diff --git a/ubersmith_client/ubersmith_request.py b/ubersmith_client/ubersmith_request.py index 4a807fc..15abb32 100644 --- a/ubersmith_client/ubersmith_request.py +++ b/ubersmith_client/ubersmith_request.py @@ -38,7 +38,6 @@ def __call__(self, **kwargs): def _process_request(self, method, **kwargs): try: return method(**kwargs) - except ConnectionError: raise exceptions.UbersmithConnectionError(self.url) except Timeout: diff --git a/ubersmith_client/ubersmith_request_get.py b/ubersmith_client/ubersmith_request_get.py index 37d4cce..3a29dc6 100644 --- a/ubersmith_client/ubersmith_request_get.py +++ b/ubersmith_client/ubersmith_request_get.py @@ -27,6 +27,7 @@ def __call__(self, **kwargs): url=self.url, auth=(self.user, self.password), timeout=self.timeout, + headers={'user-agent': 'python-ubersmithclient'}, 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 9cc7742..6bbf2b4 100644 --- a/ubersmith_client/ubersmith_request_post.py +++ b/ubersmith_client/ubersmith_request_post.py @@ -27,6 +27,7 @@ def __call__(self, **kwargs): url=self.url, auth=(self.user, self.password), timeout=self.timeout, + headers={'user-agent': 'python-ubersmithclient'}, data=params) return UbersmithRequest.process_ubersmith_response(response) From 609c9b2da294624c0599a83bfe5de550f502bc68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Tue, 18 Apr 2017 13:58:34 -0400 Subject: [PATCH 21/22] Add flake8 test to travis.yml --- .travis.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index c50f9c0..64146d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,17 @@ language: python + python: -- '2.7' -- '3.4' + - '2.7' + - '3.4' + install: -- pip install -r requirements.txt -- pip install -r test-requirements.txt -script: nosetests + - pip install -r requirements.txt + - pip install -r test-requirements.txt + +script: + - nosetests + - flake8 + deploy: provider: pypi user: internaphosting From 49cc5e2c9f30642b19254a7e058c01c4c03c465e Mon Sep 17 00:00:00 2001 From: Marc Aubry Date: Thu, 9 Nov 2017 15:13:41 -0500 Subject: [PATCH 22/22] Handle file upload * this allow ticket attachements and other misc upload --- tests/http_utils_test.py | 6 ++++ tests/ubersmith_request_get_test.py | 36 +++++++++++++++++-- tests/ubersmith_request_post_test.py | 40 +++++++++++++++++++--- ubersmith_client/_http_utils.py | 4 +++ ubersmith_client/ubersmith_request_get.py | 17 +++++---- ubersmith_client/ubersmith_request_post.py | 18 ++++++---- 6 files changed, 101 insertions(+), 20 deletions(-) diff --git a/tests/http_utils_test.py b/tests/http_utils_test.py index 2c01c50..8f1d959 100644 --- a/tests/http_utils_test.py +++ b/tests/http_utils_test.py @@ -66,3 +66,9 @@ def test_with_bools(self): 'true': True, 'false': False }, result) + + def test_filtering_files(self): + result = _http_utils.form_encode_without_files(dict(true=True, files=dict(attach='some_binary_data'))) + self.assertDictEqual({ + 'true': True, + }, result) diff --git a/tests/ubersmith_request_get_test.py b/tests/ubersmith_request_get_test.py index aad5f47..72b66a6 100644 --- a/tests/ubersmith_request_get_test.py +++ b/tests/ubersmith_request_get_test.py @@ -13,11 +13,10 @@ # limitations under the License. import unittest - from hamcrest import assert_that, equal_to from mock import patch, MagicMock -import ubersmith_client +import ubersmith_client from tests.ubersmith_json.response_data_structure import a_response_data @@ -66,6 +65,26 @@ def test_api_get_method_returns_with_arguments(self, request_mock): expected_call() + @patch('ubersmith_client.ubersmith_request_get.requests') + def test_api_get_support_ticket_submit_allow_file_upload(self, request_mock): + expected_files = {'attach[0]': ('filename.pdf', b'filecontent')} + expected_call = self.expect_a_ubersmith_call_with_files(requests_mock=request_mock, + method='support.ticket_submit', + subject='that I used to know', + body='some body', + returning=a_response_data(data='42'), + files=expected_files) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password, use_http_get=True) + + response = ubersmith_api.support.ticket_submit(subject='that I used to know', + body='some body', + files=expected_files) + + assert_that(response, equal_to('42')) + + expected_call() + def expect_a_ubersmith_call(self, requests_mock, returning=None, **kwargs): response = MagicMock(status_code=200, headers={'content-type': 'application/json'}) requests_mock.get = MagicMock(return_value=response) @@ -77,3 +96,16 @@ def assert_called_with(): response.json.assert_called_with() return assert_called_with + + def expect_a_ubersmith_call_with_files(self, requests_mock, returning=None, files=None, **kwargs): + response = MagicMock(status_code=200, headers={'content-type': 'application/json'}) + requests_mock.get = MagicMock(return_value=response) + response.json = MagicMock(return_value=returning) + + def assert_called_with(): + requests_mock.get.assert_called_with(auth=self.auth, params=kwargs, timeout=self.timeout, url=self.url, + files=files, + headers={'user-agent': 'python-ubersmithclient'}) + response.json.assert_called_with() + + return assert_called_with diff --git a/tests/ubersmith_request_post_test.py b/tests/ubersmith_request_post_test.py index edab750..fdbe1b6 100644 --- a/tests/ubersmith_request_post_test.py +++ b/tests/ubersmith_request_post_test.py @@ -13,11 +13,10 @@ # limitations under the License. import unittest - from hamcrest import assert_that, equal_to, calling, raises from mock import patch, MagicMock -import ubersmith_client +import ubersmith_client from tests.ubersmith_json.response_data_structure import a_response_data @@ -77,13 +76,46 @@ def test_api_raises_exception_with_if_data_status_is_false(self, requests_mock): self.expect_a_ubersmith_call_post(requests_mock, method='client.miss', returning=data) assert_that(calling(ubersmith_api.client.miss), raises(ubersmith_client.exceptions.UbersmithException)) - def expect_a_ubersmith_call_post(self, requests_mock, returning=None, status_code=200, **kwargs): - response = MagicMock(status_code=status_code, headers={'content-type': 'application/json'}) + @patch('ubersmith_client.ubersmith_request_post.requests') + def test_api_post_support_ticket_submit_allow_file_upload(self, request_mock): + expected_files = {'attach[0]': ('filename.pdf', b'filecontent')} + expected_call = self.expect_a_ubersmith_call_post_with_files(requests_mock=request_mock, + method='support.ticket_submit', + subject='that I used to know', + body='some body', + returning=a_response_data(data='42'), + files=expected_files) + + ubersmith_api = ubersmith_client.api.init(self.url, self.username, self.password) + + response = ubersmith_api.support.ticket_submit(subject='that I used to know', + body='some body', + files=expected_files) + + assert_that(response, equal_to('42')) + + expected_call() + + def expect_a_ubersmith_call_post(self, requests_mock, returning=None, **kwargs): + response = MagicMock(status_code=200, headers={'content-type': 'application/json'}) + requests_mock.post = MagicMock(return_value=response) + response.json = MagicMock(return_value=returning) + + def assert_called_with(): + requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs, + headers={'user-agent': 'python-ubersmithclient'}) + response.json.assert_called_with() + + return assert_called_with + + def expect_a_ubersmith_call_post_with_files(self, requests_mock, returning=None, files=None, **kwargs): + response = MagicMock(status_code=200, headers={'content-type': 'application/json'}) requests_mock.post = MagicMock(return_value=response) response.json = MagicMock(return_value=returning) def assert_called_with(): requests_mock.post.assert_called_with(auth=self.auth, timeout=self.timeout, url=self.url, data=kwargs, + files=files, headers={'user-agent': 'python-ubersmithclient'}) response.json.assert_called_with() diff --git a/ubersmith_client/_http_utils.py b/ubersmith_client/_http_utils.py index df15971..0de20b7 100644 --- a/ubersmith_client/_http_utils.py +++ b/ubersmith_client/_http_utils.py @@ -22,6 +22,10 @@ def form_encode(data): return exploded_data +def form_encode_without_files(data): + return form_encode({k: v for k, v in data.items() if k is not 'files'}) + + def _explode_enumerable(k, v): exploded_items = [] if isinstance(v, list) or isinstance(v, tuple): diff --git a/ubersmith_client/ubersmith_request_get.py b/ubersmith_client/ubersmith_request_get.py index 3a29dc6..e35c936 100644 --- a/ubersmith_client/ubersmith_request_get.py +++ b/ubersmith_client/ubersmith_request_get.py @@ -21,13 +21,16 @@ class UbersmithRequestGet(UbersmithRequest): def __call__(self, **kwargs): self._build_request_params(kwargs) - params = _http_utils.form_encode(kwargs) + params = _http_utils.form_encode_without_files(kwargs) + requests_get_args = dict(method=requests.get, + url=self.url, + auth=(self.user, self.password), + timeout=self.timeout, + headers={'user-agent': 'python-ubersmithclient'}, + params=params) + if 'files' in kwargs: + requests_get_args['files'] = kwargs['files'] - response = self._process_request(method=requests.get, - url=self.url, - auth=(self.user, self.password), - timeout=self.timeout, - headers={'user-agent': 'python-ubersmithclient'}, - params=params) + response = self._process_request(**requests_get_args) return UbersmithRequest.process_ubersmith_response(response) diff --git a/ubersmith_client/ubersmith_request_post.py b/ubersmith_client/ubersmith_request_post.py index 6bbf2b4..b2d6cab 100644 --- a/ubersmith_client/ubersmith_request_post.py +++ b/ubersmith_client/ubersmith_request_post.py @@ -21,13 +21,17 @@ class UbersmithRequestPost(UbersmithRequest): def __call__(self, **kwargs): self._build_request_params(kwargs) - params = _http_utils.form_encode(kwargs) + params = _http_utils.form_encode_without_files(kwargs) - response = self._process_request(method=requests.post, - url=self.url, - auth=(self.user, self.password), - timeout=self.timeout, - headers={'user-agent': 'python-ubersmithclient'}, - data=params) + requests_post_args = dict(method=requests.post, + url=self.url, + auth=(self.user, self.password), + timeout=self.timeout, + headers={'user-agent': 'python-ubersmithclient'}, + data=params) + if 'files' in kwargs: + requests_post_args['files'] = kwargs['files'] + + response = self._process_request(**requests_post_args) return UbersmithRequest.process_ubersmith_response(response)