airbyte-cdk 6.31.2.dev0__py3-none-any.whl → 6.32.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. airbyte_cdk/cli/source_declarative_manifest/_run.py +9 -3
  2. airbyte_cdk/connector_builder/connector_builder_handler.py +3 -2
  3. airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +7 -7
  4. airbyte_cdk/sources/declarative/auth/jwt.py +17 -11
  5. airbyte_cdk/sources/declarative/auth/oauth.py +89 -23
  6. airbyte_cdk/sources/declarative/auth/token.py +8 -3
  7. airbyte_cdk/sources/declarative/auth/token_provider.py +4 -5
  8. airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py +19 -9
  9. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +134 -43
  10. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +55 -16
  11. airbyte_cdk/sources/declarative/declarative_stream.py +3 -1
  12. airbyte_cdk/sources/declarative/extractors/record_filter.py +3 -5
  13. airbyte_cdk/sources/declarative/incremental/__init__.py +6 -0
  14. airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +400 -0
  15. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +6 -7
  16. airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +3 -0
  17. airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +35 -3
  18. airbyte_cdk/sources/declarative/manifest_declarative_source.py +20 -7
  19. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +45 -15
  20. airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py +143 -0
  21. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +343 -64
  22. airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +5 -5
  23. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +2 -4
  24. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +55 -15
  25. airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +22 -0
  26. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +4 -4
  27. airbyte_cdk/sources/declarative/requesters/http_requester.py +1 -5
  28. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +5 -6
  29. airbyte_cdk/sources/declarative/requesters/request_option.py +4 -83
  30. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +6 -7
  31. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +6 -12
  32. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +2 -5
  33. airbyte_cdk/sources/declarative/schema/__init__.py +2 -0
  34. airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +44 -5
  35. airbyte_cdk/sources/http_logger.py +1 -1
  36. airbyte_cdk/sources/streams/concurrent/clamping.py +99 -0
  37. airbyte_cdk/sources/streams/concurrent/cursor.py +51 -57
  38. airbyte_cdk/sources/streams/concurrent/cursor_types.py +32 -0
  39. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +22 -13
  40. airbyte_cdk/sources/streams/core.py +6 -6
  41. airbyte_cdk/sources/streams/http/http.py +1 -2
  42. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +231 -62
  43. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +171 -88
  44. airbyte_cdk/sources/types.py +4 -2
  45. airbyte_cdk/sources/utils/transform.py +23 -2
  46. airbyte_cdk/test/utils/manifest_only_fixtures.py +1 -2
  47. airbyte_cdk/utils/datetime_helpers.py +499 -0
  48. airbyte_cdk/utils/mapping_helpers.py +27 -86
  49. airbyte_cdk/utils/slice_hasher.py +8 -1
  50. airbyte_cdk-6.32.0.dist-info/LICENSE_SHORT +1 -0
  51. {airbyte_cdk-6.31.2.dev0.dist-info → airbyte_cdk-6.32.0.dist-info}/METADATA +6 -6
  52. {airbyte_cdk-6.31.2.dev0.dist-info → airbyte_cdk-6.32.0.dist-info}/RECORD +55 -49
  53. {airbyte_cdk-6.31.2.dev0.dist-info → airbyte_cdk-6.32.0.dist-info}/WHEEL +1 -1
  54. {airbyte_cdk-6.31.2.dev0.dist-info → airbyte_cdk-6.32.0.dist-info}/LICENSE.txt +0 -0
  55. {airbyte_cdk-6.31.2.dev0.dist-info → airbyte_cdk-6.32.0.dist-info}/entry_points.txt +0 -0
@@ -19,7 +19,11 @@ from airbyte_cdk.sources.declarative.extractors import RecordSelector
19
19
  from airbyte_cdk.sources.declarative.extractors.record_filter import (
20
20
  ClientSideIncrementalRecordFilterDecorator,
21
21
  )
22
+ from airbyte_cdk.sources.declarative.incremental import ConcurrentPerPartitionCursor
22
23
  from airbyte_cdk.sources.declarative.incremental.datetime_based_cursor import DatetimeBasedCursor
24
+ from airbyte_cdk.sources.declarative.incremental.per_partition_with_global import (
25
+ PerPartitionWithGlobalCursor,
26
+ )
23
27
  from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
