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 Optional, Dict 
    26from typing import Union 
    27import warnings 
    28 
    29from google.rpc import error_details_pb2 
    30 
    31 
    32def _warn_could_not_import_grpcio_status(): 
    33    warnings.warn( 
    34        "Please install grpcio-status to obtain helpful grpc error messages.", 
    35        ImportWarning, 
    36    )  # pragma: NO COVER 
    37 
    38 
    39try: 
    40    import grpc 
    41 
    42    try: 
    43        from grpc_status import rpc_status 
    44    except ImportError:  # pragma: NO COVER 
    45        _warn_could_not_import_grpcio_status() 
    46        rpc_status = None 
    47except ImportError:  # pragma: NO COVER 
    48    grpc = None 
    49 
    50# Lookup tables for mapping exceptions from HTTP and gRPC transports. 
    51# Populated by _GoogleAPICallErrorMeta 
    52_HTTP_CODE_TO_EXCEPTION: Dict[int, Exception] = {} 
    53_GRPC_CODE_TO_EXCEPTION: Dict[int, Exception] = {} 
    54 
    55# Additional lookup table to map integer status codes to grpc status code 
    56# grpc does not currently support initializing enums from ints 
    57# i.e., grpc.StatusCode(5) raises an error 
    58_INT_TO_GRPC_CODE = {} 
    59if grpc is not None:  # pragma: no branch 
    60    for x in grpc.StatusCode: 
    61        _INT_TO_GRPC_CODE[x.value[0]] = x 
    62 
    63 
    64class GoogleAPIError(Exception): 
    65    """Base class for all exceptions raised by Google API Clients.""" 
    66 
    67    pass 
    68 
    69 
    70class DuplicateCredentialArgs(GoogleAPIError): 
    71    """Raised when multiple credentials are passed.""" 
    72 
    73    pass 
    74 
    75 
    76class RetryError(GoogleAPIError): 
    77    """Raised when a function has exhausted all of its available retries. 
    78 
    79    Args: 
    80        message (str): The exception message. 
    81        cause (Exception): The last exception raised when retrying the 
    82            function. 
    83    """ 
    84 
    85    def __init__(self, message, cause): 
    86        super(RetryError, self).__init__(message) 
    87        self.message = message 
    88        self._cause = cause 
    89 
    90    @property 
    91    def cause(self): 
    92        """The last exception raised when retrying the function.""" 
    93        return self._cause 
    94 
    95    def __str__(self): 
    96        return "{}, last exception: {}".format(self.message, self.cause) 
    97 
    98 
    99class _GoogleAPICallErrorMeta(type): 
    100    """Metaclass for registering GoogleAPICallError subclasses.""" 
    101 
    102    def __new__(mcs, name, bases, class_dict): 
    103        cls = type.__new__(mcs, name, bases, class_dict) 
    104        if cls.code is not None: 
    105            _HTTP_CODE_TO_EXCEPTION.setdefault(cls.code, cls) 
    106        if cls.grpc_status_code is not None: 
    107            _GRPC_CODE_TO_EXCEPTION.setdefault(cls.grpc_status_code, cls) 
    108        return cls 
    109 
    110 
    111class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta): 
    112    """Base class for exceptions raised by calling API methods. 
    113 
    114    Args: 
    115        message (str): The exception message. 
    116        errors (Sequence[Any]): An optional list of error details. 
    117        details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details. 
    118        response (Union[requests.Request, grpc.Call]): The response or 
    119            gRPC call metadata. 
    120        error_info (Union[error_details_pb2.ErrorInfo, None]): An optional object containing error info 
    121            (google.rpc.error_details.ErrorInfo). 
    122    """ 
    123 
    124    code: Union[int, None] = None 
    125    """Optional[int]: The HTTP status code associated with this error. 
    126 
    127    This may be ``None`` if the exception does not have a direct mapping 
    128    to an HTTP error. 
    129 
    130    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 
    131    """ 
    132 
    133    grpc_status_code = None 
    134    """Optional[grpc.StatusCode]: The gRPC status code associated with this 
    135    error. 
    136 
    137    This may be ``None`` if the exception does not match up to a gRPC error. 
    138    """ 
    139 
    140    def __init__(self, message, errors=(), details=(), response=None, error_info=None): 
    141        super(GoogleAPICallError, self).__init__(message) 
    142        self.message = message 
    143        """str: The exception message.""" 
    144        self._errors = errors 
    145        self._details = details 
    146        self._response = response 
    147        self._error_info = error_info 
    148 
    149    def __str__(self): 
    150        error_msg = "{} {}".format(self.code, self.message) 
    151        if self.details: 
    152            error_msg = "{} {}".format(error_msg, self.details) 
    153        # Note: This else condition can be removed once proposal A from 
    154        # b/284179390 is implemented. 
    155        else: 
    156            if self.errors: 
    157                errors = [ 
    158                    f"{error.code}: {error.message}" 
    159                    for error in self.errors 
    160                    if hasattr(error, "code") and hasattr(error, "message") 
    161                ] 
    162                if errors: 
    163                    error_msg = "{} {}".format(error_msg, "\n".join(errors)) 
    164        return error_msg 
    165 
    166    @property 
    167    def reason(self): 
    168        """The reason of the error. 
    169 
    170        Reference: 
    171            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112 
    172 
    173        Returns: 
    174            Union[str, None]: An optional string containing reason of the error. 
    175        """ 
    176        return self._error_info.reason if self._error_info else None 
    177 
    178    @property 
    179    def domain(self): 
    180        """The logical grouping to which the "reason" belongs. 
    181 
    182        Reference: 
    183            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112 
    184 
    185        Returns: 
    186            Union[str, None]: An optional string containing a logical grouping to which the "reason" belongs. 
    187        """ 
    188        return self._error_info.domain if self._error_info else None 
    189 
    190    @property 
    191    def metadata(self): 
    192        """Additional structured details about this error. 
    193 
    194        Reference: 
    195            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112 
    196 
    197        Returns: 
    198            Union[Dict[str, str], None]: An optional object containing structured details about the error. 
    199        """ 
    200        return self._error_info.metadata if self._error_info else None 
    201 
    202    @property 
    203    def errors(self): 
    204        """Detailed error information. 
    205 
    206        Returns: 
    207            Sequence[Any]: A list of additional error details. 
    208        """ 
    209        return list(self._errors) 
    210 
    211    @property 
    212    def details(self): 
    213        """Information contained in google.rpc.status.details. 
    214 
    215        Reference: 
    216            https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto 
    217            https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto 
    218 
    219        Returns: 
    220            Sequence[Any]: A list of structured objects from error_details.proto 
    221        """ 
    222        return list(self._details) 
    223 
    224    @property 
    225    def response(self): 
    226        """Optional[Union[requests.Request, grpc.Call]]: The response or 
    227        gRPC call metadata.""" 
    228        return self._response 
    229 
    230 
    231class Redirection(GoogleAPICallError): 
    232    """Base class for for all redirection (HTTP 3xx) responses.""" 
    233 
    234 
    235class MovedPermanently(Redirection): 
    236    """Exception mapping a ``301 Moved Permanently`` response.""" 
    237 
    238    code = http.client.MOVED_PERMANENTLY 
    239 
    240 
    241class NotModified(Redirection): 
    242    """Exception mapping a ``304 Not Modified`` response.""" 
    243 
    244    code = http.client.NOT_MODIFIED 
    245 
    246 
    247class TemporaryRedirect(Redirection): 
    248    """Exception mapping a ``307 Temporary Redirect`` response.""" 
    249 
    250    code = http.client.TEMPORARY_REDIRECT 
    251 
    252 
    253class ResumeIncomplete(Redirection): 
    254    """Exception mapping a ``308 Resume Incomplete`` response. 
    255 
    256    .. note:: :attr:`http.client.PERMANENT_REDIRECT` is ``308``, but Google 
    257        APIs differ in their use of this status code. 
    258    """ 
    259 
    260    code = 308 
    261 
    262 
    263class ClientError(GoogleAPICallError): 
    264    """Base class for all client error (HTTP 4xx) responses.""" 
    265 
    266 
    267class BadRequest(ClientError): 
    268    """Exception mapping a ``400 Bad Request`` response.""" 
    269 
    270    code = http.client.BAD_REQUEST 
    271 
    272 
    273class InvalidArgument(BadRequest): 
    274    """Exception mapping a :attr:`grpc.StatusCode.INVALID_ARGUMENT` error.""" 
    275 
    276    grpc_status_code = grpc.StatusCode.INVALID_ARGUMENT if grpc is not None else None 
    277 
    278 
    279class FailedPrecondition(BadRequest): 
    280    """Exception mapping a :attr:`grpc.StatusCode.FAILED_PRECONDITION` 
    281    error.""" 
    282 
    283    grpc_status_code = grpc.StatusCode.FAILED_PRECONDITION if grpc is not None else None 
    284 
    285 
    286class OutOfRange(BadRequest): 
    287    """Exception mapping a :attr:`grpc.StatusCode.OUT_OF_RANGE` error.""" 
    288 
    289    grpc_status_code = grpc.StatusCode.OUT_OF_RANGE if grpc is not None else None 
    290 
    291 
    292class Unauthorized(ClientError): 
    293    """Exception mapping a ``401 Unauthorized`` response.""" 
    294 
    295    code = http.client.UNAUTHORIZED 
    296 
    297 
    298class Unauthenticated(Unauthorized): 
    299    """Exception mapping a :attr:`grpc.StatusCode.UNAUTHENTICATED` error.""" 
    300 
    301    grpc_status_code = grpc.StatusCode.UNAUTHENTICATED if grpc is not None else None 
    302 
    303 
    304class Forbidden(ClientError): 
    305    """Exception mapping a ``403 Forbidden`` response.""" 
    306 
    307    code = http.client.FORBIDDEN 
    308 
    309 
    310class PermissionDenied(Forbidden): 
    311    """Exception mapping a :attr:`grpc.StatusCode.PERMISSION_DENIED` error.""" 
    312 
    313    grpc_status_code = grpc.StatusCode.PERMISSION_DENIED if grpc is not None else None 
    314 
    315 
    316class NotFound(ClientError): 
    317    """Exception mapping a ``404 Not Found`` response or a 
    318    :attr:`grpc.StatusCode.NOT_FOUND` error.""" 
    319 
    320    code = http.client.NOT_FOUND 
    321    grpc_status_code = grpc.StatusCode.NOT_FOUND if grpc is not None else None 
    322 
    323 
    324class MethodNotAllowed(ClientError): 
    325    """Exception mapping a ``405 Method Not Allowed`` response.""" 
    326 
    327    code = http.client.METHOD_NOT_ALLOWED 
    328 
    329 
    330class Conflict(ClientError): 
    331    """Exception mapping a ``409 Conflict`` response.""" 
    332 
    333    code = http.client.CONFLICT 
    334 
    335 
    336class AlreadyExists(Conflict): 
    337    """Exception mapping a :attr:`grpc.StatusCode.ALREADY_EXISTS` error.""" 
    338 
    339    grpc_status_code = grpc.StatusCode.ALREADY_EXISTS if grpc is not None else None 
    340 
    341 
    342class Aborted(Conflict): 
    343    """Exception mapping a :attr:`grpc.StatusCode.ABORTED` error.""" 
    344 
    345    grpc_status_code = grpc.StatusCode.ABORTED if grpc is not None else None 
    346 
    347 
    348class LengthRequired(ClientError): 
    349    """Exception mapping a ``411 Length Required`` response.""" 
    350 
    351    code = http.client.LENGTH_REQUIRED 
    352 
    353 
    354class PreconditionFailed(ClientError): 
    355    """Exception mapping a ``412 Precondition Failed`` response.""" 
    356 
    357    code = http.client.PRECONDITION_FAILED 
    358 
    359 
    360class RequestRangeNotSatisfiable(ClientError): 
    361    """Exception mapping a ``416 Request Range Not Satisfiable`` response.""" 
    362 
    363    code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE 
    364 
    365 
    366class TooManyRequests(ClientError): 
    367    """Exception mapping a ``429 Too Many Requests`` response.""" 
    368 
    369    code = http.client.TOO_MANY_REQUESTS 
    370 
    371 
    372class ResourceExhausted(TooManyRequests): 
    373    """Exception mapping a :attr:`grpc.StatusCode.RESOURCE_EXHAUSTED` error.""" 
    374 
    375    grpc_status_code = grpc.StatusCode.RESOURCE_EXHAUSTED if grpc is not None else None 
    376 
    377 
    378class Cancelled(ClientError): 
    379    """Exception mapping a :attr:`grpc.StatusCode.CANCELLED` error.""" 
    380 
    381    # This maps to HTTP status code 499. See 
    382    # https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto 
    383    code = 499 
    384    grpc_status_code = grpc.StatusCode.CANCELLED if grpc is not None else None 
    385 
    386 
    387class ServerError(GoogleAPICallError): 
    388    """Base for 5xx responses.""" 
    389 
    390 
    391class InternalServerError(ServerError): 
    392    """Exception mapping a ``500 Internal Server Error`` response. or a 
    393    :attr:`grpc.StatusCode.INTERNAL` error.""" 
    394 
    395    code = http.client.INTERNAL_SERVER_ERROR 
    396    grpc_status_code = grpc.StatusCode.INTERNAL if grpc is not None else None 
    397 
    398 
    399class Unknown(ServerError): 
    400    """Exception mapping a :attr:`grpc.StatusCode.UNKNOWN` error.""" 
    401 
    402    grpc_status_code = grpc.StatusCode.UNKNOWN if grpc is not None else None 
    403 
    404 
    405class DataLoss(ServerError): 
    406    """Exception mapping a :attr:`grpc.StatusCode.DATA_LOSS` error.""" 
    407 
    408    grpc_status_code = grpc.StatusCode.DATA_LOSS if grpc is not None else None 
    409 
    410 
    411class MethodNotImplemented(ServerError): 
    412    """Exception mapping a ``501 Not Implemented`` response or a 
    413    :attr:`grpc.StatusCode.UNIMPLEMENTED` error.""" 
    414 
    415    code = http.client.NOT_IMPLEMENTED 
    416    grpc_status_code = grpc.StatusCode.UNIMPLEMENTED if grpc is not None else None 
    417 
    418 
    419class BadGateway(ServerError): 
    420    """Exception mapping a ``502 Bad Gateway`` response.""" 
    421 
    422    code = http.client.BAD_GATEWAY 
    423 
    424 
    425class ServiceUnavailable(ServerError): 
    426    """Exception mapping a ``503 Service Unavailable`` response or a 
    427    :attr:`grpc.StatusCode.UNAVAILABLE` error.""" 
    428 
    429    code = http.client.SERVICE_UNAVAILABLE 
    430    grpc_status_code = grpc.StatusCode.UNAVAILABLE if grpc is not None else None 
    431 
    432 
    433class GatewayTimeout(ServerError): 
    434    """Exception mapping a ``504 Gateway Timeout`` response.""" 
    435 
    436    code = http.client.GATEWAY_TIMEOUT 
    437 
    438 
    439class DeadlineExceeded(GatewayTimeout): 
    440    """Exception mapping a :attr:`grpc.StatusCode.DEADLINE_EXCEEDED` error.""" 
    441 
    442    grpc_status_code = grpc.StatusCode.DEADLINE_EXCEEDED if grpc is not None else None 
    443 
    444 
    445class AsyncRestUnsupportedParameterError(NotImplementedError): 
    446    """Raised when an unsupported parameter is configured against async rest transport.""" 
    447 
    448    pass 
    449 
    450 
    451def exception_class_for_http_status(status_code): 
    452    """Return the exception class for a specific HTTP status code. 
    453 
    454    Args: 
    455        status_code (int): The HTTP status code. 
    456 
    457    Returns: 
    458        :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`. 
    459    """ 
    460    return _HTTP_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError) 
    461 
    462 
    463def from_http_status(status_code, message, **kwargs): 
    464    """Create a :class:`GoogleAPICallError` from an HTTP status code. 
    465 
    466    Args: 
    467        status_code (int): The HTTP status code. 
    468        message (str): The exception message. 
    469        kwargs: Additional arguments passed to the :class:`GoogleAPICallError` 
    470            constructor. 
    471 
    472    Returns: 
    473        GoogleAPICallError: An instance of the appropriate subclass of 
    474            :class:`GoogleAPICallError`. 
    475    """ 
    476    error_class = exception_class_for_http_status(status_code) 
    477    error = error_class(message, **kwargs) 
    478 
    479    if error.code is None: 
    480        error.code = status_code 
    481 
    482    return error 
    483 
    484 
    485def _format_rest_error_message(error, method, url): 
    486    method = method.upper() if method else None 
    487    message = "{method} {url}: {error}".format( 
    488        method=method, 
    489        url=url, 
    490        error=error, 
    491    ) 
    492    return message 
    493 
    494 
    495# NOTE: We're moving away from `from_http_status` because it expects an aiohttp response compared 
    496# to `format_http_response_error` which expects a more abstract response from google.auth and is 
    497# compatible with both sync and async response types. 
    498# TODO(https://github.com/googleapis/python-api-core/issues/691): Add type hint for response. 
    499def format_http_response_error( 
    500    response, method: str, url: str, payload: Optional[Dict] = None 
    501): 
    502    """Create a :class:`GoogleAPICallError` from a google auth rest response. 
    503 
    504    Args: 
    505        response Union[google.auth.transport.Response, google.auth.aio.transport.Response]: The HTTP response. 
    506        method Optional(str): The HTTP request method. 
    507        url Optional(str): The HTTP request url. 
    508        payload Optional(dict): The HTTP response payload. If not passed in, it is read from response for a response type of google.auth.transport.Response. 
    509 
    510    Returns: 
    511        GoogleAPICallError: An instance of the appropriate subclass of 
    512            :class:`GoogleAPICallError`, with the message and errors populated 
    513            from the response. 
    514    """ 
    515    payload = {} if not payload else payload 
    516    error_message = payload.get("error", {}).get("message", "unknown error") 
    517    errors = payload.get("error", {}).get("errors", ()) 
    518    # In JSON, details are already formatted in developer-friendly way. 
    519    details = payload.get("error", {}).get("details", ()) 
    520    error_info_list = list( 
    521        filter( 
    522            lambda detail: detail.get("@type", "") 
    523            == "type.googleapis.com/google.rpc.ErrorInfo", 
    524            details, 
    525        ) 
    526    ) 
    527    error_info = error_info_list[0] if error_info_list else None 
    528    message = _format_rest_error_message(error_message, method, url) 
    529 
    530    exception = from_http_status( 
    531        response.status_code, 
    532        message, 
    533        errors=errors, 
    534        details=details, 
    535        response=response, 
    536        error_info=error_info, 
    537    ) 
    538    return exception 
    539 
    540 
    541def from_http_response(response): 
    542    """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`. 
    543 
    544    Args: 
    545        response (requests.Response): The HTTP response. 
    546 
    547    Returns: 
    548        GoogleAPICallError: An instance of the appropriate subclass of 
    549            :class:`GoogleAPICallError`, with the message and errors populated 
    550            from the response. 
    551    """ 
    552    try: 
    553        payload = response.json() 
    554    except ValueError: 
    555        payload = {"error": {"message": response.text or "unknown error"}} 
    556    return format_http_response_error( 
    557        response, response.request.method, response.request.url, payload 
    558    ) 
    559 
    560 
    561def exception_class_for_grpc_status(status_code): 
    562    """Return the exception class for a specific :class:`grpc.StatusCode`. 
    563 
    564    Args: 
    565        status_code (grpc.StatusCode): The gRPC status code. 
    566 
    567    Returns: 
    568        :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`. 
    569    """ 
    570    return _GRPC_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError) 
    571 
    572 
    573def from_grpc_status(status_code, message, **kwargs): 
    574    """Create a :class:`GoogleAPICallError` from a :class:`grpc.StatusCode`. 
    575 
    576    Args: 
    577        status_code (Union[grpc.StatusCode, int]): The gRPC status code. 
    578        message (str): The exception message. 
    579        kwargs: Additional arguments passed to the :class:`GoogleAPICallError` 
    580            constructor. 
    581 
    582    Returns: 
    583        GoogleAPICallError: An instance of the appropriate subclass of 
    584            :class:`GoogleAPICallError`. 
    585    """ 
    586 
    587    if isinstance(status_code, int): 
    588        status_code = _INT_TO_GRPC_CODE.get(status_code, status_code) 
    589 
    590    error_class = exception_class_for_grpc_status(status_code) 
    591    error = error_class(message, **kwargs) 
    592 
    593    if error.grpc_status_code is None: 
    594        error.grpc_status_code = status_code 
    595 
    596    return error 
    597 
    598 
    599def _is_informative_grpc_error(rpc_exc): 
    600    return hasattr(rpc_exc, "code") and hasattr(rpc_exc, "details") 
    601 
    602 
    603def _parse_grpc_error_details(rpc_exc): 
    604    if not rpc_status:  # pragma: NO COVER 
    605        _warn_could_not_import_grpcio_status() 
    606        return [], None 
    607    try: 
    608        status = rpc_status.from_call(rpc_exc) 
    609    except NotImplementedError:  # workaround 
    610        return [], None 
    611 
    612    if not status: 
    613        return [], None 
    614 
    615    possible_errors = [ 
    616        error_details_pb2.BadRequest, 
    617        error_details_pb2.PreconditionFailure, 
    618        error_details_pb2.QuotaFailure, 
    619        error_details_pb2.ErrorInfo, 
    620        error_details_pb2.RetryInfo, 
    621        error_details_pb2.ResourceInfo, 
    622        error_details_pb2.RequestInfo, 
    623        error_details_pb2.DebugInfo, 
    624        error_details_pb2.Help, 
    625        error_details_pb2.LocalizedMessage, 
    626    ] 
    627    error_info = None 
    628    error_details = [] 
    629    for detail in status.details: 
    630        matched_detail_cls = list( 
    631            filter(lambda x: detail.Is(x.DESCRIPTOR), possible_errors) 
    632        ) 
    633        # If nothing matched, use detail directly. 
    634        if len(matched_detail_cls) == 0: 
    635            info = detail 
    636        else: 
    637            info = matched_detail_cls[0]() 
    638            detail.Unpack(info) 
    639        error_details.append(info) 
    640        if isinstance(info, error_details_pb2.ErrorInfo): 
    641            error_info = info 
    642    return error_details, error_info 
    643 
    644 
    645def from_grpc_error(rpc_exc): 
    646    """Create a :class:`GoogleAPICallError` from a :class:`grpc.RpcError`. 
    647 
    648    Args: 
    649        rpc_exc (grpc.RpcError): The gRPC error. 
    650 
    651    Returns: 
    652        GoogleAPICallError: An instance of the appropriate subclass of 
    653            :class:`GoogleAPICallError`. 
    654    """ 
    655    # NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError. 
    656    # However, check for grpc.RpcError breaks backward compatibility. 
    657    if ( 
    658        grpc is not None and isinstance(rpc_exc, grpc.Call) 
    659    ) or _is_informative_grpc_error(rpc_exc): 
    660        details, err_info = _parse_grpc_error_details(rpc_exc) 
    661        return from_grpc_status( 
    662            rpc_exc.code(), 
    663            rpc_exc.details(), 
    664            errors=(rpc_exc,), 
    665            details=details, 
    666            response=rpc_exc, 
    667            error_info=err_info, 
    668        ) 
    669    else: 
    670        return GoogleAPICallError(str(rpc_exc), errors=(rpc_exc,), response=rpc_exc)