airbyte-cdk 6.32.0__py3-none-any.whl → 6.33.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 (20) hide show
  1. airbyte_cdk/sources/declarative/auth/token.py +3 -8
  2. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +13 -2
  3. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +14 -4
  4. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +7 -6
  5. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +10 -4
  6. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +34 -30
  7. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +4 -2
  8. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +3 -9
  9. airbyte_cdk/sources/declarative/requesters/http_requester.py +5 -1
  10. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +6 -5
  11. airbyte_cdk/sources/declarative/requesters/request_option.py +83 -4
  12. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +7 -6
  13. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +4 -1
  14. airbyte_cdk/utils/mapping_helpers.py +86 -27
  15. {airbyte_cdk-6.32.0.dist-info → airbyte_cdk-6.33.0.dist-info}/METADATA +1 -1
  16. {airbyte_cdk-6.32.0.dist-info → airbyte_cdk-6.33.0.dist-info}/RECORD +20 -20
  17. {airbyte_cdk-6.32.0.dist-info → airbyte_cdk-6.33.0.dist-info}/LICENSE.txt +0 -0
  18. {airbyte_cdk-6.32.0.dist-info → airbyte_cdk-6.33.0.dist-info}/LICENSE_SHORT +0 -0
  19. {airbyte_cdk-6.32.0.dist-info → airbyte_cdk-6.33.0.dist-info}/WHEEL +0 -0
  20. {airbyte_cdk-6.32.0.dist-info → airbyte_cdk-6.33.0.dist-info}/entry_points.txt +0 -0
@@ -5,7 +5,7 @@
5
5
  import base64
6
6
  import logging
7
7
  from dataclasses import InitVar, dataclass
8
- from typing import Any, Mapping, Union
8
+ from typing import Any, Mapping, MutableMapping, Union
9
9
 
10
10
  import requests
11
11
  from cachetools import TTLCache, cached
@@ -45,11 +45,6 @@ class ApiKeyAuthenticator(DeclarativeAuthenticator):
45
45
  config: Config
46
46
  parameters: InitVar[Mapping[str, Any]]
47
47
 
48
- def __post_init__(self, parameters: Mapping[str, Any]) -> None:
49
- self._field_name = InterpolatedString.create(
50
- self.request_option.field_name, parameters=parameters
51
- )
52
-
53
48
  @property
54
49
  def auth_header(self) -> str:
55
50
  options = self._get_request_options(RequestOptionType.header)
@@ -60,9 +55,9 @@ class ApiKeyAuthenticator(DeclarativeAuthenticator):
60
55
  return self.token_provider.get_token()
61
56
 
62
57
  def _get_request_options(self, option_type: RequestOptionType) -> Mapping[str, Any]:
63
- options = {}
58
+ options: MutableMapping[str, Any] = {}
64
59
  if self.request_option.inject_into == option_type:
65
- options[self._field_name.eval(self.config)] = self.token
60
+ self.request_option.inject_into_request(options, self.token, self.config)
66
61
  return options
67
62
 
68
63
  def get_request_params(self) -> Mapping[str, Any]:
@@ -475,10 +475,21 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
475
475
  # Also a temporary hack. In the legacy Stream implementation, as part of the read,
476
476
  # set_initial_state() is called to instantiate incoming state on the cursor. Although we no
477
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.
478
+ # like StopConditionPaginationStrategyDecorator still rely on a DatetimeBasedCursor that is
479
+ # properly initialized with state.
480
480
  if retriever.cursor:
481
481
  retriever.cursor.set_initial_state(stream_state=stream_state)
482
+
483
+ # Similar to above, the ClientSideIncrementalRecordFilterDecorator cursor is a separate instance
484
+ # from the one initialized on the SimpleRetriever, so it also must also have state initialized
485
+ # for semi-incremental streams using is_client_side_incremental to filter properly
486
+ if isinstance(retriever.record_selector, RecordSelector) and isinstance(
487
+ retriever.record_selector.record_filter, ClientSideIncrementalRecordFilterDecorator
488
+ ):
489
+ retriever.record_selector.record_filter._cursor.set_initial_state(
490
+ stream_state=stream_state
491
+ ) # type: ignore # After non-concurrent cursors are deprecated we can remove these cursor workarounds
492
+
482
493
  # We zero it out here, but since this is a cursor reference, the state is still properly
483
494
  # instantiated for the other components that reference it
484
495
  retriever.cursor = None
@@ -2847,25 +2847,35 @@ definitions:
2847
2847
  enum: [RequestPath]
2848
2848
  RequestOption:
2849
2849
  title: Request Option
2850
- description: Specifies the key field and where in the request a component's value should be injected.
2850
+ description: Specifies the key field or path and where in the request a component's value should be injected.
2851
2851
  type: object
2852
2852
  required:
2853
2853
  - type
2854
- - field_name
2855
2854
  - inject_into
2856
2855
  properties:
2857
2856
  type:
2858
2857
  type: string
2859
2858
  enum: [RequestOption]
2860
2859
  field_name:
2861
- title: Request Option
2862
- description: Configures which key should be used in the location that the descriptor is being injected into
2860
+ title: Field Name
2861
+ 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.
2863
2862
  type: string
2864
2863
  examples:
2865
2864
  - segment_id
2866
2865
  interpolation_context:
2867
2866
  - config
2868
2867
  - parameters
