airbyte-cdk 6.26.0.dev0__py3-none-any.whl → 6.26.0.dev2__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.
@@ -264,7 +264,10 @@ class ConcurrentPerPartitionCursor(Cursor):
264
264
  if not stream_state:
265
265
  return
266
266
 
267
- if self._PERPARTITION_STATE_KEY not in stream_state:
267
+ if (
268
+ self._PERPARTITION_STATE_KEY not in stream_state
269
+ and self._GLOBAL_STATE_KEY not in stream_state
270
+ ):
268
271
  # We assume that `stream_state` is in a global format that can be applied to all partitions.
269
272
  # Example: {"global_state_format_key": "global_state_format_value"}
270
273
  self._global_cursor = deepcopy(stream_state)
@@ -273,7 +276,7 @@ class ConcurrentPerPartitionCursor(Cursor):
273
276
  else:
274
277
  self._lookback_window = int(stream_state.get("lookback_window", 0))
275
278
 
276
- for state in stream_state[self._PERPARTITION_STATE_KEY]:
279
+ for state in stream_state.get(self._PERPARTITION_STATE_KEY, []):
277
280
  self._cursor_per_partition[self._to_partition_key(state["partition"])] = (
278
281
  self._create_cursor(state["cursor"])
279
282
  )
@@ -222,6 +222,8 @@ class PerPartitionCursor(DeclarativeCursor):
222
222
  next_page_token: Optional[Mapping[str, Any]] = None,
223
223
  ) -> Mapping[str, Any]:
224
224
  if stream_slice:
225
+ if self._to_partition_key(stream_slice.partition) not in self._cursor_per_partition:
226
+ self._create_cursor_for_partition(self._to_partition_key(stream_slice.partition))
225
227
  return self._partition_router.get_request_params( # type: ignore # this always returns a mapping
226
228
  stream_state=stream_state,
227
229
  stream_slice=StreamSlice(partition=stream_slice.partition, cursor_slice={}),
@@ -244,6 +246,8 @@ class PerPartitionCursor(DeclarativeCursor):
244
246
  next_page_token: Optional[Mapping[str, Any]] = None,
245
247
  ) -> Mapping[str, Any]:
246
248
  if stream_slice:
249
+ if self._to_partition_key(stream_slice.partition) not in self._cursor_per_partition:
250
+ self._create_cursor_for_partition(self._to_partition_key(stream_slice.partition))
247
251
  return self._partition_router.get_request_headers( # type: ignore # this always returns a mapping
248
252
  stream_state=stream_state,
249
253
  stream_slice=StreamSlice(partition=stream_slice.partition, cursor_slice={}),
@@ -266,6 +270,8 @@ class PerPartitionCursor(DeclarativeCursor):
266
270
  next_page_token: Optional[Mapping[str, Any]] = None,
267
271
  ) -> Union[Mapping[str, Any], str]:
268
272
  if stream_slice:
273
+ if self._to_partition_key(stream_slice.partition) not in self._cursor_per_partition:
274
+ self._create_cursor_for_partition(self._to_partition_key(stream_slice.partition))
269
275
  return self._partition_router.get_request_body_data( # type: ignore # this always returns a mapping
270
276
  stream_state=stream_state,
271
277
  stream_slice=StreamSlice(partition=stream_slice.partition, cursor_slice={}),
@@ -288,6 +294,8 @@ class PerPartitionCursor(DeclarativeCursor):
288
294
  next_page_token: Optional[Mapping[str, Any]] = None,
289
295
  ) -> Mapping[str, Any]:
290
296
  if stream_slice:
297
+ if self._to_partition_key(stream_slice.partition) not in self._cursor_per_partition:
298
+ self._create_cursor_for_partition(self._to_partition_key(stream_slice.partition))
291
299
  return self._partition_router.get_request_body_json( # type: ignore # this always returns a mapping
292
300
  stream_state=stream_state,
293
301
  stream_slice=StreamSlice(partition=stream_slice.partition, cursor_slice={}),
@@ -303,21 +311,6 @@ class PerPartitionCursor(DeclarativeCursor):
303
311
  raise ValueError("A partition needs to be provided in order to get request body json")
304
312
 
305
313
  def should_be_synced(self, record: Record) -> bool:
306
- if (
307
- record.associated_slice
308
- and self._to_partition_key(record.associated_slice.partition)
309
- not in self._cursor_per_partition
310
- ):
311
- partition_state = (
312
- self._state_to_migrate_from
313
- if self._state_to_migrate_from
314
- else self._NO_CURSOR_STATE
315
- )
316
- cursor = self._create_cursor(partition_state)
317
-
318
- self._cursor_per_partition[
319
- self._to_partition_key(record.associated_slice.partition)
320
- ] = cursor
321
314
  return self._get_cursor(record).should_be_synced(
322
315
  self._convert_record_to_cursor_record(record)
323
316
  )
@@ -356,8 +349,32 @@ class PerPartitionCursor(DeclarativeCursor):
356
349
  )
357
350
  partition_key = self._to_partition_key(record.associated_slice.partition)
358
351
  if partition_key not in self._cursor_per_partition:
359
- raise ValueError(
360
- "Invalid state as stream slices that are emitted should refer to an existing cursor"
361
- )
352
+ self._create_cursor_for_partition(partition_key)
362
353
  cursor = self._cursor_per_partition[partition_key]
363
354
  return cursor
355
+
356
+ def _create_cursor_for_partition(self, partition_key: str) -> None:
357
+ """
358
+ Dynamically creates and initializes a cursor for the specified partition.
359
+
360
+ This method is required for `ConcurrentPerPartitionCursor`. For concurrent cursors,
361
+ stream_slices is executed only for the concurrent cursor, so cursors per partition
362
+ are not created for the declarative cursor. This method ensures that a cursor is available
363
+ to create requests for the specified partition. The cursor is initialized
364
+ with the per-partition state if present in the initial state, or with the global state
365
+ adjusted by the lookback window, or with the state to migrate from.
366
+
367
+ Note:
368
+ This is a temporary workaround and should be removed once the declarative cursor
369
+ is decoupled from the concurrent cursor implementation.
370
+
371
+ Args:
372
+ partition_key (str): The unique identifier for the partition for which the cursor
373
+ needs to be created.
374
+ """
375
+ partition_state = (
376
+ self._state_to_migrate_from if self._state_to_migrate_from else self._NO_CURSOR_STATE
377
+ )
378
+ cursor = self._create_cursor(partition_state)
379
+
380
+ self._cursor_per_partition[partition_key] = cursor
@@ -2407,7 +2407,7 @@ class ModelToComponentFactory:
2407
2407
  if (
2408
2408
  not isinstance(stream_slicer, DatetimeBasedCursor)
2409
2409
  or type(stream_slicer) is not DatetimeBasedCursor
2410
- ) and not isinstance(stream_slicer, PerPartitionWithGlobalCursor):
2410
+ ):
2411
2411
  # Many of the custom component implementations of DatetimeBasedCursor override get_request_params() (or other methods).
2412
2412
  # Because we're decoupling RequestOptionsProvider from the Cursor, custom components will eventually need to reimplement
2413
2413
  # their own RequestOptionsProvider. However, right now the existing StreamSlicer/Cursor still can act as the SimpleRetriever's
@@ -295,24 +295,58 @@ class SubstreamPartitionRouter(PartitionRouter):
295
295
  return
296
296
 
297
297
  if not parent_state and incremental_dependency:
298
- # Attempt to retrieve child state
299
- substream_state = list(stream_state.values())
300
- substream_state = substream_state[0] if substream_state else {} # type: ignore [assignment] # Incorrect type for assignment
301
- parent_state = {}
302
-
303
- # Copy child state to parent streams with incremental dependencies
304
- if substream_state:
305
- for parent_config in self.parent_stream_configs:
306
- if parent_config.incremental_dependency:
307
- parent_state[parent_config.stream.name] = {
308
- parent_config.stream.cursor_field: substream_state
309
- }
298
+ # Migrate child state to parent state format
299
+ parent_state = self._migrate_child_state_to_parent_state(stream_state)
310
300
 
