Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/api_core/exceptions.py: 54%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

270 statements  

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, unicode_literals 

22 

23import http 

24import http.client 

25import warnings 

26from typing import Dict, Optional 

27 

28from google.rpc import error_details_pb2 

29 

30 

31def _warn_could_not_import_grpcio_status(): 

32 warnings.warn( 

33 "Please install grpcio-status to obtain helpful grpc error messages.", 

34 ImportWarning, 

35 ) # pragma: NO COVER 

36 

37 

38try: 

39 import grpc 

40 

41 try: 

42 from grpc_status import rpc_status 

43 except ImportError: # pragma: NO COVER 

44 _warn_could_not_import_grpcio_status() 

45 rpc_status = None 

46except ImportError: # pragma: NO COVER 

47 grpc = None 

48 

49# Lookup tables for mapping exceptions from HTTP and gRPC transports. 

50# Populated by _GoogleAPICallErrorMeta 

51_HTTP_CODE_TO_EXCEPTION: Dict[int, Exception] = {} 

52_GRPC_CODE_TO_EXCEPTION: Dict[int, Exception] = {} 

53 

54# Additional lookup table to map integer status codes to grpc status code 

55# grpc does not currently support initializing enums from ints 

56# i.e., grpc.StatusCode(5) raises an error 

57_INT_TO_GRPC_CODE = {} 

58if grpc is not None: # pragma: no branch 

59 for x in grpc.StatusCode: 

60 _INT_TO_GRPC_CODE[x.value[0]] = x 

61 

62 

63class GoogleAPIError(Exception): 

64 """Base class for all exceptions raised by Google API Clients.""" 

65 

66 pass 

67 

68 

69class DuplicateCredentialArgs(GoogleAPIError): 

70 """Raised when multiple credentials are passed.""" 

71 

72 pass 

73 

74 

75class RetryError(GoogleAPIError): 

76 """Raised when a function has exhausted all of its available retries. 

77 

78 Args: 

79 message (str): The exception message. 

80 cause (Exception): The last exception raised when retrying the 

81 function. 

82 """ 

83 

84 def __init__(self, message, cause): 

85 super(RetryError, self).__init__(message) 

86 self.message = message 

87 self._cause = cause 

88 

89 @property 

90 def cause(self): 

91 """The last exception raised when retrying the function.""" 

92 return self._cause 

93 

94 def __str__(self): 

95 return "{}, last exception: {}".format(self.message, self.cause) 

96 

97 def __reduce__(self): 

98 # ``cause`` is a required constructor argument that isn't stored in 

99 # ``self.args``, so the default ``BaseException.__reduce__`` can't 

100 # rebuild the exception (e.g. when it's pickled across processes). 

101 return (self.__class__, (self.message, self._cause), self.__dict__) 

102 

103 

104class _GoogleAPICallErrorMeta(type): 

105 """Metaclass for registering GoogleAPICallError subclasses.""" 

106 

107 def __new__(mcs, name, bases, class_dict): 

108 cls = type.__new__(mcs, name, bases, class_dict) 

109 if cls.code is not None: 

110 _HTTP_CODE_TO_EXCEPTION.setdefault(cls.code, cls) 

111 if cls.grpc_status_code is not None: 

112 _GRPC_CODE_TO_EXCEPTION.setdefault(cls.grpc_status_code, cls) 

113 return cls 

114 

115 

116class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta): 

117 """Base class for exceptions raised by calling API methods. 

118 

119 Args: 

120 message (str): The exception message. 

121 errors (Sequence[Any]): An optional list of error details. 

122 details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details. 

123 response (Union[requests.Request, grpc.Call]): The response or 

124 gRPC call metadata. 

125 error_info (Union[error_details_pb2.ErrorInfo, None]): An optional object containing error info 

126 (google.rpc.error_details.ErrorInfo). 

127 """ 

128 

129 code: Optional[int] = None 

