Skip to content

Commit

Permalink
[rest] add response backcompat mixin (#20827)
Browse files Browse the repository at this point in the history
  • Loading branch information
iscai-msft committed Sep 29, 2021
1 parent 35797ea commit bb686d8
Show file tree
Hide file tree
Showing 52 changed files with 1,902 additions and 691 deletions.
8 changes: 5 additions & 3 deletions sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

### Features Added

### Breaking Changes
### Breaking Changes in the Provisional `azure.core.rest` package

- `azure.core.rest.HttpResponse` and `azure.core.rest.AsyncHttpResponse` are now abstract base classes. They should not be initialized directly, instead
your transport responses should inherit from them and implement them.
- The properties of the `azure.core.rest` responses are now all read-only

- HttpLoggingPolicy integrates logs into one record #19925

Expand All @@ -24,8 +28,6 @@
- The `text` property on `azure.core.rest.HttpResponse` and `azure.core.rest.AsyncHttpResponse` has changed to a method, which also takes
an `encoding` parameter.
- Removed `iter_text` and `iter_lines` from `azure.core.rest.HttpResponse` and `azure.core.rest.AsyncHttpResponse`
- `azure.core.rest.HttpResponse` and `azure.core.rest.AsyncHttpResponse` are now abstract base classes. They should not be initialized directly, instead
your transport responses should inherit from them and implement them.

### Bugs Fixed

Expand Down
21 changes: 4 additions & 17 deletions sdk/core/azure-core/azure/core/_pipeline_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
RequestIdPolicy,
RetryPolicy,
)
from .pipeline._tools import to_rest_response as _to_rest_response

try:
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -192,22 +191,10 @@ def send_request(self, request, **kwargs):
:keyword bool stream: Whether the response payload will be streamed. Defaults to False.
:return: The response of your network call. Does not do error handling on your response.
:rtype: ~azure.core.rest.HttpResponse
# """
rest_request = hasattr(request, "content")
"""
stream = kwargs.pop("stream", False) # want to add default value
return_pipeline_response = kwargs.pop("_return_pipeline_response", False)
pipeline_response = self._pipeline.run(request, **kwargs) # pylint: disable=protected-access
response = pipeline_response.http_response
if rest_request:
response = _to_rest_response(response)
try:
if not kwargs.get("stream", False):
response.read()
response.close()
except Exception as exc:
response.close()
raise exc
pipeline_response = self._pipeline.run(request, stream=stream, **kwargs) # pylint: disable=protected-access
if return_pipeline_response:
pipeline_response.http_response = response
pipeline_response.http_request = request
return pipeline_response
return response
return pipeline_response.http_response
20 changes: 1 addition & 19 deletions sdk/core/azure-core/azure/core/_pipeline_client_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
RequestIdPolicy,
AsyncRetryPolicy,
)
from .pipeline._tools_async import to_rest_response as _to_rest_response

try:
from typing import TYPE_CHECKING, TypeVar
Expand Down Expand Up @@ -194,30 +193,13 @@ def _build_pipeline(self, config, **kwargs): # pylint: disable=no-self-use
return AsyncPipeline(transport, policies)

async def _make_pipeline_call(self, request, **kwargs):
rest_request = hasattr(request, "content")
return_pipeline_response = kwargs.pop("_return_pipeline_response", False)
pipeline_response = await self._pipeline.run(
request, **kwargs # pylint: disable=protected-access
)
response = pipeline_response.http_response
if rest_request:
rest_response = _to_rest_response(response)
if not kwargs.get("stream"):
try:
# in this case, the pipeline transport response already called .load_body(), so
# the body is loaded. instead of doing response.read(), going to set the body
# to the internal content
rest_response._content = response.body() # pylint: disable=protected-access
await rest_response._set_read_checks() # pylint: disable=protected-access
except Exception as exc:
await rest_response.close()
raise exc
response = rest_response
if return_pipeline_response:
pipeline_response.http_response = response
pipeline_response.http_request = request
return pipeline_response
return response
return pipeline_response.http_response

def send_request(
self,
Expand Down
57 changes: 25 additions & 32 deletions sdk/core/azure-core/azure/core/pipeline/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
# IN THE SOFTWARE.
#
# --------------------------------------------------------------------------
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any
from azure.core.rest import HttpResponse as RestHttpResponse

def await_result(func, *args, **kwargs):
"""If func returns an awaitable, raise that this runner can't handle it."""
Expand All @@ -33,38 +38,26 @@ def await_result(func, *args, **kwargs):
)
return result