311
301
  # Set state for each parent stream with an incremental dependency
312
302
  for parent_config in self.parent_stream_configs:
313
303
  if parent_config.incremental_dependency:
314
304
  parent_config.stream.state = parent_state.get(parent_config.stream.name, {})
315
305
 
306
+ def _migrate_child_state_to_parent_state(self, stream_state: StreamState) -> StreamState:
307
+ """
308
+ Migrate the child stream state to the parent stream's state format.
309
+
310
+ This method converts the global or child state into a format compatible with parent
311
+ streams. The migration occurs only for parent streams with incremental dependencies.
312
+ The method filters out per-partition states and retains only the global state in the
313
+ format `{cursor_field: cursor_value}`.
314
+
315
+ Args:
316
+ stream_state (StreamState): The state to migrate. Expected formats include:
317
+ - {"updated_at": "2023-05-27T00:00:00Z"}
318
+ - {"states": [...] } (ignored during migration)
319
+
320
+ Returns:
321
+ StreamState: A migrated state for parent streams in the format:
322
+ {
323
+ "parent_stream_name": {"parent_stream_cursor": "2023-05-27T00:00:00Z"}
324
+ }
325
+
326
+ Example:
327
+ Input: {"updated_at": "2023-05-27T00:00:00Z"}
328
+ Output: {
329
+ "parent_stream_name": {"parent_stream_cursor": "2023-05-27T00:00:00Z"}
330
+ }
331
+ """
332
+ substream_state_values = list(stream_state.values())
333
+ substream_state = substream_state_values[0] if substream_state_values else {}
334
+
335
+ # Ignore per-partition states or invalid formats
336
+ if isinstance(substream_state, (list, dict)) or len(substream_state_values) != 1:
337
+ return {}
338
+
339
+ # Copy child state to parent streams with incremental dependencies
340
+ parent_state = {}
341
+ if substream_state:
342
+ for parent_config in self.parent_stream_configs:
343
+ if parent_config.incremental_dependency:
344
+ parent_state[parent_config.stream.name] = {
345
+ parent_config.stream.cursor_field: substream_state
346
+ }
347
+
348
+ return parent_state
349
+
316
350
  def get_stream_state(self) -> Optional[Mapping[str, StreamState]]:
