airbyte-cdk 6.36.1__py3-none-any.whl → 6.36.3__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 (22) hide show
  1. airbyte_cdk/connector_builder/models.py +16 -14
  2. airbyte_cdk/connector_builder/test_reader/helpers.py +120 -22
  3. airbyte_cdk/connector_builder/test_reader/message_grouper.py +16 -3
  4. airbyte_cdk/connector_builder/test_reader/types.py +9 -1
  5. airbyte_cdk/sources/declarative/auth/token_provider.py +1 -0
  6. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +21 -3
  7. airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +1 -0
  8. airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +83 -17
  9. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +2 -2
  10. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +49 -30
  11. airbyte_cdk/sources/declarative/requesters/README.md +5 -5
  12. airbyte_cdk/sources/declarative/requesters/http_job_repository.py +60 -17
  13. airbyte_cdk/sources/declarative/requesters/http_requester.py +7 -1
  14. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +10 -3
  15. airbyte_cdk/sources/http_logger.py +3 -0
  16. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +1 -0
  17. {airbyte_cdk-6.36.1.dist-info → airbyte_cdk-6.36.3.dist-info}/METADATA +1 -1
  18. {airbyte_cdk-6.36.1.dist-info → airbyte_cdk-6.36.3.dist-info}/RECORD +22 -22
  19. {airbyte_cdk-6.36.1.dist-info → airbyte_cdk-6.36.3.dist-info}/LICENSE.txt +0 -0
  20. {airbyte_cdk-6.36.1.dist-info → airbyte_cdk-6.36.3.dist-info}/LICENSE_SHORT +0 -0
  21. {airbyte_cdk-6.36.1.dist-info → airbyte_cdk-6.36.3.dist-info}/WHEEL +0 -0
  22. {airbyte_cdk-6.36.1.dist-info → airbyte_cdk-6.36.3.dist-info}/entry_points.txt +0 -0
@@ -21,20 +21,6 @@ class HttpRequest:
21
21
  body: Optional[str] = None
22
22
 
23
23
 
24
- @dataclass
25
- class StreamReadPages:
26
- records: List[object]
27
- request: Optional[HttpRequest] = None
28
- response: Optional[HttpResponse] = None
29
-
30
-
31
- @dataclass
32
- class StreamReadSlices:
33
- pages: List[StreamReadPages]
34
- slice_descriptor: Optional[Dict[str, Any]]
35
- state: Optional[List[Dict[str, Any]]] = None
36
-
37
-
38
24
  @dataclass
39
25
  class LogMessage:
40
26
  message: str
@@ -46,11 +32,27 @@ class LogMessage:
46
32
  @dataclass
47
33
  class AuxiliaryRequest:
48
34
  title: str
35
+ type: str
49
36
  description: str
50
37
  request: HttpRequest
51
38
  response: HttpResponse
52
39
 
53
40
 
41
+ @dataclass
42
+ class StreamReadPages:
43
+ records: List[object]
44
+ request: Optional[HttpRequest] = None
45
+ response: Optional[HttpResponse] = None
46
+
47
+
48
+ @dataclass
49
+ class StreamReadSlices:
50
+ pages: List[StreamReadPages]
51
+ slice_descriptor: Optional[Dict[str, Any]]
52
+ state: Optional[List[Dict[str, Any]]] = None
53
+ auxiliary_requests: Optional[List[AuxiliaryRequest]] = None
54
+
55
+
54
56
  @dataclass
55
57
  class StreamRead(object):
56
58
  logs: List[LogMessage]
@@ -28,7 +28,7 @@ from airbyte_cdk.utils.schema_inferrer import (
28
28
  SchemaInferrer,
29
29
  )
30
30
 
31
- from .types import LOG_MESSAGES_OUTPUT_TYPE
31
+ from .types import ASYNC_AUXILIARY_REQUEST_TYPES, LOG_MESSAGES_OUTPUT_TYPE
32
32
 
33
33
  # -------
34
34
  # Parsers
@@ -226,7 +226,8 @@ def should_close_page(
226
226
  at_least_one_page_in_group
227
227
  and is_log_message(message)
228
228
  and (
229
- is_page_http_request(json_message) or message.log.message.startswith("slice:") # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
229
+ is_page_http_request(json_message)
230
+ or message.log.message.startswith(SliceLogger.SLICE_LOG_PREFIX) # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
230
231
  )
231
232
  )
232
233
 
@@ -330,6 +331,10 @@ def is_auxiliary_http_request(message: Optional[Dict[str, Any]]) -> bool:
330
331
  return is_http_log(message) and message.get("http", {}).get("is_auxiliary", False)
331
332
 
332
333
 
334
+ def is_async_auxiliary_request(message: AuxiliaryRequest) -> bool:
335
+ return message.type in ASYNC_AUXILIARY_REQUEST_TYPES
336
+
337
+
333
338
  def is_log_message(message: AirbyteMessage) -> bool:
334
339
  """
335
340
  Determines whether the provided message is of type LOG.
@@ -413,6 +418,7 @@ def handle_current_slice(
413
418
  current_slice_pages: List[StreamReadPages],
414
419
  current_slice_descriptor: Optional[Dict[str, Any]] = None,
415
420
  latest_state_message: Optional[Dict[str, Any]] = None,
421
+ auxiliary_requests: Optional[List[AuxiliaryRequest]] = None,
416
422
  ) -> StreamReadSlices:
417
423
  """
418
424
  Handles the current slice by packaging its pages, descriptor, and state into a StreamReadSlices instance.