def to_rest_request(pipeline_transport_request):
from ..rest import HttpRequest as RestHttpRequest
return RestHttpRequest(
method=pipeline_transport_request.method,
url=pipeline_transport_request.url,
headers=pipeline_transport_request.headers,
files=pipeline_transport_request.files,
data=pipeline_transport_request.data
)

def to_rest_response(pipeline_transport_response):
from .transport._requests_basic import RequestsTransportResponse
from ..rest._requests_basic import RestRequestsTransportResponse
if isinstance(pipeline_transport_response, RequestsTransportResponse):
response_type = RestRequestsTransportResponse
else:
raise ValueError("Unknown transport response")
response = response_type(
request=to_rest_request(pipeline_transport_response.request),
internal_response=pipeline_transport_response.internal_response,
block_size=pipeline_transport_response.block_size
)
return response
def is_rest(obj):
# type: (Any) -> bool
"""Return whether a request or a response is a rest request / response.
def get_block_size(response):
try:
return response._block_size # pylint: disable=protected-access
except AttributeError:
return response.block_size
Checking whether the response has the object content can sometimes result
in a ResponseNotRead error if you're checking the value on a response
that has not been read in yet. To get around this, we also have added
a check for is_stream_consumed, which is an exclusive property on our new responses.
"""
return hasattr(obj, "is_stream_consumed") or hasattr(obj, "content")

def get_internal_response(response):
def handle_non_stream_rest_response(response):
# type: (RestHttpResponse) -> None
"""Handle reading and closing of non stream rest responses.
For our new rest responses, we have to call .read() and .close() for our non-stream
responses. This way, we load in the body for users to access.
"""
try:
return response._internal_response # pylint: disable=protected-access
except AttributeError:
return response.internal_response
response.read()
response.close()
except Exception as exc:
response.close()
raise exc
46 changes: 14 additions & 32 deletions sdk/core/azure-core/azure/core/pipeline/_tools_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
# IN THE SOFTWARE.
#
# --------------------------------------------------------------------------
from ._tools import to_rest_request
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..rest import AsyncHttpResponse as RestAsyncHttpResponse


async def await_result(func, *args, **kwargs):
"""If func returns an awaitable, await it."""
Expand All @@ -33,35 +36,14 @@ async def await_result(func, *args, **kwargs):
return await result # type: ignore
return result

def _get_response_type(pipeline_transport_response):
try:
from .transport import AioHttpTransportResponse
from ..rest._aiohttp import RestAioHttpTransportResponse
if isinstance(pipeline_transport_response, AioHttpTransportResponse):
return RestAioHttpTransportResponse
except ImportError:
pass
async def handle_no_stream_rest_response(response: "RestAsyncHttpResponse") -> None:
"""Handle reading and closing of non stream rest responses.
For our new rest responses, we have to call .read() and .close() for our non-stream
responses. This way, we load in the body for users to access.
"""
try:
from .transport import AsyncioRequestsTransportResponse
from ..rest._requests_asyncio import RestAsyncioRequestsTransportResponse
if isinstance(pipeline_transport_response, AsyncioRequestsTransportResponse):
return RestAsyncioRequestsTransportResponse
except ImportError:
pass
try:
from .transport import TrioRequestsTransportResponse
from ..rest._requests_trio import RestTrioRequestsTransportResponse
if isinstance(pipeline_transport_response, TrioRequestsTransportResponse):
return RestTrioRequestsTransportResponse
except ImportError:
pass
raise ValueError("Unknown transport response")

def to_rest_response(pipeline_transport_response):
response_type = _get_response_type(pipeline_transport_response)
response = response_type(
request=to_rest_request(pipeline_transport_response.request),
internal_response=pipeline_transport_response.internal_response,
block_size=pipeline_transport_response.block_size,
)
return response
await response.read()
await response.close()
except Exception as exc:
await response.close()
raise exc
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ def deserialize_from_http_generics(
mime_type = "application/json"

# Rely on transport implementation to give me "text()" decoded correctly
if hasattr(response, "read"):
# since users can call deserialize_from_http_generics by themselves
# we want to make sure our new responses are read before we try to
# deserialize
response.read()
return cls.deserialize_from_text(response.text(encoding), mime_type, response=response)

def on_request(self, request):
Expand Down
Loading

0 comments on commit bb686d8

Please sign in to comment.