airbyte-cdk 6.33.6__py3-none-any.whl → 6.33.7__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.
@@ -21,6 +21,7 @@ from airbyte_cdk.sources.declarative.requesters.request_option import (
21
21
  )
22
22
  from airbyte_cdk.sources.message import MessageRepository
23
23
  from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
24
+ from airbyte_cdk.utils.mapping_helpers import _validate_component_request_option_paths
24
25
 
25
26
 
26
27
  @dataclass
@@ -122,6 +123,10 @@ class DatetimeBasedCursor(DeclarativeCursor):
122
123
  if not self.cursor_datetime_formats:
123
124
  self.cursor_datetime_formats = [self.datetime_format]
124
125
 
126
+ _validate_component_request_option_paths(
127
+ self.config, self.start_time_option, self.end_time_option
128
+ )
129
+
125
130
  def get_stream_state(self) -> StreamState:
126
131
  return {self.cursor_field.eval(self.config): self._cursor} if self._cursor else {} # type: ignore # cursor_field is converted to an InterpolatedString in __post_init__
127
132
 
@@ -23,6 +23,9 @@ from airbyte_cdk.sources.declarative.requesters.request_option import (
23
23
  )
24
24
  from airbyte_cdk.sources.declarative.requesters.request_path import RequestPath
25
25
  from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
26
+ from airbyte_cdk.utils.mapping_helpers import (
27
+ _validate_component_request_option_paths,
28
+ )
26
29
 
27
30
 
28
31
  @dataclass
@@ -113,6 +116,13 @@ class DefaultPaginator(Paginator):
113
116
  if isinstance(self.url_base, str):
114
117
  self.url_base = InterpolatedString(string=self.url_base, parameters=parameters)
115
118
 
119
+ if self.page_token_option and not isinstance(self.page_token_option, RequestPath):
120
+ _validate_component_request_option_paths(
121
+ self.config,
122
+ self.page_size_option,
123
+ self.page_token_option,
124
+ )
125
+
116
126
  def get_initial_token(self) -> Optional[Any]:
