3434)
3535from airbyte_cdk .models import FailureType , Level
3636from airbyte_cdk .sources .connector_state_manager import ConnectorStateManager
37- from airbyte_cdk .sources .declarative import transformations
3837from airbyte_cdk .sources .declarative .async_job .job_orchestrator import AsyncJobOrchestrator
3938from airbyte_cdk .sources .declarative .async_job .job_tracker import JobTracker
4039from airbyte_cdk .sources .declarative .async_job .repository import AsyncJobRepository
604603 WeekClampingStrategy ,
605604 Weekday ,
606605)
607- from airbyte_cdk .sources .streams .concurrent .cursor import ConcurrentCursor , CursorField
606+ from airbyte_cdk .sources .streams .concurrent .cursor import ConcurrentCursor , Cursor , CursorField
608607from airbyte_cdk .sources .streams .concurrent .state_converters .datetime_stream_state_converter import (
609608 CustomFormatConcurrentStreamStateConverter ,
610609 DateTimeStreamStateConverter ,
@@ -1475,6 +1474,7 @@ def create_concurrent_cursor_from_incrementing_count_cursor(
14751474 stream_namespace : Optional [str ],
14761475 config : Config ,
14771476 message_repository : Optional [MessageRepository ] = None ,
1477+ stream_state_migrations : Optional [List [Any ]] = None ,
14781478 ** kwargs : Any ,
14791479 ) -> ConcurrentCursor :
14801480 # Per-partition incremental streams can dynamically create child cursors which will pass their current
@@ -1485,6 +1485,7 @@ def create_concurrent_cursor_from_incrementing_count_cursor(
14851485 if "stream_state" not in kwargs
14861486 else kwargs ["stream_state" ]
14871487 )
1488+ stream_state = self .apply_stream_state_migrations (stream_state_migrations , stream_state )
14881489
14891490 component_type = component_definition .get ("type" )
14901491 if component_definition .get ("type" ) != model_type .__name__ :
@@ -1561,6 +1562,7 @@ def create_concurrent_cursor_from_perpartition_cursor(
15611562 stream_state : MutableMapping [str , Any ],
15621563 partition_router : PartitionRouter ,
15631564 stream_state_migrations : Optional [List [Any ]] = None ,
1565+ attempt_to_create_cursor_if_not_provided : bool = False ,
15641566 ** kwargs : Any ,
15651567 ) -> ConcurrentPerPartitionCursor :
15661568 component_type = component_definition .get ("type" )
@@ -1631,6 +1633,7 @@ def create_concurrent_cursor_from_perpartition_cursor(
16311633 connector_state_converter = connector_state_converter ,
16321634 cursor_field = cursor_field ,
16331635 use_global_cursor = use_global_cursor ,
1636+ attempt_to_create_cursor_if_not_provided = attempt_to_create_cursor_if_not_provided ,
16341637 )
16351638
16361639 @staticmethod
@@ -1931,30 +1934,17 @@ def create_declarative_stream(
19311934 and hasattr (model .incremental_sync , "is_data_feed" )
19321935 and model .incremental_sync .is_data_feed
19331936 )
1934- client_side_incremental_sync = None
1935- if (
1937+ client_side_filtering_enabled = (
19361938 model .incremental_sync
19371939 and hasattr (model .incremental_sync , "is_client_side_incremental" )
19381940 and model .incremental_sync .is_client_side_incremental
1939- ):
1940- supported_slicers = (
1941- DatetimeBasedCursor ,
1942- GlobalSubstreamCursor ,
1943- PerPartitionWithGlobalCursor ,
1944- )
1945- if combined_slicers and not isinstance (combined_slicers , supported_slicers ):
1946- raise ValueError (
1947- "Unsupported Slicer is used. PerPartitionWithGlobalCursor should be used here instead"
1948- )
1949- cursor = (
1950- combined_slicers
1951- if isinstance (
1952- combined_slicers , (PerPartitionWithGlobalCursor , GlobalSubstreamCursor )
1953- )
1954- else self ._create_component_from_model (model = model .incremental_sync , config = config )
1941+ )
1942+ concurrent_cursor = None
1943+ if stop_condition_on_cursor or client_side_filtering_enabled :
1944+ stream_slicer = self ._build_stream_slicer_from_partition_router (
1945+ model .retriever , config , stream_name = model .name
19551946 )
1956-
1957- client_side_incremental_sync = {"cursor" : cursor }
1947+ concurrent_cursor = self ._build_concurrent_cursor (model , stream_slicer , config )
19581948
19591949 if model .incremental_sync and isinstance (model .incremental_sync , DatetimeBasedCursorModel ):
19601950 cursor_model = model .incremental_sync
@@ -2029,8 +2019,10 @@ def create_declarative_stream(
20292019 primary_key = primary_key ,
20302020 stream_slicer = combined_slicers ,
20312021 request_options_provider = request_options_provider ,
2032- stop_condition_on_cursor = stop_condition_on_cursor ,
2033- client_side_incremental_sync = client_side_incremental_sync ,
2022+ stop_condition_cursor = concurrent_cursor ,
2023+ client_side_incremental_sync = {"cursor" : concurrent_cursor }
2024+ if client_side_filtering_enabled
2025+ else None ,
20342026 transformations = transformations ,
20352027 file_uploader = file_uploader ,
20362028 incremental_sync = model .incremental_sync ,
@@ -2185,6 +2177,67 @@ def _build_incremental_cursor(
21852177 return self ._create_component_from_model (model = model .incremental_sync , config = config ) # type: ignore[no-any-return] # Will be created Cursor as stream_slicer_model is model.incremental_sync
21862178 return None
21872179
2180+ def _build_concurrent_cursor (
2181+ self ,
2182+ model : DeclarativeStreamModel ,
2183+ stream_slicer : Optional [PartitionRouter ],
2184+ config : Config ,
2185+ ) -> Optional [StreamSlicer ]:
2186+ stream_state = self ._connector_state_manager .get_stream_state (
2187+ stream_name = model .name or "" , namespace = None
2188+ )
2189+
2190+ if model .incremental_sync and stream_slicer :
2191+ # FIXME there is a discrepancy where this logic is applied on the create_*_cursor methods for
2192+ # ConcurrentCursor but it is applied outside of create_concurrent_cursor_from_perpartition_cursor
2193+ if model .state_migrations :
2194+ state_transformations = [
2195+ self ._create_component_from_model (
2196+ state_migration , config , declarative_stream = model
2197+ )
2198+ for state_migration in model .state_migrations
2199+ ]
2200+ else :
2201+ state_transformations = []
2202+
2203+ return self .create_concurrent_cursor_from_perpartition_cursor ( # type: ignore # This is a known issue that we are creating and returning a ConcurrentCursor which does not technically implement the (low-code) StreamSlicer. However, (low-code) StreamSlicer and ConcurrentCursor both implement StreamSlicer.stream_slices() which is the primary method needed for checkpointing
2204+ state_manager = self ._connector_state_manager ,
2205+ model_type = DatetimeBasedCursorModel ,
2206+ component_definition = model .incremental_sync .__dict__ ,
2207+ stream_name = model .name or "" ,
2208+ stream_namespace = None ,
2209+ config = config or {},
2210+ stream_state = stream_state ,
2211+ stream_state_migrations = state_transformations ,
2212+ partition_router = stream_slicer ,
2213+ attempt_to_create_cursor_if_not_provided = True ,
2214+ )
2215+ elif model .incremental_sync :
2216+ if type (model .incremental_sync ) == IncrementingCountCursorModel :
2217+ return self .create_concurrent_cursor_from_incrementing_count_cursor ( # type: ignore # This is a known issue that we are creating and returning a ConcurrentCursor which does not technically implement the (low-code) StreamSlicer. However, (low-code) StreamSlicer and ConcurrentCursor both implement StreamSlicer.stream_slices() which is the primary method needed for checkpointing
2218+ model_type = IncrementingCountCursorModel ,
2219+ component_definition = model .incremental_sync .__dict__ ,
2220+ stream_name = model .name or "" ,
2221+ stream_namespace = None ,
2222+ config = config or {},
2223+ stream_state_migrations = model .state_migrations ,
2224+ )
2225+ elif type (model .incremental_sync ) == DatetimeBasedCursorModel :
2226+ return self .create_concurrent_cursor_from_datetime_based_cursor ( # type: ignore # This is a known issue that we are creating and returning a ConcurrentCursor which does not technically implement the (low-code) StreamSlicer. However, (low-code) StreamSlicer and ConcurrentCursor both implement StreamSlicer.stream_slices() which is the primary method needed for checkpointing
2227+ model_type = type (model .incremental_sync ),
2228+ component_definition = model .incremental_sync .__dict__ ,
2229+ stream_name = model .name or "" ,
2230+ stream_namespace = None ,
2231+ config = config or {},
2232+ stream_state_migrations = model .state_migrations ,
2233+ attempt_to_create_cursor_if_not_provided = True ,
2234+ )
2235+ else :
2236+ raise ValueError (
2237+ f"Incremental sync of type { type (model .incremental_sync )} is not supported"
2238+ )
2239+ return None
2240+
21882241 def _build_resumable_cursor (
21892242 self ,
21902243 model : Union [
@@ -2285,7 +2338,7 @@ def create_default_paginator(
22852338 url_base : str ,
22862339 extractor_model : Optional [Union [CustomRecordExtractorModel , DpathExtractorModel ]] = None ,
22872340 decoder : Optional [Decoder ] = None ,
2288- cursor_used_for_stop_condition : Optional [DeclarativeCursor ] = None ,
2341+ cursor_used_for_stop_condition : Optional [Cursor ] = None ,
22892342 ) -> Union [DefaultPaginator , PaginatorTestReadDecorator ]:
22902343 if decoder :
22912344 if self ._is_supported_decoder_for_pagination (decoder ):
@@ -3146,7 +3199,7 @@ def create_simple_retriever(
31463199 primary_key : Optional [Union [str , List [str ], List [List [str ]]]],
31473200 stream_slicer : Optional [StreamSlicer ],
31483201 request_options_provider : Optional [RequestOptionsProvider ] = None ,
3149- stop_condition_on_cursor : bool = False ,
3202+ stop_condition_cursor : Optional [ Cursor ] = None ,
31503203 client_side_incremental_sync : Optional [Dict [str , Any ]] = None ,
31513204 transformations : List [RecordTransformation ],
31523205 file_uploader : Optional [DefaultFileUploader ] = None ,
@@ -3277,15 +3330,14 @@ def _get_url() -> str:
32773330 ),
32783331 )
32793332
3280- cursor_used_for_stop_condition = cursor if stop_condition_on_cursor else None
32813333 paginator = (
32823334 self ._create_component_from_model (
32833335 model = model .paginator ,
32843336 config = config ,
32853337 url_base = _get_url (),
32863338 extractor_model = model .record_selector .extractor ,
32873339 decoder = decoder ,
3288- cursor_used_for_stop_condition = cursor_used_for_stop_condition ,
3340+ cursor_used_for_stop_condition = stop_condition_cursor or None ,
32893341 )
32903342 if model .paginator
32913343 else NoPagination (parameters = {})
0 commit comments