Skip to content

Commit 5f16b14

Browse files
author
annie-mac
committed
refactor
1 parent b6c53fb commit 5f16b14

20 files changed

+436
-937
lines changed

sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_fetcher.py

+43-19
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
import copy
2727
import json
2828
from abc import ABC, abstractmethod
29+
from typing import Dict, Any, List
2930

3031
from azure.cosmos import http_constants, exceptions
31-
from azure.cosmos._change_feed.aio.change_feed_state import ChangeFeedStateV1, ChangeFeedStateV2
32+
from azure.cosmos._change_feed.change_feed_start_from import ChangeFeedStartFromPointInTime
33+
from azure.cosmos._change_feed.change_feed_state import ChangeFeedStateV1, ChangeFeedStateV2
3234
from azure.cosmos.aio import _retry_utility_async
3335
from azure.cosmos.exceptions import CosmosHttpResponseError
3436

37+
# pylint: disable=protected-access
3538

3639
class ChangeFeedFetcher(ABC):
3740

@@ -49,16 +52,16 @@ def __init__(
4952
self,
5053
client,
5154
resource_link: str,
52-
feed_options: dict[str, any],
55+
feed_options: Dict[str, Any],
5356
fetch_function):
5457

5558
self._client = client
5659
self._feed_options = feed_options
5760

5861
self._change_feed_state = self._feed_options.pop("changeFeedState")
5962
if not isinstance(self._change_feed_state, ChangeFeedStateV1):
60-
raise ValueError(f"ChangeFeedFetcherV1 can not handle change feed state version {type(self._change_feed_state)}")
61-
self._change_feed_state.__class__ = ChangeFeedStateV1
63+
raise ValueError(f"ChangeFeedFetcherV1 can not handle change feed state version"
64+
f" {type(self._change_feed_state)}")
6265

6366
self._resource_link = resource_link
6467
self._fetch_function = fetch_function
@@ -74,24 +77,27 @@ async def callback():
7477

7578
return await _retry_utility_async.ExecuteAsync(self._client, self._client._global_endpoint_manager, callback)
7679

77-
async def fetch_change_feed_items(self, fetch_function) -> list[dict[str, any]]:
80+
async def fetch_change_feed_items(self, fetch_function) -> List[Dict[str, Any]]:
7881
new_options = copy.deepcopy(self._feed_options)
7982
new_options["changeFeedState"] = self._change_feed_state
8083

8184
self._change_feed_state.populate_feed_options(new_options)
82-
is_s_time_first_fetch = True
85+
is_s_time_first_fetch = self._change_feed_state._continuation is None
8386
while True:
8487
(fetched_items, response_headers) = await fetch_function(new_options)
8588
continuation_key = http_constants.HttpHeaders.ETag
8689
# In change feed queries, the continuation token is always populated. The hasNext() test is whether
8790
# there is any items in the response or not.
88-
# For start time however we get no initial results, so we need to pass continuation token? Is this true?
8991
self._change_feed_state.apply_server_response_continuation(
9092
response_headers.get(continuation_key))
9193

9294
if fetched_items:
9395
break
94-
elif is_s_time_first_fetch:
96+
97+
# When processing from point in time, there will be no initial results being returned,
98+
# so we will retry with the new continuation token again
99+
if (isinstance(self._change_feed_state._change_feed_start_from, ChangeFeedStartFromPointInTime)
100+
and is_s_time_first_fetch):
95101
is_s_time_first_fetch = False
96102
else:
97103
break
@@ -106,16 +112,15 @@ def __init__(
106112
self,
107113
client,
108114
resource_link: str,
109-
feed_options: dict[str, any],
115+
feed_options: Dict[str, Any],
110116
fetch_function):
111117

112118
self._client = client
113119
self._feed_options = feed_options
114120

115-
self._change_feed_state = self._feed_options.pop("changeFeedState")
121+
self._change_feed_state: ChangeFeedStateV2 = self._feed_options.pop("changeFeedState")
116122
if not isinstance(self._change_feed_state, ChangeFeedStateV2):
117123
raise ValueError(f"ChangeFeedFetcherV2 can not handle change feed state version {type(self._change_feed_state)}")
118-
self._change_feed_state.__class__ = ChangeFeedStateV2
119124

120125
self._resource_link = resource_link
121126
self._fetch_function = fetch_function
@@ -131,17 +136,22 @@ async def callback():
131136
return await self.fetch_change_feed_items(self._fetch_function)
132137

133138
try:
134-
return await _retry_utility_async.ExecuteAsync(self._client, self._client._global_endpoint_manager, callback)
139+
return await _retry_utility_async.ExecuteAsync(
140+
self._client,
141+
self._client._global_endpoint_manager,
142+
callback)
135143
except CosmosHttpResponseError as e:
136144
if exceptions._partition_range_is_gone(e) or exceptions._is_partition_split_or_merge(e):
137145
# refresh change feed state
138-
await self._change_feed_state.handle_feed_range_gone(self._client._routing_map_provider, self._resource_link)
146+
await self._change_feed_state.handle_feed_range_gone_async(
147+
self._client._routing_map_provider,
148+
self._resource_link)
139149
else:
140150
raise e
141151

142152
return await self.fetch_next_block()
143153

