airbyte-cdk 6.8.1rc8__py3-none-any.whl → 6.8.1rc10__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 (74) hide show
  1. airbyte_cdk/cli/source_declarative_manifest/_run.py +11 -5
  2. airbyte_cdk/config_observation.py +1 -1
  3. airbyte_cdk/connector_builder/main.py +1 -1
  4. airbyte_cdk/connector_builder/message_grouper.py +10 -10
  5. airbyte_cdk/destinations/destination.py +1 -1
  6. airbyte_cdk/destinations/vector_db_based/embedder.py +2 -2
  7. airbyte_cdk/destinations/vector_db_based/writer.py +12 -4
  8. airbyte_cdk/entrypoint.py +7 -6
  9. airbyte_cdk/logger.py +2 -2
  10. airbyte_cdk/sources/abstract_source.py +1 -1
  11. airbyte_cdk/sources/config.py +1 -1
  12. airbyte_cdk/sources/connector_state_manager.py +9 -4
  13. airbyte_cdk/sources/declarative/auth/oauth.py +1 -1
  14. airbyte_cdk/sources/declarative/auth/selective_authenticator.py +6 -1
  15. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +28 -42
  16. airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +10 -4
  17. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +116 -19
  18. airbyte_cdk/sources/declarative/decoders/noop_decoder.py +4 -1
  19. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +8 -6
  20. airbyte_cdk/sources/declarative/interpolation/jinja.py +35 -36
  21. airbyte_cdk/sources/declarative/interpolation/macros.py +1 -1
  22. airbyte_cdk/sources/declarative/manifest_declarative_source.py +53 -2
  23. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +95 -2
  24. airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +6 -0
  25. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +100 -27
  26. airbyte_cdk/sources/declarative/partition_routers/__init__.py +2 -1
  27. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +13 -7
  28. airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +1 -1
  29. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +8 -6
  30. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +1 -1
  31. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +2 -2
  32. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +1 -1
  33. airbyte_cdk/sources/declarative/resolvers/__init__.py +13 -0
  34. airbyte_cdk/sources/declarative/resolvers/components_resolver.py +55 -0
  35. airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py +106 -0
  36. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +5 -2
  37. airbyte_cdk/sources/declarative/spec/spec.py +1 -1
  38. airbyte_cdk/sources/embedded/base_integration.py +3 -2
  39. airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +12 -4
  40. airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +18 -7
  41. airbyte_cdk/sources/file_based/file_types/avro_parser.py +14 -11
  42. airbyte_cdk/sources/file_based/file_types/csv_parser.py +3 -3
  43. airbyte_cdk/sources/file_based/file_types/excel_parser.py +11 -5
  44. airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +1 -1
  45. airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +2 -2
  46. airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +6 -3
  47. airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +1 -1
  48. airbyte_cdk/sources/http_logger.py +3 -3
  49. airbyte_cdk/sources/streams/concurrent/abstract_stream.py +5 -2
  50. airbyte_cdk/sources/streams/concurrent/adapters.py +6 -3
  51. airbyte_cdk/sources/streams/concurrent/availability_strategy.py +9 -3
  52. airbyte_cdk/sources/streams/concurrent/cursor.py +1 -1
  53. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +2 -2
  54. airbyte_cdk/sources/streams/core.py +17 -14
  55. airbyte_cdk/sources/streams/http/http.py +19 -19
  56. airbyte_cdk/sources/streams/http/http_client.py +4 -48
  57. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +2 -1
  58. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +62 -33
  59. airbyte_cdk/sources/utils/record_helper.py +1 -1
  60. airbyte_cdk/sources/utils/schema_helpers.py +1 -1
  61. airbyte_cdk/sources/utils/transform.py +34 -15
  62. airbyte_cdk/test/entrypoint_wrapper.py +11 -6
  63. airbyte_cdk/test/mock_http/response_builder.py +1 -1
  64. airbyte_cdk/utils/airbyte_secrets_utils.py +1 -1
  65. airbyte_cdk/utils/event_timing.py +10 -10
  66. airbyte_cdk/utils/message_utils.py +4 -3
  67. airbyte_cdk/utils/spec_schema_transformations.py +3 -2
  68. airbyte_cdk/utils/traced_exception.py +14 -12
  69. airbyte_cdk-6.8.1rc10.dist-info/METADATA +111 -0
  70. {airbyte_cdk-6.8.1rc8.dist-info → airbyte_cdk-6.8.1rc10.dist-info}/RECORD +73 -70
  71. airbyte_cdk-6.8.1rc8.dist-info/METADATA +0 -307
  72. {airbyte_cdk-6.8.1rc8.dist-info → airbyte_cdk-6.8.1rc10.dist-info}/LICENSE.txt +0 -0
  73. {airbyte_cdk-6.8.1rc8.dist-info → airbyte_cdk-6.8.1rc10.dist-info}/WHEEL +0 -0
  74. {airbyte_cdk-6.8.1rc8.dist-info → airbyte_cdk-6.8.1rc10.dist-info}/entry_points.txt +0 -0