130 """Optional[int]: The HTTP status code associated with this error. 

131 

132 This may be ``None`` if the exception does not have a direct mapping 

133 to an HTTP error. 

134 

135 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 

136 """ 

137 

138 grpc_status_code: Optional["grpc.StatusCode"] = None 

139 """Optional[grpc.StatusCode]: The gRPC status code associated with this 

140 error. 

141 

142 This may be ``None`` if the exception does not match up to a gRPC error. 

143 """ 

144 

145 def __init__(self, message, errors=(), details=(), response=None, error_info=None): 

146 super(GoogleAPICallError, self).__init__(message) 

147 self.message = message 

148 """str: The exception message.""" 

149 self._errors = errors 

150 self._details = details 

151 self._response = response 

152 self._error_info = error_info 

153 

154 def __str__(self): 

155 error_msg = "{} {}".format(self.code, self.message) 

156 if self.details: 

157 error_msg = "{} {}".format(error_msg, self.details) 

158 # Note: This else condition can be removed once proposal A from 

159 # b/284179390 is implemented. 

160 else: 

161 if self.errors: 

162 errors = [ 

163 f"{error.code}: {error.message}" 

164 for error in self.errors 

165 if hasattr(error, "code") and hasattr(error, "message") 

166 ] 

167 if errors: 

168 error_msg = "{} {}".format(error_msg, "\n".join(errors)) 

169 return error_msg 

170 

171 @property 

172 def reason(self): 

173 """The reason of the error. 

174 

175 Reference: 

176 https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112 

177 

178 Returns: 

179 Union[str, None]: An optional string containing reason of the error. 

180 """ 

181 if not self._error_info: 

182 return None 

183 if isinstance(self._error_info, dict): 

184 return self._error_info.get("reason") 

185 return self._error_info.reason 

186 

187 @property 

188 def domain(self): 

189 """The logical grouping to which the "reason" belongs. 

190 

191 Reference: 

192 https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112 

193 

194 Returns: 

195 Union[str, None]: An optional string containing a logical grouping to which the "reason" belongs. 

196 """ 

197 if not self._error_info: 

198 return None 

199 if isinstance(self._error_info, dict): 

200 return self._error_info.get("domain") 

201 return self._error_info.domain 

202 

203 @property 

204 def metadata(self): 

205 """Additional structured details about this error. 

206 

207 Reference: 

208 https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112 

209 

210 Returns: 

211 Union[Dict[str, str], None]: An optional object containing structured details about the error. 

212 """ 

213 if not self._error_info: 

214 return None 

215 if isinstance(self._error_info, dict): 

216 return self._error_info.get("metadata") 

217 return self._error_info.metadata 

218 

219 @property 

220 def errors(self): 

221 """Detailed error information. 

222 

223 Returns: 

224 Sequence[Any]: A list of additional error details. 

225 """ 

226 return list(self._errors) 

227 

228 @property 

229 def details(self): 

230 """Information contained in google.rpc.status.details. 

231 

232 Reference: 

233 https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto 

234 https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto 

235 

236 Returns: 

237 Sequence[Any]: A list of structured objects from error_details.proto 

238 """ 

239 return list(self._details) 

240 

241 @property 

242 def response(self): 

243 """Optional[Union[requests.Request, grpc.Call]]: The response or 

244 gRPC call metadata.""" 

245 return self._response 

246 

247 

248class Redirection(GoogleAPICallError): 

249 """Base class for for all redirection (HTTP 3xx) responses.""" 

250 

251 

252class MovedPermanently(Redirection): 

253 """Exception mapping a ``301 Moved Permanently`` response.""" 

254 

255 code = http.client.MOVED_PERMANENTLY 

256 

257 

258class NotModified(Redirection): 

259 """Exception mapping a ``304 Not Modified`` response.""" 

260 

261 code = http.client.NOT_MODIFIED 

262 

263 

264class TemporaryRedirect(Redirection): 

