Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rest] add response backcompat mixin #20827

Merged
merged 14 commits into from
Sep 28, 2021
Merged
10 changes: 6 additions & 4 deletions sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Release History

## 1.18.1 (Unreleased)
## 1.19.0 (Unreleased)

### 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

### Bugs Fixed

Expand All @@ -22,8 +26,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)
iscai-msft marked this conversation as resolved.
Show resolved Hide resolved
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
2 changes: 1 addition & 1 deletion sdk/core/azure-core/azure/core/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
# regenerated.
# --------------------------------------------------------------------------

VERSION = "1.18.1"
VERSION = "1.19.0"
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")
iscai-msft marked this conversation as resolved.
Show resolved Hide resolved

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