@@ -119,6 +119,9 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
119
119
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
120
120
  CheckStream as CheckStreamModel,
121
121
  )
122
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
123
+ ComponentMappingDefinition as ComponentMappingDefinitionModel,
124
+ )
122
125
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
123
126
  CompositeErrorHandler as CompositeErrorHandlerModel,
124
127
  )
@@ -191,6 +194,9 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
191
194
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
192
195
  GzipJsonDecoder as GzipJsonDecoderModel,
193
196
  )
197
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
198
+ HttpComponentsResolver as HttpComponentsResolverModel,
199
+ )
194
200
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
195
201
  HttpRequester as HttpRequesterModel,
196
202
  )
@@ -298,6 +304,7 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
298
304
  from airbyte_cdk.sources.declarative.partition_routers import (
299
305
  CartesianProductStreamSlicer,
300
306
  ListPartitionRouter,
307
+ PartitionRouter,
301
308
  SinglePartitionRouter,
302
309
  SubstreamPartitionRouter,
303
310
  )
@@ -338,6 +345,10 @@ from airbyte_cdk.sources.declarative.requesters.request_options import (
338
345
  )
339
346
  from airbyte_cdk.sources.declarative.requesters.request_path import RequestPath
340
347
  from airbyte_cdk.sources.declarative.requesters.requester import HttpMethod
348
+ from airbyte_cdk.sources.declarative.resolvers import (
349
+ ComponentMappingDefinition,
350
+ HttpComponentsResolver,
351
+ )
341
352
  from airbyte_cdk.sources.declarative.retrievers import (
342
353
  AsyncRetriever,
343
354
  SimpleRetriever,
@@ -396,7 +407,7 @@ class ModelToComponentFactory:
396
407
  self._disable_retries = disable_retries
397
408
  self._disable_cache = disable_cache
398
409
  self._disable_resumable_full_refresh = disable_resumable_full_refresh
399
- self._message_repository = message_repository or InMemoryMessageRepository( # type: ignore
410
+ self._message_repository = message_repository or InMemoryMessageRepository(
400
411
  self._evaluate_log_level(emit_connector_builder_messages)
401
412
  )
402
413
 
@@ -467,6 +478,8 @@ class ModelToComponentFactory:
467
478
  WaitTimeFromHeaderModel: self.create_wait_time_from_header,
468
479
  WaitUntilTimeFromHeaderModel: self.create_wait_until_time_from_header,
469
480
  AsyncRetrieverModel: self.create_async_retriever,
481
+ HttpComponentsResolverModel: self.create_http_components_resolver,
482
+ ComponentMappingDefinitionModel: self.create_components_mapping_definition,
470
483
  }
471
484
 
472
485
  # Needed for the case where we need to perform a second parse on the fields of a custom component
@@ -644,7 +657,7 @@ class ModelToComponentFactory:
644
657
  declarative_stream.incremental_sync, # type: ignore # was already checked. Migration can be applied only to incremental streams.
645
658
  config,
646
659
  declarative_stream.parameters, # type: ignore # different type is expected here Mapping[str, Any], got Dict[str, Any]
647
- ) # type: ignore # The retriever type was already checked
660
+ )
648
661
 