265 """Exception mapping a ``307 Temporary Redirect`` response.""" 

266 

267 code = http.client.TEMPORARY_REDIRECT 

268 

269 

270class ResumeIncomplete(Redirection): 

271 """Exception mapping a ``308 Resume Incomplete`` response. 

272 

273 .. note:: :attr:`http.client.PERMANENT_REDIRECT` is ``308``, but Google 

274 APIs differ in their use of this status code. 

275 """ 

276 

277 code = 308 

278 

279 

280class ClientError(GoogleAPICallError): 

281 """Base class for all client error (HTTP 4xx) responses.""" 

282 

283 

284class BadRequest(ClientError): 

285 """Exception mapping a ``400 Bad Request`` response.""" 

286 

287 code = http.client.BAD_REQUEST 

288 

289 

290class InvalidArgument(BadRequest): 

291 """Exception mapping a :attr:`grpc.StatusCode.INVALID_ARGUMENT` error.""" 

292 

293 grpc_status_code = grpc.StatusCode.INVALID_ARGUMENT if grpc is not None else None 

294 

295 

296class FailedPrecondition(BadRequest): 

297 """Exception mapping a :attr:`grpc.StatusCode.FAILED_PRECONDITION` 

298 error.""" 

299 

300 grpc_status_code = grpc.StatusCode.FAILED_PRECONDITION if grpc is not None else None 

301 

302 

303class OutOfRange(BadRequest): 

304 """Exception mapping a :attr:`grpc.StatusCode.OUT_OF_RANGE` error.""" 

305 

306 grpc_status_code = grpc.StatusCode.OUT_OF_RANGE if grpc is not None else None 

307 

308 

309class Unauthorized(ClientError): 

310 """Exception mapping a ``401 Unauthorized`` response.""" 

311 

312 code = http.client.UNAUTHORIZED 

313 

314 

315class Unauthenticated(Unauthorized): 

316 """Exception mapping a :attr:`grpc.StatusCode.UNAUTHENTICATED` error.""" 

317 

318 grpc_status_code = grpc.StatusCode.UNAUTHENTICATED if grpc is not None else None 

319 

320 

321class Forbidden(ClientError): 

322 """Exception mapping a ``403 Forbidden`` response.""" 

323 

324 code = http.client.FORBIDDEN 

325 

326 

327class PermissionDenied(Forbidden): 

328 """Exception mapping a :attr:`grpc.StatusCode.PERMISSION_DENIED` error.""" 

329 

330 grpc_status_code = grpc.StatusCode.PERMISSION_DENIED if grpc is not None else None 

331 

332 

333class NotFound(ClientError): 

334 """Exception mapping a ``404 Not Found`` response or a 

335 :attr:`grpc.StatusCode.NOT_FOUND` error.""" 

336 

337 code = http.client.NOT_FOUND 

338 grpc_status_code = grpc.StatusCode.NOT_FOUND if grpc is not None else None 

339 

340 

341class MethodNotAllowed(ClientError): 

342 """Exception mapping a ``405 Method Not Allowed`` response.""" 

343 

344 code = http.client.METHOD_NOT_ALLOWED 

345 

346 

347class Conflict(ClientError): 

348 """Exception mapping a ``409 Conflict`` response.""" 

349 

350 code = http.client.CONFLICT 

351 

352 

353class AlreadyExists(Conflict): 

354 """Exception mapping a :attr:`grpc.StatusCode.ALREADY_EXISTS` error.""" 

355 

356 grpc_status_code = grpc.StatusCode.ALREADY_EXISTS if grpc is not None else None 

357 

358 

359class Aborted(Conflict): 

360 """Exception mapping a :attr:`grpc.StatusCode.ABORTED` error.""" 

361 

362 grpc_status_code = grpc.StatusCode.ABORTED if grpc is not None else None 

363 

364 

365class LengthRequired(ClientError): 

366 """Exception mapping a ``411 Length Required`` response.""" 

367 