144-
async def fetch_change_feed_items(self, fetch_function) -> list[dict[str, any]]:
154+
async def fetch_change_feed_items(self, fetch_function) -> List[Dict[str, Any]]:
145155
new_options = copy.deepcopy(self._feed_options)
146156
new_options["changeFeedState"] = self._change_feed_state
147157

@@ -154,19 +164,33 @@ async def fetch_change_feed_items(self, fetch_function) -> list[dict[str, any]]:
154164
continuation_key = http_constants.HttpHeaders.ETag
155165
# In change feed queries, the continuation token is always populated. The hasNext() test is whether
156166
# there is any items in the response or not.
157-
# For start time however we get no initial results, so we need to pass continuation token? Is this true?
158167
if fetched_items:
159168
self._change_feed_state.apply_server_response_continuation(
160169
response_headers.get(continuation_key))
161170
response_headers[continuation_key] = self._get_base64_encoded_continuation()
162171
break
163-
else:
172+
173+
# when there is no items being returned, we will decide to retry based on:
174+
# 1. When processing from point in time, there will be no initial results being returned,
175+
# so we will retry with the new continuation token
176+
# 2. if the feed range of the changeFeedState span multiple physical partitions
177+
# then we will read from the next feed range until we have looped through all physical partitions
164178
self._change_feed_state.apply_not_modified_response()
165179
self._change_feed_state.apply_server_response_continuation(
166180
response_headers.get(continuation_key))
167-
response_headers[continuation_key] = self._get_base64_encoded_continuation()
168-
should_retry = self._change_feed_state.should_retry_on_not_modified_response() or is_s_time_first_fetch
169-
is_s_time_first_fetch = False
181+
182+
#TODO: can this part logic be simplified
183+
if (isinstance(self._change_feed_state._change_feed_start_from, ChangeFeedStartFromPointInTime)
184+
and is_s_time_first_fetch):
185+
response_headers[continuation_key] = self._get_base64_encoded_continuation()
186+
is_s_time_first_fetch = False
187+
should_retry = True
188+
else:
189+
self._change_feed_state._continuation._move_to_next_token()
190+
response_headers[continuation_key] = self._get_base64_encoded_continuation()
191+
should_retry = self._change_feed_state.should_retry_on_not_modified_response()
192+
is_s_time_first_fetch = False
193+
170194
if not should_retry:
171195
break
172196

sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_iterable.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121

2222
"""Iterable change feed results in the Azure Cosmos database service.
2323
"""
24+
from typing import Dict, Any
2425

2526
from azure.core.async_paging import AsyncPageIterator
2627

2728
from azure.cosmos import PartitionKey
2829
from azure.cosmos._change_feed.aio.change_feed_fetcher import ChangeFeedFetcherV1, ChangeFeedFetcherV2
29-
from azure.cosmos._change_feed.aio.change_feed_state import ChangeFeedStateV1, ChangeFeedState
30+
from azure.cosmos._change_feed.change_feed_state import ChangeFeedState, ChangeFeedStateV1
3031
from azure.cosmos._utils import is_base64_encoded, is_key_exists_and_not_none
3132

33+
# pylint: disable=protected-access
3234

3335
class ChangeFeedIterable(AsyncPageIterator):
3436
"""Represents an iterable object of the change feed results.
@@ -66,14 +68,16 @@ def __init__(
6668

6769
change_feed_state_context = self._options.pop("changeFeedStateContext")
6870

69-
continuation = continuation_token if continuation_token is not None else change_feed_state_context.pop("continuation", None)
71+
continuation = continuation_token if continuation_token is not None\
72+
else change_feed_state_context.pop("continuation", None)
7073

7174
# analysis and validate continuation token
7275
# there are two types of continuation token we support currently:
7376
# v1 version: the continuation token would just be the _etag,
7477
# which is being returned when customer is using partition_key_range_id,
7578
# which is under deprecation and does not support split/merge
76-
# v2 version: the continuation token will be base64 encoded composition token which includes full change feed state
79+
# v2 version: the continuation token will be base64 encoded composition token
80+
# which includes full change feed state
7781
if continuation is not None:
7882
if is_base64_encoded(continuation):
7983
change_feed_state_context["continuationFeedRange"] = continuation
@@ -141,7 +145,7 @@ async def _initialize_change_feed_fetcher(self):
141145
self._fetch_function
142146
)
143147

144-
def _validate_change_feed_state_context(self, change_feed_state_context: dict[str, any]) -> None:
148+
def _validate_change_feed_state_context(self, change_feed_state_context: Dict[str, Any]) -> None:
145149

146150
if is_key_exists_and_not_none(change_feed_state_context, "continuationPkRangeId"):
147151
# if continuation token is in v1 format, throw exception if feed_range is set
@@ -158,4 +162,5 @@ def _validate_change_feed_state_context(self, change_feed_state_context: dict[st
158162
key in change_feed_state_context and change_feed_state_context[key] is not None)
159163
if count > 1:
160164
raise ValueError(
161-
"partition_key_range_id, partition_key, feed_range are exclusive parameters, please only set one of them")
165+
"partition_key_range_id, partition_key, feed_range are exclusive parameters,"
166+
" please only set one of them")

sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_start_from.py

-189
This file was deleted.

0 commit comments

Comments
 (0)