649
662
  def create_session_token_authenticator(
650
663
  self, model: SessionTokenAuthenticatorModel, config: Config, name: str, **kwargs: Any
@@ -674,7 +687,7 @@ class ModelToComponentFactory:
674
687
  return ModelToComponentFactory.create_bearer_authenticator(
675
688
  BearerAuthenticatorModel(type="BearerAuthenticator", api_token=""), # type: ignore # $parameters has a default value
676
689
  config,
677
- token_provider=token_provider, # type: ignore # $parameters defaults to None
690
+ token_provider=token_provider,
678
691
  )
679
692
  else:
680
693
  return ModelToComponentFactory.create_api_key_authenticator(
@@ -821,7 +834,6 @@ class ModelToComponentFactory:
821
834
  input_datetime_formats=datetime_based_cursor_model.cursor_datetime_formats,
822
835
  is_sequential_state=True,
823
836
  cursor_granularity=cursor_granularity,
824
- # type: ignore # Having issues w/ inspection for GapType and CursorValueType as shown in existing tests. Confirmed functionality is working in practice
825
837
  )
826
838
 
827
839
  start_date_runtime_value: Union[InterpolatedString, str, MinMaxDatetime]
@@ -894,7 +906,7 @@ class ModelToComponentFactory:
894
906
  stream_name=stream_name,
895
907
  stream_namespace=stream_namespace,
896
908
  stream_state=stream_state,
897
- message_repository=self._message_repository, # type: ignore # message_repository is always instantiated with a value by factory
909
+ message_repository=self._message_repository,
898
910
  connector_state_manager=state_manager,
899
911
  connector_state_converter=connector_state_converter,
900
912
  cursor_field=cursor_field,
@@ -1282,19 +1294,20 @@ class ModelToComponentFactory:
1282
1294
  parameters=model.parameters or {},
1283
1295
  )
1284
1296
 
1285
- def _merge_stream_slicers(
1286
- self, model: DeclarativeStreamModel, config: Config
1287
- ) -> Optional[StreamSlicer]:
1288
- stream_slicer = None
1297
+ def _build_stream_slicer_from_partition_router(
1298
+ self,
1299
+ model: Union[AsyncRetrieverModel, CustomRetrieverModel, SimpleRetrieverModel],
1300
+ config: Config,
1301
+ ) -> Optional[PartitionRouter]:
1289
1302
  if (
1290
- hasattr(model.retriever, "partition_router")
1291
- and isinstance(model.retriever, SimpleRetrieverModel)
1292
- and model.retriever.partition_router
1303
+ hasattr(model, "partition_router")
1304
+ and isinstance(model, SimpleRetrieverModel)
1305
+ and model.partition_router
1293
1306
  ):
1294
- stream_slicer_model = model.retriever.partition_router
1307
+ stream_slicer_model = model.partition_router
1295
1308
 
1296
1309
  if isinstance(stream_slicer_model, list):
1297
- stream_slicer = CartesianProductStreamSlicer(
1310
+ return CartesianProductStreamSlicer(
1298
1311
  [
1299
1312
  self._create_component_from_model(model=slicer, config=config)
1300
1313
  for slicer in stream_slicer_model
@@ -1302,9 +1315,24 @@ class ModelToComponentFactory:
1302
1315
  parameters={},
1303
1316
  )
1304
1317
  else:
1305
- stream_slicer = self._create_component_from_model(
1306
- model=stream_slicer_model, config=config
1307
- )
1318
+ return self._create_component_from_model(model=stream_slicer_model, config=config) # type: ignore[no-any-return]
1319
+ # Will be created PartitionRouter as stream_slicer_model is model.partition_router
1320
+ return None
1321
+
1322
+ def _build_resumable_cursor_from_paginator(
1323
+ self,
1324
+ model: Union[AsyncRetrieverModel, CustomRetrieverModel, SimpleRetrieverModel],
1325
+ stream_slicer: Optional[StreamSlicer],
1326
+ ) -> Optional[StreamSlicer]:
1327
+ if hasattr(model, "paginator") and model.paginator and not stream_slicer:
1328
+ # For the regular Full-Refresh streams, we use the high level `ResumableFullRefreshCursor`
1329
+ return ResumableFullRefreshCursor(parameters={})
1330
+ return None
1331
+
1332
+ def _merge_stream_slicers(
1333
+ self, model: DeclarativeStreamModel, config: Config
1334
+ ) -> Optional[StreamSlicer]:
1335
+ stream_slicer = self._build_stream_slicer_from_partition_router(model.retriever, config)
1308
1336
 
1309
1337
  if model.incremental_sync and stream_slicer:
1310
1338
  incremental_sync_model = model.incremental_sync
@@ -1347,15 +1375,7 @@ class ModelToComponentFactory:
1347
1375
  ),
1348
1376
  partition_router=stream_slicer,
1349
1377
  )