368 code = http.client.LENGTH_REQUIRED 

369 

370 

371class PreconditionFailed(ClientError): 

372 """Exception mapping a ``412 Precondition Failed`` response.""" 

373 

374 code = http.client.PRECONDITION_FAILED 

375 

376 

377class RequestRangeNotSatisfiable(ClientError): 

378 """Exception mapping a ``416 Request Range Not Satisfiable`` response.""" 

379 

380 code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE 

381 

382 

383class TooManyRequests(ClientError): 

384 """Exception mapping a ``429 Too Many Requests`` response.""" 

385 

386 code = http.client.TOO_MANY_REQUESTS 

387 

388 

389class ResourceExhausted(TooManyRequests): 

390 """Exception mapping a :attr:`grpc.StatusCode.RESOURCE_EXHAUSTED` error.""" 

391 

392 grpc_status_code = grpc.StatusCode.RESOURCE_EXHAUSTED if grpc is not None else None 

393 

394 

395class Cancelled(ClientError): 

396 """Exception mapping a :attr:`grpc.StatusCode.CANCELLED` error.""" 

397 

398 # This maps to HTTP status code 499. See 

399 # https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto 

400 code = 499 

401 grpc_status_code = grpc.StatusCode.CANCELLED if grpc is not None else None 

402 

403 

404class ServerError(GoogleAPICallError): 

405 """Base for 5xx responses.""" 

406 

407 

408class InternalServerError(ServerError): 

409 """Exception mapping a ``500 Internal Server Error`` response. or a 

410 :attr:`grpc.StatusCode.INTERNAL` error.""" 

411 

412 code = http.client.INTERNAL_SERVER_ERROR 

413 grpc_status_code = grpc.StatusCode.INTERNAL if grpc is not None else None 

414 

415 

416class Unknown(ServerError): 

417 """Exception mapping a :attr:`grpc.StatusCode.UNKNOWN` error.""" 

418 

419 grpc_status_code = grpc.StatusCode.UNKNOWN if grpc is not None else None 

420 

421 

422class DataLoss(ServerError): 

423 """Exception mapping a :attr:`grpc.StatusCode.DATA_LOSS` error.""" 

424 

425 grpc_status_code = grpc.StatusCode.DATA_LOSS if grpc is not None else None 

426 

427 

428class MethodNotImplemented(ServerError): 

429 """Exception mapping a ``501 Not Implemented`` response or a 

430 :attr:`grpc.StatusCode.UNIMPLEMENTED` error.""" 

431 

432 code = http.client.NOT_IMPLEMENTED 

433 grpc_status_code = grpc.StatusCode.UNIMPLEMENTED if grpc is not None else None 

434 

435 

436class BadGateway(ServerError): 

437 """Exception mapping a ``502 Bad Gateway`` response.""" 

438 

439 code = http.client.BAD_GATEWAY 

440 

441 

442class ServiceUnavailable(ServerError): 

443 """Exception mapping a ``503 Service Unavailable`` response or a 

444 :attr:`grpc.StatusCode.UNAVAILABLE` error.""" 

445 

446 code = http.client.SERVICE_UNAVAILABLE 

447 grpc_status_code = grpc.StatusCode.UNAVAILABLE if grpc is not None else None 

448 

449 

450class GatewayTimeout(ServerError): 

451 """Exception mapping a ``504 Gateway Timeout`` response.""" 

452 

453 code = http.client.GATEWAY_TIMEOUT 

454 

455 

456class DeadlineExceeded(GatewayTimeout): 

457 """Exception mapping a :attr:`grpc.StatusCode.DEADLINE_EXCEEDED` error.""" 

458 

459 grpc_status_code = grpc.StatusCode.DEADLINE_EXCEEDED if grpc is not None else None 

460 

461 

462class AsyncRestUnsupportedParameterError(NotImplementedError): 

463 """Raised when an unsupported parameter is configured against async rest transport.""" 

464 

465 pass 

