From ca7cfe2f10b0d2b7c1250ed419ff31f9209f92ef Mon Sep 17 00:00:00 2001 From: Corentin Ardeois Date: Tue, 22 Mar 2016 14:28:35 -0400 Subject: [PATCH 1/2] 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 2/2] 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))