Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
## Release History

### 4.15.0b3 (Unreleased)
### 4.15.0 (2026-02-11)

#### Features Added

#### Breaking Changes
* GA support of Per Partition Automatic Failover and AvailabilityStrategy features.

#### Bugs Fixed
Comment thread
simorenoh marked this conversation as resolved.
* Fixed bug where sdk was encountering a timeout issue caused by infinite recursion during the 410 (Gone) error. See [PR 44770](https://github.com/Azure/azure-sdk-for-python/pull/44770)
* Fixed crash in sync and async clients when `force_refresh_on_startup` was set to `None`, which could surface as `AttributeError: 'NoneType' object has no attribute '_WritableLocations'` during region discovery when `database_account` was `None`. See [PR 44987](https://github.com/Azure/azure-sdk-for-python/pull/44987)

#### Other Changes
* Added tests for multi-language support for full text search. See [PR 44254](https://github.com/Azure/azure-sdk-for-python/pull/44254)
* Renamed `availability_strategy_config` introduced in 4.15.0b1 to `availability_strategy` for both sync and async clients. See [PR 45086](https://github.com/Azure/azure-sdk-for-python/pull/45086).

### 4.14.6 (2026-02-02)

#### Bugs Fixed
* Fixed async client crash (`AttributeError: 'NoneType' object has no attribute '_WritableLocations'`) during region discovery when `database_account` was `None`. See [PR 44939](https://github.com/Azure/azure-sdk-for-python/pull/44939)

### 4.14.5 (2026-01-15)

#### Bugs Fixed
* Fixed bug where sdk was encountering a timeout issue caused by infinite recursion during the 410 (Gone) error.See [PR 44659](https://github.com/Azure/azure-sdk-for-python/pull/44649)

### 4.14.4 (2026-01-12)

#### Bugs Fixed
* Fixed bug where sdk was not properly retrying requests in some edge cases after partition splits.See [PR 44425](https://github.com/Azure/azure-sdk-for-python/pull/44425)

### 4.15.0b2 (2025-12-16)

Expand Down
12 changes: 6 additions & 6 deletions sdk/cosmos/azure-cosmos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ Cross region hedging availability strategy improves availability and reduces lat

#### Enabling Cross Region Hedging

You can enable cross region hedging by passing the `availability_strategy_config` parameter as a dictionary to the `CosmosClient` or per-request. The most common configuration keys are `threshold_ms` (delay before sending a hedged request) and `threshold_steps_ms` (step interval for additional hedged requests).
You can enable cross region hedging by passing the `availability_strategy` parameter as a dictionary to the `CosmosClient` or per-request. The most common configuration keys are `threshold_ms` (delay before sending a hedged request) and `threshold_steps_ms` (step interval for additional hedged requests).

#### Client-level configuration

Expand All @@ -962,7 +962,7 @@ from azure.cosmos import CosmosClient
client = CosmosClient(
"<account-uri>",
"<account-key>",
availability_strategy_config={"threshold_ms": 150, "threshold_steps_ms": 50}
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50}
)
```

Expand All @@ -973,7 +973,7 @@ client = CosmosClient(
container.read_item(
item="item_id",
partition_key="pk_value",
availability_strategy_config={"threshold_ms": 150, "threshold_steps_ms": 50}
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50}
)
```

Expand All @@ -984,7 +984,7 @@ container.read_item(
container.read_item(
item="item_id",
partition_key="pk_value",
availability_strategy_config=None
availability_strategy=None
)
```

Expand All @@ -999,7 +999,7 @@ executor = ThreadPoolExecutor(max_workers=2)
client = CosmosClient(
"<account-uri>",
"<account-key>",
availability_strategy_config={"threshold_ms": 150, "threshold_steps_ms": 50},
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50},
availability_strategy_executor=executor
)
```
Expand All @@ -1013,7 +1013,7 @@ from azure.cosmos import CosmosClient
client = CosmosClient(
"<account-uri>",
"<account-key>",
availability_strategy_config={"threshold_ms": 150, "threshold_steps_ms": 50},
availability_strategy={"threshold_ms": 150, "threshold_steps_ms": 50},
availability_strategy_max_concurrency=2
)
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from typing import Optional, Any


class CrossRegionHedgingStrategyConfig:
class CrossRegionHedgingStrategy:
"""Configuration for cross-region request hedging strategy.

:param config: Dictionary containing configuration values, defaults to None
Expand All @@ -48,15 +48,15 @@ def __init__(self, config: Optional[dict[str, Any]] = None) -> None:
if self.threshold_steps_ms <= 0:
raise ValueError("threshold_steps_ms must be positive")

def _validate_hedging_config(config: Optional[dict[str, Any]]) -> Optional[CrossRegionHedgingStrategyConfig]:
"""Validate and create a CrossRegionHedgingStrategyConfig.
def _validate_hedging_strategy(config: Optional[dict[str, Any]]) -> Optional[CrossRegionHedgingStrategy]:
"""Validate and create a CrossRegionHedgingStrategy.

:param config: Dictionary containing configuration values
:type config: Optional[Dict[str, Any]]
:returns: Validated configuration object
:rtype: Optional[CrossRegionHedgingStrategyConfig]
:rtype: Optional[CrossRegionHedgingStrategy]
"""
if config is None:
return None

return CrossRegionHedgingStrategyConfig(config)
return CrossRegionHedgingStrategy(config)
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,22 @@ def execute_single_request_with_delay(
:rtype: ResponseType
"""

availability_strategy_config = request_params.availability_strategy_config
if availability_strategy_config is None:
raise ValueError("availability_strategy_config should not be null")
availability_strategy = request_params.availability_strategy
if availability_strategy is None:
raise ValueError("availability_strategy should not be null")

delay: int
if location_index == 0:
# No delay for initial request
delay = 0
elif location_index == 1:
# First hedged request after threshold
delay = availability_strategy_config.threshold_ms
delay = availability_strategy.threshold_ms
else:
# Subsequent requests after threshold steps
steps = location_index - 1
delay = (availability_strategy_config.threshold_ms +
(steps * availability_strategy_config.threshold_steps_ms))
delay = (availability_strategy.threshold_ms +
(steps * availability_strategy.threshold_steps_ms))

if delay > 0:
time.sleep(delay / 1000)
Expand Down
2 changes: 1 addition & 1 deletion sdk/cosmos/azure-cosmos/azure/cosmos/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
'max_item_count': 'maxItemCount',
'throughput_bucket': 'throughputBucket',
'excluded_locations': Constants.Kwargs.EXCLUDED_LOCATIONS,
"availability_strategy_config": Constants.Kwargs.AVAILABILITY_STRATEGY_CONFIG
"availability_strategy": Constants.Kwargs.AVAILABILITY_STRATEGY
}

# Cosmos resource ID validation regex breakdown:
Expand Down
2 changes: 1 addition & 1 deletion sdk/cosmos/azure-cosmos/azure/cosmos/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class Kwargs:
RETRY_WRITE: Literal["retry_write"] = "retry_write"
"""Whether to retry write operations if they fail. Used either at client level or request level."""
EXCLUDED_LOCATIONS: Literal["excludedLocations"] = "excludedLocations"
AVAILABILITY_STRATEGY_CONFIG: Literal["availabilityStrategyConfig"] = "availabilityStrategyConfig"
AVAILABILITY_STRATEGY: Literal["availabilityStrategy"] = "availabilityStrategy"
"""Availability strategy config. Used either at client level or request level"""

class UserAgentFeatureFlags(IntEnum):
Expand Down
30 changes: 15 additions & 15 deletions sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
from . import documents
from . import http_constants, exceptions
from ._auth_policy import CosmosBearerTokenCredentialPolicy
from ._availability_strategy_config import _validate_hedging_config, CrossRegionHedgingStrategyConfig
from ._availability_strategy_config import _validate_hedging_strategy, CrossRegionHedgingStrategy
from ._base import _build_properties_cache
from ._change_feed.change_feed_iterable import ChangeFeedIterable
from ._change_feed.change_feed_state import ChangeFeedState
Expand Down Expand Up @@ -119,7 +119,7 @@ def __init__( # pylint: disable=too-many-statements
auth: CredentialDict,
connection_policy: Optional[ConnectionPolicy] = None,
consistency_level: Optional[str] = None,
availability_strategy_config: Optional[dict[str, Any]] = None,
availability_strategy: Optional[dict[str, Any]] = None,
availability_strategy_executor: Optional[ThreadPoolExecutor] = None,
**kwargs: Any
) -> None:
Expand All @@ -135,7 +135,7 @@ def __init__( # pylint: disable=too-many-statements
The connection policy for the client.
:param documents.ConsistencyLevel consistency_level:
The default consistency policy for client operations.
:param documents.'CrossRegionHedgingStrategyConfig' availability_strategy_config:
:param documents.'CrossRegionHedgingStrategyConfig' availability_strategy:
The availability strategy configuration for routing requests across regions.
:param concurrent.futures.ThreadPoolExecutor availability_strategy_executor:
The thread pool executor for handling availability strategy requests.
Expand All @@ -145,8 +145,8 @@ def __init__( # pylint: disable=too-many-statements
"""
self.client_id = str(uuid.uuid4())
self.url_connection = url_connection
self.availability_strategy_config: Optional[CrossRegionHedgingStrategyConfig] =\
_validate_hedging_config(availability_strategy_config)
self.availability_strategy: Optional[CrossRegionHedgingStrategy] =\
_validate_hedging_strategy(availability_strategy)
self.availability_strategy_executor: Optional[ThreadPoolExecutor] = availability_strategy_executor
self.master_key: Optional[str] = None
self.resource_tokens: Optional[Mapping[str, Any]] = None
Expand Down Expand Up @@ -2156,7 +2156,7 @@ def PatchItem(
headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
base.set_session_token_header(self, headers, path, request_params, options)
request_params.set_retry_write(options, self.connection_policy.RetryNonIdempotentWrites)
Expand Down Expand Up @@ -2254,7 +2254,7 @@ def _Batch(
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_retry_write(options, self.connection_policy.RetryNonIdempotentWrites)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor

base.set_session_token_header(self, headers, path, request_params, options)
Expand Down Expand Up @@ -2319,7 +2319,7 @@ def DeleteAllItemsByPartitionKey(
headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
_, last_response_headers = self.__Post(
path=path,
Expand Down Expand Up @@ -2782,7 +2782,7 @@ def Create(
headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
base.set_session_token_header(self, headers, path, request_params, options)
request_params.set_retry_write(options, self.connection_policy.RetryNonIdempotentWrites)
Expand Down Expand Up @@ -2835,7 +2835,7 @@ def Upsert(
headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
base.set_session_token_header(self, headers, path, request_params, options)
request_params.set_retry_write(options, self.connection_policy.RetryNonIdempotentWrites)
Expand Down Expand Up @@ -2886,7 +2886,7 @@ def Replace(
headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
base.set_session_token_header(self, headers, path, request_params, options)
request_params.set_retry_write(options, self.connection_policy.RetryNonIdempotentWrites)
Expand Down Expand Up @@ -2936,7 +2936,7 @@ def Read(
headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
base.set_session_token_header(self, headers, path, request_params, options)
result, last_response_headers = self.__Get(path, request_params, headers, **kwargs)
Expand Down Expand Up @@ -2985,7 +2985,7 @@ def DeleteResource(
base.set_session_token_header(self, headers, path, request_params, options)
request_params.set_retry_write(options, self.connection_policy.RetryNonIdempotentWrites)
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor
result, last_response_headers = self.__Delete(path, request_params, headers, **kwargs)
self.last_response_headers = last_response_headers
Expand Down Expand Up @@ -3250,7 +3250,7 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
options.get("partitionKey", None)
)
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor

base.set_session_token_header(self, headers, path, request_params, options, partition_key_range_id)
Expand Down Expand Up @@ -3296,7 +3296,7 @@ def __GetBodiesFromQueryResult(result: dict[str, Any]) -> list[dict[str, Any]]:
req_headers,
options.get("partitionKey", None))
request_params.set_excluded_location_from_options(options)
request_params.set_availability_strategy_config(options, self.availability_strategy_config)
request_params.set_availability_strategy(options, self.availability_strategy)
request_params.availability_strategy_executor = self.availability_strategy_executor

if not is_query_plan:
Expand Down
14 changes: 7 additions & 7 deletions sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from concurrent.futures.thread import ThreadPoolExecutor
from typing import Optional, Mapping, Any, Union

from ._availability_strategy_config import CrossRegionHedgingStrategyConfig
from ._availability_strategy_config import CrossRegionHedgingStrategy
from ._constants import _Constants as Constants
from .documents import _OperationType
from .http_constants import ResourceType
Expand All @@ -46,7 +46,7 @@ def __init__(
self.endpoint_override = endpoint_override
self.should_clear_session_token_on_session_read_failure: bool = False # pylint: disable=name-too-long
self.headers = headers
self.availability_strategy_config: Optional[CrossRegionHedgingStrategyConfig] = None
self.availability_strategy: Optional[CrossRegionHedgingStrategy] = None
self.availability_strategy_executor: Optional[ThreadPoolExecutor] = None
self.availability_strategy_max_concurrency: Optional[int] = None
self.use_preferred_locations: Optional[bool] = None
Expand Down Expand Up @@ -111,10 +111,10 @@ def set_retry_write(self, request_options: Mapping[str, Any], client_retry_write
def set_excluded_locations_from_circuit_breaker(self, excluded_locations: list[str]) -> None: # pylint: disable=name-too-long
self.excluded_locations_circuit_breaker = excluded_locations

def set_availability_strategy_config(
def set_availability_strategy(
self,
options: Mapping[str, Any],
client_strategy_config: Optional[CrossRegionHedgingStrategyConfig] = None) -> None:
client_strategy_config: Optional[CrossRegionHedgingStrategy] = None) -> None:
"""Sets the availability strategy config for this request from options.
If not in options, uses the client's default strategy.

Expand All @@ -126,11 +126,11 @@ def set_availability_strategy_config(
"""
# setup availabilityStrategy
# First try to get from options
if Constants.Kwargs.AVAILABILITY_STRATEGY_CONFIG in options:
self.availability_strategy_config = options[Constants.Kwargs.AVAILABILITY_STRATEGY_CONFIG]
if Constants.Kwargs.AVAILABILITY_STRATEGY in options:
self.availability_strategy = options[Constants.Kwargs.AVAILABILITY_STRATEGY]
# If not in options, use client default
elif client_strategy_config is not None:
self.availability_strategy_config = client_strategy_config
self.availability_strategy = client_strategy_config

def should_cancel_request(self) -> bool:
"""Check if this request should be cancelled due to parallel request completion.
Expand Down
Loading