317
351
  """
318
352
  Get the state of the parent streams.
@@ -154,8 +154,9 @@ class DynamicSchemaLoader(SchemaLoader):
154
154
  transformed_properties = self._transform(properties, {})
155
155
 
156
156
  return {
157
- "$schema": "http://json-schema.org/draft-07/schema#",
157
+ "$schema": "https://json-schema.org/draft-07/schema#",
158
158
  "type": "object",
159
+ "additionalProperties": True,
159
160
  "properties": transformed_properties,
160
161
  }
161
162
 
@@ -220,25 +221,17 @@ class DynamicSchemaLoader(SchemaLoader):
220
221
  )
221
222
 
222
223
  def _resolve_complex_type(self, complex_type: ComplexFieldType) -> Mapping[str, Any]:
223
- types = [complex_type]
224
- resolved_type: MutableMapping[str, Any] = {}
225
-
226
- while types:
227
- current_type = types.pop()
228
- if not current_type.items:
229
- resolved_type = self._get_airbyte_type(current_type.field_type)
230
- else:
231
- field_type = self._get_airbyte_type(current_type.field_type)
232
-
233
- if isinstance(current_type.items, str):
234
- items_type = current_type.items
235
- else:
236
- types.append(current_type.items)
237
- continue # Skip the following lines until the stack is resolved
238
- field_type["items"] = self._get_airbyte_type(items_type)
239
- resolved_type = field_type
240
-
241
- return resolved_type
224
+ if not complex_type.items:
225
+ return self._get_airbyte_type(complex_type.field_type)
226
+
227
+ field_type = self._get_airbyte_type(complex_type.field_type)
228
+ field_type["items"] = (
229
+ self._get_airbyte_type(complex_type.items)
230
+ if isinstance(complex_type.items, str)
231
+ else self._resolve_complex_type(complex_type.items)
232
+ )
233
+
234
+ return field_type
242
235
 
243
236
  def _replace_type_if_not_valid(
244
237
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: airbyte-cdk
3
- Version: 6.26.0.dev0
3
+ Version: 6.26.0.dev2
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  License: MIT
6
6
  Keywords: airbyte,connector-development-kit,cdk
@@ -88,11 +88,11 @@ airbyte_cdk/sources/declarative/extractors/record_selector.py,sha256=tjNwcURmlyD
88
88
  airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py,sha256=LhqGDfX06_dDYLKsIVnwQ_nAWCln-v8PV7Wgt_QVeTI,6533
89
89
  airbyte_cdk/sources/declarative/extractors/type_transformer.py,sha256=d6Y2Rfg8pMVEEnHllfVksWZdNVOU55yk34O03dP9muY,1626
90
90
  airbyte_cdk/sources/declarative/incremental/__init__.py,sha256=U1oZKtBaEC6IACmvziY9Wzg7Z8EgF4ZuR7NwvjlB_Sk,1255
91
- airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=1_5XVLJdJXMAA0gJbWt4pqD0xGgyBNSZ06JHCgpvo2c,14501
91
+ airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=0Ey_Rb8mf7BrhQTXLTMFRhe35-RoBw62OecKyGAyjSw,14592
92
92
  airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=_UzUnSIUsDbRgbFTXgSyZEFb4ws-KdhdQPWO8mFbV7U,22028
93
93
  airbyte_cdk/sources/declarative/incremental/declarative_cursor.py,sha256=5Bhw9VRPyIuCaD0wmmq_L3DZsa-rJgtKSEUzSd8YYD0,536
94
94
  airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py,sha256=9HO-QbL9akvjq2NP7l498RwLA4iQZlBMQW1tZbt34I8,15943
95
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py,sha256=_FSJjAwL4Zu-i2CngnhTtx8j-NPVSBKj5LwDSPta3Cg,16305
95
+ airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py,sha256=9IAJTCiRUXvhFFz-IhZtYh_KfAjLHqthsYf2jErQRls,17728
96
96
  airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py,sha256=2YBOA2NnwAeIKlIhSwUB_W-FaGnPcmrG_liY7b4mV2Y,8365
97
97
  airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py,sha256=10LFv1QPM-agVKl6eaANmEBOfd7gZgBrkoTcMggsieQ,4809
98
98
  airbyte_cdk/sources/declarative/interpolation/__init__.py,sha256=tjUJkn3B-iZ-p7RP2c3dVZejrGiQeooGmS5ibWTuUL4,437
@@ -115,14 +115,14 @@ airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=958MMX6_Z
115
115
  airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=Rir9_z3Kcd5Es0-LChrzk-0qubAsiK_RSEnLmK2OXm8,553
116
116
  airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=CXwTfD3wSQq3okcqwigpprbHhSURUokh4GK2OmOyKC8,9132
117
117
  airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=IWUOdF03o-aQn0Occo1BJCxU0Pz-QILk5L67nzw2thw,6803
118
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=mf0Iwq0VtetpC2HYq93_hZUOuFhWUB7aPjacC6RarYk,122981
118
+ airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=zfWJLlopJklDK1xvoUy2qMFcnSklmQ7wwEbdWVxYlw0,122917
119
119
  airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=HJ-Syp3p7RpyR_OK0X_a2kSyISfu3W-PKrRI16iY0a8,957
120
120
  airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py,sha256=n82J15S8bjeMZ5uROu--P3hnbQoxkY5v7RPHYx7g7ro,2929
121
121
  airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
122
122
  airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha256=t7pRdFWfFWJtQQG19c9PVeMODyO2BknRTakpM5U9N-8,4844
123
123
  airbyte_cdk/sources/declarative/partition_routers/partition_router.py,sha256=YyEIzdmLd1FjbVP3QbQ2VFCLW_P-OGbVh6VpZShp54k,2218
124
124
  airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py,sha256=SKzKjSyfccq4dxGIh-J6ejrgkCHzaiTIazmbmeQiRD4,1942
125
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=5bgXoJfBg_6i53krQMptAGb50XB5XoVfqQxKQhlLtBA,15383
125
+ airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=pEz-P6D5TGtP4isNfmtakgKD95PqMLo6fasCVLIguWk,16760
126
126
  airbyte_cdk/sources/declarative/requesters/README.md,sha256=eL1I4iLkxaw7hJi9S9d18_XcRl-R8lUSjqBVJJzvXmg,2656
127
127
  airbyte_cdk/sources/declarative/requesters/__init__.py,sha256=d7a3OoHbqaJDyyPli3nqqJ2yAW_SLX6XDaBAKOwvpxw,364
128
128
  airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py,sha256=SkEDcJxlT1683rNx93K9whoS0OyUukkuOfToGtgpF58,776
@@ -170,7 +170,7 @@ airbyte_cdk/sources/declarative/retrievers/retriever.py,sha256=XPLs593Xv8c5cKMc3
170
170
  airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=kgnhVQxRlFqJs2-rDu2-QH-p-GzQU3nKmSp6_aq8u0s,24550
171
171
  airbyte_cdk/sources/declarative/schema/__init__.py,sha256=xU45UvM5O4c1PSM13UHpCdh5hpW3HXy9vRRGEiAC1rg,795
172
172
  airbyte_cdk/sources/declarative/schema/default_schema_loader.py,sha256=KTACrIE23a83wsm3Rd9Eb4K6-20lrGqYxTHNp9yxsso,1820
173
- airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py,sha256=QG0ApZFFPVZyG8ZsdVJ5vx1vrPb7QTUdBJY8VJIaPlQ,11022
173
+ airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py,sha256=d8tfDiDcJiunvN_Yalyfx5ISY5A-iIW3HbPwX2Hagh4,10702
174
174
  airbyte_cdk/sources/declarative/schema/inline_schema_loader.py,sha256=bVETE10hRsatRJq3R3BeyRR0wIoK3gcP1gcpVRQ_P5U,464
175
175
  airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py,sha256=5Wl-fqW-pVf_dxJ4yGHMAFfC4JjKHYJhqFJT1xA57F4,4177
176
176
  airbyte_cdk/sources/declarative/schema/schema_loader.py,sha256=kjt8v0N5wWKA5zyLnrDLxf1PJKdUqvQq2RVnAOAzNSY,379
@@ -350,8 +350,8 @@ airbyte_cdk/utils/slice_hasher.py,sha256=-pHexlNYoWYPnXNH-M7HEbjmeJe9Zk7SJijdQ7d
350
350
  airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
351
351
  airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
352
352
  airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
353
- airbyte_cdk-6.26.0.dev0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
354
- airbyte_cdk-6.26.0.dev0.dist-info/METADATA,sha256=g4Da91E4C4Ye-yiU-Rmg7E52t4O0pHx_w4KVJ2meuBc,6001
355
- airbyte_cdk-6.26.0.dev0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
356
- airbyte_cdk-6.26.0.dev0.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
357
- airbyte_cdk-6.26.0.dev0.dist-info/RECORD,,
353
+ airbyte_cdk-6.26.0.dev2.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
354
+ airbyte_cdk-6.26.0.dev2.dist-info/METADATA,sha256=AHdZ_4ifgRo92ehCzwq8zihpxL4Sg-1mPR-RYovPBkk,6001
355
+ airbyte_cdk-6.26.0.dev2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
356
+ airbyte_cdk-6.26.0.dev2.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
357
+ airbyte_cdk-6.26.0.dev2.dist-info/RECORD,,