uber_rides/errors.py (82 lines of code) (raw):
# Copyright (c) 2017 Uber Technologies, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
class APIError(Exception):
"""Parent class of all Uber API errors."""
pass
class HTTPError(APIError):
"""Parent class of all HTTP errors."""
def _adapt_response(self, response):
"""Convert error responses to standardized ErrorDetails."""
if response.headers['content-type'] == 'application/json':
body = response.json()
status = response.status_code
if body.get('errors'):
return self._complex_response_to_error_adapter(body)
elif body.get('code') and body.get('message'):
return self._simple_response_to_error_adapter(status, body)
elif body.get('error'):
code = response.reason
return self._message_to_error_adapter(status, code, body)
raise UnknownHttpError(response)
def _complex_response_to_error_adapter(self, body):
"""Convert a list of error responses."""
meta = body.get('meta')
errors = body.get('errors')
e = []
for error in errors:
status = error['status']
code = error['code']
title = error['title']
e.append(ErrorDetails(status, code, title))
return e, meta
def _simple_response_to_error_adapter(self, status, original_body):
"""Convert a single error response."""
body = original_body.copy()
code = body.pop('code')
title = body.pop('message')
meta = body # save whatever is left in the response
e = [ErrorDetails(status, code, title)]
return e, meta
def _message_to_error_adapter(self, status, code, original_body):
"""Convert single string message to error response."""
body = original_body.copy()
title = body.pop('error')
meta = body # save whatever is left in the response
e = [ErrorDetails(status, code, title)]
return e, meta
class ClientError(HTTPError):
"""Raise for 4XX Errors.
Contains an array of ErrorDetails objects.
"""
def __init__(self, response, message=None):
"""
Parameters
response
The 4XX HTTP response.
message
An error message string. If one is not provided, the
default message is used.
"""
if not message:
message = (
'The request contains bad syntax or cannot be filled '
'due to a fault from the client sending the request.'
)
super(ClientError, self).__init__(message)
errors, meta = super(ClientError, self)._adapt_response(response)
self.errors = errors
self.meta = meta
class ServerError(HTTPError):
"""Raise for 5XX Errors.
Contains a single ErrorDetails object.
"""
def __init__(self, response, message=None):
"""
Parameters
response
The 5XX HTTP response.
message
An error message string. If one is not provided, the
default message is used.
"""
if not message:
message = (
'The server encounter an error or is '
'unable to process the request.'
)
super(ServerError, self).__init__(message)
self.error, self.meta = self._adapt_response(response)
def _adapt_response(self, response):
"""Convert various error responses to standardized ErrorDetails."""
errors, meta = super(ServerError, self)._adapt_response(response)
return errors[0], meta # single error instead of array
class ErrorDetails(object):
"""Class to standardize all errors."""
def __init__(self, status, code, title):
self.status = status
self.code = code
self.title = title
def __repr__(self):
return "ErrorDetails: {} {} {}".format(
str(self.status),
str(self.code),
str(self.title)
)
class UnknownHttpError(APIError):
"""Throw when an unknown HTTP error occurs.
Thrown when a previously unseen error is
received and there is no standard schema to convert
it into a well-formed HttpError.
"""
def __init__(self, response):
super(UnknownHttpError, self).__init__()
self.response = response
class UberIllegalState(APIError):
"""Raise for Illegal State Errors.
Thrown when the environment or class is not in an
appropriate state for the requested operation.
"""
pass