-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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] switch base responses to ABCs #20448
Merged
Merged
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
edab626
switch to protocol
iscai-msft c2157a5
update changelog
iscai-msft bdbbb6e
add initial tests
iscai-msft 324f1ae
switch from protocol to abc
iscai-msft 07f4ce2
improve HttpResponse docstrings
iscai-msft b5d9ade
lint
iscai-msft 312b0fc
HeadersType -> MutableMapping[str, str]
iscai-msft 9a00a7f
remove iter_text and iter_lines
iscai-msft d7e39c7
update tests
iscai-msft 90da0a4
improve docstrings
iscai-msft 60a8123
Merge branch 'remove_iter_text_lines' of https://github.com/iscai-msf…
iscai-msft 0a1eb95
have base impls handle more code
iscai-msft 0304a0c
add set_read_checks
iscai-msft 47660f3
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
iscai-msft c428a6b
commit to restart pipelines
iscai-msft f9f40a1
address xiang's comments
iscai-msft c8915df
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
iscai-msft e927262
lint
iscai-msft 7e5804d
clear json cache when encoding is updated
iscai-msft 4609104
make sure content type is empty string if doesn't exist
iscai-msft 8e4febd
update content_type to be None if there is no content type header
iscai-msft 11f4440
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
iscai-msft 590c6f4
fix passing encoding to text method error
iscai-msft ddfd235
update is_stream_consumed docs
iscai-msft d314548
remove erroneous committed code
iscai-msft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
185 changes: 185 additions & 0 deletions
185
sdk/core/azure-core/azure/core/rest/_http_response_impl.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
# -------------------------------------------------------------------------- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know there's a lot of files in the rest folder now, but once we swtich the pipelines over to rest, all of these transport response files and this file will disappear, and will be moved to existing files in |
||
# | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# | ||
# The MIT License (MIT) | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the ""Software""), to | ||
# deal in the Software without restriction, including without limitation the | ||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||
# sell copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in | ||
# all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||
# IN THE SOFTWARE. | ||
# | ||
# -------------------------------------------------------------------------- | ||
from json import loads | ||
from typing import cast, Any, Optional, Iterator | ||
from ._helpers import ( | ||
HeadersType, | ||
get_charset_encoding, | ||
decode_to_text, | ||
parse_lines_from_text, | ||
) | ||
from ..exceptions import HttpResponseError, ResponseNotReadError | ||
try: | ||
from ._rest_py3 import _HttpResponseBase, HttpResponse as _HttpResponse | ||
except (SyntaxError, ImportError): | ||
from ._rest import _HttpResponseBase, HttpResponse as _HttpResponse # type: ignore | ||
|
||
|
||
class _HttpResponseBaseImpl(_HttpResponseBase): # pylint: disable=too-many-instance-attributes | ||
|
||
def __init__(self, **kwargs): | ||
# type: (Any) -> None | ||
super(_HttpResponseBaseImpl, self).__init__() | ||
self.request = kwargs.pop("request") | ||
self._internal_response = kwargs.pop("internal_response") | ||
self.headers = {} # type: HeadersType | ||
self.is_closed = False | ||
self.is_stream_consumed = False | ||
self._connection_data_block_size = None | ||
self._json = None # this is filled in ContentDecodePolicy, when we deserialize | ||
self._content = None # type: Optional[bytes] | ||
self._text = None # type: Optional[str] | ||
|
||
@property | ||
def url(self): | ||
# type: (...) -> str | ||
"""Returns the URL that resulted in this response""" | ||
return self.request.url | ||
|
||
@property | ||
def encoding(self): | ||
# type: (...) -> Optional[str] | ||
"""Returns the response encoding. | ||
|
||
:return: The response encoding. We either return the encoding set by the user, | ||
or try extracting the encoding from the response's content type. If all fails, | ||
we return `None`. | ||
:rtype: optional[str] | ||
""" | ||
try: | ||
return self._encoding | ||
except AttributeError: | ||
self._encoding = get_charset_encoding(self) # type: Optional[str] | ||
return self._encoding | ||
|
||
@encoding.setter | ||
def encoding(self, value): | ||
# type: (str) -> None | ||
"""Sets the response encoding""" | ||
self._encoding = value | ||
iscai-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._text = None # clear text cache | ||
|
||
def text(self, encoding=None): | ||
# type: (Optional[str]) -> str | ||
"""Returns the response body as a string | ||
|
||
:param optional[str] encoding: The encoding you want to decode the text with. Can | ||
also be set independently through our encoding property | ||
:return: The response's content decoded as a string. | ||
""" | ||
if self._text is None or encoding: | ||
iscai-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
encoding_to_pass = encoding or self.encoding | ||
self._text = decode_to_text(encoding_to_pass, self.content) | ||
return self._text | ||
|
||
def json(self): | ||
# type: (...) -> Any | ||
"""Returns the whole body as a json object. | ||
|
||
:return: The JSON deserialized response body | ||
:rtype: any | ||
:raises json.decoder.JSONDecodeError or ValueError (in python 2.7) if object is not JSON decodable: | ||
""" | ||
# this will trigger errors if response is not read in | ||
self.content # pylint: disable=pointless-statement | ||
if not self._json: | ||
self._json = loads(self.text()) | ||
iscai-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return self._json | ||
|
||
def raise_for_status(self): | ||
# type: (...) -> None | ||
"""Raises an HttpResponseError if the response has an error status code. | ||
|
||
If response is good, does nothing. | ||
""" | ||
if cast(int, self.status_code) >= 400: | ||
raise HttpResponseError(response=self) | ||
|
||
@property | ||
def content(self): | ||
# type: (...) -> bytes | ||
"""Return the response's content in bytes.""" | ||
if self._content is None: | ||
raise ResponseNotReadError(self) | ||
return self._content | ||
|
||
def __repr__(self): | ||
# type: (...) -> str | ||
content_type_str = ( | ||
", Content-Type: {}".format(self.content_type) if self.content_type else "" | ||
) | ||
return "<HttpResponse: {} {}{}>".format( | ||
self.status_code, self.reason, content_type_str | ||
) | ||
|
||
class HttpResponseImpl(_HttpResponseBaseImpl, _HttpResponse): # pylint: disable=too-many-instance-attributes | ||
"""HttpResponseImpl built on top of our HttpResponse protocol class. | ||
|
||
Helper impl for creating our transport responses | ||
""" | ||
|
||
def __enter__(self): | ||
# type: (...) -> HttpResponseImpl | ||
return self | ||
|
||
def close(self): | ||
# type: (...) -> None | ||
self.is_closed = True | ||
self._internal_response.close() | ||
|
||
def __exit__(self, *args): | ||
# type: (...) -> None | ||
self.close() | ||
|
||
def read(self): | ||
# type: (...) -> bytes | ||
""" | ||
Read the response's bytes. | ||
|
||
""" | ||
if self._content is None: | ||
self._content = b"".join(self.iter_bytes()) | ||
return self.content | ||
|
||
def iter_text(self): | ||
# type: () -> Iterator[str] | ||
"""Iterate over the response text | ||
""" | ||
for byte in self.iter_bytes(): | ||
text = byte.decode(self.encoding or "utf-8") | ||
yield text | ||
|
||
def iter_lines(self): | ||
# type: () -> Iterator[str] | ||
for text in self.iter_text(): | ||
lines = parse_lines_from_text(text) | ||
for line in lines: | ||
yield line | ||
|
||
def _close_stream(self): | ||
# type: (...) -> None | ||
self.is_stream_consumed = True | ||
self.close() |
109 changes: 109 additions & 0 deletions
109
sdk/core/azure-core/azure/core/rest/_http_response_impl_async.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# -------------------------------------------------------------------------- | ||
# | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# | ||
# The MIT License (MIT) | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the ""Software""), to | ||
# deal in the Software without restriction, including without limitation the | ||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||
# sell copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in | ||
# all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||
# IN THE SOFTWARE. | ||
# | ||
# -------------------------------------------------------------------------- | ||
from typing import AsyncIterator | ||
from ._rest_py3 import AsyncHttpResponse as _AsyncHttpResponse | ||
from ._http_response_impl import _HttpResponseBaseImpl | ||
from ._helpers import parse_lines_from_text | ||
|
||
class AsyncHttpResponseImpl(_HttpResponseBaseImpl, _AsyncHttpResponse): | ||
"""AsyncHttpResponseImpl built on top of our HttpResponse protocol class. | ||
|
||
Helper impl for creating our transport responses | ||
""" | ||
|
||
async def read(self) -> bytes: | ||
"""Read the response's bytes into memory. | ||
|
||
:return: The response's bytes | ||
:rtype: bytes | ||
""" | ||
if self._content is None: | ||
parts = [] | ||
async for part in self.iter_bytes(): | ||
parts.append(part) | ||
self._content = b"".join(parts) | ||
return self._content | ||
|
||
async def iter_text(self) -> AsyncIterator[str]: | ||
"""Asynchronously iterates over the text in the response. | ||
|
||
:return: An async iterator of string. Each string chunk will be a text from the response | ||
:rtype: AsyncIterator[str] | ||
""" | ||
async for byte in self.iter_bytes(): # type: ignore | ||
text = byte.decode(self.encoding or "utf-8") | ||
iscai-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
yield text | ||
|
||
async def iter_lines(self) -> AsyncIterator[str]: | ||
"""Asynchronously iterates over the lines in the response. | ||
|
||
:return: An async iterator of string. Each string chunk will be a line from the response | ||
:rtype: AsyncIterator[str] | ||
""" | ||
async for text in self.iter_text(): | ||
lines = parse_lines_from_text(text) | ||
for line in lines: | ||
yield line | ||
|
||
async def iter_raw(self) -> AsyncIterator[bytes]: | ||
"""Asynchronously iterates over the response's bytes. Will not decompress in the process | ||
|
||
:return: An async iterator of bytes from the response | ||
:rtype: AsyncIterator[bytes] | ||
""" | ||
raise NotImplementedError() | ||
# getting around mypy behavior, see https://github.com/python/mypy/issues/10732 | ||
yield # pylint: disable=unreachable | ||
|
||
async def iter_bytes(self) -> AsyncIterator[bytes]: | ||
"""Asynchronously iterates over the response's bytes. Will decompress in the process | ||
|
||
:return: An async iterator of bytes from the response | ||
:rtype: AsyncIterator[bytes] | ||
""" | ||
raise NotImplementedError() | ||
# getting around mypy behavior, see https://github.com/python/mypy/issues/10732 | ||
yield # pylint: disable=unreachable | ||
|
||
async def close(self) -> None: | ||
"""Close the response. | ||
|
||
:return: None | ||
:rtype: None | ||
""" | ||
self.is_closed = True | ||
await self._internal_response.close() | ||
|
||
async def __aexit__(self, *args) -> None: | ||
await self.close() | ||
|
||
def __repr__(self) -> str: | ||
content_type_str = ( | ||
", Content-Type: {}".format(self.content_type) if self.content_type else "" | ||
) | ||
return "<AsyncHttpResponse: {} {}{}>".format( | ||
self.status_code, self.reason, content_type_str | ||
) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a slightly tangential change, but I wanted to move this to pipeline client to keep the rest file more focused on being the request and protocol responses