Add apiclient.exceptions hierarchy
The new apiclient.exceptions hierarchy is more comprehensive and it covers almost all HTTP error status codes. These exceptions can be used in novaclient, keystoneclient, glanceclient, and other client projects thus providing a single inteface. Users can have benefit from OpenStack clients raising exceptions of known classes while now every client has its own classes making difficult to catch, e.g., NotFound for every client. Change-Id: Ia7b25880e0ffca3526525a0f0e77c7e77c4f0076
This commit is contained in:
16
keystoneclient/apiclient/__init__.py
Normal file
16
keystoneclient/apiclient/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
442
keystoneclient/apiclient/exceptions.py
Normal file
442
keystoneclient/apiclient/exceptions.py
Normal file
@@ -0,0 +1,442 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MissingArgs(ClientException):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = "Missing argument(s): %s" % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(ClientException):
|
||||
"""Cannot authorize API client."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
"Authentication failed. Missing options: %s" %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified a AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
"AuthSystemNotFound: %s" % repr(auth_system))
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class NoUniqueMatch(ClientException):
|
||||
"""Multiple entities found instead of one."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointException(ClientException):
|
||||
"""Something is rotten in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(EndpointException):
|
||||
"""Could not find requested endpoint in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EmptyCatalog(EndpointNotFound):
|
||||
"""The service catalog is empty."""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
"AmbiguousEndpoints: %s" % repr(endpoints))
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
class HTTPError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions.
|
||||
"""
|
||||
http_status = 0
|
||||
message = "HTTP Error"
|
||||
|
||||
def __init__(self, message=None, details=None,
|
||||
response=None, request_id=None,
|
||||
url=None, method=None, http_status=None):
|
||||
self.http_status = http_status or self.http_status
|
||||
self.message = message or self.message
|
||||
self.details = details
|
||||
self.request_id = request_id
|
||||
self.response = response
|
||||
self.url = url
|
||||
self.method = method
|
||||
formatted_string = "%(message)s (HTTP %(status)s)" % {
|
||||
"message": self.message, "status": self.http_status}
|
||||
if request_id:
|
||||
formatted_string += " (Request-ID: %s)" % request_id
|
||||
super(HTTPError, self).__init__(formatted_string)
|
||||
|
||||
|
||||
class HTTPClientError(HTTPError):
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
"""
|
||||
message = "HTTP Client Error"
|
||||
|
||||
|
||||
class HTTPServerError(HTTPError):
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
"""
|
||||
message = "HTTP Server Error"
|
||||
|
||||
|
||||
class BadRequest(HTTPClientError):
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
"""
|
||||
http_status = 400
|
||||
message = "Bad Request"
|
||||
|
||||
|
||||
class Unauthorized(HTTPClientError):
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
"""
|
||||
http_status = 401
|
||||
message = "Unauthorized"
|
||||
|
||||
|
||||
class PaymentRequired(HTTPClientError):
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
"""
|
||||
http_status = 402
|
||||
message = "Payment Required"
|
||||
|
||||
|
||||
class Forbidden(HTTPClientError):
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
"""
|
||||
http_status = 403
|
||||
message = "Forbidden"
|
||||
|
||||
|
||||
class NotFound(HTTPClientError):
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
"""
|
||||
http_status = 404
|
||||
message = "Not Found"
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPClientError):
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
"""
|
||||
http_status = 405
|
||||
message = "Method Not Allowed"
|
||||
|
||||
|
||||
class NotAcceptable(HTTPClientError):
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
"""
|
||||
http_status = 406
|
||||
message = "Not Acceptable"
|
||||
|
||||
|
||||
class ProxyAuthenticationRequired(HTTPClientError):
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
"""
|
||||
http_status = 407
|
||||
message = "Proxy Authentication Required"
|
||||
|
||||
|
||||
class RequestTimeout(HTTPClientError):
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
"""
|
||||
http_status = 408
|
||||
message = "Request Timeout"
|
||||
|
||||
|
||||
class Conflict(HTTPClientError):
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
"""
|
||||
http_status = 409
|
||||
message = "Conflict"
|
||||
|
||||
|
||||
class Gone(HTTPClientError):
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
"""
|
||||
http_status = 410
|
||||
message = "Gone"
|
||||
|
||||
|
||||
class LengthRequired(HTTPClientError):
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
"""
|
||||
http_status = 411
|
||||
message = "Length Required"
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPClientError):
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
"""
|
||||
http_status = 412
|
||||
message = "Precondition Failed"
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPClientError):
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
"""
|
||||
http_status = 413
|
||||
message = "Request Entity Too Large"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.retry_after = int(kwargs.pop('retry_after'))
|
||||
except (KeyError, ValueError):
|
||||
self.retry_after = 0
|
||||
|
||||
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequestUriTooLong(HTTPClientError):
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
"""
|
||||
http_status = 414
|
||||
message = "Request-URI Too Long"
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPClientError):
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
"""
|
||||
http_status = 415
|
||||
message = "Unsupported Media Type"
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPClientError):
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
The client has asked for a portion of the file, but the server cannot
|
||||
supply that portion.
|
||||
"""
|
||||
http_status = 416
|
||||
message = "Requested Range Not Satisfiable"
|
||||
|
||||
|
||||
class ExpectationFailed(HTTPClientError):
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
"""
|
||||
http_status = 417
|
||||
message = "Expectation Failed"
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPClientError):
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
"""
|
||||
http_status = 422
|
||||
message = "Unprocessable Entity"
|
||||
|
||||
|
||||
class InternalServerError(HTTPServerError):
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
"""
|
||||
http_status = 500
|
||||
message = "Internal Server Error"
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HTTPNotImplemented(HTTPServerError):
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
"""
|
||||
http_status = 501
|
||||
message = "Not Implemented"
|
||||
|
||||
|
||||
class BadGateway(HTTPServerError):
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
The server was acting as a gateway or proxy and received an invalid
|
||||
response from the upstream server.
|
||||
"""
|
||||
http_status = 502
|
||||
message = "Bad Gateway"
|
||||
|
||||
|
||||
class ServiceUnavailable(HTTPServerError):
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
"""
|
||||
http_status = 503
|
||||
message = "Service Unavailable"
|
||||
|
||||
|
||||
class GatewayTimeout(HTTPServerError):
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
The server was acting as a gateway or proxy and did not receive a timely
|
||||
response from the upstream server.
|
||||
"""
|
||||
http_status = 504
|
||||
message = "Gateway Timeout"
|
||||
|
||||
|
||||
class HTTPVersionNotSupported(HTTPServerError):
|
||||
"""HTTP 505 - HTTPVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
http_status = 505
|
||||
message = "HTTP Version Not Supported"
|
||||
|
||||
|
||||
_code_map = dict(
|
||||
(cls.http_status, cls)
|
||||
for cls in itertools.chain(HTTPClientError.__subclasses__(),
|
||||
HTTPServerError.__subclasses__()))
|
||||
|
||||
|
||||
def from_response(response, method, url):
|
||||
"""Returns an instance of :class:`HTTPError` or subclass based on response.
|
||||
|
||||
:param response: instance of `requests.Response` class
|
||||
:param method: HTTP method used for request
|
||||
:param url: URL used for request
|
||||
"""
|
||||
kwargs = {
|
||||
"http_status": response.status_code,
|
||||
"response": response,
|
||||
"method": method,
|
||||
"url": url,
|
||||
"request_id": response.headers.get("x-compute-request-id"),
|
||||
}
|
||||
if "retry-after" in response.headers:
|
||||
kwargs["retry_after"] = response.headers["retry-after"]
|
||||
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type.startswith("application/json"):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if hasattr(body, "keys"):
|
||||
error = body[body.keys()[0]]
|
||||
kwargs["message"] = error.get("message")
|
||||
kwargs["details"] = error.get("details")
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = response.text
|
||||
|
||||
try:
|
||||
cls = _code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = HTTPServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = HTTPClientError
|
||||
else:
|
||||
cls = HTTPError
|
||||
return cls(**kwargs)
|
@@ -4,162 +4,5 @@
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
from keystoneclient.openstack.common import jsonutils
|
||||
|
||||
|
||||
class CommandError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoTokenLookupException(Exception):
|
||||
"""This form of authentication does not support looking up
|
||||
endpoints from an existing token.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(Exception):
|
||||
"""Could not find Service or Region in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EmptyCatalog(Exception):
|
||||
"""The service catalog is empty."""
|
||||
pass
|
||||
|
||||
|
||||
class NoUniqueMatch(Exception):
|
||||
"""Unable to find unique resource."""
|
||||
pass
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
|
||||
def __init__(self, code, message=None, details=None):
|
||||
self.code = code
|
||||
self.message = message or self.__class__.message
|
||||
self.details = details
|
||||
|
||||
def __str__(self):
|
||||
return "%s (HTTP %s)" % (self.message, self.code)
|
||||
|
||||
|
||||
class BadRequest(ClientException):
|
||||
"""HTTP 400 - Bad request: you sent some malformed data."""
|
||||
|
||||
http_status = 400
|
||||
message = "Bad request"
|
||||
|
||||
|
||||
class Unauthorized(ClientException):
|
||||
"""HTTP 401 - Unauthorized: bad credentials."""
|
||||
|
||||
http_status = 401
|
||||
message = "Unauthorized"
|
||||
|
||||
|
||||
class Forbidden(ClientException):
|
||||
"""HTTP 403 - Forbidden: your credentials do not allow access to this
|
||||
resource.
|
||||
"""
|
||||
http_status = 403
|
||||
message = "Forbidden"
|
||||
|
||||
|
||||
class NotFound(ClientException):
|
||||
"""HTTP 404 - Not found."""
|
||||
http_status = 404
|
||||
message = "Not found"
|
||||
|
||||
|
||||
class MethodNotAllowed(ClientException):
|
||||
"""HTTP 405 - Method not allowed."""
|
||||
http_status = 405
|
||||
message = "Method not allowed"
|
||||
|
||||
|
||||
class Conflict(ClientException):
|
||||
"""HTTP 409 - Conflict."""
|
||||
http_status = 409
|
||||
message = "Conflict"
|
||||
|
||||
|
||||
class OverLimit(ClientException):
|
||||
"""HTTP 413 - Over limit: you're over the API limits for this time
|
||||
period.
|
||||
"""
|
||||
http_status = 413
|
||||
message = "Over limit"
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HTTPNotImplemented(ClientException):
|
||||
"""HTTP 501 - Not Implemented: the server does not support this
|
||||
operation.
|
||||
"""
|
||||
http_status = 501
|
||||
message = "Not Implemented"
|
||||
|
||||
|
||||
class ServiceUnavailable(ClientException):
|
||||
"""HTTP 503 - Service Unavailable: The server is currently unavailable."""
|
||||
http_status = 503
|
||||
message = "Service Unavailable"
|
||||
|
||||
|
||||
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
|
||||
# so we can do this:
|
||||
# _code_map = dict((c.http_status, c)
|
||||
# for c in ClientException.__subclasses__())
|
||||
#
|
||||
# Instead, we have to hardcode it:
|
||||
_code_map = dict((c.http_status, c) for c in [BadRequest,
|
||||
Unauthorized,
|
||||
Forbidden,
|
||||
NotFound,
|
||||
MethodNotAllowed,
|
||||
Conflict,
|
||||
OverLimit,
|
||||
HTTPNotImplemented,
|
||||
ServiceUnavailable])
|
||||
|
||||
|
||||
def from_response(response, body=None):
|
||||
"""Return an instance of a ClientException or subclass
|
||||
based on a requests response.
|
||||
|
||||
Usage::
|
||||
|
||||
resp = requests.request(...)
|
||||
if resp.status_code != 200:
|
||||
raise exception_from_response(resp, resp.text)
|
||||
"""
|
||||
cls = _code_map.get(response.status_code, ClientException)
|
||||
if body is None:
|
||||
try:
|
||||
body = jsonutils.loads(response.text)
|
||||
except Exception:
|
||||
body = response.text
|
||||
|
||||
if body:
|
||||
if hasattr(body, 'keys'):
|
||||
error = body[body.keys()[0]]
|
||||
message = error.get('message', None)
|
||||
details = error.get('details', None)
|
||||
else:
|
||||
# If we didn't get back a properly formed error message we
|
||||
# probably couldn't communicate with Keystone at all.
|
||||
message = "Unable to communicate with identity service: %s." % body
|
||||
details = None
|
||||
return cls(code=response.status_code, message=message, details=details)
|
||||
else:
|
||||
return cls(code=response.status_code)
|
||||
#flake8: noqa
|
||||
from keystoneclient.apiclient.exceptions import *
|
||||
|
@@ -120,7 +120,7 @@ class Client(httpclient.HTTPClient):
|
||||
elif resp.status_code == 305:
|
||||
return self._check_keystone_versions(resp['location'])
|
||||
else:
|
||||
raise exceptions.from_response(resp, resp.text)
|
||||
raise exceptions.from_response(resp, "GET", url)
|
||||
except Exception as e:
|
||||
_logger.exception(e)
|
||||
|
||||
@@ -178,7 +178,8 @@ class Client(httpclient.HTTPClient):
|
||||
elif resp.status_code == 305:
|
||||
return self._check_keystone_extensions(resp['location'])
|
||||
else:
|
||||
raise exceptions.from_response(resp, resp.text)
|
||||
raise exceptions.from_response(
|
||||
resp, "GET", "%sextensions" % url)
|
||||
except Exception as e:
|
||||
_logger.exception(e)
|
||||
|
||||
|
@@ -111,7 +111,7 @@ def request(url, method='GET', headers=None, original_ip=None, debug=False,
|
||||
if resp.status_code >= 400:
|
||||
logger.debug("Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
|
67
tests/apiclient/test_exceptions.py
Normal file
67
tests/apiclient/test_exceptions.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from tests import utils
|
||||
|
||||
from keystoneclient.apiclient import exceptions
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
json_data = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
def json(self):
|
||||
return self.json_data
|
||||
|
||||
|
||||
class ExceptionsArgsTest(utils.TestCase):
|
||||
|
||||
def assert_exception(self, ex_cls, method, url, status_code, json_data):
|
||||
ex = exceptions.from_response(
|
||||
FakeResponse(status_code=status_code,
|
||||
headers={"Content-Type": "application/json"},
|
||||
json_data=json_data),
|
||||
method,
|
||||
url)
|
||||
self.assertTrue(isinstance(ex, ex_cls))
|
||||
self.assertEqual(ex.message, json_data["error"]["message"])
|
||||
self.assertEqual(ex.details, json_data["error"]["details"])
|
||||
self.assertEqual(ex.method, method)
|
||||
self.assertEqual(ex.url, url)
|
||||
self.assertEqual(ex.http_status, status_code)
|
||||
|
||||
def test_from_response_known(self):
|
||||
method = "GET"
|
||||
url = "/fake"
|
||||
status_code = 400
|
||||
json_data = {"error": {"message": "fake message",
|
||||
"details": "fake details"}}
|
||||
self.assert_exception(
|
||||
exceptions.BadRequest, method, url, status_code, json_data)
|
||||
|
||||
def test_from_response_unknown(self):
|
||||
method = "POST"
|
||||
url = "/fake-unknown"
|
||||
status_code = 499
|
||||
json_data = {"error": {"message": "fake unknown message",
|
||||
"details": "fake unknown details"}}
|
||||
self.assert_exception(
|
||||
exceptions.HTTPClientError, method, url, status_code, json_data)
|
||||
status_code = 600
|
||||
self.assert_exception(
|
||||
exceptions.HTTPError, method, url, status_code, json_data)
|
@@ -107,7 +107,8 @@ class ClientTest(utils.TestCase):
|
||||
}
|
||||
fake_err_response = utils.TestResponse({
|
||||
"status_code": 400,
|
||||
"text": json.dumps(err_response)
|
||||
"text": json.dumps(err_response),
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
})
|
||||
err_MOCK_REQUEST = mock.Mock(return_value=(fake_err_response))
|
||||
|
||||
|
Reference in New Issue
Block a user