21d5ba92e0
Curently tox ignores H405, D105, D200, and D203. H405: multi line docstring summary not separated with an empty line. D105: Missing docstring in magic method. D200: One-line docstring should fit on one line with quotes. D203: 1 blank required before class docstring. This patch removes tox ignores and fix docstrings. Change-Id: Ia3ac9796630e24ce04dc9f38a8d944334d6978d2
431 lines
11 KiB
Python
431 lines
11 KiB
Python
# 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.
|
|
|
|
"""HTTP Exceptions used by keystoneauth1."""
|
|
|
|
import inspect
|
|
import sys
|
|
|
|
import six
|
|
|
|
from keystoneauth1.exceptions import base
|
|
|
|
|
|
__all__ = ('HttpError',
|
|
|
|
'HTTPClientError',
|
|
'BadRequest',
|
|
'Unauthorized',
|
|
'PaymentRequired',
|
|
'Forbidden',
|
|
'NotFound',
|
|
'MethodNotAllowed',
|
|
'NotAcceptable',
|
|
'ProxyAuthenticationRequired',
|
|
'RequestTimeout',
|
|
'Conflict',
|
|
'Gone',
|
|
'LengthRequired',
|
|
'PreconditionFailed',
|
|
'RequestEntityTooLarge',
|
|
'RequestUriTooLong',
|
|
'UnsupportedMediaType',
|
|
'RequestedRangeNotSatisfiable',
|
|
'ExpectationFailed',
|
|
'UnprocessableEntity',
|
|
|
|
'HttpServerError',
|
|
'InternalServerError',
|
|
'HttpNotImplemented',
|
|
'BadGateway',
|
|
'ServiceUnavailable',
|
|
'GatewayTimeout',
|
|
'HttpVersionNotSupported',
|
|
|
|
'from_response')
|
|
|
|
|
|
class HttpError(base.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,
|
|
retry_after=0):
|
|
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 = "%s (HTTP %s)" % (self.message, self.http_status)
|
|
self.retry_after = retry_after
|
|
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 contains all the classes that have http_status attribute.
|
|
_code_map = dict(
|
|
(getattr(obj, 'http_status', None), obj)
|
|
for name, obj in six.iteritems(vars(sys.modules[__name__]))
|
|
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
|
)
|
|
|
|
|
|
def from_response(response, method, url):
|
|
"""Return 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
|
|
"""
|
|
req_id = response.headers.get("x-openstack-request-id")
|
|
|
|
kwargs = {
|
|
"http_status": response.status_code,
|
|
"response": response,
|
|
"method": method,
|
|
"url": url,
|
|
"request_id": req_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 isinstance(body, dict) and isinstance(body.get("error"), dict):
|
|
error = body["error"]
|
|
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)
|