24
28
  from airbyte_cdk.sources.declarative.manifest_declarative_source import ManifestDeclarativeSource
25
29
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
@@ -31,8 +35,9 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
31
35
  from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import (
32
36
  ModelToComponentFactory,
33
37
  )
38
+ from airbyte_cdk.sources.declarative.partition_routers import AsyncJobPartitionRouter
34
39
  from airbyte_cdk.sources.declarative.requesters import HttpRequester
35
- from airbyte_cdk.sources.declarative.retrievers import SimpleRetriever
40
+ from airbyte_cdk.sources.declarative.retrievers import AsyncRetriever, Retriever, SimpleRetriever
36
41
  from airbyte_cdk.sources.declarative.stream_slicers.declarative_partition_generator import (
37
42
  DeclarativePartitionFactory,
38
43
  StreamSlicerPartitionGenerator,
@@ -45,7 +50,7 @@ from airbyte_cdk.sources.streams.concurrent.abstract_stream import AbstractStrea
45
50
  from airbyte_cdk.sources.streams.concurrent.availability_strategy import (
46
51
  AlwaysAvailableAvailabilityStrategy,
47
52
  )
48
- from airbyte_cdk.sources.streams.concurrent.cursor import FinalStateCursor
53
+ from airbyte_cdk.sources.streams.concurrent.cursor import ConcurrentCursor, FinalStateCursor
49
54
  from airbyte_cdk.sources.streams.concurrent.default_stream import DefaultStream
50
55
  from airbyte_cdk.sources.streams.concurrent.helpers import get_primary_key_from_stream
51
56
 
@@ -66,6 +71,10 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
66
71
  component_factory: Optional[ModelToComponentFactory] = None,
67
72
  **kwargs: Any,
68
73
  ) -> None:
74
+ # todo: We could remove state from initialization. Now that streams are grouped during the read(), a source
75
+ # no longer needs to store the original incoming state. But maybe there's an edge case?
76
+ self._connector_state_manager = ConnectorStateManager(state=state) # type: ignore # state is always in the form of List[AirbyteStateMessage]. The ConnectorStateManager should use generics, but this can be done later
77
+
69
78
  # To reduce the complexity of the concurrent framework, we are not enabling RFR with synthetic
70
79
  # cursors. We do this by no longer automatically instantiating RFR cursors when converting
71
80
  # the declarative models into runtime components. Concurrent sources will continue to checkpoint
@@ -73,19 +82,17 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
73
82
  component_factory = component_factory or ModelToComponentFactory(
74
83
  emit_connector_builder_messages=emit_connector_builder_messages,
75
84
  disable_resumable_full_refresh=True,
85
+ connector_state_manager=self._connector_state_manager,
76
86
  )
77
87
 
78
88
  super().__init__(
79
89
  source_config=source_config,
90
+ config=config,
80
91
  debug=debug,
81
92
  emit_connector_builder_messages=emit_connector_builder_messages,
82
93
  component_factory=component_factory,
83
94
  )
84
95
 
85
- # todo: We could remove state from initialization. Now that streams are grouped during the read(), a source
86
- # no longer needs to store the original incoming state. But maybe there's an edge case?
87
- self._state = state
88
-
89
96
  concurrency_level_from_manifest = self._source_config.get("concurrency_level")
90
97
  if concurrency_level_from_manifest:
