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 2025-09-14 18:49:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-azure-core (Old)
and /work/SRC/openSUSE:Factory/.python-azure-core.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-azure-core"
Sun Sep 14 18:49:05 2025 rev:56 rq:1304279 version:1.35.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-azure-core/python-azure-core.changes
2025-07-15 16:43:41.463503481 +0200
+++
/work/SRC/openSUSE:Factory/.python-azure-core.new.1977/python-azure-core.changes
2025-09-14 18:49:29.648383492 +0200
@@ -1,0 +2,8 @@
+Fri Sep 12 10:00:31 UTC 2025 - John Paul Adrian Glaubitz
<[email protected]>
+
+- New upstream release
+ + Version 1.35.1
+ + For detailed information about changes see the
+ CHANGELOG.md file provided with this package
+
+-------------------------------------------------------------------
Old:
----
azure_core-1.35.0.tar.gz
New:
----
azure_core-1.35.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-azure-core.spec ++++++
--- /var/tmp/diff_new_pack.9MNar0/_old 2025-09-14 18:49:30.192406289 +0200
+++ /var/tmp/diff_new_pack.9MNar0/_new 2025-09-14 18:49:30.196406457 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-azure-core
-Version: 1.35.0
+Version: 1.35.1
Release: 0
Summary: Microsoft Azure Core Library for Python
License: MIT
++++++ azure_core-1.35.0.tar.gz -> azure_core-1.35.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/CHANGELOG.md
new/azure_core-1.35.1/CHANGELOG.md
--- old/azure_core-1.35.0/CHANGELOG.md 2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/CHANGELOG.md 2025-09-11 23:05:23.000000000 +0200
@@ -1,5 +1,15 @@
# Release History
+## 1.35.1 (2025-09-11)
+
+### Bugs Fixed
+
+- Fixed an issue where the `retry_backoff_max` parameter in `RetryPolicy` and
`AsyncRetryPolicy` constructors was being ignored, causing retry operations to
use default maximum backoff values instead of the user-specified limits. #42444
+
+### Other Changes
+
+- `BearerTokenCredentialPolicy` and `AsyncBearerTokenCredentialPolicy` will
now properly surface credential exceptions when handling claims challenges.
Previously, exceptions from credential token requests were suppressed; now they
are raised and chained with the original 401 `HttpResponseError` response for
better debugging visibility. #42536
+
## 1.35.0 (2025-07-02)
### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/PKG-INFO
new/azure_core-1.35.1/PKG-INFO
--- old/azure_core-1.35.0/PKG-INFO 2025-07-03 01:51:52.409240000 +0200
+++ new/azure_core-1.35.1/PKG-INFO 2025-09-11 23:06:22.317217000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: azure-core
-Version: 1.35.0
+Version: 1.35.1
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
@@ -27,6 +27,19 @@
Requires-Dist: aiohttp>=3.0; extra == "aio"
Provides-Extra: tracing
Requires-Dist: opentelemetry-api~=1.26; extra == "tracing"
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: description-content-type
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: provides-extra
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
# Azure Core shared client library for Python
@@ -259,6 +272,33 @@
)
```
+## Logging
+
+Azure libraries follow the guidance of Python's standard
[logging](https://docs.python.org/3/library/logging.html) module. By following
the Python documentation on logging, you should be able to configure logging
for Azure libraries effectively.
+
+Azure library loggers use a dot-based separated syntax, where the first
section is always `azure`, followed by the package name. For example, the Azure
Core library uses logger names that start with `azure.core`.
+
+Here's an example of how to configure logging for Azure libraries:
+
+```python
+import logging
+import sys
+
+# Enable detailed console logs across Azure libraries
+azure_logger = logging.getLogger("azure")
+azure_logger.setLevel(logging.DEBUG)
+azure_logger.addHandler(logging.StreamHandler(stream=sys.stdout))
+
+# Exclude detailed logs for network calls associated with getting Entra ID
token.
+identity_logger = logging.getLogger("azure.identity")
+identity_logger.setLevel(logging.ERROR)
+
+# Make sure regular (redacted) detailed azure.core logs are not shown, as we
are about to
+# turn on non-redacted logs by passing 'logging_enable=True' to the client
constructor
+logger = logging.getLogger("azure.core.pipeline.policies.http_logging_policy")
+logger.setLevel(logging.ERROR)
+```
+
## Contributing
This project welcomes contributions and suggestions. Most contributions require
@@ -284,6 +324,16 @@
# Release History
+## 1.35.1 (2025-09-11)
+
+### Bugs Fixed
+
+- Fixed an issue where the `retry_backoff_max` parameter in `RetryPolicy` and
`AsyncRetryPolicy` constructors was being ignored, causing retry operations to
use default maximum backoff values instead of the user-specified limits. #42444
+
+### Other Changes
+
+- `BearerTokenCredentialPolicy` and `AsyncBearerTokenCredentialPolicy` will
now properly surface credential exceptions when handling claims challenges.
Previously, exceptions from credential token requests were suppressed; now they
are raised and chained with the original 401 `HttpResponseError` response for
better debugging visibility. #42536
+
## 1.35.0 (2025-07-02)
### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/README.md
new/azure_core-1.35.1/README.md
--- old/azure_core-1.35.0/README.md 2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/README.md 2025-09-11 23:05:23.000000000 +0200
@@ -229,6 +229,33 @@
)
```
+## Logging
+
+Azure libraries follow the guidance of Python's standard
[logging](https://docs.python.org/3/library/logging.html) module. By following
the Python documentation on logging, you should be able to configure logging
for Azure libraries effectively.
+
+Azure library loggers use a dot-based separated syntax, where the first
section is always `azure`, followed by the package name. For example, the Azure
Core library uses logger names that start with `azure.core`.
+
+Here's an example of how to configure logging for Azure libraries:
+
+```python
+import logging
+import sys
+
+# Enable detailed console logs across Azure libraries
+azure_logger = logging.getLogger("azure")
+azure_logger.setLevel(logging.DEBUG)
+azure_logger.addHandler(logging.StreamHandler(stream=sys.stdout))
+
+# Exclude detailed logs for network calls associated with getting Entra ID
token.
+identity_logger = logging.getLogger("azure.identity")
+identity_logger.setLevel(logging.ERROR)
+
+# Make sure regular (redacted) detailed azure.core logs are not shown, as we
are about to
+# turn on non-redacted logs by passing 'logging_enable=True' to the client
constructor
+logger = logging.getLogger("azure.core.pipeline.policies.http_logging_policy")
+logger.setLevel(logging.ERROR)
+```
+
## Contributing
This project welcomes contributions and suggestions. Most contributions require
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/azure/core/_version.py
new/azure_core-1.35.1/azure/core/_version.py
--- old/azure_core-1.35.0/azure/core/_version.py 2025-07-03
01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/azure/core/_version.py 2025-09-11
23:05:23.000000000 +0200
@@ -9,4 +9,4 @@
# regenerated.
# --------------------------------------------------------------------------
-VERSION = "1.35.0"
+VERSION = "1.35.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_core-1.35.0/azure/core/pipeline/policies/_authentication.py
new/azure_core-1.35.1/azure/core/pipeline/policies/_authentication.py
--- old/azure_core-1.35.0/azure/core/pipeline/policies/_authentication.py
2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/azure/core/pipeline/policies/_authentication.py
2025-09-11 23:05:23.000000000 +0200
@@ -6,12 +6,14 @@
import time
import base64
from typing import TYPE_CHECKING, Optional, TypeVar, MutableMapping, Any,
Union, cast
+
from azure.core.credentials import (
TokenCredential,
SupportsTokenInfo,
TokenRequestOptions,
TokenProvider,
)
+from azure.core.exceptions import HttpResponseError
from azure.core.pipeline import PipelineRequest, PipelineResponse
from azure.core.pipeline.transport import (
HttpResponse as LegacyHttpResponse,
@@ -165,7 +167,20 @@
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 = self.on_challenge(request, response)
+ try:
+ request_authorized = self.on_challenge(request, response)
+ except Exception as ex:
+ # If the response is streamed, read it so the error
message is immediately available to the user.
+ # Otherwise, a generic error message will be given and the
user will have to read the response
+ # body to see the actual error.
+ if response.context.options.get("stream"):
+ try:
+ response.http_response.read() # type: ignore
+ except Exception: # pylint:disable=broad-except
+ pass
+ # Raise the exception from the token request with the
original 401 response
+ raise ex from
HttpResponseError(response=response.http_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
@@ -200,14 +215,11 @@
encoded_claims = get_challenge_parameter(headers, "Bearer",
"claims")
if not encoded_claims:
return False
- try:
- padding_needed = -len(encoded_claims) % 4
- claims = base64.urlsafe_b64decode(encoded_claims + "=" *
padding_needed).decode("utf-8")
- if claims:
- self.authorize_request(request, *self._scopes,
claims=claims)
- return True
- except Exception: # pylint:disable=broad-except
- return False
+ padding_needed = -len(encoded_claims) % 4
+ claims = base64.urlsafe_b64decode(encoded_claims + "=" *
padding_needed).decode("utf-8")
+ if claims:
+ self.authorize_request(request, *self._scopes, claims=claims)
+ return True
return False
def on_response(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_core-1.35.0/azure/core/pipeline/policies/_authentication_async.py
new/azure_core-1.35.1/azure/core/pipeline/policies/_authentication_async.py
--- old/azure_core-1.35.0/azure/core/pipeline/policies/_authentication_async.py
2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/azure/core/pipeline/policies/_authentication_async.py
2025-09-11 23:05:23.000000000 +0200
@@ -13,6 +13,7 @@
AsyncSupportsTokenInfo,
AsyncTokenProvider,
)
+from azure.core.exceptions import HttpResponseError
from azure.core.pipeline import PipelineRequest, PipelineResponse
from azure.core.pipeline.policies import AsyncHTTPPolicy
from azure.core.pipeline.policies._authentication import (
@@ -110,7 +111,21 @@
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)
+ try:
+ request_authorized = await self.on_challenge(request,
response)
+ except Exception as ex:
+ # If the response is streamed, read it so the error
message is immediately available to the user.
+ # Otherwise, a generic error message will be given and the
user will have to read the response
+ # body to see the actual error.
+ if response.context.options.get("stream"):
+ try:
+ await response.http_response.read() # type: ignore
+ except Exception: # pylint:disable=broad-except
+ pass
+
+ # Raise the exception from the token request with the
original 401 response
+ raise ex from
HttpResponseError(response=response.http_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
@@ -145,14 +160,11 @@
encoded_claims = get_challenge_parameter(headers, "Bearer",
"claims")
if not encoded_claims:
return False
- try:
- padding_needed = -len(encoded_claims) % 4
- claims = base64.urlsafe_b64decode(encoded_claims + "=" *
padding_needed).decode("utf-8")
- if claims:
- await self.authorize_request(request, *self._scopes,
claims=claims)
- return True
- except Exception: # pylint:disable=broad-except
- return False
+ padding_needed = -len(encoded_claims) % 4
+ claims = base64.urlsafe_b64decode(encoded_claims + "=" *
padding_needed).decode("utf-8")
+ if claims:
+ await self.authorize_request(request, *self._scopes,
claims=claims)
+ return True
return False
def on_response(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_core-1.35.0/azure/core/pipeline/policies/_retry.py
new/azure_core-1.35.1/azure/core/pipeline/policies/_retry.py
--- old/azure_core-1.35.0/azure/core/pipeline/policies/_retry.py
2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/azure/core/pipeline/policies/_retry.py
2025-09-11 23:05:23.000000000 +0200
@@ -118,7 +118,7 @@
"read": options.pop("retry_read", self.read_retries),
"status": options.pop("retry_status", self.status_retries),
"backoff": options.pop("retry_backoff_factor",
self.backoff_factor),
- "max_backoff": options.pop("retry_backoff_max", self.BACKOFF_MAX),
+ "max_backoff": options.pop("retry_backoff_max", self.backoff_max),
"methods": options.pop("retry_on_methods", self._method_whitelist),
"timeout": options.pop("timeout", self.timeout),
"history": [],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/azure_core.egg-info/PKG-INFO
new/azure_core-1.35.1/azure_core.egg-info/PKG-INFO
--- old/azure_core-1.35.0/azure_core.egg-info/PKG-INFO 2025-07-03
01:51:52.000000000 +0200
+++ new/azure_core-1.35.1/azure_core.egg-info/PKG-INFO 2025-09-11
23:06:22.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: azure-core
-Version: 1.35.0
+Version: 1.35.1
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
@@ -27,6 +27,19 @@
Requires-Dist: aiohttp>=3.0; extra == "aio"
Provides-Extra: tracing
Requires-Dist: opentelemetry-api~=1.26; extra == "tracing"
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: description-content-type
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: provides-extra
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
# Azure Core shared client library for Python
@@ -259,6 +272,33 @@
)
```
+## Logging
+
+Azure libraries follow the guidance of Python's standard
[logging](https://docs.python.org/3/library/logging.html) module. By following
the Python documentation on logging, you should be able to configure logging
for Azure libraries effectively.
+
+Azure library loggers use a dot-based separated syntax, where the first
section is always `azure`, followed by the package name. For example, the Azure
Core library uses logger names that start with `azure.core`.
+
+Here's an example of how to configure logging for Azure libraries:
+
+```python
+import logging
+import sys
+
+# Enable detailed console logs across Azure libraries
+azure_logger = logging.getLogger("azure")
+azure_logger.setLevel(logging.DEBUG)
+azure_logger.addHandler(logging.StreamHandler(stream=sys.stdout))
+
+# Exclude detailed logs for network calls associated with getting Entra ID
token.
+identity_logger = logging.getLogger("azure.identity")
+identity_logger.setLevel(logging.ERROR)
+
+# Make sure regular (redacted) detailed azure.core logs are not shown, as we
are about to
+# turn on non-redacted logs by passing 'logging_enable=True' to the client
constructor
+logger = logging.getLogger("azure.core.pipeline.policies.http_logging_policy")
+logger.setLevel(logging.ERROR)
+```
+
## Contributing
This project welcomes contributions and suggestions. Most contributions require
@@ -284,6 +324,16 @@
# Release History
+## 1.35.1 (2025-09-11)
+
+### Bugs Fixed
+
+- Fixed an issue where the `retry_backoff_max` parameter in `RetryPolicy` and
`AsyncRetryPolicy` constructors was being ignored, causing retry operations to
use default maximum backoff values instead of the user-specified limits. #42444
+
+### Other Changes
+
+- `BearerTokenCredentialPolicy` and `AsyncBearerTokenCredentialPolicy` will
now properly surface credential exceptions when handling claims challenges.
Previously, exceptions from credential token requests were suppressed; now they
are raised and chained with the original 401 `HttpResponseError` response for
better debugging visibility. #42536
+
## 1.35.0 (2025-07-02)
### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_core-1.35.0/tests/async_tests/test_authentication_async.py
new/azure_core-1.35.1/tests/async_tests/test_authentication_async.py
--- old/azure_core-1.35.0/tests/async_tests/test_authentication_async.py
2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/tests/async_tests/test_authentication_async.py
2025-09-11 23:05:23.000000000 +0200
@@ -12,7 +12,7 @@
from azure.core.credentials import AccessToken, AccessTokenInfo
from azure.core.credentials_async import AsyncTokenCredential,
AsyncSupportsTokenInfo
-from azure.core.exceptions import ServiceRequestError
+from azure.core.exceptions import ServiceRequestError, HttpResponseError,
ClientAuthenticationError
from azure.core.pipeline import AsyncPipeline, PipelineRequest,
PipelineContext, PipelineResponse
from azure.core.pipeline.policies import (
AsyncBearerTokenCredentialPolicy,
@@ -640,3 +640,98 @@
# Verify the Authorization header was set correctly
assert request.http_request.headers["Authorization"] == "Bearer
claims_token"
+
+
[email protected]
[email protected]("http_request", HTTP_REQUESTS)
+async def
test_async_bearer_policy_on_challenge_exception_chaining(http_request):
+ """Test that exceptions during async on_challenge are chained with
HttpResponseError"""
+
+ # Mock credential that raises an exception during get_token_info with
claims
+ async def mock_get_token_info(*scopes, options=None):
+ if options and "claims" in options:
+ raise ClientAuthenticationError("Failed to request token info with
claims")
+ return AccessTokenInfo("initial_token", int(time.time()) + 3600)
+
+ fake_credential = Mock(
+ spec_set=["get_token", "get_token_info"],
+ get_token=AsyncMock(return_value=AccessToken("fallback",
int(time.time()) + 3600)),
+ get_token_info=mock_get_token_info,
+ )
+ policy = AsyncBearerTokenCredentialPolicy(fake_credential, "scope")
+
+ # Create a 401 response with insufficient_claims challenge
+ test_claims = '{"access_token":{"foo":"bar"}}'
+ encoded_claims =
base64.urlsafe_b64encode(test_claims.encode()).decode().rstrip("=")
+ challenge_header = f'Bearer error="insufficient_claims",
claims="{encoded_claims}"'
+
+ response_mock = Mock(status_code=401, headers={"WWW-Authenticate":
challenge_header})
+
+ # Mock transport that returns the 401 response
+ async def mock_transport_send(request):
+ return response_mock
+
+ transport = Mock(send=mock_transport_send)
+ pipeline = AsyncPipeline(transport=transport, policies=[policy])
+
+ # Execute the request and verify exception chaining
+ with pytest.raises(ClientAuthenticationError) as exc_info:
+ await pipeline.run(http_request("GET", "https://example.com"))
+
+ # Verify the original exception is preserved
+ original_exception = exc_info.value
+ assert original_exception.message == "Failed to request token info with
claims"
+
+ # Verify the exception is chained with HttpResponseError
+ assert original_exception.__cause__ is not None
+ assert isinstance(original_exception.__cause__, HttpResponseError)
+
+ # Verify the HttpResponseError contains the original 401 response
+ http_response_error = original_exception.__cause__
+ assert http_response_error.response is response_mock
+
+
[email protected]
[email protected]("http_request", HTTP_REQUESTS)
+async def
test_async_bearer_policy_reads_streamed_response_on_challenge_exception(http_request):
+ """Test that the async policy reads streamed response body when
on_challenge raises exception"""
+
+ # Create a credential that will raise an exception when get_token is
called with claims
+ async def failing_get_token(*scopes, **kwargs):
+ if "claims" in kwargs:
+ raise ClientAuthenticationError("Failed to get token with claims")
+ return AccessToken("initial_token", int(time.time()) + 3600)
+
+ credential = Mock(spec_set=["get_token"], get_token=failing_get_token)
+ policy = AsyncBearerTokenCredentialPolicy(credential, "scope")
+
+ # Create a 401 response with insufficient_claims challenge that will
trigger the exception
+ test_claims = '{"access_token":{"foo":"bar"}}'
+ encoded_claims =
base64.urlsafe_b64encode(test_claims.encode()).decode().rstrip("=")
+ challenge_header = f'Bearer error="insufficient_claims",
claims="{encoded_claims}"'
+
+ # Create the mock HTTP response with stream reading capability
+ http_response_mock = Mock()
+ http_response_mock.status_code = 401
+ http_response_mock.headers = {"WWW-Authenticate": challenge_header}
+ http_response_mock.read = AsyncMock(return_value=b"Error details from
server")
+
+ # Mock transport that returns the HTTP response directly (it will be
wrapped by Pipeline)
+ async def mock_transport_send(request, **kwargs):
+ return http_response_mock
+
+ transport = Mock(send=mock_transport_send)
+
+ # Create pipeline with stream option
+ pipeline = AsyncPipeline(transport=transport, policies=[policy])
+
+ # Execute the request and verify exception handling
+ with pytest.raises(ClientAuthenticationError) as exc_info:
+ await pipeline.run(http_request("GET", "https://example.com"),
stream=True)
+
+ # Verify that the response.read() was called to consume the stream
+ http_response_mock.read.assert_called_once()
+
+ # Verify the exception chaining
+ assert exc_info.value.__cause__ is not None
+ assert isinstance(exc_info.value.__cause__, HttpResponseError)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_core-1.35.0/tests/async_tests/test_retry_policy_async.py
new/azure_core-1.35.1/tests/async_tests/test_retry_policy_async.py
--- old/azure_core-1.35.0/tests/async_tests/test_retry_policy_async.py
2025-07-03 01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/tests/async_tests/test_retry_policy_async.py
2025-09-11 23:05:23.000000000 +0200
@@ -314,3 +314,99 @@
await pipeline.run(http_request("GET", "http://localhost/"))
assert transport.sleep.call_count == 1
+
+
+def test_configure_retries_uses_constructor_values():
+ """Test that configure_retries method correctly gets values from
constructor."""
+ # Test with custom constructor values
+ retry_policy = AsyncRetryPolicy(
+ retry_total=5,
+ retry_connect=2,
+ retry_read=4,
+ retry_status=1,
+ retry_backoff_factor=0.5,
+ retry_backoff_max=60,
+ timeout=300,
+ )
+
+ # Call configure_retries with empty options to ensure it uses constructor
values
+ retry_settings = retry_policy.configure_retries({})
+
+ # Verify that configure_retries returns constructor values as defaults
+ assert retry_settings["total"] == 5
+ assert retry_settings["connect"] == 2
+ assert retry_settings["read"] == 4
+ assert retry_settings["status"] == 1
+ assert retry_settings["backoff"] == 0.5
+ assert retry_settings["max_backoff"] == 60
+ assert retry_settings["timeout"] == 300
+ assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT",
"DELETE", "OPTIONS", "TRACE"])
+ assert retry_settings["history"] == []
+
+
+def test_configure_retries_options_override_constructor():
+ """Test that options passed to configure_retries override constructor
values."""
+ # Test with custom constructor values
+ retry_policy = AsyncRetryPolicy(
+ retry_total=5,
+ retry_connect=2,
+ retry_read=4,
+ retry_status=1,
+ retry_backoff_factor=0.5,
+ retry_backoff_max=60,
+ timeout=300,
+ )
+
+ # Call configure_retries with options that should override constructor
values
+ options = {
+ "retry_total": 8,
+ "retry_connect": 6,
+ "retry_read": 7,
+ "retry_status": 3,
+ "retry_backoff_factor": 1.0,
+ "retry_backoff_max": 180,
+ "timeout": 600,
+ "retry_on_methods": frozenset(["GET", "POST"]),
+ }
+ retry_settings = retry_policy.configure_retries(options)
+
+ # Verify that configure_retries returns option values, not constructor
values
+ assert retry_settings["total"] == 8
+ assert retry_settings["connect"] == 6
+ assert retry_settings["read"] == 7
+ assert retry_settings["status"] == 3
+ assert retry_settings["backoff"] == 1.0
+ assert retry_settings["max_backoff"] == 180
+ assert retry_settings["timeout"] == 600
+ assert retry_settings["methods"] == frozenset(["GET", "POST"])
+ assert retry_settings["history"] == []
+
+ # Verify options dict was modified (values were popped)
+ assert "retry_total" not in options
+ assert "retry_connect" not in options
+ assert "retry_read" not in options
+ assert "retry_status" not in options
+ assert "retry_backoff_factor" not in options
+ assert "retry_backoff_max" not in options
+ assert "timeout" not in options
+ assert "retry_on_methods" not in options
+
+
+def test_configure_retries_default_values():
+ """Test that configure_retries uses default values when no constructor
args or options provided."""
+ # Test with default constructor
+ retry_policy = AsyncRetryPolicy()
+
+ # Call configure_retries with empty options
+ retry_settings = retry_policy.configure_retries({})
+
+ # Verify default values from RetryPolicyBase.__init__
+ assert retry_settings["total"] == 10 # default retry_total
+ assert retry_settings["connect"] == 3 # default retry_connect
+ assert retry_settings["read"] == 3 # default retry_read
+ assert retry_settings["status"] == 3 # default retry_status
+ assert retry_settings["backoff"] == 0.8 # default retry_backoff_factor
+ assert retry_settings["max_backoff"] == 120 # default retry_backoff_max
(BACKOFF_MAX)
+ assert retry_settings["timeout"] == 604800 # default timeout
+ assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT",
"DELETE", "OPTIONS", "TRACE"])
+ assert retry_settings["history"] == []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/tests/test_authentication.py
new/azure_core-1.35.1/tests/test_authentication.py
--- old/azure_core-1.35.0/tests/test_authentication.py 2025-07-03
01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/tests/test_authentication.py 2025-09-11
23:05:23.000000000 +0200
@@ -16,7 +16,7 @@
AzureNamedKeyCredential,
AccessTokenInfo,
)
-from azure.core.exceptions import ServiceRequestError
+from azure.core.exceptions import ServiceRequestError, HttpResponseError,
ClientAuthenticationError
from azure.core.pipeline import Pipeline, PipelineRequest, PipelineContext,
PipelineResponse
from azure.core.pipeline.transport import HttpTransport, HttpRequest
from azure.core.pipeline.policies import (
@@ -837,3 +837,93 @@
# Verify the Authorization header was set correctly
assert request.http_request.headers["Authorization"] == "Bearer
claims_token"
+
+
[email protected]("http_request", HTTP_REQUESTS)
+def test_bearer_policy_on_challenge_exception_chaining(http_request):
+ """Test that exceptions during on_challenge are chained with
HttpResponseError"""
+
+ # Mock credential that raises an exception during get_token_info with
claims
+ def mock_get_token_info(*scopes, options=None):
+ if options and "claims" in options:
+ raise ClientAuthenticationError("Failed to request token info with
claims")
+ return AccessTokenInfo("initial_token", int(time.time()) + 3600)
+
+ fake_credential = Mock(
+ spec_set=["get_token", "get_token_info"],
+ get_token=Mock(return_value=AccessToken("fallback", int(time.time()) +
3600)),
+ get_token_info=mock_get_token_info,
+ )
+ policy = BearerTokenCredentialPolicy(fake_credential, "scope")
+
+ # Create a 401 response with insufficient_claims challenge
+ test_claims = '{"access_token":{"foo":"bar"}}'
+ encoded_claims =
base64.urlsafe_b64encode(test_claims.encode()).decode().rstrip("=")
+ challenge_header = f'Bearer error="insufficient_claims",
claims="{encoded_claims}"'
+
+ response_mock = Mock(status_code=401, headers={"WWW-Authenticate":
challenge_header})
+
+ # Mock transport that returns the 401 response
+ def mock_transport_send(request):
+ return response_mock
+
+ transport = Mock(send=mock_transport_send)
+ pipeline = Pipeline(transport=transport, policies=[policy])
+
+ # Execute the request and verify exception chaining
+ with pytest.raises(ClientAuthenticationError) as exc_info:
+ pipeline.run(http_request("GET", "https://example.com"))
+
+ # Verify the original exception is preserved
+ original_exception = exc_info.value
+ assert original_exception.message == "Failed to request token info with
claims"
+
+ # Verify the exception is chained with HttpResponseError
+ assert original_exception.__cause__ is not None
+ assert isinstance(original_exception.__cause__, HttpResponseError)
+
+ # Verify the HttpResponseError contains the original 401 response
+ http_response_error = original_exception.__cause__
+ assert http_response_error.response is response_mock
+
+
[email protected]("http_request", HTTP_REQUESTS)
+def
test_bearer_policy_reads_streamed_response_on_challenge_exception(http_request):
+ """Test that the policy reads streamed response body when on_challenge
raises exception"""
+
+ # Create a credential that will raise an exception when get_token is
called with claims
+ def failing_get_token(*scopes, **kwargs):
+ if "claims" in kwargs:
+ raise ClientAuthenticationError("Failed to get token with claims")
+ return AccessToken("initial_token", int(time.time()) + 3600)
+
+ credential = Mock(spec_set=["get_token"], get_token=failing_get_token)
+ policy = BearerTokenCredentialPolicy(credential, "scope")
+
+ # Create a 401 response with insufficient_claims challenge that will
trigger the exception
+ test_claims = '{"access_token":{"foo":"bar"}}'
+ encoded_claims =
base64.urlsafe_b64encode(test_claims.encode()).decode().rstrip("=")
+ challenge_header = f'Bearer error="insufficient_claims",
claims="{encoded_claims}"'
+
+ # Create the mock HTTP response with stream reading capability
+ http_response_mock = Mock()
+ http_response_mock.status_code = 401
+ http_response_mock.headers = {"WWW-Authenticate": challenge_header}
+ http_response_mock.read = Mock(return_value=b"Error details from server")
+
+ # Mock transport that returns the HTTP response directly (it will be
wrapped by Pipeline)
+ transport = Mock(send=Mock(return_value=http_response_mock))
+
+ # Create pipeline with stream option
+ pipeline = Pipeline(transport=transport, policies=[policy])
+
+ # Execute the request and verify exception handling
+ with pytest.raises(ClientAuthenticationError) as exc_info:
+ pipeline.run(http_request("GET", "https://example.com"), stream=True)
+
+ # Verify that the response.read() was called to consume the stream
+ http_response_mock.read.assert_called_once()
+
+ # Verify the exception chaining
+ assert exc_info.value.__cause__ is not None
+ assert isinstance(exc_info.value.__cause__, HttpResponseError)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_core-1.35.0/tests/test_retry_policy.py
new/azure_core-1.35.1/tests/test_retry_policy.py
--- old/azure_core-1.35.0/tests/test_retry_policy.py 2025-07-03
01:50:16.000000000 +0200
+++ new/azure_core-1.35.1/tests/test_retry_policy.py 2025-09-11
23:05:23.000000000 +0200
@@ -314,3 +314,99 @@
pipeline.run(http_request("GET", "http://localhost/"))
assert transport.sleep.call_count == 1
+
+
+def test_configure_retries_uses_constructor_values():
+ """Test that configure_retries method correctly gets values from
constructor."""
+ # Test with custom constructor values
+ retry_policy = RetryPolicy(
+ retry_total=5,
+ retry_connect=2,
+ retry_read=4,
+ retry_status=1,
+ retry_backoff_factor=0.5,
+ retry_backoff_max=60,
+ timeout=300,
+ )
+
+ # Call configure_retries with empty options to ensure it uses constructor
values
+ retry_settings = retry_policy.configure_retries({})
+
+ # Verify that configure_retries returns constructor values as defaults
+ assert retry_settings["total"] == 5
+ assert retry_settings["connect"] == 2
+ assert retry_settings["read"] == 4
+ assert retry_settings["status"] == 1
+ assert retry_settings["backoff"] == 0.5
+ assert retry_settings["max_backoff"] == 60
+ assert retry_settings["timeout"] == 300
+ assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT",
"DELETE", "OPTIONS", "TRACE"])
+ assert retry_settings["history"] == []
+
+
+def test_configure_retries_options_override_constructor():
+ """Test that options passed to configure_retries override constructor
values."""
+ # Test with custom constructor values
+ retry_policy = RetryPolicy(
+ retry_total=5,
+ retry_connect=2,
+ retry_read=4,
+ retry_status=1,
+ retry_backoff_factor=0.5,
+ retry_backoff_max=60,
+ timeout=300,
+ )
+
+ # Call configure_retries with options that should override constructor
values
+ options = {
+ "retry_total": 8,
+ "retry_connect": 6,
+ "retry_read": 7,
+ "retry_status": 3,
+ "retry_backoff_factor": 1.0,
+ "retry_backoff_max": 180,
+ "timeout": 600,
+ "retry_on_methods": frozenset(["GET", "POST"]),
+ }
+ retry_settings = retry_policy.configure_retries(options)
+
+ # Verify that configure_retries returns option values, not constructor
values
+ assert retry_settings["total"] == 8
+ assert retry_settings["connect"] == 6
+ assert retry_settings["read"] == 7
+ assert retry_settings["status"] == 3
+ assert retry_settings["backoff"] == 1.0
+ assert retry_settings["max_backoff"] == 180
+ assert retry_settings["timeout"] == 600
+ assert retry_settings["methods"] == frozenset(["GET", "POST"])
+ assert retry_settings["history"] == []
+
+ # Verify options dict was modified (values were popped)
+ assert "retry_total" not in options
+ assert "retry_connect" not in options
+ assert "retry_read" not in options
+ assert "retry_status" not in options
+ assert "retry_backoff_factor" not in options
+ assert "retry_backoff_max" not in options
+ assert "timeout" not in options
+ assert "retry_on_methods" not in options
+
+
+def test_configure_retries_default_values():
+ """Test that configure_retries uses default values when no constructor
args or options provided."""
+ # Test with default constructor
+ retry_policy = RetryPolicy()
+
+ # Call configure_retries with empty options
+ retry_settings = retry_policy.configure_retries({})
+
+ # Verify default values from RetryPolicyBase.__init__
+ assert retry_settings["total"] == 10 # default retry_total
+ assert retry_settings["connect"] == 3 # default retry_connect
+ assert retry_settings["read"] == 3 # default retry_read
+ assert retry_settings["status"] == 3 # default retry_status
+ assert retry_settings["backoff"] == 0.8 # default retry_backoff_factor
+ assert retry_settings["max_backoff"] == 120 # default retry_backoff_max
(BACKOFF_MAX)
+ assert retry_settings["timeout"] == 604800 # default timeout
+ assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT",
"DELETE", "OPTIONS", "TRACE"])
+ assert retry_settings["history"] == []