1350
- elif (
1351
- hasattr(model.retriever, "paginator")
1352
- and model.retriever.paginator
1353
- and not stream_slicer
1354
- ):
1355
- # For the regular Full-Refresh streams, we use the high level `ResumableFullRefreshCursor`
1356
- return ResumableFullRefreshCursor(parameters={})
1357
- else:
1358
- return None
1378
+ return self._build_resumable_cursor_from_paginator(model.retriever, stream_slicer)
1359
1379
 
1360
1380
  def create_default_error_handler(
1361
1381
  self, model: DefaultErrorHandlerModel, config: Config, **kwargs: Any
@@ -1705,7 +1725,7 @@ class ModelToComponentFactory:
1705
1725
  refresh_token=model.refresh_token,
1706
1726
  scopes=model.scopes,
1707
1727
  token_expiry_date=model.token_expiry_date,
1708
- token_expiry_date_format=model.token_expiry_date_format, # type: ignore
1728
+ token_expiry_date_format=model.token_expiry_date_format,
1709
1729
  token_expiry_is_time_of_expiration=bool(model.token_expiry_date_format),
1710
1730
  token_refresh_endpoint=model.token_refresh_endpoint,
1711
1731
  config=config,
@@ -2219,3 +2239,56 @@ class ModelToComponentFactory:
2219
2239
 
2220
2240
  def _evaluate_log_level(self, emit_connector_builder_messages: bool) -> Level:
2221
2241
  return Level.DEBUG if emit_connector_builder_messages else Level.INFO
2242
+
2243
+ @staticmethod
2244
+ def create_components_mapping_definition(
2245
+ model: ComponentMappingDefinitionModel, config: Config, **kwargs: Any
2246
+ ) -> ComponentMappingDefinition:
2247
+ interpolated_value = InterpolatedString.create(
2248
+ model.value, parameters=model.parameters or {}
2249
+ )
2250
+ field_path = [
2251
+ InterpolatedString.create(path, parameters=model.parameters or {})
2252
+ for path in model.field_path
2253
+ ]
2254
+ return ComponentMappingDefinition(
2255
+ field_path=field_path, # type: ignore[arg-type] # field_path can be str and InterpolatedString
2256
+ value=interpolated_value,
2257
+ value_type=ModelToComponentFactory._json_schema_type_name_to_type(model.value_type),
2258
+ parameters=model.parameters or {},
2259
+ )
2260
+
2261
+ def create_http_components_resolver(
2262
+ self, model: HttpComponentsResolverModel, config: Config
2263
+ ) -> Any:
2264
+ stream_slicer = self._build_stream_slicer_from_partition_router(model.retriever, config)
2265
+ combined_slicers = self._build_resumable_cursor_from_paginator(
2266
+ model.retriever, stream_slicer
2267
+ )
2268
+
2269
+ retriever = self._create_component_from_model(
2270
+ model=model.retriever,
2271
+ config=config,
2272
+ name="",
2273
+ primary_key=None,
2274
+ stream_slicer=combined_slicers,
2275
+ transformations=[],
2276
+ )
2277
+
2278
+ components_mapping = [
2279
+ self._create_component_from_model(
2280
+ model=components_mapping_definition_model,
2281
+ value_type=ModelToComponentFactory._json_schema_type_name_to_type(
2282
+ components_mapping_definition_model.value_type
2283
+ ),
2284
+ config=config,
2285
+ )
2286
+ for components_mapping_definition_model in model.components_mapping
2287
+ ]
2288
+
2289
+ return HttpComponentsResolver(
2290
+ retriever=retriever,
2291
+ config=config,
2292
+ components_mapping=components_mapping,
2293
+ parameters=model.parameters or {},
2294
+ )
@@ -6,5 +6,6 @@ from airbyte_cdk.sources.declarative.partition_routers.cartesian_product_stream_
6
6
  from airbyte_cdk.sources.declarative.partition_routers.list_partition_router import ListPartitionRouter
7
7
  from airbyte_cdk.sources.declarative.partition_routers.single_partition_router import SinglePartitionRouter
8
8
  from airbyte_cdk.sources.declarative.partition_routers.substream_partition_router import SubstreamPartitionRouter
9
+ from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
9
10
 
10
- __all__ = ["CartesianProductStreamSlicer", "ListPartitionRouter", "SinglePartitionRouter", "SubstreamPartitionRouter"]
11
+ __all__ = ["CartesianProductStreamSlicer", "ListPartitionRouter", "SinglePartitionRouter", "SubstreamPartitionRouter", "PartitionRouter"]
@@ -130,11 +130,11 @@ class SubstreamPartitionRouter(PartitionRouter):
130
130
  if value:
131
131
  params.update(
132
132
  {
133
- parent_config.request_option.field_name.eval(
133
+ parent_config.request_option.field_name.eval( # type: ignore [union-attr]
134
134
  config=self.config
135
135
  ): value
136
136
  }
137
- ) # type: ignore # field_name is always casted to an interpolated string
137
+ )
138
138
  return params
139
139
 
140
140
  def stream_slices(self) -> Iterable[StreamSlice]:
@@ -162,9 +162,9 @@ class SubstreamPartitionRouter(PartitionRouter):
162
162
  extra_fields = None
163
163
  if parent_stream_config.extra_fields:
164
164
  extra_fields = [
165
- [field_path_part.eval(self.config) for field_path_part in field_path]
165
+ [field_path_part.eval(self.config) for field_path_part in field_path] # type: ignore [union-attr]
166
166
  for field_path in parent_stream_config.extra_fields
167
- ] # type: ignore # extra_fields is always casted to an interpolated string
167
+ ]
168
168
 
