Skip to content

Commit 1c4b0d0

Browse files
ohmayrgcf-owl-bot[bot]
andauthoredSep 10, 2024··
feat: Add support for creating exceptions from an asynchronous response (#688)
* feat: add suport for mapping exceptions to rest callables * avoid wrapping errors for rest callable * fix lint issues * add test coverage * address PR comments * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix lint issues * fix for none type method --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 082bce2 commit 1c4b0d0

File tree

3 files changed

+61
-16
lines changed

3 files changed

+61
-16
lines changed
 

‎google/api_core/exceptions.py

+45-15
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from __future__ import unicode_literals
2323

2424
import http.client
25-
from typing import Dict
25+
from typing import Optional, Dict
2626
from typing import Union
2727
import warnings
2828

@@ -476,22 +476,37 @@ def from_http_status(status_code, message, **kwargs):
476476
return error
477477

478478

479-
def from_http_response(response):
480-
"""Create a :class:`GoogleAPICallError` from a :class:`requests.Response`.
479+
def _format_rest_error_message(error, method, url):
480+
method = method.upper() if method else None
481+
message = "{method} {url}: {error}".format(
482+
method=method,
483+
url=url,
484+
error=error,
485+
)
486+
return message
487+
488+
489+
# NOTE: We're moving away from `from_http_status` because it expects an aiohttp response compared
490+
# to `format_http_response_error` which expects a more abstract response from google.auth and is
491+
# compatible with both sync and async response types.
492+
# TODO(https://github.com/googleapis/python-api-core/issues/691): Add type hint for response.
493+
def format_http_response_error(
494+
response, method: str, url: str, payload: Optional[Dict] = None
495+
):
496+
"""Create a :class:`GoogleAPICallError` from a google auth rest response.
481497
482498
Args:
483-
response (requests.Response): The HTTP response.
499+
response Union[google.auth.transport.Response, google.auth.aio.transport.Response]: The HTTP response.
500+
method Optional(str): The HTTP request method.
501+
url Optional(str): The HTTP request url.
502+
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.
484503
485504
Returns:
486505
GoogleAPICallError: An instance of the appropriate subclass of
487506
:class:`GoogleAPICallError`, with the message and errors populated
488507
from the response.
489508
"""
490-
try:
491-
payload = response.json()
492-
except ValueError:
493-
payload = {"error": {"message": response.text or "unknown error"}}
494-
509+
payload = {} if not payload else payload
495510
error_message = payload.get("error", {}).get("message", "unknown error")
496511
errors = payload.get("error", {}).get("errors", ())
497512
# In JSON, details are already formatted in developer-friendly way.
@@ -504,12 +519,7 @@ def from_http_response(response):
504519
)
505520
)
506521
error_info = error_info[0] if error_info else None
507-
508-
message = "{method} {url}: {error}".format(
509-
method=response.request.method,
510-
url=response.request.url,
511-
error=error_message,
512-
)
522+
message = _format_rest_error_message(error_message, method, url)
513523

514524
exception = from_http_status(
515525
response.status_code,
@@ -522,6 +532,26 @@ def from_http_response(response):
522532
return exception
523533

524534

535+
def from_http_response(response):
536+
"""Create a :class:`GoogleAPICallError` from a :class:`requests.Response`.
537+
538+
Args:
539+
response (requests.Response): The HTTP response.
540+
541+
Returns:
542+
GoogleAPICallError: An instance of the appropriate subclass of
543+
:class:`GoogleAPICallError`, with the message and errors populated
544+
from the response.
545+
"""
546+
try:
547+
payload = response.json()
548+
except ValueError:
549+
payload = {"error": {"message": response.text or "unknown error"}}
550+
return format_http_response_error(
551+
response, response.request.method, response.request.url, payload
552+
)
553+
554+
525555
def exception_class_for_grpc_status(status_code):
526556
"""Return the exception class for a specific :class:`grpc.StatusCode`.
527557

‎google/api_core/gapic_v1/method_async.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@
2525
from google.api_core.gapic_v1.method import DEFAULT # noqa: F401
2626
from google.api_core.gapic_v1.method import USE_DEFAULT_METADATA # noqa: F401
2727

28+
_DEFAULT_ASYNC_TRANSPORT_KIND = "grpc_asyncio"
29+
2830

2931
def wrap_method(
3032
func,
3133
default_retry=None,
3234
default_timeout=None,
3335
default_compression=None,
3436
client_info=client_info.DEFAULT_CLIENT_INFO,
37+
kind=_DEFAULT_ASYNC_TRANSPORT_KIND,
3538
):
3639
"""Wrap an async RPC method with common behavior.
3740
@@ -40,7 +43,8 @@ def wrap_method(
4043
and ``compression`` arguments and applies the common error mapping,
4144
retry, timeout, metadata, and compression behavior to the low-level RPC method.
4245
"""
43-
func = grpc_helpers_async.wrap_errors(func)
46+
if kind == _DEFAULT_ASYNC_TRANSPORT_KIND:
47+
func = grpc_helpers_async.wrap_errors(func)
4448

4549
metadata = [client_info.to_grpc_metadata()] if client_info is not None else None
4650

‎tests/asyncio/gapic/test_method_async.py

+11
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,14 @@ async def test_wrap_method_with_overriding_timeout_as_a_number():
252252

253253
assert result == 42
254254
method.assert_called_once_with(timeout=22, metadata=mock.ANY)
255+
256+
257+
@pytest.mark.asyncio
258+
async def test_wrap_method_without_wrap_errors():
259+
fake_call = mock.AsyncMock()
260+
261+
wrapped_method = gapic_v1.method_async.wrap_method(fake_call, kind="rest")
262+
with mock.patch("google.api_core.grpc_helpers_async.wrap_errors") as method:
263+
await wrapped_method()
264+
265+
method.assert_not_called()

0 commit comments

Comments
 (0)
Please sign in to comment.