errors.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # Copyright 2014 Google Inc. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Errors for the library.
  15. All exceptions defined by the library
  16. should be defined in this file.
  17. """
  18. from __future__ import absolute_import
  19. __author__ = "jcgregorio@google.com (Joe Gregorio)"
  20. import json
  21. from googleapiclient import _helpers as util
  22. class Error(Exception):
  23. """Base error for this module."""
  24. pass
  25. class HttpError(Error):
  26. """HTTP data was invalid or unexpected."""
  27. @util.positional(3)
  28. def __init__(self, resp, content, uri=None):
  29. self.resp = resp
  30. if not isinstance(content, bytes):
  31. raise TypeError("HTTP content should be bytes")
  32. self.content = content
  33. self.uri = uri
  34. self.error_details = ""
  35. self.reason = self._get_reason()
  36. @property
  37. def status_code(self):
  38. """Return the HTTP status code from the response content."""
  39. return self.resp.status
  40. def _get_reason(self):
  41. """Calculate the reason for the error from the response content."""
  42. reason = self.resp.reason
  43. try:
  44. try:
  45. data = json.loads(self.content.decode("utf-8"))
  46. except json.JSONDecodeError:
  47. # In case it is not json
  48. data = self.content.decode("utf-8")
  49. if isinstance(data, dict):
  50. reason = data["error"]["message"]
  51. error_detail_keyword = next(
  52. (
  53. kw
  54. for kw in ["detail", "details", "errors", "message"]
  55. if kw in data["error"]
  56. ),
  57. "",
  58. )
  59. if error_detail_keyword:
  60. self.error_details = data["error"][error_detail_keyword]
  61. elif isinstance(data, list) and len(data) > 0:
  62. first_error = data[0]
  63. reason = first_error["error"]["message"]
  64. if "details" in first_error["error"]:
  65. self.error_details = first_error["error"]["details"]
  66. else:
  67. self.error_details = data
  68. except (ValueError, KeyError, TypeError):
  69. pass
  70. if reason is None:
  71. reason = ""
  72. return reason.strip()
  73. def __repr__(self):
  74. if self.error_details:
  75. return '<HttpError %s when requesting %s returned "%s". Details: "%s">' % (
  76. self.resp.status,
  77. self.uri,
  78. self.reason,
  79. self.error_details,
  80. )
  81. elif self.uri:
  82. return '<HttpError %s when requesting %s returned "%s">' % (
  83. self.resp.status,
  84. self.uri,
  85. self.reason,
  86. )
  87. else:
  88. return '<HttpError %s "%s">' % (self.resp.status, self.reason)
  89. __str__ = __repr__
  90. class InvalidJsonError(Error):
  91. """The JSON returned could not be parsed."""
  92. pass
  93. class UnknownFileType(Error):
  94. """File type unknown or unexpected."""
  95. pass
  96. class UnknownLinkType(Error):
  97. """Link type unknown or unexpected."""
  98. pass
  99. class UnknownApiNameOrVersion(Error):
  100. """No API with that name and version exists."""
  101. pass
  102. class UnacceptableMimeTypeError(Error):
  103. """That is an unacceptable mimetype for this operation."""
  104. pass
  105. class MediaUploadSizeError(Error):
  106. """Media is larger than the method can accept."""
  107. pass
  108. class ResumableUploadError(HttpError):
  109. """Error occurred during resumable upload."""
  110. pass
  111. class InvalidChunkSizeError(Error):
  112. """The given chunksize is not valid."""
  113. pass
  114. class InvalidNotificationError(Error):
  115. """The channel Notification is invalid."""
  116. pass
  117. class BatchError(HttpError):
  118. """Error occurred during batch operations."""
  119. @util.positional(2)
  120. def __init__(self, reason, resp=None, content=None):
  121. self.resp = resp
  122. self.content = content
  123. self.reason = reason
  124. def __repr__(self):
  125. if getattr(self.resp, "status", None) is None:
  126. return '<BatchError "%s">' % (self.reason)
  127. else:
  128. return '<BatchError %s "%s">' % (self.resp.status, self.reason)
  129. __str__ = __repr__
  130. class UnexpectedMethodError(Error):
  131. """Exception raised by RequestMockBuilder on unexpected calls."""
  132. @util.positional(1)
  133. def __init__(self, methodId=None):
  134. """Constructor for an UnexpectedMethodError."""
  135. super(UnexpectedMethodError, self).__init__(
  136. "Received unexpected call %s" % methodId
  137. )
  138. class UnexpectedBodyError(Error):
  139. """Exception raised by RequestMockBuilder on unexpected bodies."""
  140. def __init__(self, expected, provided):
  141. """Constructor for an UnexpectedMethodError."""
  142. super(UnexpectedBodyError, self).__init__(
  143. "Expected: [%s] - Provided: [%s]" % (expected, provided)
  144. )