1# Copyright 2014 Google LLC 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 15"""Exceptions raised by Google API core & clients. 16 17This module provides base classes for all errors raised by libraries based 18on :mod:`google.api_core`, including both HTTP and gRPC clients. 19""" 20 21from __future__ import absolute_import 22from __future__ import unicode_literals 23 24import http.client 25from typing import Dict 26from typing import Union 27 28from google.rpc import error_details_pb2 29 30try: 31 import grpc 32 from grpc_status import rpc_status 33except ImportError: # pragma: NO COVER 34 grpc = None 35 rpc_status = None 36 37# Lookup tables for mapping exceptions from HTTP and gRPC transports. 38# Populated by _GoogleAPICallErrorMeta 39_HTTP_CODE_TO_EXCEPTION: Dict[int, Exception] = {} 40_GRPC_CODE_TO_EXCEPTION: Dict[int, Exception] = {} 41 42# Additional lookup table to map integer status codes to grpc status code 43# grpc does not currently support initializing enums from ints 44# i.e., grpc.StatusCode(5) raises an error 45_INT_TO_GRPC_CODE = {} 46if grpc is not None: # pragma: no branch 47 for x in grpc.StatusCode: 48 _INT_TO_GRPC_CODE[x.value[0]] = x 49 50 51class GoogleAPIError(Exception): 52 """Base class for all exceptions raised by Google API Clients.""" 53 54 pass 55 56 57class DuplicateCredentialArgs(GoogleAPIError): 58 """Raised when multiple credentials are passed.""" 59 60 pass 61 62 63class RetryError(GoogleAPIError): 64 """Raised when a function has exhausted all of its available retries. 65 66 Args: 67 message (str): The exception message. 68 cause (Exception): The last exception raised when retring the 69 function. 70 """ 71 72 def __init__(self, message, cause): 73 super(RetryError, self).__init__(message) 74 self.message = message 75 self._cause = cause 76 77 @property 78 def cause(self): 79 """The last exception raised when retrying the function.""" 80 return self._cause 81 82 def __str__(self): 83 return "{}, last exception: {}".format(self.message, self.cause) 84 85 86class _GoogleAPICallErrorMeta(type): 87 """Metaclass for registering GoogleAPICallError subclasses.""" 88 89 def __new__(mcs, name, bases, class_dict): 90 cls = type.__new__(mcs, name, bases, class_dict) 91 if cls.code is not None: 92 _HTTP_CODE_TO_EXCEPTION.setdefault(cls.code, cls) 93 if cls.grpc_status_code is not None: 94 _GRPC_CODE_TO_EXCEPTION.setdefault(cls.grpc_status_code, cls) 95 return cls 96 97 98class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta): 99 """Base class for exceptions raised by calling API methods. 100 101 Args: 102 message (str): The exception message. 103 errors (Sequence[Any]): An optional list of error details. 104 details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details. 105 response (Union[requests.Request, grpc.Call]): The response or 106 gRPC call metadata. 107 """ 108 109 code: Union[int, None] = None 110 """Optional[int]: The HTTP status code associated with this error. 111 112 This may be ``None`` if the exception does not have a direct mapping 113 to an HTTP error. 114 115 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 116 """ 117 118 grpc_status_code = None 119 """Optional[grpc.StatusCode]: The gRPC status code associated with this 120 error. 121 122 This may be ``None`` if the exception does not match up to a gRPC error. 123 """ 124 125 def __init__(self, message, errors=(), details=(), response=None): 126 super(GoogleAPICallError, self).__init__(message) 127 self.message = message 128 """str: The exception message.""" 129 self._errors = errors 130 self._details = details 131 self._response = response 132 133 def __str__(self): 134 if self.details: 135 return "{} {} {}".format(self.code, self.message, self.details) 136 else: 137 return "{} {}".format(self.code, self.message) 138 139 @property 140 def errors(self): 141 """Detailed error information. 142 143 Returns: 144 Sequence[Any]: A list of additional error details. 145 """ 146 return list(self._errors) 147 148 @property 149 def details(self): 150 """Information contained in google.rpc.status.details. 151 152 Reference: 153 https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto 154 https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto 155 156 Returns: 157 Sequence[Any]: A list of structured objects from error_details.proto 158 """ 159 return list(self._details) 160 161 @property 162 def response(self): 163 """Optional[Union[requests.Request, grpc.Call]]: The response or 164 gRPC call metadata.""" 165 return self._response 166 167 168class Redirection(GoogleAPICallError): 169 """Base class for for all redirection (HTTP 3xx) responses.""" 170 171 172class MovedPermanently(Redirection): 173 """Exception mapping a ``301 Moved Permanently`` response.""" 174 175 code = http.client.MOVED_PERMANENTLY 176 177 178class NotModified(Redirection): 179 """Exception mapping a ``304 Not Modified`` response.""" 180 181 code = http.client.NOT_MODIFIED 182 183 184class TemporaryRedirect(Redirection): 185 """Exception mapping a ``307 Temporary Redirect`` response.""" 186 187 code = http.client.TEMPORARY_REDIRECT 188 189 190class ResumeIncomplete(Redirection): 191 """Exception mapping a ``308 Resume Incomplete`` response. 192 193 .. note:: :attr:`http.client.PERMANENT_REDIRECT` is ``308``, but Google 194 APIs differ in their use of this status code. 195 """ 196 197 code = 308 198 199 200class ClientError(GoogleAPICallError): 201 """Base class for all client error (HTTP 4xx) responses.""" 202 203 204class BadRequest(ClientError): 205 """Exception mapping a ``400 Bad Request`` response.""" 206 207 code = http.client.BAD_REQUEST 208 209 210class InvalidArgument(BadRequest): 211 """Exception mapping a :attr:`grpc.StatusCode.INVALID_ARGUMENT` error.""" 212 213 grpc_status_code = grpc.StatusCode.INVALID_ARGUMENT if grpc is not None else None 214 215 216class FailedPrecondition(BadRequest): 217 """Exception mapping a :attr:`grpc.StatusCode.FAILED_PRECONDITION` 218 error.""" 219 220 grpc_status_code = grpc.StatusCode.FAILED_PRECONDITION if grpc is not None else None 221 222 223class OutOfRange(BadRequest): 224 """Exception mapping a :attr:`grpc.StatusCode.OUT_OF_RANGE` error.""" 225 226 grpc_status_code = grpc.StatusCode.OUT_OF_RANGE if grpc is not None else None 227 228 229class Unauthorized(ClientError): 230 """Exception mapping a ``401 Unauthorized`` response.""" 231 232 code = http.client.UNAUTHORIZED 233 234 235class Unauthenticated(Unauthorized): 236 """Exception mapping a :attr:`grpc.StatusCode.UNAUTHENTICATED` error.""" 237 238 grpc_status_code = grpc.StatusCode.UNAUTHENTICATED if grpc is not None else None 239 240 241class Forbidden(ClientError): 242 """Exception mapping a ``403 Forbidden`` response.""" 243 244 code = http.client.FORBIDDEN 245 246 247class PermissionDenied(Forbidden): 248 """Exception mapping a :attr:`grpc.StatusCode.PERMISSION_DENIED` error.""" 249 250 grpc_status_code = grpc.StatusCode.PERMISSION_DENIED if grpc is not None else None 251 252 253class NotFound(ClientError): 254 """Exception mapping a ``404 Not Found`` response or a 255 :attr:`grpc.StatusCode.NOT_FOUND` error.""" 256 257 code = http.client.NOT_FOUND 258 grpc_status_code = grpc.StatusCode.NOT_FOUND if grpc is not None else None 259 260 261class MethodNotAllowed(ClientError): 262 """Exception mapping a ``405 Method Not Allowed`` response.""" 263 264 code = http.client.METHOD_NOT_ALLOWED 265 266 267class Conflict(ClientError): 268 """Exception mapping a ``409 Conflict`` response.""" 269 270 code = http.client.CONFLICT 271 272 273class AlreadyExists(Conflict): 274 """Exception mapping a :attr:`grpc.StatusCode.ALREADY_EXISTS` error.""" 275 276 grpc_status_code = grpc.StatusCode.ALREADY_EXISTS if grpc is not None else None 277 278 279class Aborted(Conflict): 280 """Exception mapping a :attr:`grpc.StatusCode.ABORTED` error.""" 281 282 grpc_status_code = grpc.StatusCode.ABORTED if grpc is not None else None 283 284 285class LengthRequired(ClientError): 286 """Exception mapping a ``411 Length Required`` response.""" 287 288 code = http.client.LENGTH_REQUIRED 289 290 291class PreconditionFailed(ClientError): 292 """Exception mapping a ``412 Precondition Failed`` response.""" 293 294 code = http.client.PRECONDITION_FAILED 295 296 297class RequestRangeNotSatisfiable(ClientError): 298 """Exception mapping a ``416 Request Range Not Satisfiable`` response.""" 299 300 code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE 301 302 303class TooManyRequests(ClientError): 304 """Exception mapping a ``429 Too Many Requests`` response.""" 305 306 code = http.client.TOO_MANY_REQUESTS 307 308 309class ResourceExhausted(TooManyRequests): 310 """Exception mapping a :attr:`grpc.StatusCode.RESOURCE_EXHAUSTED` error.""" 311 312 grpc_status_code = grpc.StatusCode.RESOURCE_EXHAUSTED if grpc is not None else None 313 314 315class Cancelled(ClientError): 316 """Exception mapping a :attr:`grpc.StatusCode.CANCELLED` error.""" 317 318 # This maps to HTTP status code 499. See 319 # https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto 320 code = 499 321 grpc_status_code = grpc.StatusCode.CANCELLED if grpc is not None else None 322 323 324class ServerError(GoogleAPICallError): 325 """Base for 5xx responses.""" 326 327 328class InternalServerError(ServerError): 329 """Exception mapping a ``500 Internal Server Error`` response. or a 330 :attr:`grpc.StatusCode.INTERNAL` error.""" 331 332 code = http.client.INTERNAL_SERVER_ERROR 333 grpc_status_code = grpc.StatusCode.INTERNAL if grpc is not None else None 334 335 336class Unknown(ServerError): 337 """Exception mapping a :attr:`grpc.StatusCode.UNKNOWN` error.""" 338 339 grpc_status_code = grpc.StatusCode.UNKNOWN if grpc is not None else None 340 341 342class DataLoss(ServerError): 343 """Exception mapping a :attr:`grpc.StatusCode.DATA_LOSS` error.""" 344 345 grpc_status_code = grpc.StatusCode.DATA_LOSS if grpc is not None else None 346 347 348class MethodNotImplemented(ServerError): 349 """Exception mapping a ``501 Not Implemented`` response or a 350 :attr:`grpc.StatusCode.UNIMPLEMENTED` error.""" 351 352 code = http.client.NOT_IMPLEMENTED 353 grpc_status_code = grpc.StatusCode.UNIMPLEMENTED if grpc is not None else None 354 355 356class BadGateway(ServerError): 357 """Exception mapping a ``502 Bad Gateway`` response.""" 358 359 code = http.client.BAD_GATEWAY 360 361 362class ServiceUnavailable(ServerError): 363 """Exception mapping a ``503 Service Unavailable`` response or a 364 :attr:`grpc.StatusCode.UNAVAILABLE` error.""" 365 366 code = http.client.SERVICE_UNAVAILABLE 367 grpc_status_code = grpc.StatusCode.UNAVAILABLE if grpc is not None else None 368 369 370class GatewayTimeout(ServerError): 371 """Exception mapping a ``504 Gateway Timeout`` response.""" 372 373 code = http.client.GATEWAY_TIMEOUT 374 375 376class DeadlineExceeded(GatewayTimeout): 377 """Exception mapping a :attr:`grpc.StatusCode.DEADLINE_EXCEEDED` error.""" 378 379 grpc_status_code = grpc.StatusCode.DEADLINE_EXCEEDED if grpc is not None else None 380 381 382def exception_class_for_http_status(status_code): 383 """Return the exception class for a specific HTTP status code. 384 385 Args: 386 status_code (int): The HTTP status code. 387 388 Returns: 389 :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`. 390 """ 391 return _HTTP_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError) 392 393 394def from_http_status(status_code, message, **kwargs): 395 """Create a :class:`GoogleAPICallError` from an HTTP status code. 396 397 Args: 398 status_code (int): The HTTP status code. 399 message (str): The exception message. 400 kwargs: Additional arguments passed to the :class:`GoogleAPICallError` 401 constructor. 402 403 Returns: 404 GoogleAPICallError: An instance of the appropriate subclass of 405 :class:`GoogleAPICallError`. 406 """ 407 error_class = exception_class_for_http_status(status_code) 408 error = error_class(message, **kwargs) 409 410 if error.code is None: 411 error.code = status_code 412 413 return error 414 415 416def from_http_response(response): 417 """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`. 418 419 Args: 420 response (requests.Response): The HTTP response. 421 422 Returns: 423 GoogleAPICallError: An instance of the appropriate subclass of 424 :class:`GoogleAPICallError`, with the message and errors populated 425 from the response. 426 """ 427 try: 428 payload = response.json() 429 except ValueError: 430 payload = {"error": {"message": response.text or "unknown error"}} 431 432 error_message = payload.get("error", {}).get("message", "unknown error") 433 errors = payload.get("error", {}).get("errors", ()) 434 # In JSON, details are already formatted in developer-friendly way. 435 details = payload.get("error", {}).get("details", ()) 436 437 message = "{method} {url}: {error}".format( 438 method=response.request.method, url=response.request.url, error=error_message 439 ) 440 441 exception = from_http_status( 442 response.status_code, message, errors=errors, details=details, response=response 443 ) 444 return exception 445 446 447def exception_class_for_grpc_status(status_code): 448 """Return the exception class for a specific :class:`grpc.StatusCode`. 449 450 Args: 451 status_code (grpc.StatusCode): The gRPC status code. 452 453 Returns: 454 :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`. 455 """ 456 return _GRPC_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError) 457 458 459def from_grpc_status(status_code, message, **kwargs): 460 """Create a :class:`GoogleAPICallError` from a :class:`grpc.StatusCode`. 461 462 Args: 463 status_code (Union[grpc.StatusCode, int]): The gRPC status code. 464 message (str): The exception message. 465 kwargs: Additional arguments passed to the :class:`GoogleAPICallError` 466 constructor. 467 468 Returns: 469 GoogleAPICallError: An instance of the appropriate subclass of 470 :class:`GoogleAPICallError`. 471 """ 472 473 if isinstance(status_code, int): 474 status_code = _INT_TO_GRPC_CODE.get(status_code, status_code) 475 476 error_class = exception_class_for_grpc_status(status_code) 477 error = error_class(message, **kwargs) 478 479 if error.grpc_status_code is None: 480 error.grpc_status_code = status_code 481 482 return error 483 484 485def _is_informative_grpc_error(rpc_exc): 486 return hasattr(rpc_exc, "code") and hasattr(rpc_exc, "details") 487 488 489def _parse_grpc_error_details(rpc_exc): 490 try: 491 status = rpc_status.from_call(rpc_exc) 492 except NotImplementedError: # workaround 493 return [] 494 495 if not status: 496 return [] 497 498 possible_errors = [ 499 error_details_pb2.BadRequest, 500 error_details_pb2.PreconditionFailure, 501 error_details_pb2.QuotaFailure, 502 error_details_pb2.ErrorInfo, 503 error_details_pb2.RetryInfo, 504 error_details_pb2.ResourceInfo, 505 error_details_pb2.RequestInfo, 506 error_details_pb2.DebugInfo, 507 error_details_pb2.Help, 508 error_details_pb2.LocalizedMessage, 509 ] 510 error_details = [] 511 for detail in status.details: 512 matched_detail_cls = list( 513 filter(lambda x: detail.Is(x.DESCRIPTOR), possible_errors) 514 ) 515 # If nothing matched, use detail directly. 516 if len(matched_detail_cls) == 0: 517 info = detail 518 else: 519 info = matched_detail_cls[0]() 520 detail.Unpack(info) 521 error_details.append(info) 522 return error_details 523 524 525def from_grpc_error(rpc_exc): 526 """Create a :class:`GoogleAPICallError` from a :class:`grpc.RpcError`. 527 528 Args: 529 rpc_exc (grpc.RpcError): The gRPC error. 530 531 Returns: 532 GoogleAPICallError: An instance of the appropriate subclass of 533 :class:`GoogleAPICallError`. 534 """ 535 # NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError. 536 # However, check for grpc.RpcError breaks backward compatibility. 537 if isinstance(rpc_exc, grpc.Call) or _is_informative_grpc_error(rpc_exc): 538 return from_grpc_status( 539 rpc_exc.code(), 540 rpc_exc.details(), 541 errors=(rpc_exc,), 542 details=_parse_grpc_error_details(rpc_exc), 543 response=rpc_exc, 544 ) 545 else: 546 return GoogleAPICallError(str(rpc_exc), errors=(rpc_exc,), response=rpc_exc) 547