diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d224c..c4bcc52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -Current Version - - +2.6.0 May 17, 2018 + - Added `HTTPResponseError` 2.5.0 December 5, 2017 - Add links resource diff --git a/README.rst b/README.rst index 785b4ec..33761e0 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ key `__. client = Client('sk-XXX') The client will always attempt to raise a ``pybutton.ButtonClientError`` -in an error condition. +or a subclass in an error condition. All API requests will return a ``pybutton.response.Response`` instance, which supports accessing data via the `#data` method. For instance: @@ -45,20 +45,22 @@ which supports accessing data via the `#data` method. For instance: .. code:: python from pybutton import Client - from pybutton import ButtonClientError + from pybutton import ButtonClientError, HTTPResponseError client = Client("sk-XXX") try: response = client.orders.get("btnorder-XXX") + except HTTPResponseError as e: + print('API request failed: http status {}'.format(e.status_code)) except ButtonClientError as e: print(e) + else: + print(response) + # - print(response) - # - - print(response.data()) - # {'status': open, 'btn_ref': None, 'line_items': [], ...} + print(response.data()) + # {'status': open, 'btn_ref': None, 'line_items': [], ...} Configuration diff --git a/pybutton/error.py b/pybutton/error.py index 7414ae3..45076f5 100644 --- a/pybutton/error.py +++ b/pybutton/error.py @@ -7,3 +7,18 @@ class ButtonClientError(Exception): '''An Exception class for all pybutton understood errors. ''' + + +class HTTPResponseError(ButtonClientError): + '''A non-success HTTP response was returned from the remote API. + + The HTTP response status code can be retrieved from the + `.status_code` property. + + The original error object can be retrieved from the + `.cause` property. + ''' + def __init__(self, message, status_code, cause): + super(HTTPResponseError, self).__init__(message) + self.status_code = status_code + self.cause = cause diff --git a/pybutton/resources/resource.py b/pybutton/resources/resource.py index bf0eeca..b8acc28 100644 --- a/pybutton/resources/resource.py +++ b/pybutton/resources/resource.py @@ -8,7 +8,7 @@ import json from pybutton.response import Response -from pybutton.error import ButtonClientError +from pybutton.error import HTTPResponseError from pybutton.version import VERSION from pybutton.request import request from pybutton.request import request_url @@ -38,6 +38,7 @@ class Resource(object): Raises: pybutton.ButtonClientError + pybutton.HTTPResponseError ''' @@ -151,4 +152,4 @@ def _api_request(self, path, method, data=None, query=None): error = json.loads(data).get('error', {}) message = error.get('message', fallback) - raise ButtonClientError(message) + raise HTTPResponseError(message, status_code=e.code, cause=e) diff --git a/pybutton/test/request_test.py b/pybutton/test/request_test.py index 334986b..f67fd4b 100644 --- a/pybutton/test/request_test.py +++ b/pybutton/test/request_test.py @@ -103,8 +103,10 @@ def test_raises_with_invalid_response_data(self, MockRequest, try: request(url, method, headers) self.assertTrue(False) - except ButtonClientError: - pass + except ButtonClientError as e: + # We expect the generic ButtonClientError, and not a subclass, + # in this condition. + assert type(e) is ButtonClientError class RequestTestCasePy3(TestCase): diff --git a/pybutton/test/resources/resource_test.py b/pybutton/test/resources/resource_test.py index a9eb8f4..82970e4 100644 --- a/pybutton/test/resources/resource_test.py +++ b/pybutton/test/resources/resource_test.py @@ -9,7 +9,7 @@ from pybutton.request import HTTPError from pybutton.resources.resource import Resource -from pybutton.error import ButtonClientError +from pybutton.error import HTTPResponseError config = { 'hostname': 'api.usebutton.com', @@ -117,8 +117,10 @@ def side_effect(*args): try: resource._api_request('/v2/api', 'GET', data) self.assertTrue(False) - except ButtonClientError as e: + except HTTPResponseError as e: self.assertEqual(str(e), '404 bloop') + self.assertEqual(e.status_code, 404) + self.assertTrue(e.cause is not None) @patch('pybutton.resources.resource.request') def test_api_request_with_byte_response(self, request): @@ -138,8 +140,10 @@ def side_effect(*args): try: resource._api_request('/v2/api', 'GET', data) self.assertTrue(False) - except ButtonClientError as e: + except HTTPResponseError as e: self.assertEqual(str(e), 'bloop failed') + self.assertEqual(e.status_code, 404) + self.assertTrue(e.cause is not None) @patch('pybutton.resources.resource.request') def test_api_get(self, request): diff --git a/pybutton/version.py b/pybutton/version.py index 8af1c58..3fd9fb7 100644 --- a/pybutton/version.py +++ b/pybutton/version.py @@ -1 +1 @@ -VERSION = '2.5.0' +VERSION = '2.6.0'