error.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # -*- coding: utf-8 -*-
  2. # MinIO Python Library for Amazon S3 Compatible Cloud Storage,
  3. # (C) 2015-2019 MinIO, Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. # pylint: disable=too-many-lines
  17. """
  18. minio.error
  19. ~~~~~~~~~~~~~~~~~~~
  20. This module provides custom exception classes for MinIO library
  21. and API specific errors.
  22. :copyright: (c) 2015, 2016, 2017 by MinIO, Inc.
  23. :license: Apache 2.0, see LICENSE for more details.
  24. """
  25. from __future__ import absolute_import, annotations
  26. from typing import Optional, Type, TypeVar
  27. from xml.etree import ElementTree as ET
  28. try:
  29. from urllib3.response import BaseHTTPResponse # type: ignore[attr-defined]
  30. except ImportError:
  31. from urllib3.response import HTTPResponse as BaseHTTPResponse
  32. from .xml import findtext
  33. class MinioException(Exception):
  34. """Base Minio exception."""
  35. class InvalidResponseError(MinioException):
  36. """Raised to indicate that non-xml response from server."""
  37. def __init__(
  38. self, code: int, content_type: Optional[str], body: Optional[str],
  39. ):
  40. self._code = code
  41. self._content_type = content_type
  42. self._body = body
  43. super().__init__(
  44. f"non-XML response from server; Response code: {code}, "
  45. f"Content-Type: {content_type}, Body: {body}"
  46. )
  47. def __reduce__(self):
  48. return type(self), (self._code, self._content_type, self._body)
  49. class ServerError(MinioException):
  50. """Raised to indicate that S3 service returning HTTP server error."""
  51. def __init__(self, message: str, status_code: int):
  52. self._status_code = status_code
  53. super().__init__(message)
  54. @property
  55. def status_code(self) -> int:
  56. """Get HTTP status code."""
  57. return self._status_code
  58. A = TypeVar("A", bound="S3Error")
  59. class S3Error(MinioException):
  60. """
  61. Raised to indicate that error response is received
  62. when executing S3 operation.
  63. """
  64. response: BaseHTTPResponse
  65. code: Optional[str]
  66. message: Optional[str]
  67. resource: Optional[str]
  68. request_id: Optional[str]
  69. host_id: Optional[str]
  70. bucket_name: Optional[str]
  71. object_name: Optional[str]
  72. _EXC_MUTABLES = {"__traceback__", "__context__", "__cause__"}
  73. def __init__( # pylint: disable=too-many-positional-arguments
  74. self,
  75. response: BaseHTTPResponse,
  76. code: Optional[str],
  77. message: Optional[str],
  78. resource: Optional[str],
  79. request_id: Optional[str],
  80. host_id: Optional[str],
  81. bucket_name: Optional[str] = None,
  82. object_name: Optional[str] = None,
  83. ):
  84. object.__setattr__(self, "response", response)
  85. object.__setattr__(self, "code", code)
  86. object.__setattr__(self, "message", message)
  87. object.__setattr__(self, "resource", resource)
  88. object.__setattr__(self, "request_id", request_id)
  89. object.__setattr__(self, "host_id", host_id)
  90. object.__setattr__(self, "bucket_name", bucket_name)
  91. object.__setattr__(self, "object_name", object_name)
  92. bucket_message = f", bucket_name: {bucket_name}" if bucket_name else ""
  93. object_message = f", object_name: {object_name}" if object_name else ""
  94. super().__init__(
  95. f"S3 operation failed; code: {code}, message: {message}, "
  96. f"resource: {resource}, request_id: {request_id}, "
  97. f"host_id: {host_id}{bucket_message}{object_message}"
  98. )
  99. # freeze after init
  100. object.__setattr__(self, "_is_frozen", True)
  101. def __setattr__(self, name, value):
  102. if name in self._EXC_MUTABLES:
  103. object.__setattr__(self, name, value)
  104. return
  105. if getattr(self, "_is_frozen", False):
  106. raise AttributeError(
  107. f"{self.__class__.__name__} is frozen and "
  108. "does not allow attribute assignment"
  109. )
  110. object.__setattr__(self, name, value)
  111. def __delattr__(self, name):
  112. if name in self._EXC_MUTABLES:
  113. object.__delattr__(self, name)
  114. return
  115. if getattr(self, "_is_frozen", False):
  116. raise AttributeError(
  117. f"{self.__class__.__name__} is frozen and "
  118. "does not allow attribute deletion"
  119. )
  120. object.__delattr__(self, name)
  121. @classmethod
  122. def fromxml(cls: Type[A], response: BaseHTTPResponse) -> A:
  123. """Create new object with values from XML element."""
  124. element = ET.fromstring(response.data.decode())
  125. return cls(
  126. response=response,
  127. code=findtext(element, "Code"),
  128. message=findtext(element, "Message"),
  129. resource=findtext(element, "Resource"),
  130. request_id=findtext(element, "RequestId"),
  131. host_id=findtext(element, "HostId"),
  132. bucket_name=findtext(element, "BucketName"),
  133. object_name=findtext(element, "Key"),
  134. )
  135. def copy(self, code: str, message: str) -> S3Error:
  136. """Make a copy with replaced code and message."""
  137. return S3Error(
  138. response=self.response,
  139. code=code,
  140. message=message,
  141. resource=self.resource,
  142. request_id=self.request_id,
  143. host_id=self.host_id,
  144. bucket_name=self.bucket_name,
  145. object_name=self.object_name,
  146. )
  147. def __repr__(self):
  148. return (
  149. f"S3Error(code={self.code!r}, message={self.message!r}, "
  150. f"resource={self.resource!r}, request_id={self.request_id!r}, "
  151. f"host_id={self.host_id!r}, bucket_name={self.bucket_name!r}, "
  152. f"object_name={self.object_name!r})"
  153. )
  154. def __eq__(self, other):
  155. if not isinstance(other, S3Error):
  156. return NotImplemented
  157. return (
  158. self.code == other.code
  159. and self.message == other.message
  160. and self.resource == other.resource
  161. and self.request_id == other.request_id
  162. and self.host_id == other.host_id
  163. and self.bucket_name == other.bucket_name
  164. and self.object_name == other.object_name
  165. )
  166. def __hash__(self):
  167. return hash(
  168. (
  169. self.code,
  170. self.message,
  171. self.resource,
  172. self.request_id,
  173. self.host_id,
  174. self.bucket_name,
  175. self.object_name,
  176. )
  177. )
  178. class MinioAdminException(Exception):
  179. """Raised to indicate admin API execution error."""
  180. def __init__(self, code: str, body: str):
  181. self._code = code
  182. self._body = body
  183. super().__init__(
  184. f"admin request failed; Status: {code}, Body: {body}",
  185. )
  186. def __reduce__(self):
  187. return type(self), (self._code, self._body)