466 

467 

468class ResumableTransferError(GoogleAPICallError): 

469 """Base class for resumable transfer errors.""" 

470 

471 upload_url: Optional[str] = None 

472 chunk_size: Optional[int] = None 

473 

474 def __init__( 

475 self, 

476 message: str, 

477 *args, 

478 upload_url: Optional[str] = None, 

479 chunk_size: Optional[int] = None, 

480 **kwargs, 

481 ) -> None: 

482 super().__init__(message, *args, **kwargs) 

483 self.upload_url = upload_url 

484 self.chunk_size = chunk_size 

485 

486 

487class TransferStalledError(ResumableTransferError): 

488 """Raised when upload throughput stays below minimum rate past stall timeout.""" 

489 

490 

491class UnseekableStreamError(ResumableTransferError): 

492 """Raised when server recovery requires rewinding a non-seekable stream.""" 

493 

494 

495class UploadCancelledError(ResumableTransferError): 

496 """Raised when the upload is cancelled by the client or server.""" 

497 

498 

499class MissingStatusHeaderError(ResumableTransferError): 

500 """Raised when server response lacks the required X-Goog-Upload-Status header.""" 

501 

502 

503def exception_class_for_http_status(status_code): 

504 """Return the exception class for a specific HTTP status code. 

505 

506 Args: 

507 status_code (int): The HTTP status code. 

508 

509 Returns: 

510 :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`. 

511 """ 

512 return _HTTP_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError) 

513 

514 

515def from_http_status(status_code, message, **kwargs): 

516 """Create a :class:`GoogleAPICallError` from an HTTP status code. 

517 

518 Args: 

519 status_code (int): The HTTP status code. 

520 message (str): The exception message. 

521 kwargs: Additional arguments passed to the :class:`GoogleAPICallError` 

522 constructor. 

523 

524 Returns: 

525 GoogleAPICallError: An instance of the appropriate subclass of 

526 :class:`GoogleAPICallError`. 

527 """ 

528 error_class = exception_class_for_http_status(status_code) 

529 error = error_class(message, **kwargs) 

530 

531 if error.code is None: 

532 error.code = status_code 

533 

534 return error 

535 

536 

537def _format_rest_error_message(error, method, url): 

538 method = method.upper() if method else None 

539 message = "{method} {url}: {error}".format( 

540 method=method, 

541 url=url, 

542 error=error, 

543 ) 

544 return message 

545 

546 

547# NOTE: We're moving away from `from_http_status` because it expects an aiohttp response compared 

548# to `format_http_response_error` which expects a more abstract response from google.auth and is 

549# compatible with both sync and async response types. 

550# TODO(https://github.com/googleapis/python-api-core/issues/691): Add type hint for response. 

551def format_http_response_error( 

552 response, method: str, url: str, payload: Optional[Dict] = None 

553): 

554 """Create a :class:`GoogleAPICallError` from a google auth rest response. 

555 

556 Args: 

557 response Union[google.auth.transport.Response, google.auth.aio.transport.Response]: The HTTP response. 

558 method Optional(str): The HTTP request method. 

559 url Optional(str): The HTTP request url. 

560 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. 

561 

562 Returns: 

563 GoogleAPICallError: An instance of the appropriate subclass of 

564 :class:`GoogleAPICallError`, with the message and errors populated 

565 from the response. 

566 """ 

567 if isinstance(payload, list): 

568 payload = next((item for item in payload if isinstance(item, dict)), {}) 

569 payload = {} if not payload else payload 

570 error_message = payload.get("error", {}).get("message", "unknown error") 

571 errors = payload.get("error", {}).get("errors", ()) 

572 # In JSON, details are already formatted in developer-friendly way. 

573 details = payload.get("error", {}).get("details", ()) 

574 error_info_list = list( 

575 filter( 

576 lambda detail: detail.get("@type", "") 

577 == "type.googleapis.com/google.rpc.ErrorInfo", 

578 details, 

579 ) 

580 ) 