169
169
  # read_stateless() assumes the parent is not concurrent. This is currently okay since the concurrent CDK does
170
170
  # not support either substreams or RFR, but something that needs to be considered once we do
@@ -192,7 +192,10 @@ class SubstreamPartitionRouter(PartitionRouter):
192
192
  message=f"Parent stream returned records as invalid type {type(parent_record)}"
193
193
  )
194
194
  try:
195
- partition_value = dpath.get(parent_record, parent_field)
195
+ partition_value = dpath.get(
196
+ parent_record, # type: ignore [arg-type]
197
+ parent_field,
198
+ )
196
199
  except KeyError:
197
200
  continue
198
201
 
@@ -228,7 +231,10 @@ class SubstreamPartitionRouter(PartitionRouter):
228
231
  if extra_fields:
229
232
  for extra_field_path in extra_fields:
230
233
  try:
231
- extra_field_value = dpath.get(parent_record, extra_field_path)
234
+ extra_field_value = dpath.get(
235
+ parent_record, # type: ignore [arg-type]
236
+ extra_field_path,
237
+ )
232
238
  self.logger.debug(
233
239
  f"Extracted extra_field_path: {extra_field_path} with value: {extra_field_value}"
234
240
  )
@@ -291,7 +297,7 @@ class SubstreamPartitionRouter(PartitionRouter):
291
297
  if not parent_state and incremental_dependency:
292
298
  # Attempt to retrieve child state
293
299
  substream_state = list(stream_state.values())
294
- substream_state = substream_state[0] if substream_state else {}
300
+ substream_state = substream_state[0] if substream_state else {} # type: ignore [assignment] # Incorrect type for assignment
295
301
  parent_state = {}
296
302
 
297
303
  # Copy child state to parent streams with incremental dependencies
@@ -141,7 +141,7 @@ class DefaultErrorHandler(ErrorHandler):
141
141
  for backoff_strategy in self.backoff_strategies:
142
142
  backoff = backoff_strategy.backoff_time(
143
143
  response_or_exception=response_or_exception, attempt_count=attempt_count
144
- ) # type: ignore # attempt_count maintained for compatibility with low code CDK
144
+ )
145
145
  if backoff:
146
146
  return backoff
147
147
  return backoff
@@ -151,21 +151,23 @@ class HttpResponseFilter:
151
151
  :param response: The HTTP response which can be used during interpolation
152
152
  :return: The evaluated error message string to be emitted