@@ -421,6 +427,7 @@ def handle_current_slice(
421
427
  current_slice_pages (List[StreamReadPages]): The pages to be included in the slice.
422
428
  current_slice_descriptor (Optional[Dict[str, Any]]): Descriptor for the current slice, optional.
423
429
  latest_state_message (Optional[Dict[str, Any]]): The latest state message, optional.
430
+ auxiliary_requests (Optional[List[AuxiliaryRequest]]): The auxiliary requests to include, optional.
424
431
 
425
432
  Returns:
426
433
  StreamReadSlices: An object containing the current slice's pages, descriptor, and state.
@@ -429,6 +436,7 @@ def handle_current_slice(
429
436
  pages=current_slice_pages,
430
437
  slice_descriptor=current_slice_descriptor,
431
438
  state=[latest_state_message] if latest_state_message else [],
439
+ auxiliary_requests=auxiliary_requests if auxiliary_requests else [],
432
440
  )
433
441
 
434
442
 
@@ -486,29 +494,24 @@ def handle_auxiliary_request(json_message: Dict[str, JsonType]) -> AuxiliaryRequ
486
494
  Raises:
487
495
  ValueError: If any of the "airbyte_cdk", "stream", or "http" fields is not a dictionary.
488
496
  """
489
- airbyte_cdk = json_message.get("airbyte_cdk", {})
490
-
491
- if not isinstance(airbyte_cdk, dict):
492
- raise ValueError(
493
- f"Expected airbyte_cdk to be a dict, got {airbyte_cdk} of type {type(airbyte_cdk)}"
494
- )
495
-
496
- stream = airbyte_cdk.get("stream", {})
497
497
 
498
- if not isinstance(stream, dict):
499
- raise ValueError(f"Expected stream to be a dict, got {stream} of type {type(stream)}")
498
+ airbyte_cdk = get_airbyte_cdk_from_message(json_message)
499
+ stream = get_stream_from_airbyte_cdk(airbyte_cdk)
500
+ title_prefix = get_auxiliary_request_title_prefix(stream)
501
+ http = get_http_property_from_message(json_message)
502
+ request_type = get_auxiliary_request_type(stream, http)
500
503
 
501
- title_prefix = "Parent stream: " if stream.get("is_substream", False) else ""
502
- http = json_message.get("http", {})
503
-
504
- if not isinstance(http, dict):
505
- raise ValueError(f"Expected http to be a dict, got {http} of type {type(http)}")
504
+ title = title_prefix + str(http.get("title", None))
505
+ description = str(http.get("description", None))
506
+ request = create_request_from_log_message(json_message)
507
+ response = create_response_from_log_message(json_message)
506
508
 
507
509
  return AuxiliaryRequest(
508
- title=title_prefix + str(http.get("title", None)),
509
- description=str(http.get("description", None)),
510
- request=create_request_from_log_message(json_message),
511
- response=create_response_from_log_message(json_message),
510
+ title=title,
511
+ type=request_type,
512
+ description=description,
513
+ request=request,
514
+ response=response,
512
515
  )
513
516
 
514
517
 
@@ -558,7 +561,8 @@ def handle_log_message(
558
561
  at_least_one_page_in_group,
559
562
  current_page_request,
560
563
  current_page_response,
561
- auxiliary_request or log_message,
564
+ auxiliary_request,
565
+ log_message,
562
566
  )
563
567
 
564
568
 
@@ -589,3 +593,97 @@ def handle_record_message(
589
593
  datetime_format_inferrer.accumulate(message.record) # type: ignore
590
594
 
591
595
  return records_count
596
+
597
+
598
+ # -------
599
+ # Reusable Getters
600
+ # -------
601
+
602
+
603
+ def get_airbyte_cdk_from_message(json_message: Dict[str, JsonType]) -> dict: # type: ignore
604
+ """
605
+ Retrieves the "airbyte_cdk" dictionary from the provided JSON message.
606
+
607
+ This function validates that the extracted "airbyte_cdk" is of type dict,
608
+ raising a ValueError if the validation fails.
609
+
610
+ Parameters:
611
+ json_message (Dict[str, JsonType]): A dictionary representing the JSON message.
612
+
613
+ Returns:
614
+ dict: The "airbyte_cdk" dictionary extracted from the JSON message.
615
+
616
+ Raises:
617
+ ValueError: If the "airbyte_cdk" field is not a dictionary.
618
+ """
619
+ airbyte_cdk = json_message.get("airbyte_cdk", {})
620
+
621
+ if not isinstance(airbyte_cdk, dict):
622
+ raise ValueError(
623
+ f"Expected airbyte_cdk to be a dict, got {airbyte_cdk} of type {type(airbyte_cdk)}"
624
+ )
625
+
626
+ return airbyte_cdk
627
+
628
+
629
+ def get_stream_from_airbyte_cdk(airbyte_cdk: dict) -> dict: # type: ignore
630
+ """
631
+ Retrieves the "stream" dictionary from the provided "airbyte_cdk" dictionary.
632
+
633
+ This function ensures that the extracted "stream" is of type dict,
634
+ raising a ValueError if the validation fails.
635
+
636
+ Parameters:
637
+ airbyte_cdk (dict): The dictionary representing the Airbyte CDK data.
638
+
639
+ Returns:
640
+ dict: The "stream" dictionary extracted from the Airbyte CDK data.
641
+
642
+ Raises:
643
+ ValueError: If the "stream" field is not a dictionary.
644
+ """
645
+
646
+ stream = airbyte_cdk.get("stream", {})
647
+
648
+ if not isinstance(stream, dict):
649
+ raise ValueError(f"Expected stream to be a dict, got {stream} of type {type(stream)}")
650
+
651
+ return stream
652
+
653
+
654
+ def get_auxiliary_request_title_prefix(stream: dict) -> str: # type: ignore
655
+ """
656
+ Generates a title prefix based on the stream type.
657
+ """
658
+ return "Parent stream: " if stream.get("is_substream", False) else ""
659
+
660
+
661
+ def get_http_property_from_message(json_message: Dict[str, JsonType]) -> dict: # type: ignore
662
+ """
663
+ Retrieves the "http" dictionary from the provided JSON message.
664
+
665
+ This function validates that the extracted "http" is of type dict,
666
+ raising a ValueError if the validation fails.
667
+
668
+ Parameters:
669
+ json_message (Dict[str, JsonType]): A dictionary representing the JSON message.
670
+
671
+ Returns:
672
+ dict: The "http" dictionary extracted from the JSON message.
673
+
674
+ Raises:
675
+ ValueError: If the "http" field is not a dictionary.
676
+ """
677
+ http = json_message.get("http", {})
678
+
679
+ if not isinstance(http, dict):
680
+ raise ValueError(f"Expected http to be a dict, got {http} of type {type(http)}")
681
+
682
+ return http
683
+
684
+
685
+ def get_auxiliary_request_type(stream: dict, http: dict) -> str: # type: ignore
686
+ """
687
+ Determines the type of the auxiliary request based on the stream and HTTP properties.
688
+ """
689
+ return "PARENT_STREAM" if stream.get("is_substream", False) else str(http.get("type", None))
@@ -6,6 +6,7 @@
6
6
  from typing import Any, Dict, Iterator, List, Mapping, Optional
7
7
 
8
8
  from airbyte_cdk.connector_builder.models import (
9
+ AuxiliaryRequest,
9
10
  HttpRequest,
10
11
  HttpResponse,
11
12
  StreamReadPages,
@@ -24,6 +25,7 @@ from .helpers import (
24
25
  handle_current_slice,
25
26
  handle_log_message,
26
27
  handle_record_message,
28
+ is_async_auxiliary_request,
27
29
  is_config_update_message,
28
30
  is_log_message,
29
31
  is_record_message,
@@ -89,6 +91,7 @@ def get_message_groups(
89
91
  current_page_request: Optional[HttpRequest] = None
90
92
  current_page_response: Optional[HttpResponse] = None
91
93
  latest_state_message: Optional[Dict[str, Any]] = None
94
+ slice_auxiliary_requests: List[AuxiliaryRequest] = []
92
95
 
93
96
  while records_count < limit and (message := next(messages, None)):
94
97
  json_message = airbyte_message_to_json(message)
@@ -106,6 +109,7 @@ def get_message_groups(
106
109
  current_slice_pages,
107
110
  current_slice_descriptor,
108
111
  latest_state_message,
112
+ slice_auxiliary_requests,
109
113
  )
110
114
  current_slice_descriptor = parse_slice_description(message.log.message) # type: ignore
111
115
  current_slice_pages = []
@@ -118,7 +122,8 @@ def get_message_groups(
118
122
  at_least_one_page_in_group,
119
123
  current_page_request,
120
124
  current_page_response,
121
- log_or_auxiliary_request,
125
+ auxiliary_request,
126
+ log_message,
122
127
  ) = handle_log_message(
123
128
  message,
124
129
  json_message,
@@ -126,8 +131,15 @@ def get_message_groups(
126
131
  current_page_request,
127
132
  current_page_response,
128
133
  )
129
- if log_or_auxiliary_request:
130
- yield log_or_auxiliary_request
134
+
135
+ if auxiliary_request:
136
+ if is_async_auxiliary_request(auxiliary_request):
137
+ slice_auxiliary_requests.append(auxiliary_request)
138
+ else:
139
+ yield auxiliary_request
140
+
141
+ if log_message:
142
+ yield log_message
131
143
  elif is_trace_with_error(message):
132
144
  if message.trace is not None:
133
145
  yield message.trace
@@ -157,4 +169,5 @@ def get_message_groups(
157
169
  current_slice_pages,
158
170
  current_slice_descriptor,
159
171
  latest_state_message,
172
+ slice_auxiliary_requests,
160
173
  )
@@ -71,5 +71,13 @@ LOG_MESSAGES_OUTPUT_TYPE = tuple[
71
71
  bool,
72
72
  HttpRequest | None,
73
73
  HttpResponse | None,
74
- AuxiliaryRequest | AirbyteLogMessage | None,
74
+ AuxiliaryRequest | None,
75
+ AirbyteLogMessage | None,
76
+ ]
77
+
78
+ ASYNC_AUXILIARY_REQUEST_TYPES = [
79
+ "ASYNC_CREATE",
80
+ "ASYNC_POLL",
81
+ "ASYNC_ABORT",
82
+ "ASYNC_DELETE",
75
83
  ]
@@ -58,6 +58,7 @@ class SessionTokenProvider(TokenProvider):
58
58
  "Obtains session token",
59
59
  None,
60
60
  is_auxiliary=True,
61
+ type="AUTH",
61
62
  ),
62
63
  )
63
64
  if response is None:
@@ -1779,6 +1779,9 @@ definitions:
1779
1779
  - stream_interval
1780
1780
  - stream_partition
1781
1781
  - stream_slice
1782
+ - creation_response
1783
+ - polling_response
1784
+ - download_target
1782
1785
  examples:
1783
1786
  - "/products"
1784
1787
  - "/quotes/{{ stream_partition['id'] }}/quote_line_groups"
@@ -3223,7 +3226,7 @@ definitions:
3223
3226
  - polling_requester
3224
3227
  - download_requester
3225
3228
  - status_extractor
3226
- - urls_extractor
3229
+ - download_target_extractor
3227
3230
  properties:
3228
3231
  type:
3229
3232
  type: string
@@ -3240,7 +3243,7 @@ definitions:
3240
3243
  anyOf:
3241
3244
  - "$ref": "#/definitions/CustomRecordExtractor"
3242
3245
  - "$ref": "#/definitions/DpathExtractor"
3243
- urls_extractor:
3246
+ download_target_extractor:
3244
3247
  description: Responsible for fetching the final result `urls` provided by the completed / finished / ready async job.
3245
3248
  anyOf:
3246
3249
  - "$ref": "#/definitions/CustomRecordExtractor"
@@ -3261,7 +3264,7 @@ definitions:
3261
3264
  anyOf:
3262
3265
  - "$ref": "#/definitions/CustomRequester"
3263
3266
  - "$ref": "#/definitions/HttpRequester"
3264
- url_requester:
3267
+ download_target_requester:
3265
3268
  description: Requester component that describes how to prepare HTTP requests to send to the source API to extract the url from polling response by the completed async job.
3266
3269
  anyOf:
3267
3270
  - "$ref": "#/definitions/CustomRequester"
@@ -3667,6 +3670,21 @@ interpolation:
3667
3670
  self: https://api.sendgrid.com/v3/marketing/lists?page_size=1&page_token=
3668
3671
  next: https://api.sendgrid.com/v3/marketing/lists?page_size=1&page_token=0236d6d2
3669
3672
  count: 82
3673
+ - title: creation_response
3674
+ description: The response received from the creation_requester in the AsyncRetriever component.
3675
+ type: object
3676
+ examples:
3677
+ - id: "1234"
3678
+ - title: polling_response
3679
+ description: The response received from the polling_requester in the AsyncRetriever component.
3680
+ type: object
3681
+ examples:
3682
+ - id: "1234"
3683
+ - title: download_target
3684
+ description: The `URL` received from the polling_requester in the AsyncRetriever with jobStatus as `COMPLETED`.
3685
+ type: string
3686
+ examples:
3687
+ - "https://api.sendgrid.com/v3/marketing/lists?page_size=1&page_token=0236d6d2&filename=xxx_yyy_zzz.csv"
3670
3688
  - title: stream_interval
3671
3689
  description: The current stream interval being processed. The keys are defined by the incremental sync component. Default keys are `start_time` and `end_time`.
3672
3690
  type: object
@@ -136,6 +136,7 @@ class ResponseToFileExtractor(RecordExtractor):
136
136
  """
137
137
 
138
138
  try:
139
+ # TODO: Add support for other file types, like `json`, with `pd.read_json()`
139
140
  with open(path, "r", encoding=file_encoding) as data:
140
141
  chunks = pd.read_csv(
141
142
  data, chunksize=chunk_size, iterator=True, dialect="unix", dtype=object
@@ -95,6 +95,10 @@ class ConcurrentPerPartitionCursor(Cursor):
95
95
  # the oldest partitions can be efficiently removed, maintaining the most recent partitions.
96
96
  self._cursor_per_partition: OrderedDict[str, ConcurrentCursor] = OrderedDict()
97
97
  self._semaphore_per_partition: OrderedDict[str, threading.Semaphore] = OrderedDict()
98
+
99
+ # Parent-state tracking: store each partition’s parent state in creation order
100
+ self._partition_parent_state_map: OrderedDict[str, Mapping[str, Any]] = OrderedDict()
101
+
98
102
  self._finished_partitions: set[str] = set()
99
103
  self._lock = threading.Lock()
100
104
  self._timer = Timer()
@@ -155,11 +159,62 @@ class ConcurrentPerPartitionCursor(Cursor):
155
159
  and self._semaphore_per_partition[partition_key]._value == 0
156
160
  ):
157
161
  self._update_global_cursor(cursor.state[self.cursor_field.cursor_field_key])
158
- self._emit_state_message()
162
+
163
+ self._check_and_update_parent_state()
164
+
165
+ self._emit_state_message()
166
+
167
+ def _check_and_update_parent_state(self) -> None:
168
+ """
169
+ Pop the leftmost partition state from _partition_parent_state_map only if
170
+ *all partitions* up to (and including) that partition key in _semaphore_per_partition
171
+ are fully finished (i.e. in _finished_partitions and semaphore._value == 0).
172
+ Additionally, delete finished semaphores with a value of 0 to free up memory,
173
+ as they are only needed to track errors and completion status.
174
+ """
175
+ last_closed_state = None
176
+
177
+ while self._partition_parent_state_map:
178
+ # Look at the earliest partition key in creation order
179
+ earliest_key = next(iter(self._partition_parent_state_map))
180
+
181
+ # Verify ALL partitions from the left up to earliest_key are finished
182
+ all_left_finished = True
183
+ for p_key, sem in list(
184
+ self._semaphore_per_partition.items()
185
+ ): # Use list to allow modification during iteration
186
+ # If any earlier partition is still not finished, we must stop
187
+ if p_key not in self._finished_partitions or sem._value != 0:
188
+ all_left_finished = False
189
+ break
190
+ # Once we've reached earliest_key in the semaphore order, we can stop checking
191
+ if p_key == earliest_key:
192
+ break
193
+
194
+ # If the partitions up to earliest_key are not all finished, break the while-loop
195
+ if not all_left_finished:
196
+ break
197
+
198
+ # Pop the leftmost entry from parent-state map
199
+ _, closed_parent_state = self._partition_parent_state_map.popitem(last=False)
200
+ last_closed_state = closed_parent_state
201
+
202
+ # Clean up finished semaphores with value 0 up to and including earliest_key
203
+ for p_key in list(self._semaphore_per_partition.keys()):
204
+ sem = self._semaphore_per_partition[p_key]
205
+ if p_key in self._finished_partitions and sem._value == 0:
206
+ del self._semaphore_per_partition[p_key]
207
+ logger.debug(f"Deleted finished semaphore for partition {p_key} with value 0")
208
+ if p_key == earliest_key:
209
+ break
210
+
211
+ # Update _parent_state if we popped at least one partition
212
+ if last_closed_state is not None:
213
+ self._parent_state = last_closed_state
159
214
 
160
215
  def ensure_at_least_one_state_emitted(self) -> None:
161
216
  """
162
- The platform expect to have at least one state message on successful syncs. Hence, whatever happens, we expect this method to be
217
+ The platform expects at least one state message on successful syncs. Hence, whatever happens, we expect this method to be
163
218
  called.
164
219
  """
165
220
  if not any(
@@ -201,13 +256,19 @@ class ConcurrentPerPartitionCursor(Cursor):
201
256
 
202
257
  slices = self._partition_router.stream_slices()
203
258
  self._timer.start()
204
- for partition in slices:
205
- yield from self._generate_slices_from_partition(partition)
259
+ for partition, last, parent_state in iterate_with_last_flag_and_state(
260
+ slices, self._partition_router.get_stream_state
261
+ ):
262
+ yield from self._generate_slices_from_partition(partition, parent_state)
206
263
 
207
- def _generate_slices_from_partition(self, partition: StreamSlice) -> Iterable[StreamSlice]:
264
+ def _generate_slices_from_partition(
265
+ self, partition: StreamSlice, parent_state: Mapping[str, Any]
266
+ ) -> Iterable[StreamSlice]:
208
267
  # Ensure the maximum number of partitions is not exceeded
209
268
  self._ensure_partition_limit()
210
269
 
270
+ partition_key = self._to_partition_key(partition.partition)
271
+
211
272
  cursor = self._cursor_per_partition.get(self._to_partition_key(partition.partition))
212
273
  if not cursor:
213
274
  cursor = self._create_cursor(
@@ -216,18 +277,26 @@ class ConcurrentPerPartitionCursor(Cursor):
216
277
  )
217
278
  with self._lock:
218
279
  self._number_of_partitions += 1
219
- self._cursor_per_partition[self._to_partition_key(partition.partition)] = cursor
220
- self._semaphore_per_partition[self._to_partition_key(partition.partition)] = (
221
- threading.Semaphore(0)
222
- )
280
+ self._cursor_per_partition[partition_key] = cursor
281
+ self._semaphore_per_partition[partition_key] = threading.Semaphore(0)
282
+
283
+ with self._lock:
284
+ if (
285
+ len(self._partition_parent_state_map) == 0
286
+ or self._partition_parent_state_map[
287
+ next(reversed(self._partition_parent_state_map))
288
+ ]
289
+ != parent_state
290
+ ):
291
+ self._partition_parent_state_map[partition_key] = deepcopy(parent_state)
223
292
 
224
293
  for cursor_slice, is_last_slice, _ in iterate_with_last_flag_and_state(
225
294
  cursor.stream_slices(),
226
295
  lambda: None,
227
296
  ):
228
- self._semaphore_per_partition[self._to_partition_key(partition.partition)].release()
297
+ self._semaphore_per_partition[partition_key].release()
229
298
  if is_last_slice:
230
- self._finished_partitions.add(self._to_partition_key(partition.partition))
299
+ self._finished_partitions.add(partition_key)
231
300
  yield StreamSlice(
232
301
  partition=partition, cursor_slice=cursor_slice, extra_fields=partition.extra_fields
233
302
  )
@@ -257,9 +326,9 @@ class ConcurrentPerPartitionCursor(Cursor):
257
326
  while len(self._cursor_per_partition) > self.DEFAULT_MAX_PARTITIONS_NUMBER - 1:
258
327
  # Try removing finished partitions first
259
328
  for partition_key in list(self._cursor_per_partition.keys()):
260
- if (
261
- partition_key in self._finished_partitions
262
- and self._semaphore_per_partition[partition_key]._value == 0
329
+ if partition_key in self._finished_partitions and (
330
+ partition_key not in self._semaphore_per_partition
331
+ or self._semaphore_per_partition[partition_key]._value == 0
263
332
  ):
264
333
  oldest_partition = self._cursor_per_partition.pop(
265
334
  partition_key
@@ -338,9 +407,6 @@ class ConcurrentPerPartitionCursor(Cursor):
338
407
  self._cursor_per_partition[self._to_partition_key(state["partition"])] = (
339
408
  self._create_cursor(state["cursor"])
340
409
  )
341
- self._semaphore_per_partition[self._to_partition_key(state["partition"])] = (
342
- threading.Semaphore(0)
343
- )
344
410
 
345
411
  # set default state for missing partitions if it is per partition with fallback to global
346
412
  if self._GLOBAL_STATE_KEY in stream_state:
@@ -2263,7 +2263,7 @@ class AsyncRetriever(BaseModel):
2263
2263
  status_extractor: Union[CustomRecordExtractor, DpathExtractor] = Field(
2264
2264
  ..., description="Responsible for fetching the actual status of the async job."
2265
2265
  )
2266
- urls_extractor: Union[CustomRecordExtractor, DpathExtractor] = Field(
2266
+ download_target_extractor: Union[CustomRecordExtractor, DpathExtractor] = Field(
2267
2267
  ...,
2268
2268
  description="Responsible for fetching the final result `urls` provided by the completed / finished / ready async job.",
2269
2269
  )
@@ -2278,7 +2278,7 @@ class AsyncRetriever(BaseModel):
2278
2278
  ...,
2279
2279
  description="Requester component that describes how to prepare HTTP requests to send to the source API to fetch the status of the running async job.",
2280
2280
  )
2281
- url_requester: Optional[Union[CustomRequester, HttpRequester]] = Field(
2281
+ download_target_requester: Optional[Union[CustomRequester, HttpRequester]] = Field(
2282
2282
  None,
2283
2283
  description="Requester component that describes how to prepare HTTP requests to send to the source API to extract the url from polling response by the completed async job.",
2284
2284
  )
@@ -2629,6 +2629,47 @@ class ModelToComponentFactory:
2629
2629
  transformations: List[RecordTransformation],
2630
2630
  **kwargs: Any,
2631
2631
  ) -> AsyncRetriever:
2632
+ def _get_download_retriever() -> SimpleRetrieverTestReadDecorator | SimpleRetriever:
2633
+ record_selector = RecordSelector(
2634
+ extractor=download_extractor,
2635
+ name=name,
2636
+ record_filter=None,
2637
+ transformations=transformations,
2638
+ schema_normalization=TypeTransformer(TransformConfig.NoTransform),
2639
+ config=config,
2640
+ parameters={},
2641
+ )
2642
+ paginator = (
2643
+ self._create_component_from_model(
2644
+ model=model.download_paginator, decoder=decoder, config=config, url_base=""
2645
+ )
2646
+ if model.download_paginator
2647
+ else NoPagination(parameters={})
2648
+ )
2649
+ maximum_number_of_slices = self._limit_slices_fetched or 5
2650
+
2651
+ if self._limit_slices_fetched or self._emit_connector_builder_messages:
2652
+ return SimpleRetrieverTestReadDecorator(
2653
+ requester=download_requester,
2654
+ record_selector=record_selector,
2655
+ primary_key=None,
2656
+ name=job_download_components_name,
2657
+ paginator=paginator,
2658
+ config=config,
2659
+ parameters={},
2660
+ maximum_number_of_slices=maximum_number_of_slices,
2661
+ )
2662
+
2663
+ return SimpleRetriever(
2664
+ requester=download_requester,
2665
+ record_selector=record_selector,
2666
+ primary_key=None,
2667
+ name=job_download_components_name,
2668
+ paginator=paginator,
2669
+ config=config,
2670
+ parameters={},
2671
+ )
2672
+
2632
2673
  decoder = (
2633
2674
  self._create_component_from_model(model=model.decoder, config=config)
2634
2675
  if model.decoder
@@ -2682,29 +2723,7 @@ class ModelToComponentFactory:
2682
2723
  config=config,
2683
2724
  name=job_download_components_name,
2684
2725
  )
2685
- download_retriever = SimpleRetriever(
2686
- requester=download_requester,
2687
- record_selector=RecordSelector(
2688
- extractor=download_extractor,
2689
- name=name,
2690
- record_filter=None,
2691
- transformations=transformations,
2692
- schema_normalization=TypeTransformer(TransformConfig.NoTransform),
2693
- config=config,
2694
- parameters={},
2695
- ),
2696
- primary_key=None,
2697
- name=job_download_components_name,
2698
- paginator=(
2699
- self._create_component_from_model(
2700
- model=model.download_paginator, decoder=decoder, config=config, url_base=""
2701
- )
2702
- if model.download_paginator
2703
- else NoPagination(parameters={})
2704
- ),
2705
- config=config,
2706
- parameters={},
2707
- )
2726
+ download_retriever = _get_download_retriever()
2708
2727
  abort_requester = (
2709
2728
  self._create_component_from_model(
2710
2729
  model=model.abort_requester,
@@ -2725,32 +2744,32 @@ class ModelToComponentFactory:
2725
2744
  if model.delete_requester
2726
2745
  else None
2727
2746
  )
2728
- url_requester = (
2747
+ download_target_requester = (
2729
2748
  self._create_component_from_model(
2730
- model=model.url_requester,
2749
+ model=model.download_target_requester,
2731
2750
  decoder=decoder,
2732
2751
  config=config,
2733
2752
  name=f"job extract_url - {name}",
2734
2753
  )
2735
- if model.url_requester
2754
+ if model.download_target_requester
2736
2755
  else None
2737
2756
  )
2738
2757
  status_extractor = self._create_component_from_model(
2739
2758
  model=model.status_extractor, decoder=decoder, config=config, name=name
2740
2759
  )
2741
- urls_extractor = self._create_component_from_model(
2742
- model=model.urls_extractor, decoder=decoder, config=config, name=name
2760
+ download_target_extractor = self._create_component_from_model(
2761
+ model=model.download_target_extractor, decoder=decoder, config=config, name=name
2743
2762
  )
2744
2763
  job_repository: AsyncJobRepository = AsyncHttpJobRepository(
2745
2764
  creation_requester=creation_requester,
2746
2765
  polling_requester=polling_requester,
2747
2766
  download_retriever=download_retriever,
2748
- url_requester=url_requester,
2767
+ download_target_requester=download_target_requester,
2749
2768
  abort_requester=abort_requester,
2750
2769
  delete_requester=delete_requester,
2751
2770
  status_extractor=status_extractor,
2752
2771
  status_mapping=self._create_async_job_status_mapping(model.status_mapping, config),
2753
- urls_extractor=urls_extractor,
2772
+ download_target_extractor=download_target_extractor,
2754
2773
  )
2755
2774
 
2756
2775
  async_job_partition_router = AsyncJobPartitionRouter(
@@ -1,8 +1,8 @@
1
1
  # AsyncHttpJobRepository sequence diagram
2
2
 
3
3
  - Components marked as optional are not required and can be ignored.
4
- - if `url_requester` is not provided, `urls_extractor` will get urls from the `polling_job_response`
5
- - interpolation_context, e.g. `create_job_response` or `polling_job_response` can be obtained from stream_slice
4
+ - if `download_target_requester` is not provided, `download_target_extractor` will get urls from the `polling_response`
5
+ - interpolation_context, e.g. `creation_response` or `polling_response` can be obtained from stream_slice
6
6
 
7
7
  ```mermaid
8
8
  ---
@@ -12,7 +12,7 @@ sequenceDiagram
12
12
  participant AsyncHttpJobRepository as AsyncOrchestrator
13
13
  participant CreationRequester as creation_requester
14
14
  participant PollingRequester as polling_requester
15
- participant UrlRequester as url_requester (Optional)
15
+ participant UrlRequester as download_target_requester (Optional)
16
16
  participant DownloadRetriever as download_retriever
17
17
  participant AbortRequester as abort_requester (Optional)
18
18
  participant DeleteRequester as delete_requester (Optional)
@@ -25,14 +25,14 @@ sequenceDiagram
25
25
 
26
26
  loop Poll for job status
27
27
  AsyncHttpJobRepository ->> PollingRequester: Check job status
28
- PollingRequester ->> Reporting Server: Status request (interpolation_context: `create_job_response`)
28
+ PollingRequester ->> Reporting Server: Status request (interpolation_context: `creation_response`)
29
29
  Reporting Server -->> PollingRequester: Status response
30
30
  PollingRequester -->> AsyncHttpJobRepository: Job status
31
31
  end
32
32
 
33
33
  alt Status: Ready
34
34
  AsyncHttpJobRepository ->> UrlRequester: Request download URLs (if applicable)
35
- UrlRequester ->> Reporting Server: URL request (interpolation_context: `polling_job_response`)
35
+ UrlRequester ->> Reporting Server: URL request (interpolation_context: `polling_response`)
36
36
  Reporting Server -->> UrlRequester: Download URLs
37
37
  UrlRequester -->> AsyncHttpJobRepository: Download URLs
38
38
 
@@ -23,6 +23,7 @@ from airbyte_cdk.sources.declarative.extractors.response_to_file_extractor impor
23
23
  )
24
24
  from airbyte_cdk.sources.declarative.requesters.requester import Requester
25
25
  from airbyte_cdk.sources.declarative.retrievers.simple_retriever import SimpleRetriever
26
+ from airbyte_cdk.sources.http_logger import format_http_message
26
27
  from airbyte_cdk.sources.types import Record, StreamSlice
27
28
  from airbyte_cdk.utils import AirbyteTracedException
28
29
 
@@ -42,13 +43,13 @@ class AsyncHttpJobRepository(AsyncJobRepository):
42
43
  delete_requester: Optional[Requester]
43
44
  status_extractor: DpathExtractor
44
45
  status_mapping: Mapping[str, AsyncJobStatus]
45
- urls_extractor: DpathExtractor
46
+ download_target_extractor: DpathExtractor
46
47
 
47
48
  job_timeout: Optional[timedelta] = None
48
49
  record_extractor: RecordExtractor = field(
49
50
  init=False, repr=False, default_factory=lambda: ResponseToFileExtractor({})
50
51
  )
51
- url_requester: Optional[Requester] = (
52
+ download_target_requester: Optional[Requester] = (
52
53
  None # use it in case polling_requester provides some <id> and extra request is needed to obtain list of urls to download from
53
54
  )
54
55
 
@@ -71,7 +72,15 @@ class AsyncHttpJobRepository(AsyncJobRepository):
71
72
  """
72
73
 
73
74
  polling_response: Optional[requests.Response] = self.polling_requester.send_request(
74
- stream_slice=stream_slice
75
+ stream_slice=stream_slice,
76
+ log_formatter=lambda polling_response: format_http_message(
77
+ response=polling_response,
78
+ title="Async Job -- Polling",
79
+ description="Poll the status of the server-side async job.",
80
+ stream_name=None,
81
+ is_auxiliary=True,
82
+ type="ASYNC_POLL",
83
+ ),
75
84
  )
76
85
  if polling_response is None:
77
86
  raise AirbyteTracedException(
@@ -118,8 +127,17 @@ class AsyncHttpJobRepository(AsyncJobRepository):
118
127
  """
119
128
 
120
129
  response: Optional[requests.Response] = self.creation_requester.send_request(
121
- stream_slice=stream_slice
130
+ stream_slice=stream_slice,
131
+ log_formatter=lambda response: format_http_message(
132
+ response=response,
133
+ title="Async Job -- Create",
134
+ description="Create the server-side async job.",
135
+ stream_name=None,
136
+ is_auxiliary=True,
137
+ type="ASYNC_CREATE",
138
+ ),
122
139
  )
140
+
123
141
  if not response:
124
142
  raise AirbyteTracedException(
125
143
  internal_message="Always expect a response or an exception from creation_requester",
@@ -193,12 +211,15 @@ class AsyncHttpJobRepository(AsyncJobRepository):
193
211
 
194
212
  """
195
213
 
196
- for url in self._get_download_url(job):
214
+ for target_url in self._get_download_targets(job):
197
215
  job_slice = job.job_parameters()
198
216
  stream_slice = StreamSlice(
199
217
  partition=job_slice.partition,
200
218
  cursor_slice=job_slice.cursor_slice,
201
- extra_fields={**job_slice.extra_fields, "url": url},
219
+ extra_fields={
220
+ **job_slice.extra_fields,
221
+ "download_target": target_url,
222
+ },
202
223
  )
203
224
  for message in self.download_retriever.read_records({}, stream_slice):
204
225
  if isinstance(message, Record):
@@ -217,13 +238,33 @@ class AsyncHttpJobRepository(AsyncJobRepository):
217
238
  if not self.abort_requester:
218
239
  return
219
240
 
220
- self.abort_requester.send_request(stream_slice=self._get_create_job_stream_slice(job))
241
+ abort_response = self.abort_requester.send_request(
242
+ stream_slice=self._get_create_job_stream_slice(job),
243
+ log_formatter=lambda abort_response: format_http_message(
244
+ response=abort_response,
245
+ title="Async Job -- Abort",
246
+ description="Abort the running server-side async job.",
247
+ stream_name=None,
248
+ is_auxiliary=True,
249
+ type="ASYNC_ABORT",
250
+ ),
251
+ )
221
252
 
222
253
  def delete(self, job: AsyncJob) -> None:
223
254
  if not self.delete_requester:
224
255
  return
225
256
 
226
- self.delete_requester.send_request(stream_slice=self._get_create_job_stream_slice(job))
257
+ delete_job_reponse = self.delete_requester.send_request(
258
+ stream_slice=self._get_create_job_stream_slice(job),
259
+ log_formatter=lambda delete_job_reponse: format_http_message(
260
+ response=delete_job_reponse,
261
+ title="Async Job -- Delete",
262
+ description="Delete the specified job from the list of Jobs.",
263
+ stream_name=None,
264
+ is_auxiliary=True,
265
+ type="ASYNC_DELETE",
266
+ ),
267
+ )
227
268
  self._clean_up_job(job.api_job_id())
228
269
 
229
270
  def _clean_up_job(self, job_id: str) -> None:
@@ -231,27 +272,29 @@ class AsyncHttpJobRepository(AsyncJobRepository):
231
272
  del self._polling_job_response_by_id[job_id]
232
273
 
233
274
  def _get_create_job_stream_slice(self, job: AsyncJob) -> StreamSlice:
275
+ creation_response = self._create_job_response_by_id[job.api_job_id()].json()
234
276
  stream_slice = StreamSlice(
235
- partition={"create_job_response": self._create_job_response_by_id[job.api_job_id()]},
277
+ partition={},
236
278
  cursor_slice={},
279
+ extra_fields={"creation_response": creation_response},
237
280
  )
238
281
  return stream_slice
239
282
 
240
- def _get_download_url(self, job: AsyncJob) -> Iterable[str]:
241
- if not self.url_requester:
283
+ def _get_download_targets(self, job: AsyncJob) -> Iterable[str]:
284
+ if not self.download_target_requester:
242
285
  url_response = self._polling_job_response_by_id[job.api_job_id()]
243
286
  else:
287
+ polling_response = self._polling_job_response_by_id[job.api_job_id()].json()
244
288
  stream_slice: StreamSlice = StreamSlice(
245
- partition={
246
- "polling_job_response": self._polling_job_response_by_id[job.api_job_id()]
247
- },
289
+ partition={},
248
290
  cursor_slice={},
291
+ extra_fields={"polling_response": polling_response},
249
292
  )
250
- url_response = self.url_requester.send_request(stream_slice=stream_slice) # type: ignore # we expect url_requester to always be presented, otherwise raise an exception as we cannot proceed with the report
293
+ url_response = self.download_target_requester.send_request(stream_slice=stream_slice) # type: ignore # we expect download_target_requester to always be presented, otherwise raise an exception as we cannot proceed with the report
251
294
  if not url_response:
252
295
  raise AirbyteTracedException(
253
- internal_message="Always expect a response or an exception from url_requester",
296
+ internal_message="Always expect a response or an exception from download_target_requester",
254
297
  failure_type=FailureType.system_error,
255
298
  )
256
299
 
257
- yield from self.urls_extractor.extract_records(url_response) # type: ignore # we expect urls_extractor to always return list of strings
300
+ yield from self.download_target_extractor.extract_records(url_response) # type: ignore # we expect download_target_extractor to always return list of strings
@@ -85,7 +85,7 @@ class HttpRequester(Requester):
85
85
  self._parameters = parameters
86
86
 
87
87
  if self.error_handler is not None and hasattr(self.error_handler, "backoff_strategies"):
88
- backoff_strategies = self.error_handler.backoff_strategies
88
+ backoff_strategies = self.error_handler.backoff_strategies # type: ignore
89
89
  else:
90
90
  backoff_strategies = None
91
91
 
@@ -125,6 +125,12 @@ class HttpRequester(Requester):
125
125
  kwargs = {
126
126
  "stream_slice": stream_slice,
127
127
  "next_page_token": next_page_token,
128
+ # update the interpolation context with extra fields, if passed.
129
+ **(
130
+ stream_slice.extra_fields
131
+ if stream_slice is not None and hasattr(stream_slice, "extra_fields")
132
+ else {}
133
+ ),
128
134
  }
129
135
  path = str(self._path.eval(self.config, **kwargs))
130
136
  return path.lstrip("/")
@@ -1,13 +1,12 @@
1
1
  # Copyright (c) 2024 Airbyte, Inc., all rights reserved.
2
2
 
3
3
 
4
- from dataclasses import InitVar, dataclass
4
+ from dataclasses import InitVar, dataclass, field
5
5
  from typing import Any, Iterable, Mapping, Optional
6
6
 
7
7
  from typing_extensions import deprecated
8
8
 
9
9
  from airbyte_cdk.sources.declarative.async_job.job import AsyncJob
10
- from airbyte_cdk.sources.declarative.async_job.job_orchestrator import AsyncPartition
11
10
  from airbyte_cdk.sources.declarative.extractors.record_selector import RecordSelector
12
11
  from airbyte_cdk.sources.declarative.partition_routers.async_job_partition_router import (
13
12
  AsyncJobPartitionRouter,
@@ -16,6 +15,7 @@ from airbyte_cdk.sources.declarative.retrievers.retriever import Retriever
16
15
  from airbyte_cdk.sources.source import ExperimentalClassWarning
17
16
  from airbyte_cdk.sources.streams.core import StreamData
18
17
  from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
18
+ from airbyte_cdk.sources.utils.slice_logger import AlwaysLogSliceLogger
19
19
 
20
20
 
21
21
  @deprecated(
@@ -28,6 +28,10 @@ class AsyncRetriever(Retriever):
28
28
  parameters: InitVar[Mapping[str, Any]]
29
29
  record_selector: RecordSelector
30
30
  stream_slicer: AsyncJobPartitionRouter
31
+ slice_logger: AlwaysLogSliceLogger = field(
32
+ init=False,
33
+ default_factory=lambda: AlwaysLogSliceLogger(),
34
+ )
31
35
 
32
36
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
33
37
  self._parameters = parameters
@@ -75,13 +79,16 @@ class AsyncRetriever(Retriever):
75
79
  return stream_slice.extra_fields.get("jobs", []) if stream_slice else []
76
80
 
77
81
  def stream_slices(self) -> Iterable[Optional[StreamSlice]]:
78
- return self.stream_slicer.stream_slices()
82
+ yield from self.stream_slicer.stream_slices()
79
83
 
80
84
  def read_records(
81
85
  self,
82
86
  records_schema: Mapping[str, Any],
83
87
  stream_slice: Optional[StreamSlice] = None,
84
88
  ) -> Iterable[StreamData]:
89
+ # emit the slice_descriptor log message, for connector builder TestRead
90
+ yield self.slice_logger.create_slice_log_message(stream_slice.cursor_slice) # type: ignore
91
+
85
92
  stream_state: StreamState = self._get_stream_state()
86
93
  jobs: Iterable[AsyncJob] = self._validate_and_get_stream_slice_jobs(stream_slice)
87
94
  records: Iterable[Mapping[str, Any]] = self.stream_slicer.fetch_records(jobs)
@@ -15,11 +15,14 @@ def format_http_message(
15
15
  description: str,
16
16
  stream_name: Optional[str],
17
17
  is_auxiliary: bool | None = None,
18
+ type: Optional[str] = None,
18
19
  ) -> LogMessage:
20
+ request_type: str = type if type else "HTTP"
19
21
  request = response.request
20
22
  log_message = {
21
23
  "http": {
22
24
  "title": title,
25
+ "type": request_type,
23
26
  "description": description,
24
27
  "request": {
25
28
  "method": request.method,
@@ -396,6 +396,7 @@ class AbstractOauth2Authenticator(AuthBase):
396
396
  "Obtains access token",
397
397
  self._NO_STREAM_NAME,
398
398
  is_auxiliary=True,
399
+ type="AUTH",
399
400
  ),
400
401
  )
401
402
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 6.36.1
3
+ Version: 6.36.3
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -9,12 +9,12 @@ airbyte_cdk/connector_builder/README.md,sha256=Hw3wvVewuHG9-QgsAq1jDiKuLlStDxKBz
9
9
  airbyte_cdk/connector_builder/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
10
10
  airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=BntqkP63RBPvGCtB3CrLLtYplfSlBR42kwXyyk4YGas,4268
11
11
  airbyte_cdk/connector_builder/main.py,sha256=ubAPE0Oo5gjZOa-KMtLLJQkc8_inUpFR3sIb2DEh2No,3722
12
- airbyte_cdk/connector_builder/models.py,sha256=uCHpOdJx2PyZtIqk-mt9eSVuFMQoEqrW-9sjCz0Z-AQ,1500
12
+ airbyte_cdk/connector_builder/models.py,sha256=9pIZ98LW_d6fRS39VdnUOf3cxGt4TkC5MJ0_OrzcCRk,1578
13
13
  airbyte_cdk/connector_builder/test_reader/__init__.py,sha256=iTwBMoI9vaJotEgpqZbFjlxRcbxXYypSVJ9YxeHk7wc,120
14
- airbyte_cdk/connector_builder/test_reader/helpers.py,sha256=niu_EhzwVXnvtzj2Rf_unrxqLRC4Twbe-t17HyNoRJY,23662
15
- airbyte_cdk/connector_builder/test_reader/message_grouper.py,sha256=L0xKpWQhfRecKblI3uYO9twPTPJYJzlOhK2P8zHWXRU,6487
14
+ airbyte_cdk/connector_builder/test_reader/helpers.py,sha256=Iczn-_iczS2CaIAunWwyFcX0uLTra8Wh9JVfzm1Gfxo,26765
15
+ airbyte_cdk/connector_builder/test_reader/message_grouper.py,sha256=84BAEPIBHMq3WCfO14WNvh_q7OsjGgDt0q1FTu8eW-w,6918
16
16
  airbyte_cdk/connector_builder/test_reader/reader.py,sha256=GurMB4ITO_PntvhIHSJkXbhynLilI4DObY5A2axavXo,20667
17
- airbyte_cdk/connector_builder/test_reader/types.py,sha256=jP28aOlCS8Q6V7jMksfJKsuAJ-m2dNYiXaUjYvb0DBA,2404
17
+ airbyte_cdk/connector_builder/test_reader/types.py,sha256=hPZG3jO03kBaPyW94NI3JHRS1jxXGSNBcN1HFzOxo5Y,2528
18
18
  airbyte_cdk/destinations/__init__.py,sha256=FyDp28PT_YceJD5HDFhA-mrGfX9AONIyMQ4d68CHNxQ,213
19
19
  airbyte_cdk/destinations/destination.py,sha256=CIq-yb8C_0QvcKCtmStaHfiqn53GEfRAIGGCkJhKP1Q,5880
20
20
  airbyte_cdk/destinations/vector_db_based/README.md,sha256=QAe8c_1Afme4r2TCE10cTSaxUE3zgCBuArSuRQqK8tA,2115
@@ -60,7 +60,7 @@ airbyte_cdk/sources/declarative/auth/jwt.py,sha256=SICqNsN2Cn_EgKadIgWuZpQxuMHyz
60
60
  airbyte_cdk/sources/declarative/auth/oauth.py,sha256=SUfib1oSzlyRRnOSg8Bui73mfyrcyr9OssdchbKdu4s,14162
61
61
  airbyte_cdk/sources/declarative/auth/selective_authenticator.py,sha256=qGwC6YsCldr1bIeKG6Qo-A9a5cTdHw-vcOn3OtQrS4c,1540
62
62
  airbyte_cdk/sources/declarative/auth/token.py,sha256=2EnE78EhBOY9hbeZnQJ9AuFaM-G7dccU-oKo_LThRQk,11070
63
- airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=9CuSsmOoHkvlc4k-oZ3Jx5luAgfTMm1I_5HOZxw7wMU,3075
63
+ airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=Jzuxlmt1_-_aFC_n0OmP8L1nDOacLzbEVVx3kjdX_W8,3104
64
64
  airbyte_cdk/sources/declarative/checks/__init__.py,sha256=nsVV5Bo0E_tBNd8A4Xdsdb-75PpcLo5RQu2RQ_Gv-ME,806
65
65
  airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py,sha256=HUktywjI8pqOeED08UGqponUSwxs2TOAECTowlWlrRE,2138
66
66
  airbyte_cdk/sources/declarative/checks/check_stream.py,sha256=dAA-UhmMj0WLXCkRQrilWCfJmncBzXCZ18ptRNip3XA,2139
@@ -71,7 +71,7 @@ airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=KBF9wdPC
71
71
  airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=l9LG7Qm6e5r_qgqfVKnx3mXYtg1I9MmMjomVIPfU4XA,177
72
72
  airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=SX9JjdesN1edN2WVUVMzU_ptqp2QB1OnsnjZ4mwcX7w,2579
73
73
  airbyte_cdk/sources/declarative/datetime/min_max_datetime.py,sha256=0BHBtDNQZfvwM45-tY5pNlTcKAFSGGNxemoi0Jic-0E,5785
74
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=5o5GsltzbVL2jyXvjWzUoV_r5xpwG_YdLSVUuG_d_34,144548
74
+ airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=ezTm6hN-IvM_0FSgcxc-Axv_9lJBqmJc_vAR_RgsrO4,145340
75
75
  airbyte_cdk/sources/declarative/declarative_source.py,sha256=nF7wBqFd3AQmEKAm4CnIo29CJoQL562cJGSCeL8U8bA,1531
76
76
  airbyte_cdk/sources/declarative/declarative_stream.py,sha256=venZjfpvtqr3oFSuvMBWtn4h9ayLhD4L65ACuXCDZ64,10445
77
77
  airbyte_cdk/sources/declarative/decoders/__init__.py,sha256=JHb_0d3SE6kNY10mxA5YBEKPeSbsWYjByq1gUQxepoE,953
@@ -89,10 +89,10 @@ airbyte_cdk/sources/declarative/extractors/http_selector.py,sha256=2zWZ4ewTqQC8V
89
89
  airbyte_cdk/sources/declarative/extractors/record_extractor.py,sha256=XJELMjahAsaomlvQgN2zrNO0DJX0G0fr9r682gUz7Pg,691
90
90
  airbyte_cdk/sources/declarative/extractors/record_filter.py,sha256=yTdEkyDUSW2KbFkEwJJMlS963C955LgCCOVfTmmScpQ,3367
91
91
  airbyte_cdk/sources/declarative/extractors/record_selector.py,sha256=HCqx7IyENM_aRF4it2zJN26_vDu6WeP8XgCxQWHUvcY,6934
92
- airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py,sha256=LhqGDfX06_dDYLKsIVnwQ_nAWCln-v8PV7Wgt_QVeTI,6533
92
+ airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py,sha256=L6hQV7bdwEp8y-TBMeQY-xmrNyRggL14lKXdWnzYFfA,6622
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
- airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=Pg2phEFT9T8AzUjK6hVhn0rgR3yY6JPF-Dfv0g1m5dQ,19191
95
+ airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=MT5JbdEbnPzk3VWZGGvThe4opoX5dHhSXFrnTRYC6dg,22210
96
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=2tsE6FgXzemf4fZZ4uGtd8QpRBl9GJ2CRqSNJE5p0EI,16077
@@ -113,13 +113,13 @@ airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW
113
113
  airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py,sha256=iemy3fKLczcU0-Aor7tx5jcT6DRedKMqyK7kCOp01hg,3924
114
114
  airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
115
115
  airbyte_cdk/sources/declarative/models/__init__.py,sha256=nUFxNCiKeYRVXuZEKA7GD-lTHxsiKcQ8FitZjKhPIvE,100
116
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=gNL9DqajD2A8UBnKAz7F7YQuYH7frQyHiPQPIMGq2xo,101958
116
+ airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=N9zrGClmsg0st7YKWL6KHE0-haYVVeUF_MZc3NJ6j5Q,101981
117
117
  airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
118
118
  airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=958MMX6_ZOJUlDDdNr9Krosgi2bCKGx2Z765M2Woz18,5505
119
119
  airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=Rir9_z3Kcd5Es0-LChrzk-0qubAsiK_RSEnLmK2OXm8,553
120
120
  airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=CXwTfD3wSQq3okcqwigpprbHhSURUokh4GK2OmOyKC8,9132
121
121
  airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=IWUOdF03o-aQn0Occo1BJCxU0Pz-QILk5L67nzw2thw,6803
122
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=IVQdcy1-kXz-T23zsMdoPBK2xuDE7gXHRtNTE5PBQ80,133990
122
+ airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=8gjOsCS8VdkH2-lImilDq8M8-dmGKtWpWyzd5ajzNok,134955
123
123
  airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=HJ-Syp3p7RpyR_OK0X_a2kSyISfu3W-PKrRI16iY0a8,957
124
124
  airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py,sha256=VelO7zKqKtzMJ35jyFeg0ypJLQC0plqqIBNXoBW1G2E,3001
125
125
  airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
@@ -127,7 +127,7 @@ airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha25
127
127
  airbyte_cdk/sources/declarative/partition_routers/partition_router.py,sha256=YyEIzdmLd1FjbVP3QbQ2VFCLW_P-OGbVh6VpZShp54k,2218
128
128
  airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py,sha256=SKzKjSyfccq4dxGIh-J6ejrgkCHzaiTIazmbmeQiRD4,1942
129
129
  airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=LlWj-Ofs-xfjlqmDzH8OYpyblP2Pb8bPDdR9g1UZyt0,17693
130
- airbyte_cdk/sources/declarative/requesters/README.md,sha256=eL1I4iLkxaw7hJi9S9d18_XcRl-R8lUSjqBVJJzvXmg,2656
130
+ airbyte_cdk/sources/declarative/requesters/README.md,sha256=DQll2qsIzzTiiP35kJp16ONpr7cFeUQNgPfhl5krB24,2675
131
131
  airbyte_cdk/sources/declarative/requesters/__init__.py,sha256=d7a3OoHbqaJDyyPli3nqqJ2yAW_SLX6XDaBAKOwvpxw,364
132
132
  airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py,sha256=SkEDcJxlT1683rNx93K9whoS0OyUukkuOfToGtgpF58,776
133
133
  airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py,sha256=1WZdpFmWL6W_Dko0qjflTaKIWeqt8jHT-D6HcujIp3s,884
@@ -142,8 +142,8 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.
142
142
  airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py,sha256=q0YkeYUUWO6iErUy0vjqiOkhg8_9d5YcCmtlpXAJJ9E,1314
143
143
  airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py,sha256=Tan66odx8VHzfdyyXMQkXz2pJYksllGqvxmpoajgcK4,669
144
144
  airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py,sha256=E-fQbt4ShfxZVoqfnmOx69C6FUPWZz8BIqI3DN9Kcjs,7935
145
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=3GtOefPH08evlSUxaILkiKLTHbIspFY4qd5B3ZqNE60,10063
146
- airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=pR2uR5b9eGyvYIOYwus3mz3OaqRu1ozwja_ys1SE7hc,14952
145
+ airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=4wpP0ZNTMLugi-Rc1OFdFaxWfRZSl45nzhHqMFCE8SQ,11924
146
+ airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=Sie8IyntFu66UoJASwpWV0WrRDBr9lpHWSOws7vZfM0,15228
147
147
  airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
148
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
@@ -169,7 +169,7 @@ airbyte_cdk/sources/declarative/resolvers/components_resolver.py,sha256=KPjKc0yb
169
169
  airbyte_cdk/sources/declarative/resolvers/config_components_resolver.py,sha256=dz4iJV9liD_LzY_Mn4XmAStoUll60R3MIGWV4aN3pgg,5223
170
170
  airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py,sha256=AiojNs8wItJFrENZBFUaDvau3sgwudO6Wkra36upSPo,4639
171
171
  airbyte_cdk/sources/declarative/retrievers/__init__.py,sha256=ix9m1dkR69DcXCXUKC5RK_ZZM7ojTLBQ4IkWQTfmfCk,456
172
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py,sha256=2oQn_vo7uJKp4pdMnsF5CG5Iwc9rkPeEOLoAm_9bcus,3222
172
+ airbyte_cdk/sources/declarative/retrievers/async_retriever.py,sha256=dwYZ70eg9DKHEqZydHhMFPkEILbNcXu7E-djOCikNgI,3530
173
173
  airbyte_cdk/sources/declarative/retrievers/retriever.py,sha256=XPLs593Xv8c5cKMc37XzUAYmzlXd1a7eSsspM-CMuWA,1696
174
174
  airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=bOAKQLgMv1Vca-ozMPRVAg1V5nkyUoPwqC02lKpnLiM,24575
175
175
  airbyte_cdk/sources/declarative/schema/__init__.py,sha256=xU45UvM5O4c1PSM13UHpCdh5hpW3HXy9vRRGEiAC1rg,795
@@ -250,7 +250,7 @@ airbyte_cdk/sources/file_based/stream/identities_stream.py,sha256=DwgNU-jDp5vZ_W
250
250
  airbyte_cdk/sources/file_based/stream/permissions_file_based_stream.py,sha256=i0Jn0zuAPomLa4pHSu9TQ3gAN5xXhNzPTYVwUDiDEyE,3523
251
251
  airbyte_cdk/sources/file_based/types.py,sha256=INxG7OPnkdUP69oYNKMAbwhvV1AGvLRHs1J6pIia2FI,218
252
252
  airbyte_cdk/sources/http_config.py,sha256=OBZeuyFilm6NlDlBhFQvHhTWabEvZww6OHDIlZujIS0,730
253
- airbyte_cdk/sources/http_logger.py,sha256=l_1fk5YwdonZ1wvAsTwjj6d36fj2WrVraIAMj5jTQdM,1575
253
+ airbyte_cdk/sources/http_logger.py,sha256=H93kPAujHhPmXNX0JSFG3D-SL6yEFA5PtKot9Hu3TYA,1690
254
254
  airbyte_cdk/sources/message/__init__.py,sha256=y98fzHsQBwXwp2zEa4K5mxGFqjnx9lDn9O0pTk-VS4U,395
255
255
  airbyte_cdk/sources/message/repository.py,sha256=SG7avgti_-dj8FcRHTTrhgLLGJbElv14_zIB0SH8AIc,4763
256
256
  airbyte_cdk/sources/source.py,sha256=KIBBH5VLEb8BZ8B9aROlfaI6OLoJqKDPMJ10jkAR7nk,3611
@@ -303,7 +303,7 @@ airbyte_cdk/sources/streams/http/http.py,sha256=0uariNq8OFnlX7iqOHwBhecxA-Hfd5hS
303
303
  airbyte_cdk/sources/streams/http/http_client.py,sha256=tDE0ROtxjGMVphvsw8INvGMtZ97hIF-v47pZ3jIyiwc,23011
304
304
  airbyte_cdk/sources/streams/http/rate_limiting.py,sha256=IwdjrHKUnU97XO4qONgYRv4YYW51xQ8SJm4WLafXDB8,6351
305
305
  airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py,sha256=RN0D3nOX1xLgwEwKWu6pkGy3XqBFzKSNZ8Lf6umU2eY,413
306
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=cM5CM1mnbTEMiY6gKHblGXr9KTS5VEziGoc-TXC302k,18791
306
+ airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=P9U8vtcrZ3m0InSG2W0H4gTYTxjQxkIe6mhF9xvO8Ug,18824
307
307
  airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py,sha256=Y3n7J-sk5yGjv_OxtY6Z6k0PEsFZmtIRi-x0KCbaHdA,1010
308
308
  airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=C2j2uVfi9d-3KgHO3NGxIiFdfASjHOtsd6g_LWPYOAs,20311
309
309
  airbyte_cdk/sources/streams/http/requests_native_auth/token.py,sha256=h5PTzcdH-RQLeCg7xZ45w_484OPUDSwNWl_iMJQmZoI,2526
@@ -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.36.1.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
364
- airbyte_cdk-6.36.1.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
365
- airbyte_cdk-6.36.1.dist-info/METADATA,sha256=M6sRWwrp7ag_VCNrcA-JX0lQwll_VT2BPXLkpCHe5dA,6010
366
- airbyte_cdk-6.36.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
367
- airbyte_cdk-6.36.1.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
368
- airbyte_cdk-6.36.1.dist-info/RECORD,,
363
+ airbyte_cdk-6.36.3.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
364
+ airbyte_cdk-6.36.3.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
365
+ airbyte_cdk-6.36.3.dist-info/METADATA,sha256=hOSYqconZWZ42hn8s2vyw_43Ou2dMvbj6M6XoIFMPmo,6010
366
+ airbyte_cdk-6.36.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
367
+ airbyte_cdk-6.36.3.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
368
+ airbyte_cdk-6.36.3.dist-info/RECORD,,