2868
+ field_path:
2869
+ title: Field Path
2870
+ description: Configures a path to be used for nested structures in JSON body requests (e.g. GraphQL queries)
2871
+ type: array
2872
+ items:
2873
+ type: string
2874
+ examples:
2875
+ - ["data", "viewer", "id"]
2876
+ interpolation_context:
2877
+ - config
2878
+ - parameters
2869
2879
  inject_into:
2870
2880
  title: Inject Into
2871
2881
  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.
@@ -365,14 +365,15 @@ class DatetimeBasedCursor(DeclarativeCursor):
365
365
  options: MutableMapping[str, Any] = {}
366
366
  if not stream_slice:
367
367
  return options
368
+
368
369
  if self.start_time_option and self.start_time_option.inject_into == option_type:
369
- options[self.start_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore # field_name is always casted to an interpolated string
370
- self._partition_field_start.eval(self.config)
371
- )
370
+ start_time_value = stream_slice.get(self._partition_field_start.eval(self.config))
371
+ self.start_time_option.inject_into_request(options, start_time_value, self.config)
372
+
372
373
  if self.end_time_option and self.end_time_option.inject_into == option_type:
373
- options[self.end_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore [union-attr]
374
- self._partition_field_end.eval(self.config)
375
- )
374
+ end_time_value = stream_slice.get(self._partition_field_end.eval(self.config))
375
+ self.end_time_option.inject_into_request(options, end_time_value, self.config)
376
+
376
377
  return options
377
378
 
378
379
  def should_be_synced(self, record: Record) -> bool:
@@ -1200,11 +1200,17 @@ class InjectInto(Enum):
1200
1200
 
1201
1201
  class RequestOption(BaseModel):
1202
1202
  type: Literal["RequestOption"]