581 error_info = error_info_list[0] if error_info_list else None 

582 message = _format_rest_error_message(error_message, method, url) 

583 

584 exception = from_http_status( 

585 response.status_code, 

586 message, 

587 errors=errors, 

588 details=details, 

589 response=response, 

590 error_info=error_info, 

591 ) 

592 return exception 

593 

594 

595def from_http_response(response): 

596 """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`. 

597 

598 Args: 

599 response (requests.Response): The HTTP response. 

600 

601 Returns: 

602 GoogleAPICallError: An instance of the appropriate subclass of 

603 :class:`GoogleAPICallError`, with the message and errors populated 

604 from the response. 

605 """ 

606 try: 

607 payload = response.json() 

608 except ValueError: 

609 payload = {"error": {"message": response.text or "unknown error"}} 

610 return format_http_response_error( 

611 response, response.request.method, response.request.url, payload 

612 ) 

613 

614 

615def exception_class_for_grpc_status(status_code): 

616 """Return the exception class for a specific :class:`grpc.StatusCode`. 

617 

618 Args: 

619 status_code (grpc.StatusCode): The gRPC status code. 

620 

621 Returns: 

622 :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`. 

623 """ 

624 return _GRPC_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError) 

625 

626 

627def from_grpc_status(status_code, message, **kwargs): 

628 """Create a :class:`GoogleAPICallError` from a :class:`grpc.StatusCode`. 

629 

630 Args: 

631 status_code (Union[grpc.StatusCode, int]): The gRPC status code. 

632 message (str): The exception message. 

633 kwargs: Additional arguments passed to the :class:`GoogleAPICallError` 

634 constructor. 

635 

636 Returns: 

637 GoogleAPICallError: An instance of the appropriate subclass of 

638 :class:`GoogleAPICallError`. 

639 """ 

640 

641 if isinstance(status_code, int): 

642 status_code = _INT_TO_GRPC_CODE.get(status_code, status_code) 

643 

644 error_class = exception_class_for_grpc_status(status_code) 

645 error = error_class(message, **kwargs) 

646 

647 if error.grpc_status_code is None: 

648 error.grpc_status_code = status_code 

649 

650 return error 

651 

652 

653def _extract_status_code(exc: BaseException | None, default: str | None = None) -> str: 

654 """Extract canonical status code name string from an exception. 

655 

656 Status code name strings are resolved by inspecting the following: 

657 * Chained exceptions: Unwraps RetryError or __cause__ to the root error. 

658 * Enum & code attributes: Inspects .grpc_status_code on GoogleAPICallError 

659 or .code on gRPC/HTTP errors. 

660 * Integer status codes: Maps raw gRPC integer status codes or HTTP status 

661 codes to canonical enum names. 

662 * Fallback: Uses ``default`` if provided, otherwise defaults to the 

663 exception class name. 

664 

665 Args: 

666 exc (BaseException | None): The exception to extract the status code 

667 name from. 

668 default (str | None): Optional fallback status code string when 

669 ``exc`` carries no status code. 

670 

671 Returns: 

672 str: The canonical status code name (e.g. "NOT_FOUND", "UNAVAILABLE", 

673 "BAD_REQUEST") or class name. 

674 """ 

675 if exc is None: 

676 return default or "" 

677 

678 # 1. Unwrap chained exceptions: unwrap RetryError or __cause__ to the root failure 

679 target = getattr(exc, "cause", None) or getattr(exc, "__cause__", None) or exc 

680 

681 # 2. Check enum & code attributes: .grpc_status_code enum or callable/non-callable .code 

682 status = getattr(target, "grpc_status_code", None) 

683 if status is None and hasattr(target, "code"): 

684 try: 

685 status = target.code() if callable(target.code) else target.code 

686 # Defensive fallback: observability status extraction must never raise 

687 # if a third-party or mock exception implements a failing .code() method. 