117
127
  """
118
128
  Return the page token that should be used for the first request of a stream
@@ -17,6 +17,7 @@ class SupportedHttpMethods(str, Enum):
17
17
  GET = "get"
18
18
  PATCH = "patch"
19
19
  POST = "post"
20
+ PUT = "put"
20
21
  DELETE = "delete"
21
22
 
22
23
 
@@ -77,7 +78,7 @@ class HttpMocker(contextlib.ContextDecorator):
77
78
  additional_matcher=self._matches_wrapper(matcher),
78
79
  response_list=[
79
80
  {
80
- "text": response.body,
81
+ self._get_body_field(response): response.body,
81
82
  "status_code": response.status_code,
82
83
  "headers": response.headers,
83
84
  }
@@ -85,6 +86,10 @@ class HttpMocker(contextlib.ContextDecorator):
85
86
  ],
86
87
  )
87
88
 
89
+ @staticmethod
90
+ def _get_body_field(response: HttpResponse) -> str:
91
+ return "text" if isinstance(response.body, str) else "content"
92
+
88
93
  def get(self, request: HttpRequest, responses: Union[HttpResponse, List[HttpResponse]]) -> None:
89
94
  self._mock_request_method(SupportedHttpMethods.GET, request, responses)
90
95
 
@@ -98,6 +103,9 @@ class HttpMocker(contextlib.ContextDecorator):
98
103
  ) -> None:
99
104
  self._mock_request_method(SupportedHttpMethods.POST, request, responses)
100
105
 
106
+ def put(self, request: HttpRequest, responses: Union[HttpResponse, List[HttpResponse]]) -> None:
107
+ self._mock_request_method(SupportedHttpMethods.PUT, request, responses)
108
+
101
109
  def delete(
102
110
  self, request: HttpRequest, responses: Union[HttpResponse, List[HttpResponse]]
103
111
  ) -> None:
@@ -1,19 +1,22 @@
1
1
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
2
2
 
3
3
  from types import MappingProxyType
4
- from typing import Mapping
4
+ from typing import Mapping, Union
5
5
 
6
6
 
7
7
  class HttpResponse:
8
8
  def __init__(
9
- self, body: str, status_code: int = 200, headers: Mapping[str, str] = MappingProxyType({})
9
+ self,
10
+ body: Union[str, bytes],
11
+ status_code: int = 200,
12
+ headers: Mapping[str, str] = MappingProxyType({}),
10
13
  ):
11
14
  self._body = body
12
15
  self._status_code = status_code
13
16
  self._headers = headers
14
17
 
15
18
  @property
16
- def body(self) -> str:
19
+ def body(self) -> Union[str, bytes]:
17
20
  return self._body
18
21
 
19
22
  @property
@@ -6,6 +6,12 @@
6
6
  import copy
7
7
  from typing import Any, Dict, List, Mapping, Optional, Union
8
8
 
9
+ from airbyte_cdk.sources.declarative.requesters.request_option import (
10
+ RequestOption,
11
+ RequestOptionType,
12
+ )
13
+ from airbyte_cdk.sources.types import Config
14
+
9
15
 
10
16
  def _merge_mappings(
11
17
  target: Dict[str, Any],
@@ -33,13 +39,17 @@ def _merge_mappings(
33
39
  if isinstance(target_value, dict) and isinstance(source_value, dict):
34
40
  # Only body_json supports nested_structures
35
41
  if not allow_same_value_merge:
36
- raise ValueError(f"Duplicate keys found: {'.'.join(current_path)}")
42
+ raise ValueError(
43
+ f"Request body collision, duplicate keys detected at key path: {'.'.join(current_path)}. Please ensure that all keys in the request are unique."
44
+ )
37
45
  # If both are dictionaries, recursively merge them
38
46
  _merge_mappings(target_value, source_value, current_path, allow_same_value_merge)
39
47
 
40
48
  elif not allow_same_value_merge or target_value != source_value:
41
49
  # If same key has different values, that's a conflict
42
- raise ValueError(f"Duplicate keys found: {'.'.join(current_path)}")
50
+ raise ValueError(
51
+ f"Request body collision, duplicate keys detected at key path: {'.'.join(current_path)}. Please ensure that all keys in the request are unique."
52
+ )
43
53
  else:
44
54
  # No conflict, just copy the value (using deepcopy for nested structures)
45
55
  target[key] = copy.deepcopy(source_value)
@@ -102,3 +112,34 @@ def combine_mappings(
102
112
  _merge_mappings(result, mapping, allow_same_value_merge=allow_same_value_merge)
103
113
 
104
114
  return result
115
+
116
+
117
+ def _validate_component_request_option_paths(
118
+ config: Config, *request_options: Optional[RequestOption]
119
+ ) -> None:
120
+ """
121
+ Validates that a component with multiple request options does not have conflicting paths.
122
+ Uses dummy values for validation since actual values might not be available at init time.
123
+ """
124
+ grouped_options: Dict[RequestOptionType, List[RequestOption]] = {}
125
+ for option in request_options:
126
+ if option:
127
+ grouped_options.setdefault(option.inject_into, []).append(option)
128
+
129
+ for inject_type, options in grouped_options.items():
130
+ if len(options) <= 1:
131
+ continue
132
+
133
+ option_dicts: List[Optional[Union[Mapping[str, Any], str]]] = []
134
+ for i, option in enumerate(options):
135
+ option_dict: Dict[str, Any] = {}
136
+ # Use indexed dummy values to ensure we catch conflicts
137
+ option.inject_into_request(option_dict, f"dummy_value_{i}", config)
138
+ option_dicts.append(option_dict)
139
+
140
+ try:
141
+ combine_mappings(
142
+ option_dicts, allow_same_value_merge=(inject_type == RequestOptionType.body_json)
143
+ )
144
+ except ValueError as error:
145
+ raise ValueError(error)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 6.33.6
3
+ Version: 6.33.7
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -93,7 +93,7 @@ airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py,sha256=
93
93
  airbyte_cdk/sources/declarative/extractors/type_transformer.py,sha256=d6Y2Rfg8pMVEEnHllfVksWZdNVOU55yk34O03dP9muY,1626
94
94
  airbyte_cdk/sources/declarative/incremental/__init__.py,sha256=U1oZKtBaEC6IACmvziY9Wzg7Z8EgF4ZuR7NwvjlB_Sk,1255
95
95
  airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=5dbO47TFmC5Oz8TZ8DKXwXeZElz70xy2v2HJlZr5qVs,17751
96
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=5Bl_2EeA4as0e3J23Yxp8Q8BXzh0nJ2NcGSgj3V0h2o,21954
96
+ airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=Rbe6lJLTtZ5en33MwZiB9-H9-AwDMNHgwBZs8EqhYqk,22172
97
97
  airbyte_cdk/sources/declarative/incremental/declarative_cursor.py,sha256=5Bhw9VRPyIuCaD0wmmq_L3DZsa-rJgtKSEUzSd8YYD0,536
98
98
  airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py,sha256=9HO-QbL9akvjq2NP7l498RwLA4iQZlBMQW1tZbt34I8,15943
99
99
  airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py,sha256=9IAJTCiRUXvhFFz-IhZtYh_KfAjLHqthsYf2jErQRls,17728
@@ -145,7 +145,7 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.p
145
145
  airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=3GtOefPH08evlSUxaILkiKLTHbIspFY4qd5B3ZqNE60,10063
146
146
  airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=Ek5hS60-CYjvEaFD-bI7qA-bPgbOPb9hTbMBU4n5zNs,14994
147
147
  airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
148
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=dSm_pKGOZjzvg-X_Vif-MjrnlUG23fCa69bocq8dVIs,11693
148
+ airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=ZW4lwWNAzb4zL0jKc-HjowP5-y0Zg9xi0YlK6tkx_XY,12057
149
149
  airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py,sha256=j6j9QRPaTbKQ2N661RFVKthhkWiodEp6ut0tKeEd0Ng,2019
150
150
  airbyte_cdk/sources/declarative/requesters/paginators/paginator.py,sha256=OlN-y0PEOMzlUNUh3pzonoTpIJpGwkP4ibFengvpLVU,2230
151
151
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py,sha256=2gly8fuZpDNwtu1Qg6oE2jBLGqQRdzSLJdnpk_iDV6I,767
@@ -333,9 +333,9 @@ airbyte_cdk/test/catalog_builder.py,sha256=-y05Cz1x0Dlk6oE9LSKhCozssV2gYBNtMdV5Y
333
333
  airbyte_cdk/test/entrypoint_wrapper.py,sha256=9XBii_YguQp0d8cykn3hy102FsJcwIBQzSB7co5ho0s,9802
334
334
  airbyte_cdk/test/mock_http/__init__.py,sha256=jE5kC6CQ0OXkTqKhciDnNVZHesBFVIA2YvkdFGwva7k,322
335
335
  airbyte_cdk/test/mock_http/matcher.py,sha256=4Qj8UnJKZIs-eodshryce3SN1Ayc8GZpBETmP6hTEyc,1446
336
- airbyte_cdk/test/mock_http/mocker.py,sha256=HJjgFdapr7OALj0sfk-LVXYBiymbUDieaGa8U1_q730,7358
336
+ airbyte_cdk/test/mock_http/mocker.py,sha256=ghX44cLwhs7lqz1gYMizGX8zfPnDvt3YNI2w5jLpzIs,7726
337
337
  airbyte_cdk/test/mock_http/request.py,sha256=tdB8cqk2vLgCDTOKffBKsM06llYs4ZecgtH6DKyx6yY,4112
338
- airbyte_cdk/test/mock_http/response.py,sha256=U9KEsUkK2dPXYwnfwrwp6CcYSSpMYKLjfTrPFKSMCaM,602
338
+ airbyte_cdk/test/mock_http/response.py,sha256=s4-cQQqTtmeej0pQDWqmG0vUWpHS-93lIWMpW3zSVyU,662
339
339
  airbyte_cdk/test/mock_http/response_builder.py,sha256=debPx_lRYBaQVSwCoKLa0F8KFk3h0qG7bWxFBATa0cc,7958
340
340
  airbyte_cdk/test/state_builder.py,sha256=kLPql9lNzUJaBg5YYRLJlY_Hy5JLHJDVyKPMZMoYM44,946
341
341
  airbyte_cdk/test/utils/__init__.py,sha256=Hu-1XT2KDoYjDF7-_ziDwv5bY3PueGjANOCbzeOegDg,57
@@ -351,7 +351,7 @@ airbyte_cdk/utils/datetime_format_inferrer.py,sha256=Ne2cpk7Tx3eZDEW2Q3O7jnNOY9g
351
351
  airbyte_cdk/utils/datetime_helpers.py,sha256=8mqzZ67Or2PBp7tLtrhh6XFv4wFzYsjCL_DOQJRaftI,17751
352
352
  airbyte_cdk/utils/event_timing.py,sha256=aiuFmPU80buLlNdKq4fDTEqqhEIelHPF6AalFGwY8as,2557
353
353
  airbyte_cdk/utils/is_cloud_environment.py,sha256=DayV32Irh-SdnJ0MnjvstwCJ66_l5oEsd8l85rZtHoc,574
354
- airbyte_cdk/utils/mapping_helpers.py,sha256=4EOyUzNAGkq-M0QF5rPeBfT4v_eV7qBrEaAtsTH1k8Y,4309
354
+ airbyte_cdk/utils/mapping_helpers.py,sha256=imUTULHmZ1Ks-MRMRLIVqHCX1eJi_j6tFQrYsKIKtM4,5967
355
355
  airbyte_cdk/utils/message_utils.py,sha256=OTzbkwN7AdMDA3iKYq1LKwfPFxpyEDfdgEF9BED3dkU,1366
356
356
  airbyte_cdk/utils/oneof_option_config.py,sha256=N8EmWdYdwt0FM7fuShh6H8nj_r4KEL9tb2DJJtwsPow,1180
357
357
  airbyte_cdk/utils/print_buffer.py,sha256=PhMOi0C4Z91kWKrSvCQXcp8qRh1uCimpIdvrg6voZIA,2810
@@ -360,9 +360,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
360
360
  airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
361
361
  airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
362
362
  airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
363
- airbyte_cdk-6.33.6.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
364
- airbyte_cdk-6.33.6.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
365
- airbyte_cdk-6.33.6.dist-info/METADATA,sha256=b79F_9jrOfMU8OTn1MJm9vsET_aXDQbiRYtYaZu5xlM,6010
366
- airbyte_cdk-6.33.6.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
367
- airbyte_cdk-6.33.6.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
368
- airbyte_cdk-6.33.6.dist-info/RECORD,,
363
+ airbyte_cdk-6.33.7.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
364
+ airbyte_cdk-6.33.7.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
365
+ airbyte_cdk-6.33.7.dist-info/METADATA,sha256=wx51UyfmmCxI6vcmkCr28bbvwR6P5gcokPbuCEwS83Q,6010
366
+ airbyte_cdk-6.33.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
367
+ airbyte_cdk-6.33.7.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
368
+ airbyte_cdk-6.33.7.dist-info/RECORD,,