91
98
  concurrency_level_component = self._constructor.create_component(
@@ -175,8 +182,6 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
175
182
  concurrent_streams: List[AbstractStream] = []
176
183
  synchronous_streams: List[Stream] = []
177
184
 
178
- state_manager = ConnectorStateManager(state=self._state) # type: ignore # state is always in the form of List[AirbyteStateMessage]. The ConnectorStateManager should use generics, but this can be done later
179
-
180
185
  # Combine streams and dynamic_streams. Note: both cannot be empty at the same time,
181
186
  # and this is validated during the initialization of the source.
182
187
  streams = self._stream_configs(self._source_config) + self._dynamic_stream_configs(
@@ -216,45 +221,52 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
216
221
  if self._is_datetime_incremental_without_partition_routing(
217
222
  declarative_stream, incremental_sync_component_definition
218
223
  ):
219
- stream_state = state_manager.get_stream_state(
224
+ stream_state = self._connector_state_manager.get_stream_state(
220
225
  stream_name=declarative_stream.name, namespace=declarative_stream.namespace
221
226
  )
222
227
 
223
- cursor = self._constructor.create_concurrent_cursor_from_datetime_based_cursor(
224
- state_manager=state_manager,
225
- model_type=DatetimeBasedCursorModel,
226
- component_definition=incremental_sync_component_definition, # type: ignore # Not None because of the if condition above
227
- stream_name=declarative_stream.name,
228
- stream_namespace=declarative_stream.namespace,
229
- config=config or {},
230
- stream_state=stream_state,
231
- )
228
+ retriever = self._get_retriever(declarative_stream, stream_state)
232
229
 
233
- retriever = declarative_stream.retriever
234
-
235
- # This is an optimization so that we don't invoke any cursor or state management flows within the
236
- # low-code framework because state management is handled through the ConcurrentCursor.
237
- if declarative_stream and isinstance(retriever, SimpleRetriever):
238
- # Also a temporary hack. In the legacy Stream implementation, as part of the read,
239
- # set_initial_state() is called to instantiate incoming state on the cursor. Although we no
240
- # longer rely on the legacy low-code cursor for concurrent checkpointing, low-code components
241
- # like StopConditionPaginationStrategyDecorator and ClientSideIncrementalRecordFilterDecorator
242
- # still rely on a DatetimeBasedCursor that is properly initialized with state.
243
- if retriever.cursor:
244
- retriever.cursor.set_initial_state(stream_state=stream_state)
245
- # We zero it out here, but since this is a cursor reference, the state is still properly
246
- # instantiated for the other components that reference it
247
- retriever.cursor = None
230
+ if isinstance(declarative_stream.retriever, AsyncRetriever) and isinstance(
231
+ declarative_stream.retriever.stream_slicer, AsyncJobPartitionRouter
232
+ ):
233
+ cursor = declarative_stream.retriever.stream_slicer.stream_slicer
248
234
 
249
- partition_generator = StreamSlicerPartitionGenerator(
250
- DeclarativePartitionFactory(
251
- declarative_stream.name,
252
- declarative_stream.get_json_schema(),
253
- retriever,
254
- self.message_repository,
255
- ),
256
- cursor,
257
- )
235
+ if not isinstance(cursor, ConcurrentCursor | ConcurrentPerPartitionCursor):
236
+ # This should never happen since we instantiate ConcurrentCursor in
237
+ # model_to_component_factory.py
238
+ raise ValueError(
239
+ f"Expected AsyncJobPartitionRouter stream_slicer to be of type ConcurrentCursor, but received{cursor.__class__}"
240
+ )
241
+
242
+ partition_generator = StreamSlicerPartitionGenerator(
243
+ partition_factory=DeclarativePartitionFactory(
244
+ declarative_stream.name,
245
+ declarative_stream.get_json_schema(),
246
+ retriever,
247
+ self.message_repository,
248
+ ),
249
+ stream_slicer=declarative_stream.retriever.stream_slicer,
250
+ )
251
+ else:
252
+ cursor = (
253
+ self._constructor.create_concurrent_cursor_from_datetime_based_cursor(
254
+ model_type=DatetimeBasedCursorModel,
255
+ component_definition=incremental_sync_component_definition, # type: ignore # Not None because of the if condition above
256
+ stream_name=declarative_stream.name,
257
+ stream_namespace=declarative_stream.namespace,
258
+ config=config or {},
259
+ )
260
+ )
261
+ partition_generator = StreamSlicerPartitionGenerator(
262
+ partition_factory=DeclarativePartitionFactory(
263
+ declarative_stream.name,
264
+ declarative_stream.get_json_schema(),
265
+ retriever,
266
+ self.message_repository,
267
+ ),
268
+ stream_slicer=cursor,
269
+ )
258
270
 
259
271
  concurrent_streams.append(
260
272
  DefaultStream(
@@ -304,6 +316,60 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
304
316
  cursor=final_state_cursor,
305
317
  )
306
318
  )
319
+ elif (
320
+ incremental_sync_component_definition
321
+ and incremental_sync_component_definition.get("type", "")
322
+ == DatetimeBasedCursorModel.__name__
323
+ and self._stream_supports_concurrent_partition_processing(
324
+ declarative_stream=declarative_stream
325
+ )
326
+ and hasattr(declarative_stream.retriever, "stream_slicer")
327
+ and isinstance(
328
+ declarative_stream.retriever.stream_slicer, PerPartitionWithGlobalCursor
329
+ )
330
+ ):
331
+ stream_state = self._connector_state_manager.get_stream_state(
332
+ stream_name=declarative_stream.name, namespace=declarative_stream.namespace
333
+ )
334
+ partition_router = declarative_stream.retriever.stream_slicer._partition_router
335
+
336
+ perpartition_cursor = (
337
+ self._constructor.create_concurrent_cursor_from_perpartition_cursor(
338
+ state_manager=self._connector_state_manager,
339
+ model_type=DatetimeBasedCursorModel,
340
+ component_definition=incremental_sync_component_definition,
341
+ stream_name=declarative_stream.name,
342
+ stream_namespace=declarative_stream.namespace,
343
+ config=config or {},
344
+ stream_state=stream_state,
345
+ partition_router=partition_router,
346
+ )
347
+ )
348
+
349
+ retriever = self._get_retriever(declarative_stream, stream_state)
350
+
351
+ partition_generator = StreamSlicerPartitionGenerator(
352
+ DeclarativePartitionFactory(
353
+ declarative_stream.name,
354
+ declarative_stream.get_json_schema(),
355
+ retriever,
356
+ self.message_repository,
357
+ ),
358
+ perpartition_cursor,
359
+ )
360
+
361
+ concurrent_streams.append(
362
+ DefaultStream(
363
+ partition_generator=partition_generator,
364
+ name=declarative_stream.name,
365
+ json_schema=declarative_stream.get_json_schema(),
366
+ availability_strategy=AlwaysAvailableAvailabilityStrategy(),
367
+ primary_key=get_primary_key_from_stream(declarative_stream.primary_key),
368
+ cursor_field=perpartition_cursor.cursor_field.cursor_field_key,
369
+ logger=self.logger,
370
+ cursor=perpartition_cursor,
371
+ )
372
+ )
307
373
  else:
308
374
  synchronous_streams.append(declarative_stream)
309
375
  else:
@@ -325,7 +391,10 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
325
391
  declarative_stream=declarative_stream
326
392
  )
327
393
  and hasattr(declarative_stream.retriever, "stream_slicer")
328
- and isinstance(declarative_stream.retriever.stream_slicer, DatetimeBasedCursor)
394
+ and (
395
+ isinstance(declarative_stream.retriever.stream_slicer, DatetimeBasedCursor)
396
+ or isinstance(declarative_stream.retriever.stream_slicer, AsyncJobPartitionRouter)
397
+ )
329
398
  )
330
399
 
331
400
  def _stream_supports_concurrent_partition_processing(
@@ -394,6 +463,28 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
394
463
  return False
395
464
  return True
396
465
 
466
+ @staticmethod
467
+ def _get_retriever(
468
+ declarative_stream: DeclarativeStream, stream_state: Mapping[str, Any]
469
+ ) -> Retriever:
470
+ retriever = declarative_stream.retriever
471
+
472
+ # This is an optimization so that we don't invoke any cursor or state management flows within the
473
+ # low-code framework because state management is handled through the ConcurrentCursor.
474
+ if declarative_stream and isinstance(retriever, SimpleRetriever):
475
+ # Also a temporary hack. In the legacy Stream implementation, as part of the read,
476
+ # set_initial_state() is called to instantiate incoming state on the cursor. Although we no
477
+ # longer rely on the legacy low-code cursor for concurrent checkpointing, low-code components
478
+ # like StopConditionPaginationStrategyDecorator and ClientSideIncrementalRecordFilterDecorator
479
+ # still rely on a DatetimeBasedCursor that is properly initialized with state.
480
+ if retriever.cursor:
481
+ retriever.cursor.set_initial_state(stream_state=stream_state)
482
+ # We zero it out here, but since this is a cursor reference, the state is still properly
483
+ # instantiated for the other components that reference it
484
+ retriever.cursor = None
485
+
486
+ return retriever
487
+
397
488
  @staticmethod
398
489
  def _select_streams(
399
490
  streams: List[AbstractStream], configured_catalog: ConfiguredAirbyteCatalog
@@ -320,6 +320,11 @@ definitions:
320
320
  title: Stream Count
321
321
  description: Numbers of the streams to try reading from when running a check operation.
322
322
  type: integer
323
+ use_check_availability:
324
+ title: Use Check Availability
325
+ description: Enables stream check availability. This field is automatically set by the CDK.
326
+ type: boolean
327
+ default: true
323
328
  CompositeErrorHandler:
324
329
  title: Composite Error Handler
325
330
  description: Error handler that sequentially iterates over a list of error handlers.
@@ -784,6 +789,29 @@ definitions:
784
789
  type:
785
790
  type: string
786
791
  enum: [DatetimeBasedCursor]
792
+ clamping:
793
+ title: Date Range Clamping
794
+ description: This option is used to adjust the upper and lower boundaries of each datetime window to beginning and end of the provided target period (day, week, month)
795
+ type: object
796
+ required:
797
+ - target
798
+ properties:
799
+ target:
800
+ title: Target
801
+ description: The period of time that datetime windows will be clamped by
802
+ # This should ideally be an enum. However, we don't use an enum because we want to allow for connectors
803
+ # to support interpolation on the connector config to get the target which is an arbitrary string
804
+ type: string
805
+ interpolation_context:
806
+ - config
807
+ examples:
808
+ - "DAY"
809
+ - "WEEK"
810
+ - "MONTH"
811
+ - "{{ config['target'] }}"
812
+ target_details:
813
+ type: object
814
+ additionalProperties: true
787
815
  cursor_field:
788
816
  title: Cursor Field
789
817
  description: The location of the value on a record that will be used as a bookmark during sync. To ensure no data loss, the API must return records in ascending order based on the cursor field. Nested fields are not supported, so the field must be at the top level of the record. You can use a combination of Add Field and Remove Field transformations to move the nested field to the top.
@@ -1058,8 +1086,6 @@ definitions:
1058
1086
  type: object
1059
1087
  required:
1060
1088
  - type
1061
- - client_id
1062
- - client_secret
1063
1089
  properties:
1064
1090
  type:
1065
1091
  type: string
@@ -1254,6 +1280,15 @@ definitions:
1254
1280
  default: []
1255
1281
  examples:
1256
1282
  - ["invalid_grant", "invalid_permissions"]
1283
+ profile_assertion:
1284
+ title: Profile Assertion
1285
+ description: The authenticator being used to authenticate the client authenticator.
1286
+ "$ref": "#/definitions/JwtAuthenticator"
1287
+ use_profile_assertion:
1288
+ title: Use Profile Assertion
1289
+ description: Enable using profile assertion as a flow for OAuth authorization.
1290
+ type: boolean
1291
+ default: false
1257
1292
  $parameters:
1258
1293
  type: object
1259
1294
  additionalProperties: true
@@ -1770,6 +1805,19 @@ definitions:
1770
1805
  $parameters:
1771
1806
  type: object
1772
1807
  additionalProperties: true
1808
+ ComplexFieldType:
1809
+ title: Schema Field Type
1810
+ description: (This component is experimental. Use at your own risk.) Represents a complex field type.
1811
+ type: object
1812
+ required:
1813
+ - field_type
1814
+ properties:
1815
+ field_type:
1816
+ type: string
1817
+ items:
1818
+ anyOf:
1819
+ - type: string
1820
+ - "$ref": "#/definitions/ComplexFieldType"
1773
1821
  TypesMap:
1774
1822
  title: Types Map
1775
1823
  description: (This component is experimental. Use at your own risk.) Represents a mapping between a current type and its corresponding target type.
@@ -1784,6 +1832,7 @@ definitions:
1784
1832
  - type: array
1785
1833
  items:
1786
1834
  type: string
1835
+ - "$ref": "#/definitions/ComplexFieldType"
1787
1836
  current_type:
1788
1837
  anyOf:
1789
1838
  - type: string
@@ -2798,35 +2847,25 @@ definitions:
2798
2847
  enum: [RequestPath]
2799
2848
  RequestOption:
2800
2849
  title: Request Option
2801
- description: Specifies the key field or path and where in the request a component's value should be injected.
2850
+ description: Specifies the key field and where in the request a component's value should be injected.
2802
2851
  type: object
2803
2852
  required:
2804
2853
  - type
2854
+ - field_name
2805
2855
  - inject_into
2806
2856
  properties:
2807
2857
  type:
2808
2858
  type: string
2809
2859
  enum: [RequestOption]
2810
2860
  field_name:
2811
- title: Field Name
2812
- description: Configures which key should be used in the location that the descriptor is being injected into. We hope to eventually deprecate this field in favor of `field_path` for all request_options, but must currently maintain it for backwards compatibility in the Builder.
2861
+ title: Request Option
2862
+ description: Configures which key should be used in the location that the descriptor is being injected into
2813
2863
  type: string
2814
2864
  examples:
2815
2865
  - segment_id
2816
2866
  interpolation_context:
2817
2867
  - config
2818
2868
  - parameters
2819
- field_path:
2820
- title: Field Path
2821
- description: Configures a path to be used for nested structures in JSON body requests (e.g. GraphQL queries)
2822
- type: array
2823
- items:
2824
- type: string
2825
- examples:
2826
- - ["data", "viewer", "id"]
2827
- interpolation_context:
2828
- - config
2829
- - parameters
2830
2869
  inject_into:
2831
2870
  title: Inject Into
2832
2871
  description: Configures where the descriptor should be set on the HTTP requests. Note that request parameters that are already encoded in the URL path will not be duplicated.
@@ -138,7 +138,9 @@ class DeclarativeStream(Stream):
138
138
  """
139
139
  :param: stream_state We knowingly avoid using stream_state as we want cursors to manage their own state.
140
140
  """
141
- if stream_slice is None or stream_slice == {}:
141
+ if stream_slice is None or (
142
+ not isinstance(stream_slice, StreamSlice) and stream_slice == {}
143
+ ):
142
144
  # As the parameter is Optional, many would just call `read_records(sync_mode)` during testing without specifying the field
143
145
  # As part of the declarative model without custom components, this should never happen as the CDK would wire up a
144
146
  # SinglePartitionRouter that would create this StreamSlice properly
@@ -59,13 +59,11 @@ class ClientSideIncrementalRecordFilterDecorator(RecordFilter):
59
59
 
60
60
  def __init__(
61
61
  self,
62
- date_time_based_cursor: DatetimeBasedCursor,
63
- substream_cursor: Optional[Union[PerPartitionWithGlobalCursor, GlobalSubstreamCursor]],
62
+ cursor: Union[DatetimeBasedCursor, PerPartitionWithGlobalCursor, GlobalSubstreamCursor],
64
63
  **kwargs: Any,
65
64
  ):
66
65
  super().__init__(**kwargs)
67
- self._date_time_based_cursor = date_time_based_cursor
68
- self._substream_cursor = substream_cursor
66
+ self._cursor = cursor
69
67
 
70
68
  def filter_records(
71
69
  self,
@@ -77,7 +75,7 @@ class ClientSideIncrementalRecordFilterDecorator(RecordFilter):
77
75
  records = (
78
76
  record
79
77
  for record in records
80
- if (self._substream_cursor or self._date_time_based_cursor).should_be_synced(
78
+ if self._cursor.should_be_synced(
81
79
  # Record is created on the fly to align with cursors interface; stream name is ignored as we don't need it here
82
80
  # Record stream name is empty cause it is not used durig the filtering
83
81
  Record(data=record, associated_slice=stream_slice, stream_name="")
@@ -2,6 +2,10 @@
2
2
  # Copyright (c) 2022 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
+ from airbyte_cdk.sources.declarative.incremental.concurrent_partition_cursor import (
6
+ ConcurrentCursorFactory,
7
+ ConcurrentPerPartitionCursor,
8
+ )
5
9
  from airbyte_cdk.sources.declarative.incremental.datetime_based_cursor import DatetimeBasedCursor
6
10
  from airbyte_cdk.sources.declarative.incremental.declarative_cursor import DeclarativeCursor
7
11
  from airbyte_cdk.sources.declarative.incremental.global_substream_cursor import (
@@ -21,6 +25,8 @@ from airbyte_cdk.sources.declarative.incremental.resumable_full_refresh_cursor i
21
25
 
22
26
  __all__ = [
23
27
  "CursorFactory",
28
+ "ConcurrentCursorFactory",
29
+ "ConcurrentPerPartitionCursor",
24
30
  "DatetimeBasedCursor",
25
31
  "DeclarativeCursor",
26
32
  "GlobalSubstreamCursor",