diff --git a/.travis.yml b/.travis.yml index e3a7d90..bc0d0f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,10 +10,8 @@ install: - python setup.py install - if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi - if [ "$TRAVIS_PYTHON_VERSION" != "3.2" ]; then travis_retry pip install coverage; fi -- pip install coveralls script: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; else python -m unittest discover; fi -- coverage run tests/test_unit.py notifications: hipchat: rooms: @@ -29,5 +27,3 @@ env: global: - secure: 7j0Ox9ZQss0PewrhGih86Ro0pQJpv23Bb0uABJV7kKN/W37ofrG0m6joamf8EhDDleaPoIqfbARhUpAlqFZF0Uo/5rREqpKkmP4e1zuYMu20VbFv6PXwZ+7ByAuKXN2/Xs54MImlL1+RaduMPNRpbcfT1mdqJgSC+3tVcWodzuRG9RPzxtWYLe93QfwNHV/VMsDPDIY12FZTErbXd/hBCEQXep5rNfK+TtLIGn0ZnS7TktTcD0ld+0ruhunbDjnkpXPVSJDuLaGRpotq0oyaGifnjVM5gVubP+KCL3h24tIXjJ7uI36Eu3EuF4qsg0fmNjuM/WjgwZ9Ta4I2MHlXtFs//qMMArOw5AvPg25adrEwGO4Veh3I3tJGL7hJeM7AZX4rAycXiGIHvpP2G/nX6e/EqRrnFBDOStmBhxEaknLJ/p2Cv6AOvxTMKDo8y+tJY1jp3H1iwCBYyW6KuFKVPDYtu8VLxJunaqNX4LxiJN7VHgvTSgqImjzEy5tVxVt079ciyeznSKKGHLHDAl1ioQpmv/Oyas007A4PKJJAf73go8Yt+GM6qe3K6U3tIBKWL8e0cK1kejk9TLC0D9KXbmhmK81QzpBdQfkrveYi/kucVv0zdrGl+Uy8zcq+vYxceyCdDYcTxCS66bWNFTD2t1dML5gRpdNVVSc27ZM9wtA= - secure: NlSZq/v2vjPQSSjlAbrM1JAfCdBSF/OqmO1HV/7U8HAmyGj7WjAcBkH5qWb5lP/xgUSzP3rEtNBJQNNHHiHHxSY0TtplUkJHrBqZOWGd4nG4GB/w8thj4nOiuok9lQhU2wi4mhRnzw2gGG9XpRpnYqL3a0CWWZ8XilSdL3M1H4fE2rwCSbKo35wpaapAT2BkN/zXeJ62wYX0vsz14EAzRSPlX+zfSo4esjig/B4ubgD1KKq3vRWGX0oU1/b6LYxrRl+OPqql9s3nKa0SuHtzLH4CVM0JTpJ8PxYq/LaLn03evAtgjR3aJJUlXaYL+yVBdATGrtyGUAJTVvRtbWsiaW4KNs+e5eWD+KM1ei18DYHWTMsjRbKLh3DrnUxFSFezMkOgUX4I9aohqPW9q9eTbSi2nR2mEcfDrGPArTZKtmGvx09gil5BAvsYc9A2Ob+TdV0N/bHROdK1R381mY4xWYytZ070+J4YHIKi/AwEJXtYgedc/PDr6fxh9RKDXNybyP2y/i+b72bnij9ZyJc0scDAlRQ4MU/h4cFDohI9quIYpJZ3N3eUeVp7TNX4AT2z+aNj74pBy15eMJv8WYhuBauk3jexhpMQi5yDr7aqlb2/NRyd91oP5QZOcjo7nnPcJp8QyvKtWFeID+c5dV3wcIMeOXmPz1KWWGlJMrV1vZI= -after_success: - coveralls \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b7ba36..31d4af0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,13 +16,13 @@ We use [Milestones](https://github.com/sendgrid/python-http-client/milestones) t ## CLAs and CCLAs -Before you get started, SendGrid requires that a SendGrid Contributor License Agreement (CLA) or a SendGrid Company Contributor Licensing Agreement (CCLA) be filled out by every contributor to a SendGrid open source project. +Before you get started, SendGrid requires that a SendGrid Contributor License Agreement (CLA) be filled out by every contributor to a SendGrid open source project. -Our goal with the CLA and CCLA is to clarify the rights of our contributors and reduce other risks arising from inappropriate contributions. The CLA also clarifies the rights SendGrid holds in each contribution and helps to avoid misunderstandings over what rights each contributor is required to grant to SendGrid when making a contribution. In this way the CLA and CCLA encourage broad participation by our open source community and help us build strong open source projects, free from any individual contributor withholding or revoking rights to any contribution. +Our goal with the CLA is to clarify the rights of our contributors and reduce other risks arising from inappropriate contributions. The CLA also clarifies the rights SendGrid holds in each contribution and helps to avoid misunderstandings over what rights each contributor is required to grant to SendGrid when making a contribution. In this way the CLA encourages broad participation by our open source community and helps us build strong open source projects, free from any individual contributor withholding or revoking rights to any contribution. -SendGrid does not merge a pull request made against a SendGrid open source project until that pull request is associated with a signed CLA (or CCLA). Copies of the CLA and CCLA are available [here](https://drive.google.com/a/sendgrid.com/file/d/0B0PlcM9qA91LN2VEUTJWU2RIVXc/view). +SendGrid does not merge a pull request made against a SendGrid open source project until that pull request is associated with a signed CLA. Copies of the CLA are available [here](https://gist.github.com/SendGridDX/98b42c0a5d500058357b80278fde3be8#file-sendgrid_cla). -You may submit your completed [CLA or CCLA](https://drive.google.com/a/sendgrid.com/file/d/0B0PlcM9qA91LN2VEUTJWU2RIVXc/view) to SendGrid at [dx@sendgrid.com](mailto:dx@sendgrid.com). SendGrid will then confirm you are ready to begin making contributions. +When you create a Pull Request, after a few seconds, a comment will appear with a link to the CLA. Click the link and fill out the brief form and then click the "I agree" button and you are all set. You will not be asked to re-sign the CLA unless we make a change. There are a few ways to contribute, which we'll enumerate below: diff --git a/python_http_client/__init__.py b/python_http_client/__init__.py index 3ff722b..bceb1af 100644 --- a/python_http_client/__init__.py +++ b/python_http_client/__init__.py @@ -1 +1,16 @@ from .client import Client +from .exceptions import ( + HTTPError, + BadRequestsError, + UnauthorizedError, + ForbiddenError, + NotFoundError, + MethodNotAllowedError, + PayloadTooLargeError, + UnsupportedMediaTypeError, + TooManyRequestsError, + InternalServerError, + ServiceUnavailableError, + GatewayTimeoutError +) + diff --git a/python_http_client/client.py b/python_http_client/client.py index dd24c48..83c0423 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -1,14 +1,16 @@ """HTTP Client library""" import json - +from .exceptions import handle_error try: # Python 3 import urllib.request as urllib from urllib.parse import urlencode + from urllib.error import HTTPError except ImportError: # Python 2 import urllib2 as urllib + from urllib2 import HTTPError from urllib import urlencode @@ -135,7 +137,12 @@ def _make_request(self, opener, request): :type request: urllib.Request object :return: urllib response """ - return opener.open(request) + try: + return opener.open(request) + except HTTPError as err: + exc = handle_error(err) + exc.__cause__ = None + raise exc def _(self, name): """Add variable values to the url. diff --git a/python_http_client/exceptions.py b/python_http_client/exceptions.py new file mode 100644 index 0000000..d61d390 --- /dev/null +++ b/python_http_client/exceptions.py @@ -0,0 +1,60 @@ +class HTTPError(Exception): + ''' Base of all other errors''' + def __init__(self,error): + self.status_code = error.code + self.reason = error.reason + self.body = error.read() + self.headers = error.hdrs + +class BadRequestsError(HTTPError): + pass + +class UnauthorizedError(HTTPError): + pass + +class ForbiddenError(HTTPError): + pass + +class NotFoundError(HTTPError): + pass + +class MethodNotAllowedError(HTTPError): + pass + +class PayloadTooLargeError(HTTPError): + pass + +class UnsupportedMediaTypeError(HTTPError): + pass + +class TooManyRequestsError(HTTPError): + pass + +class InternalServerError(HTTPError): + pass + +class ServiceUnavailableError(HTTPError): + pass + +class GatewayTimeoutError(HTTPError): + pass + +err_dict = { 400 : BadRequestsError, + 401 : UnauthorizedError, + 403 : ForbiddenError, + 404 : NotFoundError, + 405 : MethodNotAllowedError, + 413 : PayloadTooLargeError, + 415 : UnsupportedMediaTypeError, + 429 : TooManyRequestsError, + 500 : InternalServerError, + 503 : ServiceUnavailableError, + 504 : GatewayTimeoutError +} + +def handle_error(error): + try: + exc = err_dict[error.code](error) + except KeyError as e: + return HTTPError(error) + return exc diff --git a/tests/test_unit.py b/tests/test_unit.py index f0c1847..d5e8956 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -5,6 +5,14 @@ except ImportError: import unittest from python_http_client.client import Client, Response +from python_http_client.exceptions import ( + handle_error, + HTTPError, + BadRequestsError, + NotFoundError, + UnsupportedMediaTypeError, + ServiceUnavailableError +) try: @@ -21,6 +29,15 @@ basestring = str +class MockException(HTTPError): + def __init__(self,code): + self.code = code + self.reason = 'REASON' + self.hdrs = 'HEADERS' + def read(self): + return 'BODY' + + class MockResponse(urllib.HTTPSHandler): def __init__(self, response_code): @@ -43,7 +60,11 @@ def __init__(self, host, response_code): Client.__init__(self, host) def _make_request(self, opener, request): - return MockResponse(self.response_code) + if 200 <= self.response_code <299: # if successsful code + return MockResponse(self.response_code) + else: + raise handle_error(MockException(self.response_code)) + class TestClient(unittest.TestCase): @@ -148,6 +169,20 @@ def test__getattr__(self): r = mock_client.delete() self.assertEqual(r.status_code, 204) + mock_client.response_code = 400 + self.assertRaises(BadRequestsError,mock_client.get) + + mock_client.response_code = 404 + self.assertRaises(NotFoundError,mock_client.post) + + mock_client.response_code = 415 + self.assertRaises(UnsupportedMediaTypeError,mock_client.patch) + + mock_client.response_code = 503 + self.assertRaises(ServiceUnavailableError,mock_client.delete) + + mock_client.response_code = 523 + self.assertRaises(HTTPError,mock_client.delete) if __name__ == '__main__': unittest.main()