1203
- field_name: str = Field(
1204
- ...,
1205
- description="Configures which key should be used in the location that the descriptor is being injected into",
1203
+ field_name: Optional[str] = Field(
1204
+ None,
1205
+ 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.",
1206
1206
  examples=["segment_id"],
1207
- title="Request Option",
1207
+ title="Field Name",
1208
+ )
1209
+ field_path: Optional[List[str]] = Field(
1210
+ None,
1211
+ description="Configures a path to be used for nested structures in JSON body requests (e.g. GraphQL queries)",
1212
+ examples=[["data", "viewer", "id"]],
1213
+ title="Field Path",
1208
1214
  )
1209
1215
  inject_into: InjectInto = Field(
1210
1216
  ...,
@@ -733,8 +733,8 @@ class ModelToComponentFactory:
733
733
  }
734
734
  return names_to_types[value_type]
735
735
 
736
- @staticmethod
737
736
  def create_api_key_authenticator(
737
+ self,
738
738
  model: ApiKeyAuthenticatorModel,
739
739
  config: Config,
740
740
  token_provider: Optional[TokenProvider] = None,
@@ -756,10 +756,8 @@ class ModelToComponentFactory:
756
756
  )
757
757
 
758
758
  request_option = (
759
- RequestOption(
760
- inject_into=RequestOptionType(model.inject_into.inject_into.value),
761
- field_name=model.inject_into.field_name,
762
- parameters=model.parameters or {},
759
+ self._create_component_from_model(
760
+ model.inject_into, config, parameters=model.parameters or {}
763
761
  )
764
762
  if model.inject_into
765
763
  else RequestOption(
@@ -768,6 +766,7 @@ class ModelToComponentFactory:
768
766
  parameters=model.parameters or {},
769
767
  )
770
768
  )
769
+
771
770
  return ApiKeyAuthenticator(
772
771
  token_provider=(
773
772
  token_provider
@@ -849,7 +848,7 @@ class ModelToComponentFactory:
849
848
  token_provider=token_provider,
850
849
  )
851
850
  else:
852
- return ModelToComponentFactory.create_api_key_authenticator(
851
+ return self.create_api_key_authenticator(
853
852
  ApiKeyAuthenticatorModel(
854
853
  type="ApiKeyAuthenticator",
855
854
  api_token="",
@@ -1489,19 +1488,15 @@ class ModelToComponentFactory:
1489
1488
  )
1490
1489
 
1491
1490
  end_time_option = (
1492
- RequestOption(
1493
- inject_into=RequestOptionType(model.end_time_option.inject_into.value),
1494
- field_name=model.end_time_option.field_name,
1495
- parameters=model.parameters or {},
1491
+ self._create_component_from_model(
1492
+ model.end_time_option, config, parameters=model.parameters or {}
1496
1493
  )
1497
1494
  if model.end_time_option
1498
1495
  else None
1499
1496
  )
1500
1497
  start_time_option = (
1501
- RequestOption(
1502
- inject_into=RequestOptionType(model.start_time_option.inject_into.value),
1503
- field_name=model.start_time_option.field_name,
1504
- parameters=model.parameters or {},
1498
+ self._create_component_from_model(
1499
+ model.start_time_option, config, parameters=model.parameters or {}
1505
1500
  )
1506
1501
  if model.start_time_option
1507
1502
  else None
@@ -1572,19 +1567,15 @@ class ModelToComponentFactory:
1572
1567
  cursor_model = model.incremental_sync
1573
1568
 
1574
1569
  end_time_option = (
1575
- RequestOption(
1576
- inject_into=RequestOptionType(cursor_model.end_time_option.inject_into.value),
1577
- field_name=cursor_model.end_time_option.field_name,
1578
- parameters=cursor_model.parameters or {},
1570
+ self._create_component_from_model(
1571
+ cursor_model.end_time_option, config, parameters=cursor_model.parameters or {}
1579
1572
  )
1580
1573
  if cursor_model.end_time_option
1581
1574
  else None
1582
1575
  )
1583
1576
  start_time_option = (
1584
- RequestOption(
1585
- inject_into=RequestOptionType(cursor_model.start_time_option.inject_into.value),
1586
- field_name=cursor_model.start_time_option.field_name,
1587
- parameters=cursor_model.parameters or {},
1577
+ self._create_component_from_model(
1578
+ cursor_model.start_time_option, config, parameters=cursor_model.parameters or {}
1588
1579
  )
1589
1580
  if cursor_model.start_time_option
1590
1581
  else None
@@ -2150,16 +2141,11 @@ class ModelToComponentFactory:
2150
2141
  additional_jwt_payload=model.additional_jwt_payload,
2151
2142
  )
2152
2143
 
2153
- @staticmethod
2154
2144
  def create_list_partition_router(
2155
- model: ListPartitionRouterModel, config: Config, **kwargs: Any
2145
+ self, model: ListPartitionRouterModel, config: Config, **kwargs: Any
2156
2146
  ) -> ListPartitionRouter:
2157
2147
  request_option = (
2158
- RequestOption(
2159
- inject_into=RequestOptionType(model.request_option.inject_into.value),
2160
- field_name=model.request_option.field_name,
2161
- parameters=model.parameters or {},
2162
- )
2148
+ self._create_component_from_model(model.request_option, config)
2163
2149
  if model.request_option
2164
2150
  else None
2165
2151
  )
@@ -2355,7 +2341,25 @@ class ModelToComponentFactory:
2355
2341
  model: RequestOptionModel, config: Config, **kwargs: Any
2356
2342
  ) -> RequestOption:
2357
2343
  inject_into = RequestOptionType(model.inject_into.value)
2358
- return RequestOption(field_name=model.field_name, inject_into=inject_into, parameters={})
2344
+ field_path: Optional[List[Union[InterpolatedString, str]]] = (
2345
+ [
2346
+ InterpolatedString.create(segment, parameters=kwargs.get("parameters", {}))
2347
+ for segment in model.field_path
2348
+ ]
2349
+ if model.field_path
2350
+ else None
2351
+ )
2352
+ field_name = (
2353
+ InterpolatedString.create(model.field_name, parameters=kwargs.get("parameters", {}))
2354
+ if model.field_name
2355
+ else None
2356
+ )
2357
+ return RequestOption(
2358
+ field_name=field_name,
2359
+ field_path=field_path,
2360
+ inject_into=inject_into,
2361
+ parameters=kwargs.get("parameters", {}),
2362
+ )
2359
2363
 
2360
2364
  def create_record_selector(
2361
2365
  self,
@@ -3,7 +3,7 @@
3
3
  #
4
4
 
5
5
  from dataclasses import InitVar, dataclass
6
- from typing import Any, Iterable, List, Mapping, Optional, Union
6
+ from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Union
7
7
 
8
8
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
9
9
  from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
@@ -100,7 +100,9 @@ class ListPartitionRouter(PartitionRouter):
100
100
  ):
101
101
  slice_value = stream_slice.get(self._cursor_field.eval(self.config))
102
102
  if slice_value:
103
- return {self.request_option.field_name.eval(self.config): slice_value} # type: ignore # field_name is always casted to InterpolatedString
103
+ options: MutableMapping[str, Any] = {}
104
+ self.request_option.inject_into_request(options, slice_value, self.config)
105
+ return options
104
106
  else:
105
107
  return {}
106
108
  else:
@@ -4,7 +4,7 @@
4
4
  import copy
5
5
  import logging
6
6
  from dataclasses import InitVar, dataclass
7
- from typing import TYPE_CHECKING, Any, Iterable, List, Mapping, Optional, Union
7
+ from typing import TYPE_CHECKING, Any, Iterable, List, Mapping, MutableMapping, Optional, Union
8
8
 
9
9
  import dpath
10
10
 
@@ -118,7 +118,7 @@ class SubstreamPartitionRouter(PartitionRouter):
118
118
  def _get_request_option(
119
119
  self, option_type: RequestOptionType, stream_slice: Optional[StreamSlice]
120
120
  ) -> Mapping[str, Any]:
121
- params = {}
121
+ params: MutableMapping[str, Any] = {}
122
122
  if stream_slice:
123
123
  for parent_config in self.parent_stream_configs:
124
124
  if (
@@ -128,13 +128,7 @@ class SubstreamPartitionRouter(PartitionRouter):
128
128
  key = parent_config.partition_field.eval(self.config) # type: ignore # partition_field is always casted to an interpolated string
129
129
  value = stream_slice.get(key)
130
130
  if value:
131
- params.update(
132
- {
133
- parent_config.request_option.field_name.eval( # type: ignore [union-attr]
134
- config=self.config
135
- ): value
136
- }
137
- )
131
+ parent_config.request_option.inject_into_request(params, value, self.config)
138
132
  return params
139
133
 
140
134
  def stream_slices(self) -> Iterable[StreamSlice]:
@@ -199,6 +199,9 @@ class HttpRequester(Requester):
199
199
  Raise a ValueError if there's a key collision
200
200
  Returned merged mapping otherwise
201
201
  """
202
+
203
+ is_body_json = requester_method.__name__ == "get_request_body_json"
204
+
202
205
  return combine_mappings(
203
206
  [
204
207
  requester_method(
@@ -208,7 +211,8 @@ class HttpRequester(Requester):
208
211
  ),
209
212
  auth_options_method(),
210
213
  extra_options,
211
- ]
214
+ ],
215
+ allow_same_value_merge=is_body_json,
212
216
  )
213
217
 
214
218
  def _request_headers(
@@ -187,7 +187,7 @@ class DefaultPaginator(Paginator):
187
187
  def _get_request_options(
188
188
  self, option_type: RequestOptionType, next_page_token: Optional[Mapping[str, Any]]
189
189
  ) -> MutableMapping[str, Any]:
190
- options = {}
190
+ options: MutableMapping[str, Any] = {}
191
191
 
192
192
  token = next_page_token.get("next_page_token") if next_page_token else None
193
193
  if (
@@ -196,15 +196,16 @@ class DefaultPaginator(Paginator):
196
196
  and isinstance(self.page_token_option, RequestOption)
197
197
  and self.page_token_option.inject_into == option_type
198
198
  ):
199
- options[self.page_token_option.field_name.eval(config=self.config)] = token # type: ignore # field_name is always cast to an interpolated string
199
+ self.page_token_option.inject_into_request(options, token, self.config)
200
+
200
201
  if (
201
202
  self.page_size_option
202
203
  and self.pagination_strategy.get_page_size()
203
204
  and self.page_size_option.inject_into == option_type
204
205
  ):
205
- options[self.page_size_option.field_name.eval(config=self.config)] = ( # type: ignore [union-attr]
206
- self.pagination_strategy.get_page_size()
207
- ) # type: ignore # field_name is always cast to an interpolated string
206
+ page_size = self.pagination_strategy.get_page_size()
207
+ self.page_size_option.inject_into_request(options, page_size, self.config)
208
+
208
209
  return options
209
210
 
210
211
 
@@ -4,9 +4,10 @@
4
4
 
5
5
  from dataclasses import InitVar, dataclass
6
6
  from enum import Enum
7
- from typing import Any, Mapping, Union
7
+ from typing import Any, List, Literal, Mapping, MutableMapping, Optional, Union
8
8
 
9
9
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
10
+ from airbyte_cdk.sources.types import Config
10
11
 
11
12
 
12
13
  class RequestOptionType(Enum):
@@ -26,13 +27,91 @@ class RequestOption:
26
27
  Describes an option to set on a request
27
28
 
28
29
  Attributes:
29
- field_name (str): Describes the name of the parameter to inject
30
+ field_name (str): Describes the name of the parameter to inject. Mutually exclusive with field_path.
31
+ field_path (list(str)): Describes the path to a nested field as a list of field names.
32
+ Only valid for body_json injection type, and mutually exclusive with field_name.
30
33
  inject_into (RequestOptionType): Describes where in the HTTP request to inject the parameter
31
34
  """
32
35
 
33
- field_name: Union[InterpolatedString, str]
34
36
  inject_into: RequestOptionType
35
37
  parameters: InitVar[Mapping[str, Any]]
38
+ field_name: Optional[Union[InterpolatedString, str]] = None
39
+ field_path: Optional[List[Union[InterpolatedString, str]]] = None
36
40
 
37
41
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
38
- self.field_name = InterpolatedString.create(self.field_name, parameters=parameters)
42
+ # Validate inputs. We should expect either field_name or field_path, but not both
43
+ if self.field_name is None and self.field_path is None:
44
+ raise ValueError("RequestOption requires either a field_name or field_path")
45
+
46
+ if self.field_name is not None and self.field_path is not None:
47
+ raise ValueError(
48
+ "Only one of field_name or field_path can be provided to RequestOption"
49
+ )
50
+
51
+ # Nested field injection is only supported for body JSON injection
52
+ if self.field_path is not None and self.inject_into != RequestOptionType.body_json:
53
+ raise ValueError(
54
+ "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
55
+ )
56
+
57
+ # Convert field_name and field_path into InterpolatedString objects if they are strings
58
+ if self.field_name is not None:
59
+ self.field_name = InterpolatedString.create(self.field_name, parameters=parameters)
60
+ elif self.field_path is not None:
61
+ self.field_path = [
62
+ InterpolatedString.create(segment, parameters=parameters)
63
+ for segment in self.field_path
64
+ ]
65
+
66
+ @property
67
+ def _is_field_path(self) -> bool:
68
+ """Returns whether this option is a field path (ie, a nested field)"""
69
+ return self.field_path is not None
70
+
71
+ def inject_into_request(
72
+ self,
73
+ target: MutableMapping[str, Any],
74
+ value: Any,
75
+ config: Config,
76
+ ) -> None:
77
+ """
78
+ Inject a request option value into a target request structure using either field_name or field_path.
79
+ For non-body-json injection, only top-level field names are supported.
80
+ For body-json injection, both field names and nested field paths are supported.
81
+
82
+ Args:
83
+ target: The request structure to inject the value into
84
+ value: The value to inject
85
+ config: The config object to use for interpolation
86
+ """
87
+ if self._is_field_path:
88
+ if self.inject_into != RequestOptionType.body_json:
89
+ raise ValueError(
90
+ "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
91
+ )
92
+
93
+ assert self.field_path is not None # for type checker
94
+ current = target
95
+ # Convert path segments into strings, evaluating any interpolated segments
96
+ # Example: ["data", "{{ config[user_type] }}", "id"] -> ["data", "admin", "id"]
97
+ *path_parts, final_key = [
98
+ str(
99
+ segment.eval(config=config)
100
+ if isinstance(segment, InterpolatedString)
101
+ else segment
102
+ )
103
+ for segment in self.field_path
104
+ ]
105
+
106
+ # Build a nested dictionary structure and set the final value at the deepest level
107
+ for part in path_parts:
108
+ current = current.setdefault(part, {})
109
+ current[final_key] = value
110
+ else:
111
+ # For non-nested fields, evaluate the field name if it's an interpolated string
112
+ key = (
113
+ self.field_name.eval(config=config)
114
+ if isinstance(self.field_name, InterpolatedString)
115
+ else self.field_name
116
+ )
117
+ target[str(key)] = value
@@ -80,12 +80,13 @@ class DatetimeBasedRequestOptionsProvider(RequestOptionsProvider):
80
80
  options: MutableMapping[str, Any] = {}
81
81
  if not stream_slice:
82
82
  return options
83
+
83
84
  if self.start_time_option and self.start_time_option.inject_into == option_type:
84
- options[self.start_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore # field_name is always casted to an interpolated string
85
- self._partition_field_start.eval(self.config)
86
- )
85
+ start_time_value = stream_slice.get(self._partition_field_start.eval(self.config))
86
+ self.start_time_option.inject_into_request(options, start_time_value, self.config)
87
+
87
88
  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( # type: ignore [union-attr]
89
- self._partition_field_end.eval(self.config)
90
- )
89
+ end_time_value = stream_slice.get(self._partition_field_end.eval(self.config))
90
+ self.end_time_option.inject_into_request(options, end_time_value, self.config)
91
+
91
92
  return options
@@ -128,6 +128,9 @@ class SimpleRetriever(Retriever):
128
128
  Returned merged mapping otherwise
129
129
  """
130
130
  # FIXME we should eventually remove the usage of stream_state as part of the interpolation
131
+
132
+ is_body_json = paginator_method.__name__ == "get_request_body_json"
133
+
131
134
  mappings = [
132
135
  paginator_method(
133
136
  stream_state=stream_state,
@@ -143,7 +146,7 @@ class SimpleRetriever(Retriever):
143
146
  next_page_token=next_page_token,
144
147
  )
145
148
  )
146
- return combine_mappings(mappings)
149
+ return combine_mappings(mappings, allow_same_value_merge=is_body_json)
147
150
 
148
151
  def _request_headers(
149
152
  self,
@@ -3,43 +3,102 @@
3
3
  #
4
4
 
5
5
 
6
- from typing import Any, List, Mapping, Optional, Set, Union
6
+ import copy
7
+ from typing import Any, Dict, List, Mapping, Optional, Union
8
+
9
+
10
+ def _merge_mappings(
11
+ target: Dict[str, Any],
12
+ source: Mapping[str, Any],
13
+ path: Optional[List[str]] = None,
14
+ allow_same_value_merge: bool = False,
15
+ ) -> None:
16
+ """
17
+ Recursively merge two dictionaries, raising an error if there are any conflicts.
18
+ For body_json requests (allow_same_value_merge=True), a conflict occurs only when the same path has different values.
19
+ For other request types (allow_same_value_merge=False), any duplicate key is a conflict, regardless of value.
20
+
21
+ Args:
22
+ target: The dictionary to merge into
23
+ source: The dictionary to merge from
24
+ path: The current path in the nested structure (for error messages)
25
+ allow_same_value_merge: Whether to allow merging the same value into the same key. Set to false by default, should only be true for body_json injections
26
+ """
27
+ path = path or []
28
+ for key, source_value in source.items():
29
+ current_path = path + [str(key)]
30
+
31
+ if key in target:
32
+ target_value = target[key]
33
+ if isinstance(target_value, dict) and isinstance(source_value, dict):
34
+ # Only body_json supports nested_structures
35
+ if not allow_same_value_merge:
36
+ raise ValueError(f"Duplicate keys found: {'.'.join(current_path)}")
37
+ # If both are dictionaries, recursively merge them
38
+ _merge_mappings(target_value, source_value, current_path, allow_same_value_merge)
39
+
40
+ elif not allow_same_value_merge or target_value != source_value:
41
+ # If same key has different values, that's a conflict
42
+ raise ValueError(f"Duplicate keys found: {'.'.join(current_path)}")
43
+ else:
44
+ # No conflict, just copy the value (using deepcopy for nested structures)
45
+ target[key] = copy.deepcopy(source_value)
7
46
 
8
47
 
9
48
  def combine_mappings(
10
49
  mappings: List[Optional[Union[Mapping[str, Any], str]]],
50
+ allow_same_value_merge: bool = False,
11
51
  ) -> Union[Mapping[str, Any], str]:
12
52
  """
13
- Combine multiple mappings into a single mapping. If any of the mappings are a string, return
14
- that string. Raise errors in the following cases:
15
- * If there are duplicate keys across mappings
16
- * If there are multiple string mappings
17
- * If there are multiple mappings containing keys and one of them is a string
53
+ Combine multiple mappings into a single mapping.
54
+
55
+ For body_json requests (allow_same_value_merge=True):
56
+ - Supports nested structures (e.g., {"data": {"user": {"id": 1}}})
57
+ - Allows duplicate keys if their values match
58
+ - Raises error if same path has different values
59
+
60
+ For other request types (allow_same_value_merge=False):
61
+ - Only supports flat structures
62
+ - Any duplicate key raises an error, regardless of value
63
+
64
+ Args:
65
+ mappings: List of mappings to combine
66
+ allow_same_value_merge: Whether to allow duplicate keys with matching values.
67
+ Should only be True for body_json requests.
68
+
69
+ Returns:
70
+ A single mapping combining all inputs, or a string if there is exactly one
71
+ string mapping and no other non-empty mappings.
72
+
73
+ Raises:
74
+ ValueError: If there are:
75
+ - Multiple string mappings
76
+ - Both a string mapping and non-empty dictionary mappings
77
+ - Conflicting keys/paths based on allow_same_value_merge setting
18
78
  """
19
- all_keys: List[Set[str]] = []
20
- for part in mappings:
21
- if part is None:
22
- continue
23
- keys = set(part.keys()) if not isinstance(part, str) else set()
24
- all_keys.append(keys)
25
-
26
- string_options = sum(isinstance(mapping, str) for mapping in mappings)
27
- # If more than one mapping is a string, raise a ValueError
79
+ if not mappings:
80
+ return {}
81
+
82
+ # Count how many string options we have, ignoring None values
83
+ string_options = sum(isinstance(mapping, str) for mapping in mappings if mapping is not None)
28
84
  if string_options > 1:
29
85
  raise ValueError("Cannot combine multiple string options")
30
86
 
31
- if string_options == 1 and sum(len(keys) for keys in all_keys) > 0:
32
- raise ValueError("Cannot combine multiple options if one is a string")
87
+ # Filter out None values and empty mappings
88
+ non_empty_mappings = [
89
+ m for m in mappings if m is not None and not (isinstance(m, Mapping) and not m)
90
+ ]
33
91
 
34
- # If any mapping is a string, return it
35
- for mapping in mappings:
36
- if isinstance(mapping, str):
37
- return mapping
92
+ # If there is only one string option and no other non-empty mappings, return it
93
+ if string_options == 1:
94
+ if len(non_empty_mappings) > 1:
95
+ raise ValueError("Cannot combine multiple options if one is a string")
96
+ return next(m for m in non_empty_mappings if isinstance(m, str))
38
97
 
39
- # If there are duplicate keys across mappings, raise a ValueError
40
- intersection = set().union(*all_keys)
41
- if len(intersection) < sum(len(keys) for keys in all_keys):
42
- raise ValueError(f"Duplicate keys found: {intersection}")
98
+ # Start with an empty result and merge each mapping into it
99
+ result: Dict[str, Any] = {}
100
+ for mapping in non_empty_mappings:
101
+ if mapping and isinstance(mapping, Mapping):
102
+ _merge_mappings(result, mapping, allow_same_value_merge=allow_same_value_merge)
43
103
 
44
- # Return the combined mappings
45
- return {key: value for mapping in mappings if mapping for key, value in mapping.items()} # type: ignore # mapping can't be string here
104
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 6.32.0
3
+ Version: 6.33.0
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -55,7 +55,7 @@ airbyte_cdk/sources/declarative/auth/declarative_authenticator.py,sha256=nf-OmRU
55
55
  airbyte_cdk/sources/declarative/auth/jwt.py,sha256=SICqNsN2Cn_EgKadIgWuZpQxuMHyzrMZD_2-Uwy10rY,8539
56
56
  airbyte_cdk/sources/declarative/auth/oauth.py,sha256=fibXa-dqtM54jIUscWbz7DEA5uY6F2o1LfARjEeGRy0,13926
57
57
  airbyte_cdk/sources/declarative/auth/selective_authenticator.py,sha256=qGwC6YsCldr1bIeKG6Qo-A9a5cTdHw-vcOn3OtQrS4c,1540
58
- airbyte_cdk/sources/declarative/auth/token.py,sha256=r4u3WXyVa7WmiSZ9-eZXlrUI-pS0D4YWJnwjLzwV-Fk,11210
58
+ airbyte_cdk/sources/declarative/auth/token.py,sha256=2EnE78EhBOY9hbeZnQJ9AuFaM-G7dccU-oKo_LThRQk,11070
59
59
  airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=9CuSsmOoHkvlc4k-oZ3Jx5luAgfTMm1I_5HOZxw7wMU,3075
60
60
  airbyte_cdk/sources/declarative/checks/__init__.py,sha256=nsVV5Bo0E_tBNd8A4Xdsdb-75PpcLo5RQu2RQ_Gv-ME,806
61
61
  airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py,sha256=HUktywjI8pqOeED08UGqponUSwxs2TOAECTowlWlrRE,2138
@@ -63,11 +63,11 @@ airbyte_cdk/sources/declarative/checks/check_stream.py,sha256=dAA-UhmMj0WLXCkRQr
63
63
  airbyte_cdk/sources/declarative/checks/connection_checker.py,sha256=MBRJo6WJlZQHpIfOGaNOkkHUmgUl_4wDM6VPo41z5Ss,1383
64
64
  airbyte_cdk/sources/declarative/concurrency_level/__init__.py,sha256=5XUqrmlstYlMM0j6crktlKQwALek0uiz2D3WdM46MyA,191
65
65
  airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py,sha256=YIwCTCpOr_QSNW4ltQK0yUGWInI8PKNY216HOOegYLk,2101
66
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=x5a_Wv0chLAo2knBg-yQZoo5zUyfDsOuF4tEz3oHehc,27421
66
+ airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=7uqf_zQd2T08AYdMDJ80Zt0W1QHqZd-dvltXC-3g8W4,28136
67
67
  airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=l9LG7Qm6e5r_qgqfVKnx3mXYtg1I9MmMjomVIPfU4XA,177
68
68
  airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=SX9JjdesN1edN2WVUVMzU_ptqp2QB1OnsnjZ4mwcX7w,2579
69
69
  airbyte_cdk/sources/declarative/datetime/min_max_datetime.py,sha256=0BHBtDNQZfvwM45-tY5pNlTcKAFSGGNxemoi0Jic-0E,5785
70
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=43cBrshQer-KiNwjlA6peDyfrST88VQIZmfgdAZTiLc,139874
70
+ airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=51R-WLE1Xhrk57DoiHFxWZSP8V9HtCEi1UE8_KtEOuM,140375
71
71
  airbyte_cdk/sources/declarative/declarative_source.py,sha256=nF7wBqFd3AQmEKAm4CnIo29CJoQL562cJGSCeL8U8bA,1531
72
72
  airbyte_cdk/sources/declarative/declarative_stream.py,sha256=venZjfpvtqr3oFSuvMBWtn4h9ayLhD4L65ACuXCDZ64,10445
73
73
  airbyte_cdk/sources/declarative/decoders/__init__.py,sha256=KSpQetKGqPCv-38QgcVJ5kzM5nzbFldTSsYDCS3Xf0Y,1035
@@ -89,7 +89,7 @@ airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py,sha256=
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
91
  airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=5dbO47TFmC5Oz8TZ8DKXwXeZElz70xy2v2HJlZr5qVs,17751
92
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=_UzUnSIUsDbRgbFTXgSyZEFb4ws-KdhdQPWO8mFbV7U,22028
92
+ airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=5Bl_2EeA4as0e3J23Yxp8Q8BXzh0nJ2NcGSgj3V0h2o,21954
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
95
  airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py,sha256=9IAJTCiRUXvhFFz-IhZtYh_KfAjLHqthsYf2jErQRls,17728
@@ -109,20 +109,20 @@ airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW
109
109
  airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py,sha256=iemy3fKLczcU0-Aor7tx5jcT6DRedKMqyK7kCOp01hg,3924
110
110
  airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
111
111
  airbyte_cdk/sources/declarative/models/__init__.py,sha256=nUFxNCiKeYRVXuZEKA7GD-lTHxsiKcQ8FitZjKhPIvE,100
112
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=UJ9sYWqoohaT0mr0ALxOP74t0TTJRhfZMHBoqViaoVU,98273
112
+ airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=7qzq5ZV3V90uYRMa_-CGTbJ7aVv5jyPqGGjpJb9zdHk,98706
113
113
  airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
114
114
  airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=958MMX6_ZOJUlDDdNr9Krosgi2bCKGx2Z765M2Woz18,5505
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=f8gzijhJtUEJqNZvZihaFyBqrn8xqxjYrncgiXeKZY8,128557
118
+ airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=_BXGXZ6RP3m9KYk3NxhBQgYzrqAwud1gWTie204dUhY,128424
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=VelO7zKqKtzMJ35jyFeg0ypJLQC0plqqIBNXoBW1G2E,3001
121
121
  airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
122
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha256=t7pRdFWfFWJtQQG19c9PVeMODyO2BknRTakpM5U9N-8,4844
122
+ airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha256=tmGGpMoOBmaMfhVZq53AEWxoHm2lmNVi6hA2_IVEnAA,4882
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=pEz-P6D5TGtP4isNfmtakgKD95PqMLo6fasCVLIguWk,16760
125
+ airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=sqNF8hPdDn7EUCB2-Y-8xtZFPO1ncVNrCsdPljdczJ8,16575
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
@@ -139,9 +139,9 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_
139
139
  airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py,sha256=Tan66odx8VHzfdyyXMQkXz2pJYksllGqvxmpoajgcK4,669
140
140
  airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py,sha256=E-fQbt4ShfxZVoqfnmOx69C6FUPWZz8BIqI3DN9Kcjs,7935
141
141
  airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=3GtOefPH08evlSUxaILkiKLTHbIspFY4qd5B3ZqNE60,10063
142
- airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=RqYPkgJFAWfcZBTc-JBcGHPm4JL1ZQOhs9GKU4MP2eE,14723
142
+ airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=C6YT7t4UZMfarFeQ9fc362R5TbQ2jNSew8ESP-9yuZQ,14851
143
143
  airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
144
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=FnSl3qPvv5wD6ieAI2Ic5c4dqBk-3fRe4tCaWzq3YwM,11840
144
+ airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=dSm_pKGOZjzvg-X_Vif-MjrnlUG23fCa69bocq8dVIs,11693
145
145
  airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py,sha256=j6j9QRPaTbKQ2N661RFVKthhkWiodEp6ut0tKeEd0Ng,2019
146
146
  airbyte_cdk/sources/declarative/requesters/paginators/paginator.py,sha256=OlN-y0PEOMzlUNUh3pzonoTpIJpGwkP4ibFengvpLVU,2230
147
147
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py,sha256=2gly8fuZpDNwtu1Qg6oE2jBLGqQRdzSLJdnpk_iDV6I,767
@@ -150,9 +150,9 @@ airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_incremen
150
150
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py,sha256=Z2i6a-oKMmOTxHxsTVSnyaShkJ3u8xZw1xIJdx2yxss,2731
151
151
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py,sha256=ZBshGQNr5Bb_V8dqnWRISqdXFcjm1CKIXnlfbRhNl8g,1308
152
152
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py,sha256=LoKXdUbSgHEtSwtA8DFrnX6SpQbRVVwreY8NguTKTcI,2229
153
- airbyte_cdk/sources/declarative/requesters/request_option.py,sha256=_qmv8CLQQ3fERt6BuMZeRu6tZXscPoeARx1VJdWMQ_M,1055
153
+ airbyte_cdk/sources/declarative/requesters/request_option.py,sha256=Bl0gxGWudmwT3FXBozTN00WYle2jd6ry_S1YylCnwqM,4825
154
154
  airbyte_cdk/sources/declarative/requesters/request_options/__init__.py,sha256=WCwpKqM4wKqy-DHJaCHbKAlFqRVOqMi9K5qonxIfi_Y,809
155
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py,sha256=FLkg0uzC9bc-zFnALWr0FLYpKsz8iK2xQsd4UOyeW08,3706
155
+ airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py,sha256=31nG6_0igidJFQon37-WeQkTpG3g2A5ZmlluI3ilZdE,3632
156
156
  airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py,sha256=SRROdPJZ5kuqHLOlkh115pWP9nDGfDxRYPgH9oD3hPo,1798
157
157
  airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py,sha256=UW4cAtzkQ261AyLI1cmCL2WLdI3ZDYGUTmrqKB9W3u8,2422
158
158
  airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py,sha256=Vr2-qa8iHC0vJ4cCtPl7lAUlhrnl4lUuPLMSFrzxMIg,3024
@@ -167,7 +167,7 @@ airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py,sha256=Aio
167
167
  airbyte_cdk/sources/declarative/retrievers/__init__.py,sha256=ix9m1dkR69DcXCXUKC5RK_ZZM7ojTLBQ4IkWQTfmfCk,456
168
168
  airbyte_cdk/sources/declarative/retrievers/async_retriever.py,sha256=2oQn_vo7uJKp4pdMnsF5CG5Iwc9rkPeEOLoAm_9bcus,3222
169
169
  airbyte_cdk/sources/declarative/retrievers/retriever.py,sha256=XPLs593Xv8c5cKMc37XzUAYmzlXd1a7eSsspM-CMuWA,1696
170
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=kgnhVQxRlFqJs2-rDu2-QH-p-GzQU3nKmSp6_aq8u0s,24550
170
+ airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=uvsBqSUimi85YfSjPuOUoAlewwtvaYwgsLg2EDcswLE,24665
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
173
  airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py,sha256=J8Q_iJYhcSQLWyt0bTZCbDAGpxt9G8FCc6Q9jtGsNzw,10703
@@ -342,7 +342,7 @@ airbyte_cdk/utils/datetime_format_inferrer.py,sha256=Ne2cpk7Tx3eZDEW2Q3O7jnNOY9g
342
342
  airbyte_cdk/utils/datetime_helpers.py,sha256=8mqzZ67Or2PBp7tLtrhh6XFv4wFzYsjCL_DOQJRaftI,17751
343
343
  airbyte_cdk/utils/event_timing.py,sha256=aiuFmPU80buLlNdKq4fDTEqqhEIelHPF6AalFGwY8as,2557
344
344
  airbyte_cdk/utils/is_cloud_environment.py,sha256=DayV32Irh-SdnJ0MnjvstwCJ66_l5oEsd8l85rZtHoc,574
345
- airbyte_cdk/utils/mapping_helpers.py,sha256=H7BH-Yr8hSRG4W0zZcQ0ZzKOY5QFn8fNkGcEsE3xZN8,1736
345
+ airbyte_cdk/utils/mapping_helpers.py,sha256=4EOyUzNAGkq-M0QF5rPeBfT4v_eV7qBrEaAtsTH1k8Y,4309
346
346
  airbyte_cdk/utils/message_utils.py,sha256=OTzbkwN7AdMDA3iKYq1LKwfPFxpyEDfdgEF9BED3dkU,1366
347
347
  airbyte_cdk/utils/oneof_option_config.py,sha256=N8EmWdYdwt0FM7fuShh6H8nj_r4KEL9tb2DJJtwsPow,1180
348
348
  airbyte_cdk/utils/print_buffer.py,sha256=PhMOi0C4Z91kWKrSvCQXcp8qRh1uCimpIdvrg6voZIA,2810
@@ -351,9 +351,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
351
351
  airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
352
352
  airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
353
353
  airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
354
- airbyte_cdk-6.32.0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
355
- airbyte_cdk-6.32.0.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
356
- airbyte_cdk-6.32.0.dist-info/METADATA,sha256=y_ghboeo7rvFlmHSnjuS0EuII2y0kvZzRcW2tEN1oqE,6010
357
- airbyte_cdk-6.32.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
358
- airbyte_cdk-6.32.0.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
359
- airbyte_cdk-6.32.0.dist-info/RECORD,,
354
+ airbyte_cdk-6.33.0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
355
+ airbyte_cdk-6.33.0.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
356
+ airbyte_cdk-6.33.0.dist-info/METADATA,sha256=zem-s1VWtFeGuvTO6ohvvi687MVXqmQ_QrcRWM6KnAM,6010
357
+ airbyte_cdk-6.33.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
358
+ airbyte_cdk-6.33.0.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
359
+ airbyte_cdk-6.33.0.dist-info/RECORD,,