153
153
  """
154
- return self.error_message.eval(
154
+ return self.error_message.eval( # type: ignore [no-any-return, union-attr]
155
155
  self.config, response=self._safe_response_json(response), headers=response.headers
156
- ) # type: ignore # error_message is always cast to an interpolated string
156
+ )
157
157
 
158
158
  def _response_matches_predicate(self, response: requests.Response) -> bool:
159
159
  return (
160
160
  bool(
161
- self.predicate.condition
162
- and self.predicate.eval(
163
- None, response=self._safe_response_json(response), headers=response.headers
161
+ self.predicate.condition # type: ignore [union-attr]
162
+ and self.predicate.eval( # type: ignore [union-attr]
163
+ None, # type: ignore [arg-type]
164
+ response=self._safe_response_json(response),
165
+ headers=response.headers,
164
166
  )
165
167
  )
166
168
  if self.predicate
167
169
  else False
168
- ) # type: ignore # predicate is always cast to an interpolated string
170
+ )
169
171
 
170
172
  def _response_contains_error_message(self, response: requests.Response) -> bool:
171
173
  if not self.error_message_contains:
@@ -194,7 +194,7 @@ class DefaultPaginator(Paginator):
194
194
  and self.pagination_strategy.get_page_size()
195
195
  and self.page_size_option.inject_into == option_type
196
196
  ):
197
- options[self.page_size_option.field_name.eval(config=self.config)] = (
197
+ options[self.page_size_option.field_name.eval(config=self.config)] = ( # type: ignore [union-attr]
198
198
  self.pagination_strategy.get_page_size()
199
199
  ) # type: ignore # field_name is always cast to an interpolated string
200
200
  return options
@@ -85,7 +85,7 @@ class DatetimeBasedRequestOptionsProvider(RequestOptionsProvider):
85
85
  self._partition_field_start.eval(self.config)
86
86
  )
87
87
  if self.end_time_option and self.end_time_option.inject_into == option_type:
88
- options[self.end_time_option.field_name.eval(config=self.config)] = stream_slice.get(
88
+ options[self.end_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore [union-attr]
89
89
  self._partition_field_end.eval(self.config)
90
- ) # type: ignore # field_name is always casted to an interpolated string
90
+ )
91
91
  return options
@@ -5,7 +5,7 @@
5
5
  from dataclasses import InitVar, dataclass, field
6
6
  from typing import Any, Mapping, MutableMapping, Optional, Union
7
7
 
8
- from deprecated import deprecated
8
+ from typing_extensions import deprecated
9
9
 
10
10
  from airbyte_cdk.sources.declarative.interpolation.interpolated_nested_mapping import NestedMapping
11
11
  from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_nested_request_input_provider import (
@@ -0,0 +1,13 @@
1
+ #
2
+ # Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ from airbyte_cdk.sources.declarative.resolvers.components_resolver import ComponentsResolver, ComponentMappingDefinition, ResolvedComponentMappingDefinition
6
+ from airbyte_cdk.sources.declarative.resolvers.http_components_resolver import HttpComponentsResolver
7
+ from airbyte_cdk.sources.declarative.models import HttpComponentsResolver as HttpComponentsResolverModel
8
+
9
+ COMPONENTS_RESOLVER_TYPE_MAPPING = {
10
+ "HttpComponentsResolver": HttpComponentsResolverModel
11
+ }
12
+
13
+ __all__ = ["ComponentsResolver", "HttpComponentsResolver", "ComponentMappingDefinition", "ResolvedComponentMappingDefinition", "COMPONENTS_RESOLVER_TYPE_MAPPING"]
@@ -0,0 +1,55 @@
1
+ #
2
+ # Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import InitVar, dataclass
7
+ from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union
8
+
9
+ from typing_extensions import deprecated
10
+
11
+ from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
12
+ from airbyte_cdk.sources.source import ExperimentalClassWarning
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class ComponentMappingDefinition:
17
+ """Defines the configuration for mapping a component in a stream. This class specifies
18
+ what field in the stream template should be updated with value, supporting dynamic interpolation
19
+ and type enforcement."""
20
+
21
+ field_path: List["InterpolatedString"]
22
+ value: Union["InterpolatedString", str]
23
+ value_type: Optional[Type[Any]]
24
+ parameters: InitVar[Mapping[str, Any]]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ResolvedComponentMappingDefinition:
29
+ """Defines resolved configuration for mapping a component in a stream. This class specifies
30
+ what field in the stream template should be updated with value, supporting dynamic interpolation
31
+ and type enforcement."""
32
+
33
+ field_path: List["InterpolatedString"]
34
+ value: "InterpolatedString"
35
+ value_type: Optional[Type[Any]]
36
+ parameters: InitVar[Mapping[str, Any]]
37
+
38
+
39
+ @deprecated("This class is experimental. Use at your own risk.", category=ExperimentalClassWarning)
40
+ @dataclass
41
+ class ComponentsResolver(ABC):
42
+ """
43
+ Abstract base class for resolving components in a stream template.
44
+ """
45
+
46
+ @abstractmethod
47
+ def resolve_components(
48
+ self, stream_template_config: Dict[str, Any]
49
+ ) -> Iterable[Dict[str, Any]]:
50
+ """
51
+ Maps and populates values into a stream template configuration.
52
+ :param stream_template_config: The stream template with placeholders for components.
53
+ :yields: The resolved stream config with populated values.
54
+ """
55
+ pass
@@ -0,0 +1,106 @@
1
+ #
2
+ # Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ from copy import deepcopy
6
+ from dataclasses import InitVar, dataclass, field
7
+ from typing import Any, Dict, Iterable, List, Mapping
8
+
9
+ import dpath
10
+ from typing_extensions import deprecated
11
+
12
+ from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
13
+ from airbyte_cdk.sources.declarative.resolvers.components_resolver import (
14
+ ComponentMappingDefinition,
15
+ ComponentsResolver,
16
+ ResolvedComponentMappingDefinition,
17
+ )
18
+ from airbyte_cdk.sources.declarative.retrievers.retriever import Retriever
19
+ from airbyte_cdk.sources.source import ExperimentalClassWarning
20
+ from airbyte_cdk.sources.types import Config
21
+
22
+
23
+ @deprecated("This class is experimental. Use at your own risk.", category=ExperimentalClassWarning)
24
+ @dataclass
25
+ class HttpComponentsResolver(ComponentsResolver):
26
+ """
27
+ Resolves and populates stream templates with components fetched via an HTTP retriever.
28
+
29
+ Attributes:
30
+ retriever (Retriever): The retriever used to fetch data from an API.
31
+ config (Config): Configuration object for the resolver.
32
+ components_mapping (List[ComponentMappingDefinition]): List of mappings to resolve.
33
+ parameters (InitVar[Mapping[str, Any]]): Additional parameters for interpolation.
34
+ """
35
+
36
+ retriever: Retriever
37
+ config: Config
38
+ components_mapping: List[ComponentMappingDefinition]
39
+ parameters: InitVar[Mapping[str, Any]]
40
+ _resolved_components: List[ResolvedComponentMappingDefinition] = field(
41
+ init=False, repr=False, default_factory=list
42
+ )
43
+
44
+ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
45
+ """
46
+ Initializes and parses component mappings, converting them to resolved definitions.
47
+
48
+ Args:
49
+ parameters (Mapping[str, Any]): Parameters for interpolation.
50
+ """
51
+ for component_mapping in self.components_mapping:
52
+ if isinstance(component_mapping.value, (str, InterpolatedString)):
53
+ interpolated_value = (
54
+ InterpolatedString.create(component_mapping.value, parameters=parameters)
55
+ if isinstance(component_mapping.value, str)
56
+ else component_mapping.value
57
+ )
58
+
59
+ field_path = [
60
+ InterpolatedString.create(path, parameters=parameters)
61
+ for path in component_mapping.field_path
62
+ ]
63
+
64
+ self._resolved_components.append(
65
+ ResolvedComponentMappingDefinition(
66
+ field_path=field_path,
67
+ value=interpolated_value,
68
+ value_type=component_mapping.value_type,
69
+ parameters=parameters,
70
+ )
71
+ )
72
+ else:
73
+ raise ValueError(
74
+ f"Expected a string or InterpolatedString for value in mapping: {component_mapping}"
75
+ )
76
+
77
+ def resolve_components(
78
+ self, stream_template_config: Dict[str, Any]
79
+ ) -> Iterable[Dict[str, Any]]:
80
+ """
81
+ Resolves components in the stream template configuration by populating values.
82
+
83
+ Args:
84
+ stream_template_config (Dict[str, Any]): Stream template to populate.
85
+
86
+ Yields:
87
+ Dict[str, Any]: Updated configurations with resolved components.
88
+ """
89
+ kwargs = {"stream_template_config": stream_template_config}
90
+
91
+ for components_values in self.retriever.read_records({}):
92
+ updated_config = deepcopy(stream_template_config)
93
+ kwargs["components_values"] = components_values # type: ignore[assignment] # component_values will always be of type Mapping[str, Any]
94
+
95
+ for resolved_component in self._resolved_components:
96
+ valid_types = (
97
+ (resolved_component.value_type,) if resolved_component.value_type else None
98
+ )
99
+ value = resolved_component.value.eval(
100
+ self.config, valid_types=valid_types, **kwargs
101
+ )
102
+
103
+ path = [path.eval(self.config, **kwargs) for path in resolved_component.field_path]
104
+ dpath.set(updated_config, path, value)
105
+
106
+ yield updated_config
@@ -4,7 +4,7 @@
4
4
  from dataclasses import InitVar, dataclass, field
5
5
  from typing import Any, Callable, Iterable, Mapping, Optional
6
6
 
7
- from deprecated.classic import deprecated
7
+ from typing_extensions import deprecated
8
8
 
9
9
  from airbyte_cdk.models import FailureType
10
10
  from airbyte_cdk.sources.declarative.async_job.job_orchestrator import (
@@ -21,7 +21,10 @@ from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
21
21
  from airbyte_cdk.utils.traced_exception import AirbyteTracedException
22
22
 
23
23
 
24
- @deprecated("This class is experimental. Use at your own risk.", category=ExperimentalClassWarning)
24
+ @deprecated(
25
+ "This class is experimental. Use at your own risk.",
26
+ category=ExperimentalClassWarning,
27
+ )
25
28
  @dataclass
26
29
  class AsyncRetriever(Retriever):
27
30
  config: Config
@@ -9,7 +9,7 @@ from airbyte_cdk.models import (
9
9
  AdvancedAuth,
10
10
  ConnectorSpecification,
11
11
  ConnectorSpecificationSerializer,
12
- ) # type: ignore [attr-defined]
12
+ )
13
13
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import AuthFlow
14
14
 
15
15
 
@@ -52,8 +52,9 @@ class BaseEmbeddedIntegration(ABC, Generic[TConfig, TOutput]):
52
52
  for message in self.source.read(self.config, configured_catalog, state):
53
53
  if message.type == Type.RECORD:
54
54
  output = self._handle_record(
55
- message.record, get_defined_id(stream, message.record.data)
56
- ) # type: ignore[union-attr] # record has `data`
55
+ message.record,
56
+ get_defined_id(stream, message.record.data), # type: ignore[union-attr, arg-type]
57
+ )
57
58
  if output:
58
59
  yield output
59
60
  elif message.type is Type.STATE and message.state:
@@ -2,6 +2,8 @@
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import logging
6
8
  from abc import abstractmethod
7
9
  from typing import TYPE_CHECKING, Optional, Tuple
@@ -22,8 +24,11 @@ if TYPE_CHECKING:
22
24
 
23
25
  class AbstractFileBasedAvailabilityStrategy(AvailabilityStrategy):
24
26
  @abstractmethod
25
- def check_availability(
26
- self, stream: Stream, logger: logging.Logger, _: Optional[Source]
27
+ def check_availability( # type: ignore[override] # Signature doesn't match base class
28
+ self,
29
+ stream: Stream,
30
+ logger: logging.Logger,
31
+ _: Optional[Source],
27
32
  ) -> Tuple[bool, Optional[str]]:
28
33
  """
29
34
  Perform a connection check for the stream.
@@ -34,7 +39,10 @@ class AbstractFileBasedAvailabilityStrategy(AvailabilityStrategy):
34
39
 
35
40
  @abstractmethod
36
41
  def check_availability_and_parsability(
37
- self, stream: "AbstractFileBasedStream", logger: logging.Logger, _: Optional[Source]
42
+ self,
43
+ stream: AbstractFileBasedStream,
44
+ logger: logging.Logger,
45
+ _: Optional[Source],
38
46
  ) -> Tuple[bool, Optional[str]]:
39
47
  """
40
48
  Performs a connection check for the stream, as well as additional checks that
@@ -46,7 +54,7 @@ class AbstractFileBasedAvailabilityStrategy(AvailabilityStrategy):
46
54
 
47
55
 
48
56
  class AbstractFileBasedAvailabilityStrategyWrapper(AbstractAvailabilityStrategy):
49
- def __init__(self, stream: "AbstractFileBasedStream"):
57
+ def __init__(self, stream: AbstractFileBasedStream) -> None:
50
58
  self.stream = stream
51
59
 
52
60
  def check_availability(self, logger: logging.Logger) -> StreamAvailability: