Skip to content

[Cosmos] Etag handling bugfix and documentation rollback #40282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 31, 2025
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
1 change: 1 addition & 0 deletions sdk/cosmos/azure-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#### Breaking Changes

#### Bugs Fixed
* Fixed bug introduced in 4.10.0b3 with explicitly setting `etag` keyword argument as `None` causing exceptions. See [PR 40282](https://github.com/Azure/azure-sdk-for-python/pull/40282).

#### Other Changes

Expand Down
3 changes: 2 additions & 1 deletion sdk/cosmos/azure-cosmos/azure/cosmos/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def _get_match_headers(kwargs: Dict[str, Any]) -> Tuple[Optional[str], Optional[
elif match_condition == MatchConditions.IfMissing:
if_none_match = '*'
elif match_condition is None:
if 'etag' in kwargs:
etag = kwargs.pop('etag', None)
if etag is not None:
raise ValueError("'etag' specified without 'match_condition'.")
else:
raise TypeError("Invalid match condition: {}".format(match_condition))
Expand Down
23 changes: 10 additions & 13 deletions sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,8 @@ async def upsert_item(
post_trigger_include: Optional[str] = None,
session_token: Optional[str] = None,
initial_headers: Optional[Dict[str, str]] = None,
etag: Optional[str] = None,
match_condition: Optional[MatchConditions] = None,
priority: Optional[Literal["High", "Low"]] = None,
no_response: Optional[bool] = None,
**kwargs: Any
Expand All @@ -740,6 +742,10 @@ async def upsert_item(
:keyword str post_trigger_include: trigger id to be used as post operation trigger.
:keyword str session_token: Token for use with Session consistency.
:keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
:keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource
has changed, and act according to the condition specified by the `match_condition` parameter.
:keyword match_condition: The match condition to use upon the etag.
:paramtype match_condition: ~azure.core.MatchConditions
:keyword response_hook: A callable invoked with the response metadata.
:paramtype response_hook: Callable[[Mapping[str, str], Dict[str, Any]], None]
:keyword Literal["High", "Low"] priority: Priority based execution allows users to set a priority for each
Expand All @@ -753,19 +759,6 @@ async def upsert_item(
`no_response` is specified.
:rtype: ~azure.cosmos.CosmosDict[str, Any]
"""
etag = kwargs.get('etag')
if etag is not None:
warnings.warn(
"The 'etag' flag does not apply to this method and is always ignored even if passed."
" It will now be removed in the future.",
DeprecationWarning)
match_condition = kwargs.get('match_condition')
if match_condition is not None:
warnings.warn(
"The 'match_condition' flag does not apply to this method and is always ignored even if passed."
" It will now be removed in the future.",
DeprecationWarning)

if pre_trigger_include is not None:
kwargs['pre_trigger_include'] = pre_trigger_include
if post_trigger_include is not None:
Expand All @@ -776,6 +769,10 @@ async def upsert_item(
kwargs['initial_headers'] = initial_headers
if priority is not None:
kwargs['priority'] = priority
if etag is not None:
kwargs['etag'] = etag
if match_condition is not None:
kwargs['match_condition'] = match_condition
if no_response is not None:
kwargs['no_response'] = no_response
request_options = _build_options(kwargs)
Expand Down
21 changes: 9 additions & 12 deletions sdk/cosmos/azure-cosmos/azure/cosmos/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,8 @@ def upsert_item( # pylint:disable=docstring-missing-param
*,
session_token: Optional[str] = None,
initial_headers: Optional[Dict[str, str]] = None,
etag: Optional[str] = None,
match_condition: Optional[MatchConditions] = None,
priority: Optional[Literal["High", "Low"]] = None,
no_response: Optional[bool] = None,
response_hook: Optional[Callable[[Mapping[str, str], Dict[str, Any]], None]] = None,
Expand All @@ -782,6 +784,9 @@ def upsert_item( # pylint:disable=docstring-missing-param
:param str post_trigger_include: trigger id to be used as post operation trigger.
:keyword str session_token: Token for use with Session consistency.
:keyword Dict[str, str] initial_headers: Initial headers to be sent as part of the request.
:keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource
has changed, and act according to the condition specified by the `match_condition` parameter.
:keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag.
:keyword response_hook: A callable invoked with the response metadata.
:paramtype response_hook: Callable[[Mapping[str, str], Dict[str, Any]], None]
:keyword Literal["High", "Low"] priority: Priority based execution allows users to set a priority for each
Expand All @@ -794,18 +799,6 @@ def upsert_item( # pylint:disable=docstring-missing-param
:returns: A CosmosDict representing the upserted item. The dict will be empty if `no_response` is specified.
:rtype: ~azure.cosmos.CosmosDict[str, Any]
"""
etag = kwargs.get('etag')
if etag is not None:
warnings.warn(
"The 'etag' flag does not apply to this method and is always ignored even if passed."
" It will now be removed in the future.",
DeprecationWarning)
match_condition = kwargs.get('match_condition')
if match_condition is not None:
warnings.warn(
"The 'match_condition' flag does not apply to this method and is always ignored even if passed."
" It will now be removed in the future.",
DeprecationWarning)
if pre_trigger_include is not None:
kwargs['pre_trigger_include'] = pre_trigger_include
if post_trigger_include is not None:
Expand All @@ -816,6 +809,10 @@ def upsert_item( # pylint:disable=docstring-missing-param
kwargs['initial_headers'] = initial_headers
if priority is not None:
kwargs['priority'] = priority
if etag is not None:
kwargs['etag'] = etag
if match_condition is not None:
kwargs['match_condition'] = match_condition
if no_response is not None:
kwargs['no_response'] = no_response
if response_hook is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ def test_etag_match_condition_compatibility(self):
item2 = container.upsert_item({"id": str(uuid.uuid4()), "pk": 0}, etag=str(uuid.uuid4()),
match_condition=MatchConditions.IfNotModified)
assert item2 is not None
item = container.create_item({"id": str(uuid.uuid4()), "pk": 0}, etag=None, match_condition=None)
assert item is not None
item2 = container.upsert_item({"id": str(uuid.uuid4()), "pk": 0}, etag=None, match_condition=None)
assert item2 is not None
batch_operations = [
("create", ({"id": str(uuid.uuid4()), "pk": 0},)),
("replace", (item2['id'], {"id": str(uuid.uuid4()), "pk": 0})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ async def test_etag_match_condition_compatibility_async(self):
item2 = await container.upsert_item({"id": str(uuid.uuid4()), "pk": 0}, etag=str(uuid.uuid4()),
match_condition=MatchConditions.IfNotModified)
assert item2 is not None
item = await container.create_item({"id": str(uuid.uuid4()), "pk": 0}, etag=None, match_condition=None)
assert item is not None
item2 = await container.upsert_item({"id": str(uuid.uuid4()), "pk": 0}, etag=None,
match_condition=None)
assert item2 is not None
batch_operations = [
("create", ({"id": str(uuid.uuid4()), "pk": 0},)),
("replace", (item2['id'], {"id": str(uuid.uuid4()), "pk": 0})),
Expand Down
Loading