Skip to content

Commit

Permalink
Exceptions now expose X-Dropbox-Request-Id header.
Browse files Browse the repository at this point in the history
  • Loading branch information
braincore committed Jan 24, 2016
1 parent 002e6df commit 9f83c61
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 34 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@
# built documents.
#
# The short X.Y version.
version = '4.0'
version = '4.0.1'
# The full version, including alpha/beta/rc tags.
release = '4.0'
release = '4.0.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
23 changes: 14 additions & 9 deletions dropbox/dropbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
]

# TODO(kelkabany): We need to auto populate this as done in the v1 SDK.
__version__ = '4.0'
__version__ = '4.0.1'

import contextlib
import json
Expand Down Expand Up @@ -49,11 +49,14 @@ def __init__(self, obj_result, http_resp=None):
class RouteErrorResult(object):
"""The error result of a call to a route."""

def __init__(self, obj_result):
def __init__(self, request_id, obj_result):
"""
:param str request_id: A request_id can be shared with Dropbox Support
to pinpoint the exact request that returns an error.
:param str obj_result: The result of a route not including the binary
payload portion, if one exists.
"""
self.request_id = request_id
self.obj_result = obj_result

class Dropbox(DropboxBase):
Expand Down Expand Up @@ -192,7 +195,8 @@ def request(self,
returned_data_type, obj, strict=False)

if isinstance(res, RouteErrorResult):
raise ApiError(deserialized_result,
raise ApiError(res.request_id,
deserialized_result,
user_message_text,
user_message_locale)
elif route_style == self.ROUTE_STYLE_DOWNLOAD:
Expand Down Expand Up @@ -316,18 +320,19 @@ def request_json_string(self,
verify=True,
)

request_id = r.headers.get('x-dropbox-request-id')
if r.status_code >= 500:
raise InternalServerError(r.status_code, r.text)
raise InternalServerError(request_id, r.status_code, r.text)
elif r.status_code == 400:
raise BadInputError(r.text)
raise BadInputError(request_id, r.text)
elif r.status_code == 401:
assert r.headers.get('content-type') == 'application/json', (
'Expected content-type to be application/json, got %r' %
r.headers.get('content-type'))
raise AuthError(r.json())
raise AuthError(request_id, r.json())
elif r.status_code == 429:
# TODO(kelkabany): Use backoff if provided in response.
raise RateLimitError()
raise RateLimitError(request_id)
elif 200 <= r.status_code <= 299:
if route_style == self.ROUTE_STYLE_DOWNLOAD:
raw_resp = r.headers['dropbox-api-result']
Expand All @@ -342,9 +347,9 @@ def request_json_string(self,
return RouteResult(raw_resp)
elif r.status_code in (403, 404, 409):
raw_resp = r.content.decode('utf-8')
return RouteErrorResult(raw_resp)
return RouteErrorResult(request_id, raw_resp)
else:
raise HttpError(r.status_code, r.text)
raise HttpError(request_id, r.status_code, r.text)

def _get_route_url(self, hostname, route_name):
"""Returns the URL of the route.
Expand Down
47 changes: 26 additions & 21 deletions dropbox/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,84 @@
class DropboxException(Exception):
"""All errors related to making an API request extend this."""
pass

def __init__(self, request_id, *args, **kwargs):
# A request_id can be shared with Dropbox Support to pinpoint the exact
# request that returns an error.
super(DropboxException, self).__init__(request_id, *args, **kwargs)
self.request_id = request_id


class ApiError(DropboxException):
"""Errors produced by the Dropbox API."""

def __init__(self, error, user_message_text, user_message_locale):
def __init__(self, request_id, error, user_message_text, user_message_locale):
"""
:param (str) request_id: A request_id can be shared with Dropbox
Support to pinpoint the exact request that returns an error.
:param error: An instance of the error data type for the route.
:param (str) user_message_text: A human-readable message that can be
displayed to the end user. Is None, if unavailable.
:param (str) user_message_locale: The locale of ``user_message_text``,
if present.
"""
super(ApiError, self).__init__(error)
super(ApiError, self).__init__(request_id, error)
self.error = error
self.user_message_text = user_message_text
self.user_message_locale = user_message_locale

def __repr__(self):
return 'ApiError({})'.format(self.error)
return 'ApiError({!r}, {})'.format(self.request_id, self.error)


class HttpError(DropboxException):
"""Errors produced at the HTTP layer."""

def __init__(self, status_code, body):
super(HttpError, self).__init__(status_code, body)
def __init__(self, request_id, status_code, body):
super(HttpError, self).__init__(request_id, status_code, body)
self.status_code = status_code
self.body = body

def __repr__(self):
return 'HttpError({}, {!r})'.format(self.status_code, self.body)
return 'HttpError({!r}, {}, {!r})'.format(
self.request_id, self.status_code, self.body)


class BadInputError(HttpError):
"""Errors due to bad input parameters to an API Operation."""

def __init__(self, message):
super(BadInputError, self).__init__(400, message)
def __init__(self, request_id, message):
super(BadInputError, self).__init__(request_id, 400, message)
self.message = message

def __repr__(self):
return 'BadInputError({!r})'.format(self.message)
return 'BadInputError({!r}, {!r})'.format(self.request_id, self.message)


class AuthError(HttpError):
"""Errors due to invalid authentication credentials."""

def __init__(self, error):
super(AuthError, self).__init__(401, None)
def __init__(self, request_id, error):
super(AuthError, self).__init__(request_id, 401, None)
self.error = error

def __repr__(self):
return 'AuthError({!r})'.format(self.error)
return 'AuthError({!r}, {!r})'.format(self.request_id, self.error)


class RateLimitError(HttpError):
"""Error caused by rate limiting."""

def __init__(self, backoff=None):
super(RateLimitError, self).__init__(429, None)
def __init__(self, request_id, backoff=None):
super(RateLimitError, self).__init__(request_id, 429, None)
self.backoff = backoff

def __repr__(self):
return 'RateLimitError({!r})'.format(self.backoff)
return 'RateLimitError({!r}, {!r})'.format(self.request_id, self.backoff)


class InternalServerError(HttpError):
"""Errors due to a problem on Dropbox."""

def __init__(self, status_code, message):
self.status_code = status_code
self.message = message

def __repr__(self):
return 'InternalServerError({}, {!r})'.format(self.status_code, self.message)
return 'InternalServerError({!r}, {}, {!r})'.format(
self.request_id, self.status_code, self.body)
2 changes: 1 addition & 1 deletion dropbox/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
else:
url_encode = urllib.urlencode

SDK_VERSION = "4.0"
SDK_VERSION = "4.0.1"

TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

dist = setup(
name='dropbox',
version='4.0',
version='4.0.1',
description='Official Dropbox API Client',
author='Dropbox',
author_email='[email protected]',
Expand Down

0 comments on commit 9f83c61

Please sign in to comment.