diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/azure/core/rest')
9 files changed, 1928 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/__init__.py b/.venv/lib/python3.12/site-packages/azure/core/rest/__init__.py new file mode 100644 index 00000000..078efaaa --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/__init__.py @@ -0,0 +1,36 @@ +# -------------------------------------------------------------------------- +# +# 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 ._rest_py3 import ( + HttpRequest, + HttpResponse, + AsyncHttpResponse, +) + +__all__ = [ + "HttpRequest", + "HttpResponse", + "AsyncHttpResponse", +] diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_aiohttp.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_aiohttp.py new file mode 100644 index 00000000..64833e31 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_aiohttp.py @@ -0,0 +1,228 @@ +# -------------------------------------------------------------------------- +# +# 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. +# +# -------------------------------------------------------------------------- +import collections.abc +import asyncio +from itertools import groupby +from typing import Iterator, cast +from multidict import CIMultiDict +from ._http_response_impl_async import ( + AsyncHttpResponseImpl, + AsyncHttpResponseBackcompatMixin, +) +from ..pipeline.transport._aiohttp import AioHttpStreamDownloadGenerator +from ..utils._pipeline_transport_rest_shared import _pad_attr_name, _aiohttp_body_helper +from ..exceptions import ResponseNotReadError + + +class _ItemsView(collections.abc.ItemsView): + def __init__(self, ref): + super().__init__(ref) + self._ref = ref + + def __iter__(self): + for key, groups in groupby(self._ref.__iter__(), lambda x: x[0]): + yield tuple([key, ", ".join(group[1] for group in groups)]) + + def __contains__(self, item): + if not (isinstance(item, (list, tuple)) and len(item) == 2): + return False + for k, v in self.__iter__(): + if item[0].lower() == k.lower() and item[1] == v: + return True + return False + + def __repr__(self): + return f"dict_items({list(self.__iter__())})" + + +class _KeysView(collections.abc.KeysView): + def __init__(self, items): + super().__init__(items) + self._items = items + + def __iter__(self) -> Iterator[str]: + for key, _ in self._items: + yield key + + def __contains__(self, key): + try: + for k in self.__iter__(): + if cast(str, key).lower() == k.lower(): + return True + except AttributeError: # Catch "lower()" if key not a string + pass + return False + + def __repr__(self) -> str: + return f"dict_keys({list(self.__iter__())})" + + +class _ValuesView(collections.abc.ValuesView): + def __init__(self, items): + super().__init__(items) + self._items = items + + def __iter__(self): + for _, value in self._items: + yield value + + def __contains__(self, value): + for v in self.__iter__(): + if value == v: + return True + return False + + def __repr__(self): + return f"dict_values({list(self.__iter__())})" + + +class _CIMultiDict(CIMultiDict): + """Dictionary with the support for duplicate case-insensitive keys.""" + + def __iter__(self): + return iter(self.keys()) + + def keys(self): + """Return a new view of the dictionary's keys. + + :return: A new view of the dictionary's keys + :rtype: ~collections.abc.KeysView + """ + return _KeysView(self.items()) + + def items(self): + """Return a new view of the dictionary's items. + + :return: A new view of the dictionary's items + :rtype: ~collections.abc.ItemsView + """ + return _ItemsView(super().items()) + + def values(self): + """Return a new view of the dictionary's values. + + :return: A new view of the dictionary's values + :rtype: ~collections.abc.ValuesView + """ + return _ValuesView(self.items()) + + def __getitem__(self, key: str) -> str: + return ", ".join(self.getall(key, [])) + + def get(self, key, default=None): + values = self.getall(key, None) + if values: + values = ", ".join(values) + return values or default + + +class _RestAioHttpTransportResponseBackcompatMixin(AsyncHttpResponseBackcompatMixin): + """Backcompat mixin for aiohttp responses. + + Need to add it's own mixin because it has function load_body, which other + transport responses don't have, and also because we need to synchronously + decompress the body if users call .body() + """ + + def body(self) -> bytes: + """Return the whole body as bytes in memory. + + Have to modify the default behavior here. In AioHttp, we do decompression + when accessing the body method. The behavior here is the same as if the + caller did an async read of the response first. But for backcompat reasons, + we need to support this decompression within the synchronous body method. + + :return: The response's bytes + :rtype: bytes + """ + return _aiohttp_body_helper(self) + + async def _load_body(self) -> None: + """Load in memory the body, so it could be accessible from sync methods.""" + self._content = await self.read() # type: ignore + + def __getattr__(self, attr): + backcompat_attrs = ["load_body"] + attr = _pad_attr_name(attr, backcompat_attrs) + return super().__getattr__(attr) + + +class RestAioHttpTransportResponse(AsyncHttpResponseImpl, _RestAioHttpTransportResponseBackcompatMixin): + def __init__(self, *, internal_response, decompress: bool = True, **kwargs): + headers = _CIMultiDict(internal_response.headers) + super().__init__( + internal_response=internal_response, + status_code=internal_response.status, + headers=headers, + content_type=headers.get("content-type"), + reason=internal_response.reason, + stream_download_generator=AioHttpStreamDownloadGenerator, + content=None, + **kwargs, + ) + self._decompress = decompress + self._decompressed_content = False + + def __getstate__(self): + state = self.__dict__.copy() + # Remove the unpicklable entries. + state["_internal_response"] = None # aiohttp response are not pickable (see headers comments) + state["headers"] = CIMultiDict(self.headers) # MultiDictProxy is not pickable + return state + + @property + def content(self) -> bytes: + """Return the response's content in bytes. + + :return: The response's content in bytes + :rtype: bytes + """ + if self._content is None: + raise ResponseNotReadError(self) + return _aiohttp_body_helper(self) + + async def read(self) -> bytes: + """Read the response's bytes into memory. + + :return: The response's bytes + :rtype: bytes + """ + if not self._content: + self._stream_download_check() + self._content = await self._internal_response.read() + await self._set_read_checks() + return _aiohttp_body_helper(self) + + async def close(self) -> None: + """Close the response. + + :return: None + :rtype: None + """ + if not self.is_closed: + self._is_closed = True + self._internal_response.close() + await asyncio.sleep(0) diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_helpers.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_helpers.py new file mode 100644 index 00000000..3ef5201d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_helpers.py @@ -0,0 +1,423 @@ +# -------------------------------------------------------------------------- +# +# 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 __future__ import annotations +import copy +import codecs +import email.message +from json import dumps +from typing import ( + Optional, + Union, + Mapping, + Sequence, + Tuple, + IO, + Any, + Iterable, + MutableMapping, + AsyncIterable, + cast, + Dict, + TYPE_CHECKING, +) +import xml.etree.ElementTree as ET +from urllib.parse import urlparse +from azure.core.serialization import AzureJSONEncoder +from ..utils._pipeline_transport_rest_shared import ( + _format_parameters_helper, + _pad_attr_name, + _prepare_multipart_body_helper, + _serialize_request, + _format_data_helper, + get_file_items, +) + +if TYPE_CHECKING: + # This avoid a circular import + from ._rest_py3 import HttpRequest + +################################### TYPES SECTION ######################### + +binary_type = str +PrimitiveData = Optional[Union[str, int, float, bool]] + +ParamsType = Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]] + +FileContent = Union[str, bytes, IO[str], IO[bytes]] + +FileType = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], +] + +FilesType = Union[Mapping[str, FileType], Sequence[Tuple[str, FileType]]] + +ContentTypeBase = Union[str, bytes, Iterable[bytes]] +ContentType = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] + +DataType = Optional[Union[bytes, Dict[str, Union[str, int]]]] + +########################### HELPER SECTION ################################# + + +def _verify_data_object(name, value): + if not isinstance(name, str): + raise TypeError("Invalid type for data name. Expected str, got {}: {}".format(type(name), name)) + if value is not None and not isinstance(value, (str, bytes, int, float)): + raise TypeError("Invalid type for data value. Expected primitive type, got {}: {}".format(type(name), name)) + + +def set_urlencoded_body(data, has_files): + body = {} + default_headers = {} + for f, d in data.items(): + if not d: + continue + if isinstance(d, list): + for item in d: + _verify_data_object(f, item) + else: + _verify_data_object(f, d) + body[f] = d + if not has_files: + # little hacky, but for files we don't send a content type with + # boundary so requests / aiohttp etc deal with it + default_headers["Content-Type"] = "application/x-www-form-urlencoded" + return default_headers, body + + +def set_multipart_body(files: FilesType): + formatted_files = [(f, _format_data_helper(d)) for f, d in get_file_items(files) if d is not None] + return {}, dict(formatted_files) if isinstance(files, Mapping) else formatted_files + + +def set_xml_body(content): + headers = {} + bytes_content = ET.tostring(content, encoding="utf8") + body = bytes_content.replace(b"encoding='utf8'", b"encoding='utf-8'") + if body: + headers["Content-Length"] = str(len(body)) + return headers, body + + +def set_content_body( + content: Any, +) -> Tuple[MutableMapping[str, str], Optional[ContentTypeBase]]: + headers: MutableMapping[str, str] = {} + + if isinstance(content, ET.Element): + # XML body + return set_xml_body(content) + if isinstance(content, (str, bytes)): + headers = {} + body = content + if isinstance(content, str): + headers["Content-Type"] = "text/plain" + if body: + headers["Content-Length"] = str(len(body)) + return headers, body + if any(hasattr(content, attr) for attr in ["read", "__iter__", "__aiter__"]): + return headers, content + raise TypeError( + "Unexpected type for 'content': '{}'. ".format(type(content)) + + "We expect 'content' to either be str, bytes, a open file-like object or an iterable/asynciterable." + ) + + +def set_json_body(json: Any) -> Tuple[Dict[str, str], Any]: + headers = {"Content-Type": "application/json"} + if hasattr(json, "read"): + content_headers, body = set_content_body(json) + headers.update(content_headers) + else: + body = dumps(json, cls=AzureJSONEncoder) + headers.update({"Content-Length": str(len(body))}) + return headers, body + + +def lookup_encoding(encoding: str) -> bool: + # including check for whether encoding is known taken from httpx + try: + codecs.lookup(encoding) + return True + except LookupError: + return False + + +def get_charset_encoding(response) -> Optional[str]: + content_type = response.headers.get("Content-Type") + + if not content_type: + return None + # https://peps.python.org/pep-0594/#cgi + m = email.message.Message() + m["content-type"] = content_type + encoding = cast(str, m.get_param("charset")) # -> utf-8 + if encoding is None or not lookup_encoding(encoding): + return None + return encoding + + +def decode_to_text(encoding: Optional[str], content: bytes) -> str: + if not content: + return "" + if encoding == "utf-8": + encoding = "utf-8-sig" + if encoding: + return content.decode(encoding) + return codecs.getincrementaldecoder("utf-8-sig")(errors="replace").decode(content) + + +class HttpRequestBackcompatMixin: + def __getattr__(self, attr: str) -> Any: + backcompat_attrs = [ + "files", + "data", + "multipart_mixed_info", + "query", + "body", + "format_parameters", + "set_streamed_data_body", + "set_text_body", + "set_xml_body", + "set_json_body", + "set_formdata_body", + "set_bytes_body", + "set_multipart_mixed", + "prepare_multipart_body", + "serialize", + ] + attr = _pad_attr_name(attr, backcompat_attrs) + return self.__getattribute__(attr) + + def __setattr__(self, attr: str, value: Any) -> None: + backcompat_attrs = [ + "multipart_mixed_info", + "files", + "data", + "body", + ] + attr = _pad_attr_name(attr, backcompat_attrs) + super(HttpRequestBackcompatMixin, self).__setattr__(attr, value) + + @property + def _multipart_mixed_info( + self, + ) -> Optional[Tuple[Sequence[Any], Sequence[Any], str, Dict[str, Any]]]: + """DEPRECATED: Information used to make multipart mixed requests. + This is deprecated and will be removed in a later release. + + :rtype: tuple + :return: (requests, policies, boundary, kwargs) + """ + try: + return self._multipart_mixed_info_val + except AttributeError: + return None + + @_multipart_mixed_info.setter + def _multipart_mixed_info(self, val: Optional[Tuple[Sequence[Any], Sequence[Any], str, Dict[str, Any]]]): + """DEPRECATED: Set information to make multipart mixed requests. + This is deprecated and will be removed in a later release. + + :param tuple val: (requests, policies, boundary, kwargs) + """ + self._multipart_mixed_info_val = val + + @property + def _query(self) -> Dict[str, Any]: + """DEPRECATED: Query parameters passed in by user + This is deprecated and will be removed in a later release. + + :rtype: dict + :return: Query parameters + """ + query = urlparse(self.url).query + if query: + return {p[0]: p[-1] for p in [p.partition("=") for p in query.split("&")]} + return {} + + @property + def _body(self) -> DataType: + """DEPRECATED: Body of the request. You should use the `content` property instead + This is deprecated and will be removed in a later release. + + :rtype: bytes + :return: Body of the request + """ + return self._data + + @_body.setter + def _body(self, val: DataType) -> None: + """DEPRECATED: Set the body of the request + This is deprecated and will be removed in a later release. + + :param bytes val: Body of the request + """ + self._data = val + + def _format_parameters(self, params: MutableMapping[str, str]) -> None: + """DEPRECATED: Format the query parameters + This is deprecated and will be removed in a later release. + You should pass the query parameters through the kwarg `params` + instead. + + :param dict params: Query parameters + """ + _format_parameters_helper(self, params) + + def _set_streamed_data_body(self, data): + """DEPRECATED: Set the streamed request body. + This is deprecated and will be removed in a later release. + You should pass your stream content through the `content` kwarg instead + + :param data: Streamed data + :type data: bytes or iterable + """ + if not isinstance(data, binary_type) and not any( + hasattr(data, attr) for attr in ["read", "__iter__", "__aiter__"] + ): + raise TypeError("A streamable data source must be an open file-like object or iterable.") + headers = self._set_body(content=data) + self._files = None + self.headers.update(headers) + + def _set_text_body(self, data): + """DEPRECATED: Set the text body + This is deprecated and will be removed in a later release. + You should pass your text content through the `content` kwarg instead + + :param str data: Text data + """ + headers = self._set_body(content=data) + self.headers.update(headers) + self._files = None + + def _set_xml_body(self, data): + """DEPRECATED: Set the xml body. + This is deprecated and will be removed in a later release. + You should pass your xml content through the `content` kwarg instead + + :param data: XML data + :type data: xml.etree.ElementTree.Element + """ + headers = self._set_body(content=data) + self.headers.update(headers) + self._files = None + + def _set_json_body(self, data): + """DEPRECATED: Set the json request body. + This is deprecated and will be removed in a later release. + You should pass your json content through the `json` kwarg instead + + :param data: JSON data + :type data: dict + """ + headers = self._set_body(json=data) + self.headers.update(headers) + self._files = None + + def _set_formdata_body(self, data=None): + """DEPRECATED: Set the formrequest body. + This is deprecated and will be removed in a later release. + You should pass your stream content through the `files` kwarg instead + + :param data: Form data + :type data: dict + """ + if data is None: + data = {} + content_type = self.headers.pop("Content-Type", None) if self.headers else None + + if content_type and content_type.lower() == "application/x-www-form-urlencoded": + headers = self._set_body(data=data) + self._files = None + else: # Assume "multipart/form-data" + headers = self._set_body(files=data) + self._data = None + self.headers.update(headers) + + def _set_bytes_body(self, data): + """DEPRECATED: Set the bytes request body. + This is deprecated and will be removed in a later release. + You should pass your bytes content through the `content` kwarg instead + + :param bytes data: Bytes data + """ + headers = self._set_body(content=data) + # we don't want default Content-Type + # in 2.7, byte strings are still strings, so they get set with text/plain content type + + headers.pop("Content-Type", None) + self.headers.update(headers) + self._files = None + + def _set_multipart_mixed(self, *requests: HttpRequest, **kwargs: Any) -> None: + """DEPRECATED: Set the multipart mixed info. + This is deprecated and will be removed in a later release. + + :param requests: Requests to be sent in the multipart request + :type requests: list[HttpRequest] + """ + self.multipart_mixed_info: Tuple[Sequence[HttpRequest], Sequence[Any], str, Dict[str, Any]] = ( + requests, + kwargs.pop("policies", []), + kwargs.pop("boundary", None), + kwargs, + ) + + def _prepare_multipart_body(self, content_index=0): + """DEPRECATED: Prepare your request body for multipart requests. + This is deprecated and will be removed in a later release. + + :param int content_index: The index of the request to be sent in the multipart request + :returns: The updated index after all parts in this request have been added. + :rtype: int + """ + return _prepare_multipart_body_helper(self, content_index) + + def _serialize(self): + """DEPRECATED: Serialize this request using application/http spec. + This is deprecated and will be removed in a later release. + + :rtype: bytes + :return: The serialized request + """ + return _serialize_request(self) + + def _add_backcompat_properties(self, request, memo): + """While deepcopying, we also need to add the private backcompat attrs. + + :param HttpRequest request: The request to copy from + :param dict memo: The memo dict used by deepcopy + """ + request._multipart_mixed_info = copy.deepcopy( # pylint: disable=protected-access + self._multipart_mixed_info, memo + ) diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_http_response_impl.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_http_response_impl.py new file mode 100644 index 00000000..4357f1de --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_http_response_impl.py @@ -0,0 +1,475 @@ +# -------------------------------------------------------------------------- +# +# 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 Any, Optional, Iterator, MutableMapping, Callable +from http.client import HTTPResponse as _HTTPResponse +from ._helpers import ( + get_charset_encoding, + decode_to_text, +) +from ..exceptions import ( + HttpResponseError, + ResponseNotReadError, + StreamConsumedError, + StreamClosedError, +) +from ._rest_py3 import ( + _HttpResponseBase, + HttpResponse as _HttpResponse, + HttpRequest as _HttpRequest, +) +from ..utils._utils import case_insensitive_dict +from ..utils._pipeline_transport_rest_shared import ( + _pad_attr_name, + BytesIOSocket, + _decode_parts_helper, + _get_raw_parts_helper, + _parts_helper, +) + + +class _HttpResponseBackcompatMixinBase: + """Base Backcompat mixin for responses. + + This mixin is used by both sync and async HttpResponse + backcompat mixins. + """ + + def __getattr__(self, attr): + backcompat_attrs = [ + "body", + "internal_response", + "block_size", + "stream_download", + ] + attr = _pad_attr_name(attr, backcompat_attrs) + return self.__getattribute__(attr) + + def __setattr__(self, attr, value): + backcompat_attrs = [ + "block_size", + "internal_response", + "request", + "status_code", + "headers", + "reason", + "content_type", + "stream_download", + ] + attr = _pad_attr_name(attr, backcompat_attrs) + super(_HttpResponseBackcompatMixinBase, self).__setattr__(attr, value) + + def _body(self): + """DEPRECATED: Get the response body. + This is deprecated and will be removed in a later release. + You should get it through the `content` property instead + + :return: The response body. + :rtype: bytes + """ + self.read() + return self.content + + def _decode_parts(self, message, http_response_type, requests): + """Helper for _decode_parts. + + Rebuild an HTTP response from pure string. + + :param message: The body as an email.Message type + :type message: ~email.message.Message + :param http_response_type: The type of response to build + :type http_response_type: type + :param requests: A list of requests to process + :type requests: list[~azure.core.rest.HttpRequest] + :return: A list of responses + :rtype: list[~azure.core.rest.HttpResponse] + """ + + def _deserialize_response(http_response_as_bytes, http_request, http_response_type): + local_socket = BytesIOSocket(http_response_as_bytes) + response = _HTTPResponse(local_socket, method=http_request.method) + response.begin() + return http_response_type(request=http_request, internal_response=response) + + return _decode_parts_helper( + self, + message, + http_response_type or RestHttpClientTransportResponse, + requests, + _deserialize_response, + ) + + def _get_raw_parts(self, http_response_type=None): + """Helper for get_raw_parts + + Assuming this body is multipart, return the iterator or parts. + + If parts are application/http use http_response_type or HttpClientTransportResponse + as envelope. + + :param http_response_type: The type of response to build + :type http_response_type: type + :return: An iterator of responses + :rtype: Iterator[~azure.core.rest.HttpResponse] + """ + return _get_raw_parts_helper(self, http_response_type or RestHttpClientTransportResponse) + + def _stream_download(self, pipeline, **kwargs): + """DEPRECATED: Generator for streaming request body data. + This is deprecated and will be removed in a later release. + You should use `iter_bytes` or `iter_raw` instead. + + :param pipeline: The pipeline object + :type pipeline: ~azure.core.pipeline.Pipeline + :return: An iterator for streaming request body data. + :rtype: iterator[bytes] + """ + return self._stream_download_generator(pipeline, self, **kwargs) + + +class HttpResponseBackcompatMixin(_HttpResponseBackcompatMixinBase): + """Backcompat mixin for sync HttpResponses""" + + def __getattr__(self, attr): + backcompat_attrs = ["parts"] + attr = _pad_attr_name(attr, backcompat_attrs) + return super(HttpResponseBackcompatMixin, self).__getattr__(attr) + + def parts(self): + """DEPRECATED: Assuming the content-type is multipart/mixed, will return the parts as an async iterator. + This is deprecated and will be removed in a later release. + + :rtype: Iterator + :return: The parts of the response + :raises ValueError: If the content is not multipart/mixed + """ + return _parts_helper(self) + + +class _HttpResponseBaseImpl( + _HttpResponseBase, _HttpResponseBackcompatMixinBase +): # pylint: disable=too-many-instance-attributes + """Base Implementation class for azure.core.rest.HttpRespone and azure.core.rest.AsyncHttpResponse + + Since the rest responses are abstract base classes, we need to implement them for each of our transport + responses. This is the base implementation class shared by HttpResponseImpl and AsyncHttpResponseImpl. + The transport responses will be built on top of HttpResponseImpl and AsyncHttpResponseImpl + + :keyword request: The request that led to the response + :type request: ~azure.core.rest.HttpRequest + :keyword any internal_response: The response we get directly from the transport. For example, for our requests + transport, this will be a requests.Response. + :keyword optional[int] block_size: The block size we are using in our transport + :keyword int status_code: The status code of the response + :keyword str reason: The HTTP reason + :keyword str content_type: The content type of the response + :keyword MutableMapping[str, str] headers: The response headers + :keyword Callable stream_download_generator: The stream download generator that we use to stream the response. + """ + + def __init__(self, **kwargs) -> None: + super(_HttpResponseBaseImpl, self).__init__() + self._request = kwargs.pop("request") + self._internal_response = kwargs.pop("internal_response") + self._block_size: int = kwargs.pop("block_size", None) or 4096 + self._status_code: int = kwargs.pop("status_code") + self._reason: str = kwargs.pop("reason") + self._content_type: str = kwargs.pop("content_type") + self._headers: MutableMapping[str, str] = kwargs.pop("headers") + self._stream_download_generator: Callable = kwargs.pop("stream_download_generator") + self._is_closed = False + self._is_stream_consumed = False + self._json = None # this is filled in ContentDecodePolicy, when we deserialize + self._content: Optional[bytes] = None + self._text: Optional[str] = None + + @property + def request(self) -> _HttpRequest: + """The request that resulted in this response. + + :rtype: ~azure.core.rest.HttpRequest + :return: The request that resulted in this response. + """ + return self._request + + @property + def url(self) -> str: + """The URL that resulted in this response. + + :rtype: str + :return: The URL that resulted in this response. + """ + return self.request.url + + @property + def is_closed(self) -> bool: + """Whether the network connection has been closed yet. + + :rtype: bool + :return: Whether the network connection has been closed yet. + """ + return self._is_closed + + @property + def is_stream_consumed(self) -> bool: + """Whether the stream has been consumed. + + :rtype: bool + :return: Whether the stream has been consumed. + """ + return self._is_stream_consumed + + @property + def status_code(self) -> int: + """The status code of this response. + + :rtype: int + :return: The status code of this response. + """ + return self._status_code + + @property + def headers(self) -> MutableMapping[str, str]: + """The response headers. + + :rtype: MutableMapping[str, str] + :return: The response headers. + """ + return self._headers + + @property + def content_type(self) -> Optional[str]: + """The content type of the response. + + :rtype: optional[str] + :return: The content type of the response. + """ + return self._content_type + + @property + def reason(self) -> str: + """The reason phrase for this response. + + :rtype: str + :return: The reason phrase for this response. + """ + return self._reason + + @property + def encoding(self) -> 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: Optional[str] = get_charset_encoding(self) + return self._encoding + + @encoding.setter + def encoding(self, value: str) -> None: + """Sets the response encoding. + + :param str value: Sets the response encoding. + """ + self._encoding = value + self._text = None # clear text cache + self._json = None # clear json cache as well + + def text(self, encoding: Optional[str] = None) -> 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. + :rtype: str + """ + if encoding: + return decode_to_text(encoding, self.content) + if self._text: + return self._text + self._text = decode_to_text(self.encoding, self.content) + return self._text + + def json(self) -> 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()) + return self._json + + def _stream_download_check(self): + if self.is_stream_consumed: + raise StreamConsumedError(self) + if self.is_closed: + raise StreamClosedError(self) + + self._is_stream_consumed = True + + def raise_for_status(self) -> None: + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + """ + if self.status_code >= 400: + raise HttpResponseError(response=self) + + @property + def content(self) -> bytes: + """Return the response's content in bytes. + + :return: The response's content in bytes. + :rtype: bytes + """ + if self._content is None: + raise ResponseNotReadError(self) + return self._content + + def __repr__(self) -> 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, HttpResponseBackcompatMixin): + """HttpResponseImpl built on top of our HttpResponse protocol class. + + Since ~azure.core.rest.HttpResponse is an abstract base class, we need to + implement HttpResponse for each of our transports. This is an implementation + that each of the sync transport responses can be built on. + + :keyword request: The request that led to the response + :type request: ~azure.core.rest.HttpRequest + :keyword any internal_response: The response we get directly from the transport. For example, for our requests + transport, this will be a requests.Response. + :keyword optional[int] block_size: The block size we are using in our transport + :keyword int status_code: The status code of the response + :keyword str reason: The HTTP reason + :keyword str content_type: The content type of the response + :keyword MutableMapping[str, str] headers: The response headers + :keyword Callable stream_download_generator: The stream download generator that we use to stream the response. + """ + + def __enter__(self) -> "HttpResponseImpl": + return self + + def close(self) -> None: + if not self.is_closed: + self._is_closed = True + self._internal_response.close() + + def __exit__(self, *args) -> None: + self.close() + + def _set_read_checks(self): + self._is_stream_consumed = True + self.close() + + def read(self) -> bytes: + """Read the response's bytes. + + :return: The response's bytes + :rtype: bytes + """ + if self._content is None: + self._content = b"".join(self.iter_bytes()) + self._set_read_checks() + return self.content + + def iter_bytes(self, **kwargs) -> Iterator[bytes]: + """Iterates over the response's bytes. Will decompress in the process. + + :return: An iterator of bytes from the response + :rtype: Iterator[str] + """ + if self._content is not None: + chunk_size = self._block_size + for i in range(0, len(self.content), chunk_size): + yield self.content[i : i + chunk_size] + else: + self._stream_download_check() + yield from self._stream_download_generator( + response=self, + pipeline=None, + decompress=True, + ) + self.close() + + def iter_raw(self, **kwargs) -> Iterator[bytes]: + """Iterates over the response's bytes. Will not decompress in the process. + + :return: An iterator of bytes from the response + :rtype: Iterator[str] + """ + self._stream_download_check() + yield from self._stream_download_generator(response=self, pipeline=None, decompress=False) + self.close() + + +class _RestHttpClientTransportResponseBackcompatBaseMixin(_HttpResponseBackcompatMixinBase): + def body(self): + if self._content is None: + self._content = self.internal_response.read() + return self.content + + +class _RestHttpClientTransportResponseBase(_HttpResponseBaseImpl, _RestHttpClientTransportResponseBackcompatBaseMixin): + def __init__(self, **kwargs): + internal_response = kwargs.pop("internal_response") + headers = case_insensitive_dict(internal_response.getheaders()) + super(_RestHttpClientTransportResponseBase, self).__init__( + internal_response=internal_response, + status_code=internal_response.status, + reason=internal_response.reason, + headers=headers, + content_type=headers.get("Content-Type"), + stream_download_generator=None, + **kwargs + ) + + +class RestHttpClientTransportResponse(_RestHttpClientTransportResponseBase, HttpResponseImpl): + """Create a Rest HTTPResponse from an http.client response.""" + + def iter_bytes(self, **kwargs): + raise TypeError("We do not support iter_bytes for this transport response") + + def iter_raw(self, **kwargs): + raise TypeError("We do not support iter_raw for this transport response") + + def read(self): + if self._content is None: + self._content = self._internal_response.read() + return self._content diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_http_response_impl_async.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_http_response_impl_async.py new file mode 100644 index 00000000..e582a103 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_http_response_impl_async.py @@ -0,0 +1,155 @@ +# -------------------------------------------------------------------------- +# +# 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 Any, AsyncIterator, Optional, Type +from types import TracebackType +from ._rest_py3 import AsyncHttpResponse as _AsyncHttpResponse +from ._http_response_impl import ( + _HttpResponseBaseImpl, + _HttpResponseBackcompatMixinBase, + _RestHttpClientTransportResponseBase, +) +from ..utils._pipeline_transport_rest_shared import _pad_attr_name +from ..utils._pipeline_transport_rest_shared_async import _PartGenerator + + +class AsyncHttpResponseBackcompatMixin(_HttpResponseBackcompatMixinBase): + """Backcompat mixin for async responses""" + + def __getattr__(self, attr): + backcompat_attrs = ["parts"] + attr = _pad_attr_name(attr, backcompat_attrs) + return super().__getattr__(attr) + + def parts(self): + """DEPRECATED: Assuming the content-type is multipart/mixed, will return the parts as an async iterator. + This is deprecated and will be removed in a later release. + :rtype: AsyncIterator + :return: The parts of the response + :raises ValueError: If the content is not multipart/mixed + """ + if not self.content_type or not self.content_type.startswith("multipart/mixed"): + raise ValueError("You can't get parts if the response is not multipart/mixed") + + return _PartGenerator(self, default_http_response_type=RestAsyncHttpClientTransportResponse) + + +class AsyncHttpResponseImpl(_HttpResponseBaseImpl, _AsyncHttpResponse, AsyncHttpResponseBackcompatMixin): + """AsyncHttpResponseImpl built on top of our HttpResponse protocol class. + + Since ~azure.core.rest.AsyncHttpResponse is an abstract base class, we need to + implement HttpResponse for each of our transports. This is an implementation + that each of the sync transport responses can be built on. + + :keyword request: The request that led to the response + :type request: ~azure.core.rest.HttpRequest + :keyword any internal_response: The response we get directly from the transport. For example, for our requests + transport, this will be a requests.Response. + :keyword optional[int] block_size: The block size we are using in our transport + :keyword int status_code: The status code of the response + :keyword str reason: The HTTP reason + :keyword str content_type: The content type of the response + :keyword MutableMapping[str, str] headers: The response headers + :keyword Callable stream_download_generator: The stream download generator that we use to stream the response. + """ + + async def _set_read_checks(self): + self._is_stream_consumed = True + await self.close() + + 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) + await self._set_read_checks() + return self._content + + async def iter_raw(self, **kwargs: Any) -> 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] + """ + self._stream_download_check() + async for part in self._stream_download_generator(response=self, pipeline=None, decompress=False): + yield part + await self.close() + + async def iter_bytes(self, **kwargs: Any) -> 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] + """ + if self._content is not None: + for i in range(0, len(self.content), self._block_size): + yield self.content[i : i + self._block_size] + else: + self._stream_download_check() + async for part in self._stream_download_generator(response=self, pipeline=None, decompress=True): + yield part + await self.close() + + async def close(self) -> None: + """Close the response. + + :return: None + :rtype: None + """ + if not self.is_closed: + self._is_closed = True + await self._internal_response.close() + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + traceback: Optional[TracebackType] = None, + ) -> 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) + + +class RestAsyncHttpClientTransportResponse(_RestHttpClientTransportResponseBase, AsyncHttpResponseImpl): + """Create a Rest HTTPResponse from an http.client response.""" + + async def iter_bytes(self, **kwargs): + raise TypeError("We do not support iter_bytes for this transport response") + + async def iter_raw(self, **kwargs): + raise TypeError("We do not support iter_raw for this transport response") + + async def read(self): + if self._content is None: + self._content = self._internal_response.read() + return self._content diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_asyncio.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_asyncio.py new file mode 100644 index 00000000..35e89667 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_asyncio.py @@ -0,0 +1,47 @@ +# -------------------------------------------------------------------------- +# +# 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. +# +# -------------------------------------------------------------------------- +import asyncio +from ._http_response_impl_async import AsyncHttpResponseImpl +from ._requests_basic import _RestRequestsTransportResponseBase +from ..pipeline.transport._requests_asyncio import AsyncioStreamDownloadGenerator + + +class RestAsyncioRequestsTransportResponse(AsyncHttpResponseImpl, _RestRequestsTransportResponseBase): # type: ignore + """Asynchronous streaming of data from the response.""" + + def __init__(self, **kwargs): + super().__init__(stream_download_generator=AsyncioStreamDownloadGenerator, **kwargs) + + async def close(self) -> None: + """Close the response. + + :return: None + :rtype: None + """ + if not self.is_closed: + self._is_closed = True + self._internal_response.close() + await asyncio.sleep(0) diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_basic.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_basic.py new file mode 100644 index 00000000..d5ee4504 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_basic.py @@ -0,0 +1,104 @@ +# -------------------------------------------------------------------------- +# +# 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. +# +# -------------------------------------------------------------------------- +import collections.abc as collections +from requests.structures import ( # pylint: disable=networking-import-outside-azure-core-transport + CaseInsensitiveDict, +) + +from ._http_response_impl import ( + _HttpResponseBaseImpl, + HttpResponseImpl, + _HttpResponseBackcompatMixinBase, +) +from ..pipeline.transport._requests_basic import StreamDownloadGenerator + + +class _ItemsView(collections.ItemsView): + def __contains__(self, item): + if not (isinstance(item, (list, tuple)) and len(item) == 2): + return False # requests raises here, we just return False + for k, v in self.__iter__(): + if item[0].lower() == k.lower() and item[1] == v: + return True + return False + + def __repr__(self): + return "ItemsView({})".format(dict(self.__iter__())) + + +class _CaseInsensitiveDict(CaseInsensitiveDict): + """Overriding default requests dict so we can unify + to not raise if users pass in incorrect items to contains. + Instead, we return False + """ + + def items(self): + """Return a new view of the dictionary's items. + + :rtype: ~collections.abc.ItemsView[str, str] + :returns: a view object that displays a list of (key, value) tuple pairs + """ + return _ItemsView(self) + + +class _RestRequestsTransportResponseBaseMixin(_HttpResponseBackcompatMixinBase): + """Backcompat mixin for the sync and async requests responses + + Overriding the default mixin behavior here because we need to synchronously + read the response's content for the async requests responses + """ + + def _body(self): + # Since requests is not an async library, for backcompat, users should + # be able to access the body directly without loading it first (like we have to do + # in aiohttp). So here, we set self._content to self._internal_response.content, + # which is similar to read, without the async call. + if self._content is None: + self._content = self._internal_response.content + return self._content + + +class _RestRequestsTransportResponseBase(_HttpResponseBaseImpl, _RestRequestsTransportResponseBaseMixin): + def __init__(self, **kwargs): + internal_response = kwargs.pop("internal_response") + content = None + if internal_response._content_consumed: + content = internal_response.content + headers = _CaseInsensitiveDict(internal_response.headers) + super(_RestRequestsTransportResponseBase, self).__init__( + internal_response=internal_response, + status_code=internal_response.status_code, + headers=headers, + reason=internal_response.reason, + content_type=headers.get("content-type"), + content=content, + **kwargs + ) + + +class RestRequestsTransportResponse(HttpResponseImpl, _RestRequestsTransportResponseBase): + def __init__(self, **kwargs): + super(RestRequestsTransportResponse, self).__init__(stream_download_generator=StreamDownloadGenerator, **kwargs) diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_trio.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_trio.py new file mode 100644 index 00000000..bad6e85b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_requests_trio.py @@ -0,0 +1,42 @@ +# -------------------------------------------------------------------------- +# +# 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. +# +# -------------------------------------------------------------------------- +import trio # pylint: disable=networking-import-outside-azure-core-transport +from ._http_response_impl_async import AsyncHttpResponseImpl +from ._requests_basic import _RestRequestsTransportResponseBase +from ..pipeline.transport._requests_trio import TrioStreamDownloadGenerator + + +class RestTrioRequestsTransportResponse(AsyncHttpResponseImpl, _RestRequestsTransportResponseBase): # type: ignore + """Asynchronous streaming of data from the response.""" + + def __init__(self, **kwargs): + super().__init__(stream_download_generator=TrioStreamDownloadGenerator, **kwargs) + + async def close(self) -> None: + if not self.is_closed: + self._is_closed = True + self._internal_response.close() + await trio.sleep(0) diff --git a/.venv/lib/python3.12/site-packages/azure/core/rest/_rest_py3.py b/.venv/lib/python3.12/site-packages/azure/core/rest/_rest_py3.py new file mode 100644 index 00000000..61ac041b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/core/rest/_rest_py3.py @@ -0,0 +1,418 @@ +# -------------------------------------------------------------------------- +# +# 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. +# +# -------------------------------------------------------------------------- +import abc +import copy +from typing import ( + Any, + AsyncIterable, + AsyncIterator, + Iterable, + Iterator, + Optional, + Union, + MutableMapping, + Dict, + AsyncContextManager, +) + +from ..utils._utils import case_insensitive_dict + +from ._helpers import ( + ParamsType, + FilesType, + set_json_body, + set_multipart_body, + set_urlencoded_body, + _format_parameters_helper, + HttpRequestBackcompatMixin, + set_content_body, +) + +ContentType = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] + +################################## CLASSES ###################################### + + +class HttpRequest(HttpRequestBackcompatMixin): + """An HTTP request. + + It should be passed to your client's `send_request` method. + + >>> from azure.core.rest import HttpRequest + >>> request = HttpRequest('GET', 'http://www.example.com') + <HttpRequest [GET], url: 'http://www.example.com'> + >>> response = client.send_request(request) + <HttpResponse: 200 OK> + + :param str method: HTTP method (GET, HEAD, etc.) + :param str url: The url for your request + :keyword mapping params: Query parameters to be mapped into your URL. Your input + should be a mapping of query name to query value(s). + :keyword mapping headers: HTTP headers you want in your request. Your input should + be a mapping of header name to header value. + :keyword any json: A JSON serializable object. We handle JSON-serialization for your + object, so use this for more complicated data structures than `data`. + :keyword content: Content you want in your request body. Think of it as the kwarg you should input + if your data doesn't fit into `json`, `data`, or `files`. Accepts a bytes type, or a generator + that yields bytes. + :paramtype content: str or bytes or iterable[bytes] or asynciterable[bytes] + :keyword dict data: Form data you want in your request body. Use for form-encoded data, i.e. + HTML forms. + :keyword mapping files: Files you want to in your request body. Use for uploading files with + multipart encoding. Your input should be a mapping of file name to file content. + Use the `data` kwarg in addition if you want to include non-file data files as part of your request. + :ivar str url: The URL this request is against. + :ivar str method: The method type of this request. + :ivar mapping headers: The HTTP headers you passed in to your request + :ivar any content: The content passed in for the request + """ + + def __init__( + self, + method: str, + url: str, + *, + params: Optional[ParamsType] = None, + headers: Optional[MutableMapping[str, str]] = None, + json: Any = None, + content: Optional[ContentType] = None, + data: Optional[Dict[str, Any]] = None, + files: Optional[FilesType] = None, + **kwargs: Any + ): + self.url = url + self.method = method + + if params: + _format_parameters_helper(self, params) + self._files = None + self._data: Any = None + + default_headers = self._set_body( + content=content, + data=data, + files=files, + json=json, + ) + self.headers: MutableMapping[str, str] = case_insensitive_dict(default_headers) + self.headers.update(headers or {}) + + if kwargs: + raise TypeError( + "You have passed in kwargs '{}' that are not valid kwargs.".format("', '".join(list(kwargs.keys()))) + ) + + def _set_body( + self, + content: Optional[ContentType] = None, + data: Optional[Dict[str, Any]] = None, + files: Optional[FilesType] = None, + json: Any = None, + ) -> MutableMapping[str, str]: + """Sets the body of the request, and returns the default headers. + + :param content: Content you want in your request body. + :type content: str or bytes or iterable[bytes] or asynciterable[bytes] + :param dict data: Form data you want in your request body. + :param dict files: Files you want to in your request body. + :param any json: A JSON serializable object. + :return: The default headers for the request + :rtype: MutableMapping[str, str] + """ + default_headers: MutableMapping[str, str] = {} + if data is not None and not isinstance(data, dict): + # should we warn? + content = data + if content is not None: + default_headers, self._data = set_content_body(content) + return default_headers + if json is not None: + default_headers, self._data = set_json_body(json) + return default_headers + if files: + default_headers, self._files = set_multipart_body(files) + if data: + default_headers, self._data = set_urlencoded_body(data, has_files=bool(files)) + return default_headers + + @property + def content(self) -> Any: + """Get's the request's content + + :return: The request's content + :rtype: any + """ + return self._data or self._files + + def __repr__(self) -> str: + return "<HttpRequest [{}], url: '{}'>".format(self.method, self.url) + + def __deepcopy__(self, memo: Optional[Dict[int, Any]] = None) -> "HttpRequest": + try: + request = HttpRequest( + method=self.method, + url=self.url, + headers=self.headers, + ) + request._data = copy.deepcopy(self._data, memo) + request._files = copy.deepcopy(self._files, memo) + self._add_backcompat_properties(request, memo) + return request + except (ValueError, TypeError): + return copy.copy(self) + + +class _HttpResponseBase(abc.ABC): + """Base abstract base class for HttpResponses.""" + + @property + @abc.abstractmethod + def request(self) -> HttpRequest: + """The request that resulted in this response. + + :rtype: ~azure.core.rest.HttpRequest + :return: The request that resulted in this response. + """ + + @property + @abc.abstractmethod + def status_code(self) -> int: + """The status code of this response. + + :rtype: int + :return: The status code of this response. + """ + + @property + @abc.abstractmethod + def headers(self) -> MutableMapping[str, str]: + """The response headers. Must be case-insensitive. + + :rtype: MutableMapping[str, str] + :return: The response headers. Must be case-insensitive. + """ + + @property + @abc.abstractmethod + def reason(self) -> str: + """The reason phrase for this response. + + :rtype: str + :return: The reason phrase for this response. + """ + + @property + @abc.abstractmethod + def content_type(self) -> Optional[str]: + """The content type of the response. + + :rtype: str + :return: The content type of the response. + """ + + @property + @abc.abstractmethod + def is_closed(self) -> bool: + """Whether the network connection has been closed yet. + + :rtype: bool + :return: Whether the network connection has been closed yet. + """ + + @property + @abc.abstractmethod + def is_stream_consumed(self) -> bool: + """Whether the stream has been consumed. + + :rtype: bool + :return: Whether the stream has been consumed. + """ + + @property + @abc.abstractmethod + def encoding(self) -> 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] + """ + + @encoding.setter + def encoding(self, value: Optional[str]) -> None: + """Sets the response encoding. + + :param optional[str] value: The encoding to set + """ + + @property + @abc.abstractmethod + def url(self) -> str: + """The URL that resulted in this response. + + :rtype: str + :return: The URL that resulted in this response. + """ + + @property + @abc.abstractmethod + def content(self) -> bytes: + """Return the response's content in bytes. + + :rtype: bytes + :return: The response's content in bytes. + """ + + @abc.abstractmethod + def text(self, encoding: Optional[str] = None) -> 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. + :rtype: str + """ + + @abc.abstractmethod + def json(self) -> 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: + """ + + @abc.abstractmethod + def raise_for_status(self) -> None: + """Raises an HttpResponseError if the response has an error status code. + + If response is good, does nothing. + + :raises ~azure.core.HttpResponseError if the object has an error status code.: + """ + + +class HttpResponse(_HttpResponseBase): + """Abstract base class for HTTP responses. + + Use this abstract base class to create your own transport responses. + + Responses implementing this ABC are returned from your client's `send_request` method + if you pass in an :class:`~azure.core.rest.HttpRequest` + + >>> from azure.core.rest import HttpRequest + >>> request = HttpRequest('GET', 'http://www.example.com') + <HttpRequest [GET], url: 'http://www.example.com'> + >>> response = client.send_request(request) + <HttpResponse: 200 OK> + """ + + @abc.abstractmethod + def __enter__(self) -> "HttpResponse": ... + + @abc.abstractmethod + def __exit__(self, *args: Any) -> None: ... + + @abc.abstractmethod + def close(self) -> None: ... + + @abc.abstractmethod + def read(self) -> bytes: + """Read the response's bytes. + + :return: The read in bytes + :rtype: bytes + """ + + @abc.abstractmethod + def iter_raw(self, **kwargs: Any) -> Iterator[bytes]: + """Iterates over the response's bytes. Will not decompress in the process. + + :return: An iterator of bytes from the response + :rtype: Iterator[str] + """ + + @abc.abstractmethod + def iter_bytes(self, **kwargs: Any) -> Iterator[bytes]: + """Iterates over the response's bytes. Will decompress in the process. + + :return: An iterator of bytes from the response + :rtype: Iterator[str] + """ + + def __repr__(self) -> 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 AsyncHttpResponse(_HttpResponseBase, AsyncContextManager["AsyncHttpResponse"]): + """Abstract base class for Async HTTP responses. + + Use this abstract base class to create your own transport responses. + + Responses implementing this ABC are returned from your async client's `send_request` + method if you pass in an :class:`~azure.core.rest.HttpRequest` + + >>> from azure.core.rest import HttpRequest + >>> request = HttpRequest('GET', 'http://www.example.com') + <HttpRequest [GET], url: 'http://www.example.com'> + >>> response = await client.send_request(request) + <AsyncHttpResponse: 200 OK> + """ + + @abc.abstractmethod + async def read(self) -> bytes: + """Read the response's bytes into memory. + + :return: The response's bytes + :rtype: bytes + """ + + @abc.abstractmethod + async def iter_raw(self, **kwargs: Any) -> 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 + + @abc.abstractmethod + async def iter_bytes(self, **kwargs: Any) -> 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 + + @abc.abstractmethod + async def close(self) -> None: ... |