Skip to content

Commit

Permalink
Merge pull request #179 from betamos/master
Browse files Browse the repository at this point in the history
Add subclasses for HttpError for simplified control flow
  • Loading branch information
cherba29 authored Oct 3, 2017
2 parents 7aff8d8 + 22e9a5c commit ee23913
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 14 deletions.
5 changes: 2 additions & 3 deletions apitools/base/py/base_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,9 +595,8 @@ def __ProcessHttpResponse(self, method_config, http_response, request):
"""Process the given http response."""
if http_response.status_code not in (http_client.OK,
http_client.NO_CONTENT):
raise exceptions.HttpError(
http_response.info, http_response.content,
http_response.request_url, method_config, request)
raise exceptions.HttpError.FromResponse(
http_response, method_config=method_config, request=request)
if http_response.status_code == http_client.NO_CONTENT:
# TODO(craigcitro): Find out why _replace doesn't seem to work
# here.
Expand Down
5 changes: 2 additions & 3 deletions apitools/base/py/base_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,9 @@ def fakeMakeRequest(*unused_args, **unused_kwargs):
service = FakeService(client=client)
request = SimpleMessage()
with mock(base_api.http_wrapper, 'MakeRequest', fakeMakeRequest):
with self.assertRaises(exceptions.HttpError) as error_context:
with self.assertRaises(exceptions.HttpBadRequestError) as err:
service._RunMethod(method_config, request)
http_error = error_context.exception
self.assertEquals(400, http_error.status_code)
http_error = err.exception
self.assertEquals('http://www.google.com', http_error.url)
self.assertEquals('{"field": "abc"}', http_error.content)
self.assertEquals(method_config, http_error.method_config)
Expand Down
49 changes: 42 additions & 7 deletions apitools/base/py/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,43 @@ def status_code(self):
return int(self.response['status'])

@classmethod
def FromResponse(cls, http_response):
return cls(http_response.info, http_response.content,
http_response.request_url)
def FromResponse(cls, http_response, **kwargs):
try:
status_code = int(http_response.info.get('status'))
error_cls = _HTTP_ERRORS.get(status_code, cls)
except ValueError:
error_cls = cls
return error_cls(http_response.info, http_response.content,
http_response.request_url, **kwargs)


class HttpBadRequestError(HttpError):
"""HTTP 400 Bad Request."""


class HttpUnauthorizedError(HttpError):
"""HTTP 401 Unauthorized."""


class HttpForbiddenError(HttpError):
"""HTTP 403 Forbidden."""


class HttpNotFoundError(HttpError):
"""HTTP 404 Not Found."""


class HttpConflictError(HttpError):
"""HTTP 409 Conflict."""


_HTTP_ERRORS = {
400: HttpBadRequestError,
401: HttpUnauthorizedError,
403: HttpForbiddenError,
404: HttpNotFoundError,
409: HttpConflictError,
}


class InvalidUserInputError(InvalidDataError):
Expand Down Expand Up @@ -143,14 +177,15 @@ class RetryAfterError(HttpError):

"""The response contained a retry-after header."""

def __init__(self, response, content, url, retry_after):
super(RetryAfterError, self).__init__(response, content, url)
def __init__(self, response, content, url, retry_after, **kwargs):
super(RetryAfterError, self).__init__(response, content, url, **kwargs)
self.retry_after = int(retry_after)

@classmethod
def FromResponse(cls, http_response):
def FromResponse(cls, http_response, **kwargs):
return cls(http_response.info, http_response.content,
http_response.request_url, http_response.retry_after)
http_response.request_url, http_response.retry_after,
**kwargs)


class BadStatusCodeError(HttpError):
Expand Down
68 changes: 68 additions & 0 deletions apitools/base/py/exceptions_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2017 Google Inc.
#
# 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 unittest2

from apitools.base.py import exceptions
from apitools.base.py import http_wrapper


def _MakeResponse(status_code):
return http_wrapper.Response(
info={'status': status_code}, content='{"field": "abc"}',
request_url='http://www.google.com')


class HttpErrorFromResponseTest(unittest2.TestCase):

"""Tests for exceptions.HttpError.FromResponse."""

def testBadRequest(self):
err = exceptions.HttpError.FromResponse(_MakeResponse(400))
self.assertIsInstance(err, exceptions.HttpError)
self.assertIsInstance(err, exceptions.HttpBadRequestError)
self.assertEquals(err.status_code, 400)

def testUnauthorized(self):
err = exceptions.HttpError.FromResponse(_MakeResponse(401))
self.assertIsInstance(err, exceptions.HttpError)
self.assertIsInstance(err, exceptions.HttpUnauthorizedError)
self.assertEquals(err.status_code, 401)

def testForbidden(self):
err = exceptions.HttpError.FromResponse(_MakeResponse(403))
self.assertIsInstance(err, exceptions.HttpError)
self.assertIsInstance(err, exceptions.HttpForbiddenError)
self.assertEquals(err.status_code, 403)

def testNotFound(self):
err = exceptions.HttpError.FromResponse(_MakeResponse(404))
self.assertIsInstance(err, exceptions.HttpError)
self.assertIsInstance(err, exceptions.HttpNotFoundError)
self.assertEquals(err.status_code, 404)

def testConflict(self):
err = exceptions.HttpError.FromResponse(_MakeResponse(409))
self.assertIsInstance(err, exceptions.HttpError)
self.assertIsInstance(err, exceptions.HttpConflictError)
self.assertEquals(err.status_code, 409)

def testUnknownStatus(self):
err = exceptions.HttpError.FromResponse(_MakeResponse(499))
self.assertIsInstance(err, exceptions.HttpError)
self.assertEquals(err.status_code, 499)

def testMalformedStatus(self):
err = exceptions.HttpError.FromResponse(_MakeResponse('BAD'))
self.assertIsInstance(err, exceptions.HttpError)
5 changes: 4 additions & 1 deletion apitools/scripts/oauth2l_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ class _FakeResponse(object):

def __init__(self, status_code, scopes=None):
self.status_code = status_code
self.info = {
'reason': str(http_client.responses[self.status_code]),
'status': str(self.status_code),
}
if self.status_code == http_client.OK:
self.content = json.dumps({'scope': ' '.join(scopes or [])})
else:
self.content = 'Error'
self.info = str(http_client.responses[self.status_code])
self.request_url = 'some-url'


Expand Down

0 comments on commit ee23913

Please sign in to comment.