Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-azure-core for openSUSE:Factory checked in at 2023-09-15 22:05:52 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-azure-core (Old) and /work/SRC/openSUSE:Factory/.python-azure-core.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-azure-core" Fri Sep 15 22:05:52 2023 rev:43 rq:1111566 version:1.29.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-azure-core/python-azure-core.changes 2023-08-23 15:00:00.946247749 +0200 +++ /work/SRC/openSUSE:Factory/.python-azure-core.new.1766/python-azure-core.changes 2023-09-15 22:11:26.049918928 +0200 @@ -1,0 +2,8 @@ +Fri Sep 15 09:41:14 UTC 2023 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- New upstream release + + Version 1.29.4 + + For detailed information about changes see the + CHANGELOG.md file provided with this package + +------------------------------------------------------------------- Old: ---- azure-core-1.29.3.tar.gz New: ---- azure-core-1.29.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-azure-core.spec ++++++ --- /var/tmp/diff_new_pack.SquIEr/_old 2023-09-15 22:11:27.201960119 +0200 +++ /var/tmp/diff_new_pack.SquIEr/_new 2023-09-15 22:11:27.205960262 +0200 @@ -21,7 +21,7 @@ %define skip_python2 1 %endif Name: python-azure-core -Version: 1.29.3 +Version: 1.29.4 Release: 0 Summary: Microsoft Azure Core Library for Python License: MIT ++++++ azure-core-1.29.3.tar.gz -> azure-core-1.29.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/CHANGELOG.md new/azure-core-1.29.4/CHANGELOG.md --- old/azure-core-1.29.3/CHANGELOG.md 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/CHANGELOG.md 2023-09-07 19:37:17.000000000 +0200 @@ -1,5 +1,15 @@ # Release History +## 1.29.4 (2023-09-07) + +### Bugs Fixed + +- Fixed the issue that some urls trigger an infinite loop. #31346 +- Fixed issue where IndexError was raised if multipart responses did not match the number of requests. #31471 +- Fixed issue unbound variable exception if dict is invalid in CloudEvent.from_dict. #31835 +- Fixed issue asyncBearerTokenCredentialPolicy is not backward compatible with SansIOHTTPPolicy. #31836 +- Fixed issue mypy complains with new version of azure-core. #31564 + ## 1.29.3 (2023-08-22) ### Bugs Fixed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/PKG-INFO new/azure-core-1.29.4/PKG-INFO --- old/azure-core-1.29.3/PKG-INFO 2023-08-22 21:16:02.061326500 +0200 +++ new/azure-core-1.29.4/PKG-INFO 2023-09-07 19:38:17.038747800 +0200 @@ -1,11 +1,12 @@ Metadata-Version: 2.1 Name: azure-core -Version: 1.29.3 +Version: 1.29.4 Summary: Microsoft Azure Core Library for Python Home-page: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/core/azure-core Author: Microsoft Corporation Author-email: azpysdkh...@microsoft.com License: MIT License +Keywords: azure,azure sdk Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only @@ -18,8 +19,12 @@ Classifier: License :: OSI Approved :: MIT License Requires-Python: >=3.7 Description-Content-Type: text/markdown -Provides-Extra: aio License-File: LICENSE +Requires-Dist: requests>=2.18.4 +Requires-Dist: six>=1.11.0 +Requires-Dist: typing-extensions>=4.6.0 +Provides-Extra: aio +Requires-Dist: aiohttp>=3.0; extra == "aio" # Azure Core shared client library for Python @@ -282,6 +287,16 @@ # Release History +## 1.29.4 (2023-09-07) + +### Bugs Fixed + +- Fixed the issue that some urls trigger an infinite loop. #31346 +- Fixed issue where IndexError was raised if multipart responses did not match the number of requests. #31471 +- Fixed issue unbound variable exception if dict is invalid in CloudEvent.from_dict. #31835 +- Fixed issue asyncBearerTokenCredentialPolicy is not backward compatible with SansIOHTTPPolicy. #31836 +- Fixed issue mypy complains with new version of azure-core. #31564 + ## 1.29.3 (2023-08-22) ### Bugs Fixed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/_pipeline_client.py new/azure-core-1.29.4/azure/core/_pipeline_client.py --- old/azure-core-1.29.3/azure/core/_pipeline_client.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/_pipeline_client.py 2023-09-07 19:37:17.000000000 +0200 @@ -172,7 +172,8 @@ policies = policies_1 if transport is None: - from .pipeline.transport import RequestsTransport # pylint: disable=no-name-in-module + # Use private import for better typing, mypy and pyright don't like PEP562 + from .pipeline.transport._requests_basic import RequestsTransport transport = RequestsTransport(**kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/_pipeline_client_async.py new/azure-core-1.29.4/azure/core/_pipeline_client_async.py --- old/azure-core-1.29.3/azure/core/_pipeline_client_async.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/_pipeline_client_async.py 2023-09-07 19:37:17.000000000 +0200 @@ -254,7 +254,8 @@ policies = policies_1 if not transport: - from .pipeline.transport import AioHttpTransport # pylint: disable=no-name-in-module + # Use private import for better typing, mypy and pyright don't like PEP562 + from .pipeline.transport._aiohttp import AioHttpTransport transport = AioHttpTransport(**kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/_version.py new/azure-core-1.29.4/azure/core/_version.py --- old/azure-core-1.29.3/azure/core/_version.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/_version.py 2023-09-07 19:37:17.000000000 +0200 @@ -9,4 +9,4 @@ # regenerated. # -------------------------------------------------------------------------- -VERSION = "1.29.3" +VERSION = "1.29.4" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/credentials_async.py new/azure-core-1.29.4/azure/core/credentials_async.py --- old/azure-core-1.29.3/azure/core/credentials_async.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/credentials_async.py 2023-09-07 19:37:17.000000000 +0200 @@ -29,6 +29,7 @@ :rtype: AccessToken :return: An AccessToken instance containing the token string and its expiration time in Unix time. """ + ... async def close(self) -> None: pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/exceptions.py new/azure-core-1.29.4/azure/core/exceptions.py --- old/azure-core-1.29.3/azure/core/exceptions.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/exceptions.py 2023-09-07 19:37:17.000000000 +0200 @@ -40,6 +40,7 @@ TypeVar, Generic, Dict, + NoReturn, TYPE_CHECKING, ) from typing_extensions import Protocol, runtime_checkable @@ -79,7 +80,7 @@ ] -def raise_with_traceback(exception: Callable, *args: Any, **kwargs: Any) -> None: +def raise_with_traceback(exception: Callable, *args: Any, **kwargs: Any) -> NoReturn: """Raise exception with a specified traceback. This MUST be called inside a "except" clause. @@ -113,18 +114,18 @@ @property def reason(self) -> Optional[str]: - pass + ... @property def status_code(self) -> Optional[int]: - pass + ... def text(self) -> str: - pass + ... @property def request(self) -> object: # object as type, since all we need is str() on it - pass + ... class ErrorMap(Generic[KeyType, ValueType]): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/messaging.py new/azure-core-1.29.4/azure/core/messaging.py --- old/azure-core-1.29.3/azure/core/messaging.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/messaging.py 2023-09-07 19:37:17.000000000 +0200 @@ -192,26 +192,26 @@ except KeyError as err: # https://github.com/cloudevents/spec Cloud event spec requires source, type, # specversion. We autopopulate everything other than source, type. - if not all(_ in event for _ in ("source", "type")): - if all( - _ in event - for _ in ( - "subject", - "eventType", - "data", - "dataVersion", - "id", - "eventTime", - ) - ): - raise ValueError( - "The event you are trying to parse follows the Eventgrid Schema. You can parse" - + " EventGrid events using EventGridEvent.from_dict method in the azure-eventgrid library." - ) from err + # So we will assume the KeyError is coming from source/type access. + if all( + key in event + for key in ( + "subject", + "eventType", + "data", + "dataVersion", + "id", + "eventTime", + ) + ): raise ValueError( - "The event does not conform to the cloud event spec https://github.com/cloudevents/spec." - + " The `source` and `type` params are required." + "The event you are trying to parse follows the Eventgrid Schema. You can parse" + + " EventGrid events using EventGridEvent.from_dict method in the azure-eventgrid library." ) from err + raise ValueError( + "The event does not conform to the cloud event spec https://github.com/cloudevents/spec." + + " The `source` and `type` params are required." + ) from err return event_obj @classmethod diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/_tools.py new/azure-core-1.29.4/azure/core/pipeline/_tools.py --- old/azure-core-1.29.3/azure/core/pipeline/_tools.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/_tools.py 2023-09-07 19:37:17.000000000 +0200 @@ -23,14 +23,19 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- -from typing import TYPE_CHECKING +from __future__ import annotations +from typing import TYPE_CHECKING, Union, Callable, TypeVar +from typing_extensions import TypeGuard, ParamSpec if TYPE_CHECKING: - from typing import Any - from azure.core.rest import HttpResponse as RestHttpResponse + from azure.core.rest import HttpResponse, HttpRequest, AsyncHttpResponse -def await_result(func, *args, **kwargs): +P = ParamSpec("P") +T = TypeVar("T") + + +def await_result(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: """If func returns an awaitable, raise that this runner can't handle it. :param func: The function to run. @@ -47,7 +52,7 @@ return result -def is_rest(obj) -> bool: +def is_rest(obj: object) -> TypeGuard[Union[HttpRequest, HttpResponse, AsyncHttpResponse]]: """Return whether a request or a response is a rest request / response. Checking whether the response has the object content can sometimes result @@ -63,7 +68,7 @@ return hasattr(obj, "is_stream_consumed") or hasattr(obj, "content") -def handle_non_stream_rest_response(response: "RestHttpResponse") -> None: +def handle_non_stream_rest_response(response: HttpResponse) -> 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. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/_tools_async.py new/azure-core-1.29.4/azure/core/pipeline/_tools_async.py --- old/azure-core-1.29.3/azure/core/pipeline/_tools_async.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/_tools_async.py 2023-09-07 19:37:17.000000000 +0200 @@ -23,13 +23,27 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable, TypeVar, Awaitable, Union, overload +from typing_extensions import ParamSpec if TYPE_CHECKING: from ..rest import AsyncHttpResponse as RestAsyncHttpResponse +P = ParamSpec("P") +T = TypeVar("T") -async def await_result(func, *args, **kwargs): + +@overload +async def await_result(func: Callable[P, Awaitable[T]], *args: P.args, **kwargs: P.kwargs) -> T: + ... + + +@overload +async def await_result(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: + ... + + +async def await_result(func: Callable[P, Union[T, Awaitable[T]]], *args: P.args, **kwargs: P.kwargs) -> T: """If func returns an awaitable, await it. :param func: The function to run. @@ -40,9 +54,8 @@ :return: The result of the function """ result = func(*args, **kwargs) - if hasattr(result, "__await__"): - # type ignore on await: https://github.com/python/mypy/issues/7587 - return await result # type: ignore + if isinstance(result, Awaitable): + return await result return result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/policies/_authentication_async.py new/azure-core-1.29.4/azure/core/pipeline/policies/_authentication_async.py --- old/azure-core-1.29.3/azure/core/pipeline/policies/_authentication_async.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/policies/_authentication_async.py 2023-09-07 19:37:17.000000000 +0200 @@ -92,28 +92,28 @@ await await_result(self.on_request, request) try: response = await self.next.send(request) - await await_result(self.on_response, request, response) except Exception: # pylint:disable=broad-except - handled = await await_result(self.on_exception, request) - if not handled: - raise + await await_result(self.on_exception, request) + raise else: - if response.http_response.status_code == 401: - self._token = None # any cached token is invalid - if "WWW-Authenticate" in response.http_response.headers: - request_authorized = await self.on_challenge(request, response) - if request_authorized: - # if we receive a challenge response, we retrieve a new token - # which matches the new target. In this case, we don't want to remove - # token from the request so clear the 'insecure_domain_change' tag - request.context.options.pop("insecure_domain_change", False) - try: - response = await self.next.send(request) - await await_result(self.on_response, request, response) - except Exception: # pylint:disable=broad-except - handled = await await_result(self.on_exception, request) - if not handled: - raise + await await_result(self.on_response, request, response) + + if response.http_response.status_code == 401: + self._token = None # any cached token is invalid + if "WWW-Authenticate" in response.http_response.headers: + request_authorized = await self.on_challenge(request, response) + if request_authorized: + # if we receive a challenge response, we retrieve a new token + # which matches the new target. In this case, we don't want to remove + # token from the request so clear the 'insecure_domain_change' tag + request.context.options.pop("insecure_domain_change", False) + try: + response = await self.next.send(request) + except Exception: # pylint:disable=broad-except + await await_result(self.on_exception, request) + raise + else: + await await_result(self.on_response, request, response) return response diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/policies/_distributed_tracing.py new/azure-core-1.29.4/azure/core/pipeline/policies/_distributed_tracing.py --- old/azure-core-1.29.3/azure/core/pipeline/policies/_distributed_tracing.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/policies/_distributed_tracing.py 2023-09-07 19:37:17.000000000 +0200 @@ -26,7 +26,7 @@ """Traces network calls using the implementation library from the settings.""" import logging import sys -import urllib +import urllib.parse from typing import TYPE_CHECKING, Optional, Tuple, TypeVar, Union, Any, Type from types import TracebackType diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/transport/_aiohttp.py new/azure-core-1.29.4/azure/core/pipeline/transport/_aiohttp.py --- old/azure-core-1.29.3/azure/core/pipeline/transport/_aiohttp.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/transport/_aiohttp.py 2023-09-07 19:37:17.000000000 +0200 @@ -23,6 +23,7 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- +from __future__ import annotations import sys from typing import Any, Optional, AsyncIterator as AsyncIteratorType, TYPE_CHECKING, overload, cast, Union, Type from types import TracebackType @@ -56,6 +57,7 @@ HttpRequest as RestHttpRequest, AsyncHttpResponse as RestAsyncHttpResponse, ) + from ...rest._aiohttp import RestAioHttpTransportResponse # Matching requests, because why not? CONTENT_CHUNK_SIZE = 10 * 1024 @@ -91,6 +93,8 @@ self._loop = loop self._session_owner = session_owner self.session = session + if not self._session_owner and not self.session: + raise ValueError("session_owner cannot be False if no session is provided") self.connection_config = ConnectionConfiguration(**kwargs) self._use_env_settings = kwargs.pop("use_env_settings", True) @@ -118,8 +122,9 @@ if self._loop is not None: clientsession_kwargs["loop"] = self._loop self.session = aiohttp.ClientSession(**clientsession_kwargs) - if self.session is not None: - await self.session.__aenter__() + # pyright has trouble to understand that self.session is not None, since we raised at worst in the init + self.session = cast(aiohttp.ClientSession, self.session) + await self.session.__aenter__() async def close(self): """Closes the connection.""" @@ -188,7 +193,7 @@ """ @overload - async def send(self, request: "RestHttpRequest", **config: Any) -> "RestAsyncHttpResponse": + async def send(self, request: RestHttpRequest, **config: Any) -> RestAsyncHttpResponse: """Send the `azure.core.rest` request using this HTTP sender. Will pre-load the body into memory to be available with a sync method. @@ -206,8 +211,8 @@ """ async def send( - self, request: Union[HttpRequest, "RestHttpRequest"], **config - ) -> Union[AsyncHttpResponse, "RestAsyncHttpResponse"]: + self, request: Union[HttpRequest, RestHttpRequest], **config + ) -> Union[AsyncHttpResponse, RestAsyncHttpResponse]: """Send the request using this HTTP sender. Will pre-load the body into memory to be available with a sync method. @@ -240,7 +245,7 @@ config["proxy"] = proxies[protocol] break - response: Optional[Union[AsyncHttpResponse, "RestAsyncHttpResponse"]] = None + response: Optional[Union[AsyncHttpResponse, RestAsyncHttpResponse]] = None config["ssl"] = self._build_ssl_config( cert=config.pop("connection_cert", self.connection_config.cert), verify=config.pop("connection_verify", self.connection_config.verify), @@ -262,7 +267,7 @@ data=self._get_request_data(request), timeout=socket_timeout, allow_redirects=False, - **config + **config, ) if _is_rest(request): from azure.core.rest._aiohttp import RestAioHttpTransportResponse @@ -307,7 +312,33 @@ on the *content-encoding* header. """ - def __init__(self, pipeline: AsyncPipeline, response: AsyncHttpResponse, *, decompress: bool = True) -> None: + @overload + def __init__( + self, + pipeline: AsyncPipeline[HttpRequest, AsyncHttpResponse], + response: AioHttpTransportResponse, + *, + decompress: bool = True, + ) -> None: + ... + + @overload + def __init__( + self, + pipeline: AsyncPipeline[RestHttpRequest, RestAsyncHttpResponse], + response: RestAioHttpTransportResponse, + *, + decompress: bool = True, + ) -> None: + ... + + def __init__( + self, + pipeline: AsyncPipeline, + response: Union[AioHttpTransportResponse, RestAioHttpTransportResponse], + *, + decompress: bool = True, + ) -> None: self.pipeline = pipeline self.request = response.request self.response = response @@ -380,7 +411,7 @@ aiohttp_response: aiohttp.ClientResponse, block_size: Optional[int] = None, *, - decompress: bool = True + decompress: bool = True, ) -> None: super(AioHttpTransportResponse, self).__init__(request, aiohttp_response, block_size=block_size) # https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.ClientResponse @@ -462,7 +493,9 @@ except aiohttp.client_exceptions.ClientError as err: raise ServiceRequestError(err, error=err) from err - def stream_download(self, pipeline, **kwargs) -> AsyncIteratorType[bytes]: + def stream_download( + self, pipeline: AsyncPipeline[HttpRequest, AsyncHttpResponse], **kwargs + ) -> AsyncIteratorType[bytes]: """Generator for streaming response body data. :param pipeline: The pipeline object diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/transport/_base.py new/azure-core-1.29.4/azure/core/pipeline/transport/_base.py --- old/azure-core-1.29.3/azure/core/pipeline/transport/_base.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/transport/_base.py 2023-09-07 19:37:17.000000000 +0200 @@ -23,6 +23,7 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- +from __future__ import annotations import abc from email.message import Message import json @@ -48,6 +49,7 @@ Sequence, MutableMapping, ContextManager, + TYPE_CHECKING, ) from http.client import HTTPResponse as _HTTPResponse @@ -68,9 +70,12 @@ HTTPResponseType = TypeVar("HTTPResponseType") HTTPRequestType = TypeVar("HTTPRequestType") -PipelineType = TypeVar("PipelineType") DataType = Union[bytes, str, Dict[str, Union[str, int]]] +if TYPE_CHECKING: + # We need a transport to define a pipeline, this "if" avoid a circular import + from azure.core.pipeline import Pipeline + _LOGGER = logging.getLogger(__name__) binary_type = str @@ -89,6 +94,7 @@ :rtype: str :returns: Template completed """ + last_template = template components = template.split("/") while components: try: @@ -97,7 +103,11 @@ formatted_components = template.split("/") components = [c for c in formatted_components if "{{{}}}".format(key.args[0]) not in c] template = "/".join(components) - # No URL sections left - returning None + if last_template == template: + raise ValueError( + f"The value provided for the url part '{template}' was incorrect, and resulted in an invalid url" + ) from key + last_template = template def _urljoin(base_url: str, stub_url: str) -> str: @@ -488,7 +498,7 @@ class HttpResponse(_HttpResponseBase): # pylint: disable=abstract-method - def stream_download(self, pipeline: PipelineType, **kwargs: Any) -> Iterator[bytes]: + def stream_download(self, pipeline: Pipeline[HttpRequest, "HttpResponse"], **kwargs: Any) -> Iterator[bytes]: """Generator for streaming request body data. Should be implemented by sub-classes if streaming download diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/transport/_base_async.py new/azure-core-1.29.4/azure/core/pipeline/transport/_base_async.py --- old/azure-core-1.29.3/azure/core/pipeline/transport/_base_async.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/transport/_base_async.py 2023-09-07 19:37:17.000000000 +0200 @@ -32,23 +32,25 @@ TypeVar, Generic, Any, - TYPE_CHECKING, AsyncContextManager, Optional, Type, + TYPE_CHECKING, ) from types import TracebackType from ._base import _HttpResponseBase, _HttpClientTransportResponse, HttpRequest from ...utils._pipeline_transport_rest_shared_async import _PartGenerator -if TYPE_CHECKING: - from ..._pipeline_client_async import AsyncPipelineClient AsyncHTTPResponseType = TypeVar("AsyncHTTPResponseType") HTTPResponseType = TypeVar("HTTPResponseType") HTTPRequestType = TypeVar("HTTPRequestType") +if TYPE_CHECKING: + # We need a transport to define a pipeline, this "if" avoid a circular import + from .._base_async import AsyncPipeline + class _ResponseStopIteration(Exception): pass @@ -76,7 +78,7 @@ """ def stream_download( - self, pipeline: AsyncPipelineClient[HttpRequest, "AsyncHttpResponse"], **kwargs: Any + self, pipeline: AsyncPipeline[HttpRequest, "AsyncHttpResponse"], **kwargs: Any ) -> AsyncIteratorType[bytes]: """Generator for streaming response body data. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/pipeline/transport/_requests_basic.py new/azure-core-1.29.4/azure/core/pipeline/transport/_requests_basic.py --- old/azure-core-1.29.3/azure/core/pipeline/transport/_requests_basic.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/pipeline/transport/_requests_basic.py 2023-09-07 19:37:17.000000000 +0200 @@ -24,7 +24,7 @@ # # -------------------------------------------------------------------------- import logging -from typing import Iterator, Optional, Union, TypeVar, overload, TYPE_CHECKING +from typing import Iterator, Optional, Union, TypeVar, overload, cast, TYPE_CHECKING from urllib3.util.retry import Retry from urllib3.exceptions import ( DecodeError as CoreDecodeError, @@ -247,6 +247,8 @@ def __init__(self, **kwargs) -> None: self.session = kwargs.get("session", None) self._session_owner = kwargs.get("session_owner", True) + if not self._session_owner and not self.session: + raise ValueError("session_owner cannot be False if no session is provided") self.connection_config = ConnectionConfiguration(**kwargs) self._use_env_settings = kwargs.pop("use_env_settings", True) @@ -274,6 +276,8 @@ if not self.session and self._session_owner: self.session = requests.Session() self._init_session(self.session) + # pyright has trouble to understand that self.session is not None, since we raised at worst in the init + self.session = cast(requests.Session, self.session) def close(self): if self._session_owner and self.session: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/rest/_http_response_impl.py new/azure-core-1.29.4/azure/core/rest/_http_response_impl.py --- old/azure-core-1.29.3/azure/core/rest/_http_response_impl.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/rest/_http_response_impl.py 2023-09-07 19:37:17.000000000 +0200 @@ -24,7 +24,7 @@ # # -------------------------------------------------------------------------- from json import loads -from typing import cast, Any, Optional, Iterator, MutableMapping, Callable +from typing import Any, Optional, Iterator, MutableMapping, Callable from http.client import HTTPResponse as _HTTPResponse from ._helpers import ( get_charset_encoding, @@ -344,7 +344,7 @@ If response is good, does nothing. """ - if cast(int, self.status_code) >= 400: + if self.status_code >= 400: raise HttpResponseError(response=self) @property @@ -415,7 +415,7 @@ :rtype: Iterator[str] """ if self._content is not None: - chunk_size = cast(int, self._block_size) + chunk_size = self._block_size for i in range(0, len(self.content), chunk_size): yield self.content[i : i + chunk_size] else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/settings.py new/azure-core-1.29.4/azure/core/settings.py --- old/azure-core-1.29.3/azure/core/settings.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/settings.py 2023-09-07 19:37:17.000000000 +0200 @@ -31,7 +31,7 @@ import logging import os import sys -from typing import Type, Optional, Callable, cast, Union, Dict, Any, TypeVar, Tuple, Generic, Mapping, List +from typing import Type, Optional, Callable, Union, Dict, Any, TypeVar, Tuple, Generic, Mapping, List from azure.core.tracing import AbstractSpan ValidInputType = TypeVar("ValidInputType") @@ -65,9 +65,9 @@ :raises ValueError: If conversion to bool fails """ - if value in (True, False): - return cast(bool, value) - val = cast(str, value).lower() + if isinstance(value, bool): + return value + val = value.lower() if val in ["yes", "1", "on", "true", "True"]: return True if val in ["no", "0", "off", "false", "False"]: @@ -103,9 +103,11 @@ :raises ValueError: If conversion to log level fails """ - if value in set(_levels.values()): - return cast(int, value) - val = cast(str, value).upper() + if isinstance(value, int): + # If it's an int, return it. We don't need to check if it's in _levels, as custom int levels are allowed. + # https://docs.python.org/3/library/logging.html#levels + return value + val = value.upper() level = _levels.get(val) if not level: raise ValueError("Cannot convert {} to log level, valid values are: {}".format(value, ", ".join(_levels))) @@ -183,7 +185,6 @@ ) if not isinstance(value, str): - value = cast(Type[AbstractSpan], value) return value value = value.lower() @@ -271,7 +272,7 @@ return self._convert(value) # 3. previously user-set value - if self._user_value is not _unset: + if not isinstance(self._user_value, _Unset): return self._convert(self._user_value) # 2. environment variable @@ -283,7 +284,7 @@ return self._convert(self._system_hook()) # 0. implicit default - if self._default is not _unset: + if not isinstance(self._default, _Unset): return self._convert(self._default) raise RuntimeError("No configured value found for setting %r" % self._name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/tracing/_abstract_span.py new/azure-core-1.29.4/azure/core/tracing/_abstract_span.py --- old/azure-core-1.29.3/azure/core/tracing/_abstract_span.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/tracing/_abstract_span.py 2023-09-07 19:37:17.000000000 +0200 @@ -7,9 +7,9 @@ from enum import Enum from urllib.parse import urlparse -from typing import Any, Sequence, Optional, Union, Callable, Dict, Type +from typing import Any, Sequence, Optional, Union, Callable, Dict, Type, Generic, TypeVar from types import TracebackType -from typing_extensions import Protocol, ContextManager +from typing_extensions import Protocol, ContextManager, runtime_checkable from azure.core.pipeline.transport import HttpRequest, HttpResponse, AsyncHttpResponse from azure.core.rest import ( HttpResponse as RestHttpResponse, @@ -31,6 +31,7 @@ Sequence[float], ] Attributes = Dict[str, AttributeValue] +SpanType = TypeVar("SpanType") class SpanKind(Enum): @@ -42,7 +43,8 @@ INTERNAL = 6 -class AbstractSpan(Protocol): +@runtime_checkable +class AbstractSpan(Protocol, Generic[SpanType]): """Wraps a span from a distributed tracing implementation. If a span is given wraps the span. Else a new span is created. @@ -55,11 +57,11 @@ """ def __init__( # pylint: disable=super-init-not-called - self, span: Optional[Any] = None, name: Optional[str] = None, **kwargs: Any + self, span: Optional[SpanType] = None, name: Optional[str] = None, **kwargs: Any ) -> None: pass - def span(self, name: str = "child_span", **kwargs: Any) -> AbstractSpan: + def span(self, name: str = "child_span", **kwargs: Any) -> AbstractSpan[SpanType]: """ Create a child span for the current span and append it to the child spans list. The child span must be wrapped by an implementation of AbstractSpan @@ -89,7 +91,7 @@ """ ... - def __enter__(self) -> AbstractSpan: + def __enter__(self) -> AbstractSpan[SpanType]: """Start a span.""" ... @@ -157,7 +159,7 @@ ... @property - def span_instance(self) -> Any: + def span_instance(self) -> SpanType: """ Returns the span the class is wrapping. """ @@ -188,7 +190,7 @@ ... @classmethod - def get_current_span(cls) -> Any: + def get_current_span(cls) -> SpanType: """ Get the current span from the execution context. Return None otherwise. @@ -208,7 +210,7 @@ ... @classmethod - def set_current_span(cls, span: Any) -> None: + def set_current_span(cls, span: SpanType) -> None: """Set the given span as the current span in the execution context. :param span: The span to set as the current span @@ -226,11 +228,11 @@ ... @classmethod - def change_context(cls, span: AbstractSpan) -> ContextManager[AbstractSpan]: + def change_context(cls, span: SpanType) -> ContextManager[SpanType]: """Change the context for the life of this context manager. :param span: The span to run in the new context - :type span: AbstractSpan + :type span: Any :rtype: contextmanager :return: A context manager that will run the given span in the new context """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/utils/_connection_string_parser.py new/azure-core-1.29.4/azure/core/utils/_connection_string_parser.py --- old/azure-core-1.29.3/azure/core/utils/_connection_string_parser.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/utils/_connection_string_parser.py 2023-09-07 19:37:17.000000000 +0200 @@ -26,7 +26,7 @@ cs_args = [s.split("=", 1) for s in conn_str.strip().rstrip(";").split(";")] if any(len(tup) != 2 or not all(tup) for tup in cs_args): raise ValueError("Connection string is either blank or malformed.") - args_dict = dict(cs_args) # type: ignore + args_dict = dict(cs_args) if len(cs_args) != len(args_dict): raise ValueError("Connection string is either blank or malformed.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure/core/utils/_pipeline_transport_rest_shared.py new/azure-core-1.29.4/azure/core/utils/_pipeline_transport_rest_shared.py --- old/azure-core-1.29.3/azure/core/utils/_pipeline_transport_rest_shared.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/azure/core/utils/_pipeline_transport_rest_shared.py 2023-09-07 19:37:17.000000000 +0200 @@ -245,10 +245,17 @@ for index, raw_response in enumerate(message.get_payload()): content_type = raw_response.get_content_type() if content_type == "application/http": + try: + matching_request = requests[index] + except IndexError: + # If we have no matching request, this could mean that we had an empty batch. + # The request object is only needed to get the HTTP METHOD and to store in the response object, + # so let's just use the parent request so allow the rest of the deserialization to continue. + matching_request = response.request responses.append( deserialize_response( raw_response.get_payload(decode=True), - requests[index], + matching_request, http_response_type=http_response_type, ) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/azure_core.egg-info/PKG-INFO new/azure-core-1.29.4/azure_core.egg-info/PKG-INFO --- old/azure-core-1.29.3/azure_core.egg-info/PKG-INFO 2023-08-22 21:16:01.000000000 +0200 +++ new/azure-core-1.29.4/azure_core.egg-info/PKG-INFO 2023-09-07 19:38:16.000000000 +0200 @@ -1,11 +1,12 @@ Metadata-Version: 2.1 Name: azure-core -Version: 1.29.3 +Version: 1.29.4 Summary: Microsoft Azure Core Library for Python Home-page: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/core/azure-core Author: Microsoft Corporation Author-email: azpysdkh...@microsoft.com License: MIT License +Keywords: azure,azure sdk Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only @@ -18,8 +19,12 @@ Classifier: License :: OSI Approved :: MIT License Requires-Python: >=3.7 Description-Content-Type: text/markdown -Provides-Extra: aio License-File: LICENSE +Requires-Dist: requests>=2.18.4 +Requires-Dist: six>=1.11.0 +Requires-Dist: typing-extensions>=4.6.0 +Provides-Extra: aio +Requires-Dist: aiohttp>=3.0; extra == "aio" # Azure Core shared client library for Python @@ -282,6 +287,16 @@ # Release History +## 1.29.4 (2023-09-07) + +### Bugs Fixed + +- Fixed the issue that some urls trigger an infinite loop. #31346 +- Fixed issue where IndexError was raised if multipart responses did not match the number of requests. #31471 +- Fixed issue unbound variable exception if dict is invalid in CloudEvent.from_dict. #31835 +- Fixed issue asyncBearerTokenCredentialPolicy is not backward compatible with SansIOHTTPPolicy. #31836 +- Fixed issue mypy complains with new version of azure-core. #31564 + ## 1.29.3 (2023-08-22) ### Bugs Fixed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/setup.py new/azure-core-1.29.4/setup.py --- old/azure-core-1.29.3/setup.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/setup.py 2023-09-07 19:37:17.000000000 +0200 @@ -43,6 +43,7 @@ author="Microsoft Corporation", author_email="azpysdkh...@microsoft.com", url="https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/core/azure-core", + keywords="azure, azure sdk", classifiers=[ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/tests/test_basic_transport.py new/azure-core-1.29.4/tests/test_basic_transport.py --- old/azure-core-1.29.3/tests/test_basic_transport.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/tests/test_basic_transport.py 2023-09-07 19:37:17.000000000 +0200 @@ -687,6 +687,41 @@ assert res1.headers["x-ms-fun"] == "true" +@pytest.mark.parametrize("http_request,mock_response", request_and_responses_product(MOCK_RESPONSES)) +def test_multipart_receive_with_empty_requests(http_request, mock_response): + + request = http_request("POST", "http://account.blob.core.windows.net/?comp=batch") + request.set_multipart_mixed() + + body_as_bytes = ( + b"--batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f\r\n" + b"Content-Type: application/http\r\n" + b"Content-Transfer-Encoding: binary\r\n" + b"\r\n" + b"HTTP/1.1 400 Bad Request\r\n" + b"DataServiceVersion: 1.0;\r\n" + b"Content-Type: application/xml;charset=utf-8\r\n" + b"\r\n" + b'<?xml version="1.0" encoding="utf-8"?><error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code>InvalidInput</code><message xml:lang="en-US">An error occurred while processing this request.\nRequestId:1a930d9b-8002-0020-575c-d1b166000000\nTime:2023-08-17T22:44:06.8465534Z</message></error>\r\n' + b"--batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f--\r\n" + ) + + response = mock_response( + request, + body_as_bytes, + "multipart/mixed; boundary=batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f", + ) + + response = response.parts() + assert len(response) == 1 + res0 = response[0] + assert res0.status_code == 400 + assert res0.reason == "Bad Request" + assert res0.headers["DataServiceVersion"] == "1.0;" + assert res0.request.method == "POST" + assert res0.request.url == "http://account.blob.core.windows.net/?comp=batch" + + @pytest.mark.parametrize("mock_response", MOCK_RESPONSES) def test_raise_for_status_bad_response(mock_response): response = mock_response(request=None, body=None, content_type=None) @@ -756,6 +791,101 @@ @pytest.mark.parametrize("http_request,mock_response", request_and_responses_product(MOCK_RESPONSES)) +def test_multipart_receive_with_empty_changeset(http_request, mock_response): + + changeset = http_request(None, None) + changeset.set_multipart_mixed() + request = http_request("POST", "http://account.blob.core.windows.net/?comp=batch") + request.set_multipart_mixed(changeset) + + body_as_bytes = ( + b"--batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f\r\n" + b"Content-Type: multipart/mixed; boundary=changesetresponse_390b0b55-6892-4fce-8427-001ca15662f5\r\n" + b"\r\n" + b"--changesetresponse_390b0b55-6892-4fce-8427-001ca15662f5\r\n" + b"Content-Type: application/http\r\n" + b"Content-Transfer-Encoding: binary\r\n" + b"\r\n" + b"HTTP/1.1 400 Bad Request\r\n" + b"DataServiceVersion: 1.0;\r\n" + b"Content-Type: application/xml;charset=utf-8\r\n" + b"\r\n" + b'<?xml version="1.0" encoding="utf-8"?><error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code>InvalidInput</code><message xml:lang="en-US">An error occurred while processing this request.\nRequestId:1a930d9b-8002-0020-575c-d1b166000000\nTime:2023-08-17T22:44:06.8465534Z</message></error>\r\n' + b"--changesetresponse_390b0b55-6892-4fce-8427-001ca15662f5--\r\n" + b"--batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f--\r\n" + ) + + response = mock_response( + request, body_as_bytes, "multipart/mixed; boundary=batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f" + ) + parts = [] + for part in response.parts(): + parts.append(part) + assert len(parts) == 1 + res0 = parts[0] + assert res0.status_code == 400 + assert res0.reason == "Bad Request" + assert "DataServiceVersion" in res0.headers + assert res0.request.method == "POST" + assert res0.request.url == "http://account.blob.core.windows.net/?comp=batch" + + # Test against other HTTP verbs to see if http.client.HttpResponse has any concerns. + changeset = http_request("PATCH", "https://foo.com") + changeset.set_multipart_mixed() + request = http_request("POST", "http://account.blob.core.windows.net/?comp=batch") + request.set_multipart_mixed(changeset) + response = mock_response( + request, body_as_bytes, "multipart/mixed; boundary=batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f" + ) + parts = [] + for part in response.parts(): + parts.append(part) + assert len(parts) == 1 + res0 = parts[0] + assert res0.status_code == 400 + assert res0.reason == "Bad Request" + assert "DataServiceVersion" in res0.headers + assert res0.request.method == "POST" + assert res0.request.url == "http://account.blob.core.windows.net/?comp=batch" + + changeset = http_request("DELETE", None) + changeset.set_multipart_mixed() + request = http_request("POST", "http://account.blob.core.windows.net/?comp=batch") + request.set_multipart_mixed(changeset) + response = mock_response( + request, body_as_bytes, "multipart/mixed; boundary=batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f" + ) + parts = [] + for part in response.parts(): + parts.append(part) + assert len(parts) == 1 + res0 = parts[0] + assert res0.status_code == 400 + assert res0.reason == "Bad Request" + assert "DataServiceVersion" in res0.headers + assert res0.request.method == "POST" + assert res0.request.url == "http://account.blob.core.windows.net/?comp=batch" + + changeset = http_request("HEAD", None) + changeset.set_multipart_mixed() + request = http_request("POST", "http://account.blob.core.windows.net/?comp=batch") + request.set_multipart_mixed(changeset) + response = mock_response( + request, body_as_bytes, "multipart/mixed; boundary=batchresponse_b1e4a276-83db-40e9-b21f-f5bc7f7f905f" + ) + parts = [] + for part in response.parts(): + parts.append(part) + assert len(parts) == 1 + res0 = parts[0] + assert res0.status_code == 400 + assert res0.reason == "Bad Request" + assert "DataServiceVersion" in res0.headers + assert res0.request.method == "POST" + assert res0.request.url == "http://account.blob.core.windows.net/?comp=batch" + + +@pytest.mark.parametrize("http_request,mock_response", request_and_responses_product(MOCK_RESPONSES)) def test_multipart_receive_with_multiple_changesets(http_request, mock_response): changeset1 = http_request(None, None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-core-1.29.3/tests/test_pipeline.py new/azure-core-1.29.4/tests/test_pipeline.py --- old/azure-core-1.29.3/tests/test_pipeline.py 2023-08-22 21:14:14.000000000 +0200 +++ new/azure-core-1.29.4/tests/test_pipeline.py 2023-09-07 19:37:17.000000000 +0200 @@ -50,7 +50,7 @@ SansIOHTTPPolicy, SensitiveHeaderCleanupPolicy, ) -from azure.core.pipeline.transport._base import PipelineClientBase +from azure.core.pipeline.transport._base import PipelineClientBase, _format_url_section from azure.core.pipeline.transport import ( HttpTransport, RequestsTransport, @@ -204,6 +204,18 @@ assert formatted == "https://bing.com/path/subpath?query=testvalue&x=2ndvalue&a=X&c=Y" +def test_format_url_braces_with_dot(): + base_url = "https://bing.com/{aaa.bbb}" + with pytest.raises(ValueError): + url = _format_url_section(base_url) + + +def test_format_url_single_brace(): + base_url = "https://bing.com/{aaa.bbb" + with pytest.raises(ValueError): + url = _format_url_section(base_url) + + def test_format_incorrect_endpoint(): # https://github.com/Azure/azure-sdk-for-python/pull/12106 client = PipelineClientBase("{Endpoint}/text/analytics/v3.0")