688 except Exception: 

689 status = None 

690 

691 name = getattr(status, "name", None) 

692 if name: 

693 return str(name) 

694 

695 # 3. Check integer status codes: map raw gRPC integer codes or HTTP status codes to canonical names 

696 if isinstance(status, int): 

697 if status in _INT_TO_GRPC_CODE: 

698 grpc_code = _INT_TO_GRPC_CODE[status] 

699 return getattr(grpc_code, "name", str(grpc_code)) 

700 try: 

701 return http.HTTPStatus(status).name 

702 except ValueError: 

703 return str(status) 

704 

705 # 4. Fallback: default to explicit default or exception class name for standard Python errors 

706 return default or target.__class__.__name__ 

707 

708 

709def _is_informative_grpc_error(rpc_exc): 

710 return hasattr(rpc_exc, "code") and hasattr(rpc_exc, "details") 

711 

712 

713def _parse_grpc_error_details(rpc_exc): 

714 if not rpc_status: # pragma: NO COVER 

715 _warn_could_not_import_grpcio_status() 

716 return [], None 

717 try: 

718 status = rpc_status.from_call(rpc_exc) 

719 except NotImplementedError: # workaround 

720 return [], None 

721 

722 if not status: 

723 return [], None 

724 

725 possible_errors = [ 

726 error_details_pb2.BadRequest, 

727 error_details_pb2.PreconditionFailure, 

728 error_details_pb2.QuotaFailure, 

729 error_details_pb2.ErrorInfo, 

730 error_details_pb2.RetryInfo, 

731 error_details_pb2.ResourceInfo, 

732 error_details_pb2.RequestInfo, 

733 error_details_pb2.DebugInfo, 

734 error_details_pb2.Help, 

735 error_details_pb2.LocalizedMessage, 

736 ] 

737 error_info = None 

738 error_details = [] 

739 for detail in status.details: 

740 matched_detail_cls = list( 

741 filter(lambda x: detail.Is(x.DESCRIPTOR), possible_errors) 

742 ) 

743 # If nothing matched, use detail directly. 

744 if len(matched_detail_cls) == 0: 

745 info = detail 

746 else: 

747 info = matched_detail_cls[0]() 

748 detail.Unpack(info) 

749 error_details.append(info) 

750 if isinstance(info, error_details_pb2.ErrorInfo): 

751 error_info = info 

752 return error_details, error_info 

753 

754 

755def from_grpc_error(rpc_exc): 

756 """Create a :class:`GoogleAPICallError` from a :class:`grpc.RpcError`. 

757 

758 Args: 

759 rpc_exc (grpc.RpcError): The gRPC error. 

760 

761 Returns: 

762 GoogleAPICallError: An instance of the appropriate subclass of 

763 :class:`GoogleAPICallError`. 

764 """ 

765 # NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError. 

766 # However, check for grpc.RpcError breaks backward compatibility. 

767 if ( 

768 grpc is not None and isinstance(rpc_exc, grpc.Call) 

769 ) or _is_informative_grpc_error(rpc_exc): 

770 details, err_info = _parse_grpc_error_details(rpc_exc) 

771 message = rpc_exc.details() 

772 if ( 

773 grpc is not None 

774 and rpc_exc.code() == grpc.StatusCode.UNIMPLEMENTED 

775 and "Received http2 header with status: 404" in message 

776 ): 

777 message = ( 

778 f"{message}. This usually indicates that the 'api_endpoint' " 

779 "configuration in ClientOptions is incorrect, contains a typo, " 

780 "or is an invalid regional endpoint for this service." 

781 ) 

782 return from_grpc_status( 

783 rpc_exc.code(), 

784 message, 

785 errors=(rpc_exc,), 

786 details=details, 

787 response=rpc_exc, 

788 error_info=err_info, 

789 ) 

790 else: 

791 return GoogleAPICallError(str(rpc_exc), errors=(rpc_exc,), response=rpc_exc)