airbyte-cdk 6.37.0.dev1__py3-none-any.whl → 6.37.2__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 (45) 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/concurrent_declarative_source.py +43 -7
  7. airbyte_cdk/sources/declarative/datetime/datetime_parser.py +7 -1
  8. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +77 -48
  9. airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +13 -2
  10. airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +1 -0
  11. airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +83 -17
  12. airbyte_cdk/sources/declarative/interpolation/macros.py +2 -0
  13. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +37 -50
  14. airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py +18 -4
  15. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +171 -70
  16. airbyte_cdk/sources/declarative/partition_routers/__init__.py +0 -4
  17. airbyte_cdk/sources/declarative/requesters/README.md +5 -5
  18. airbyte_cdk/sources/declarative/requesters/http_job_repository.py +60 -17
  19. airbyte_cdk/sources/declarative/requesters/http_requester.py +49 -17
  20. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +25 -4
  21. airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +6 -1
  22. airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +7 -2
  23. airbyte_cdk/sources/declarative/requesters/requester.py +7 -1
  24. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +10 -3
  25. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +21 -4
  26. airbyte_cdk/sources/declarative/transformations/keys_to_snake_transformation.py +2 -2
  27. airbyte_cdk/sources/http_logger.py +3 -0
  28. airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +2 -1
  29. airbyte_cdk/sources/streams/concurrent/state_converters/incrementing_count_stream_state_converter.py +92 -0
  30. airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +3 -3
  31. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +1 -0
  32. airbyte_cdk/sources/types.py +1 -0
  33. airbyte_cdk/utils/mapping_helpers.py +18 -1
  34. {airbyte_cdk-6.37.0.dev1.dist-info → airbyte_cdk-6.37.2.dist-info}/METADATA +4 -4
  35. {airbyte_cdk-6.37.0.dev1.dist-info → airbyte_cdk-6.37.2.dist-info}/RECORD +39 -44
  36. airbyte_cdk/sources/declarative/partition_routers/grouping_partition_router.py +0 -136
  37. airbyte_cdk/sources/embedded/__init__.py +0 -3
  38. airbyte_cdk/sources/embedded/base_integration.py +0 -61
  39. airbyte_cdk/sources/embedded/catalog.py +0 -57
  40. airbyte_cdk/sources/embedded/runner.py +0 -57
  41. airbyte_cdk/sources/embedded/tools.py +0 -27
  42. {airbyte_cdk-6.37.0.dev1.dist-info → airbyte_cdk-6.37.2.dist-info}/LICENSE.txt +0 -0
  43. {airbyte_cdk-6.37.0.dev1.dist-info → airbyte_cdk-6.37.2.dist-info}/LICENSE_SHORT +0 -0
  44. {airbyte_cdk-6.37.0.dev1.dist-info → airbyte_cdk-6.37.2.dist-info}/WHEEL +0 -0
  45. {airbyte_cdk-6.37.0.dev1.dist-info → airbyte_cdk-6.37.2.dist-info}/entry_points.txt +0 -0
@@ -25,8 +25,8 @@ from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
25
25
  from airbyte_cdk.sources.streams.call_rate import APIBudget
26
26
  from airbyte_cdk.sources.streams.http import HttpClient
27
27
  from airbyte_cdk.sources.streams.http.error_handlers import ErrorHandler
28
- from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
29
- from airbyte_cdk.utils.mapping_helpers import combine_mappings
28
+ from airbyte_cdk.sources.types import Config, EmptyString, StreamSlice, StreamState
29
+ from airbyte_cdk.utils.mapping_helpers import combine_mappings, get_interpolation_context
30
30
 
31
31
 
32
32
  @dataclass
@@ -49,9 +49,10 @@ class HttpRequester(Requester):
49
49
 
50
50
  name: str
51
51
  url_base: Union[InterpolatedString, str]
52
- path: Union[InterpolatedString, str]
53
52
  config: Config
54
53
  parameters: InitVar[Mapping[str, Any]]
54
+
55
+ path: Optional[Union[InterpolatedString, str]] = None
55
56
  authenticator: Optional[DeclarativeAuthenticator] = None
56
57
  http_method: Union[str, HttpMethod] = HttpMethod.GET
57
58
  request_options_provider: Optional[InterpolatedRequestOptionsProvider] = None
@@ -66,7 +67,9 @@ class HttpRequester(Requester):
66
67
 
67
68
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
68
69
  self._url_base = InterpolatedString.create(self.url_base, parameters=parameters)
69
- self._path = InterpolatedString.create(self.path, parameters=parameters)
70
+ self._path = InterpolatedString.create(
71
+ self.path if self.path else EmptyString, parameters=parameters
72
+ )
70
73
  if self.request_options_provider is None:
71
74
  self._request_options_provider = InterpolatedRequestOptionsProvider(
72
75
  config=self.config, parameters=parameters
@@ -85,7 +88,7 @@ class HttpRequester(Requester):
85
88
  self._parameters = parameters
86
89
 
87
90
  if self.error_handler is not None and hasattr(self.error_handler, "backoff_strategies"):
88
- backoff_strategies = self.error_handler.backoff_strategies
91
+ backoff_strategies = self.error_handler.backoff_strategies # type: ignore
89
92
  else:
90
93
  backoff_strategies = None
91
94
 
@@ -112,21 +115,33 @@ class HttpRequester(Requester):
112
115
  def get_authenticator(self) -> DeclarativeAuthenticator:
113
116
  return self._authenticator
114
117
 
115
- def get_url_base(self) -> str:
116
- return os.path.join(self._url_base.eval(self.config), "")
118
+ def get_url_base(
119
+ self,
120
+ *,
121
+ stream_state: Optional[StreamState] = None,
122
+ stream_slice: Optional[StreamSlice] = None,
123
+ next_page_token: Optional[Mapping[str, Any]] = None,
124
+ ) -> str:
125
+ interpolation_context = get_interpolation_context(
126
+ stream_state=stream_state,
127
+ stream_slice=stream_slice,
128
+ next_page_token=next_page_token,
129
+ )
130
+ return os.path.join(self._url_base.eval(self.config, **interpolation_context), EmptyString)
117
131
 
118
132
  def get_path(
119
133
  self,
120
134
  *,
121
- stream_state: Optional[StreamState],
122
- stream_slice: Optional[StreamSlice],
123
- next_page_token: Optional[Mapping[str, Any]],
135
+ stream_state: Optional[StreamState] = None,
136
+ stream_slice: Optional[StreamSlice] = None,
137
+ next_page_token: Optional[Mapping[str, Any]] = None,
124
138
  ) -> str:
125
- kwargs = {
126
- "stream_slice": stream_slice,
127
- "next_page_token": next_page_token,
128
- }
129
- path = str(self._path.eval(self.config, **kwargs))
139
+ interpolation_context = get_interpolation_context(
140
+ stream_state=stream_state,
141
+ stream_slice=stream_slice,
142
+ next_page_token=next_page_token,
143
+ )
144
+ path = str(self._path.eval(self.config, **interpolation_context))
130
145
  return path.lstrip("/")
131
146
 
132
147
  def get_method(self) -> HttpMethod:
@@ -324,7 +339,20 @@ class HttpRequester(Requester):
324
339
 
325
340
  @classmethod
326
341
  def _join_url(cls, url_base: str, path: str) -> str:
327
- return urljoin(url_base, path)
342
+ """
343
+ Joins a base URL with a given path and returns the resulting URL with any trailing slash removed.
344
+
345
+ This method ensures that there are no duplicate slashes when concatenating the base URL and the path,
346
+ which is useful when the full URL is provided from an interpolation context.
347
+
348
+ Args:
349
+ url_base (str): The base URL to which the path will be appended.
350
+ path (str): The path to join with the base URL.
351
+
352
+ Returns:
353
+ str: The concatenated URL with the trailing slash (if any) removed.
354
+ """
355
+ return urljoin(url_base, path).rstrip("/")
328
356
 
329
357
  def send_request(
330
358
  self,
@@ -341,7 +369,11 @@ class HttpRequester(Requester):
341
369
  request, response = self._http_client.send_request(
342
370
  http_method=self.get_method().value,
343
371
  url=self._join_url(
344
- self.get_url_base(),
372
+ self.get_url_base(
373
+ stream_state=stream_state,
374
+ stream_slice=stream_slice,
375
+ next_page_token=next_page_token,
376
+ ),
345
377
  path
346
378
  or self.get_path(
347
379
  stream_state=stream_state,
@@ -25,6 +25,7 @@ from airbyte_cdk.sources.declarative.requesters.request_path import RequestPath
25
25
  from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
26
26
  from airbyte_cdk.utils.mapping_helpers import (
27
27
  _validate_component_request_option_paths,
28
+ get_interpolation_context,
28
29
  )
29
30
 
30
31
 
@@ -150,11 +151,22 @@ class DefaultPaginator(Paginator):
150
151
  else:
151
152
  return None
152
153
 
153
- def path(self, next_page_token: Optional[Mapping[str, Any]]) -> Optional[str]:
154
+ def path(
155
+ self,
156
+ next_page_token: Optional[Mapping[str, Any]],
157
+ stream_state: Optional[Mapping[str, Any]] = None,
158
+ stream_slice: Optional[StreamSlice] = None,
159
+ ) -> Optional[str]:
154
160
  token = next_page_token.get("next_page_token") if next_page_token else None
155
161
  if token and self.page_token_option and isinstance(self.page_token_option, RequestPath):
162
+ # make additional interpolation context
163
+ interpolation_context = get_interpolation_context(
164
+ stream_state=stream_state,
165
+ stream_slice=stream_slice,
166
+ next_page_token=next_page_token,
167
+ )
156
168
  # Replace url base to only return the path
157
- return str(token).replace(self.url_base.eval(self.config), "") # type: ignore # url_base is casted to a InterpolatedString in __post_init__
169
+ return str(token).replace(self.url_base.eval(self.config, **interpolation_context), "") # type: ignore # url_base is casted to a InterpolatedString in __post_init__
158
170
  else:
159
171
  return None
160
172
 
@@ -258,8 +270,17 @@ class PaginatorTestReadDecorator(Paginator):
258
270
  response, last_page_size, last_record, last_page_token_value
259
271
  )
260
272
 
261
- def path(self, next_page_token: Optional[Mapping[str, Any]]) -> Optional[str]:
262
- return self._decorated.path(next_page_token)
273
+ def path(
274
+ self,
275
+ next_page_token: Optional[Mapping[str, Any]],
276
+ stream_state: Optional[Mapping[str, Any]] = None,
277
+ stream_slice: Optional[StreamSlice] = None,
278
+ ) -> Optional[str]:
279
+ return self._decorated.path(
280
+ next_page_token=next_page_token,
281
+ stream_state=stream_state,
282
+ stream_slice=stream_slice,
283
+ )
263
284
 
264
285
  def get_request_params(
265
286
  self,
@@ -19,7 +19,12 @@ class NoPagination(Paginator):
19
19
 
20
20
  parameters: InitVar[Mapping[str, Any]]
21
21
 
22
- def path(self, next_page_token: Optional[Mapping[str, Any]]) -> Optional[str]:
22
+ def path(
23
+ self,
24
+ next_page_token: Optional[Mapping[str, Any]],
25
+ stream_state: Optional[Mapping[str, Any]] = None,
26
+ stream_slice: Optional[StreamSlice] = None,
27
+ ) -> Optional[str]:
23
28
  return None
24
29
 
25
30
  def get_request_params(
@@ -11,7 +11,7 @@ import requests
11
11
  from airbyte_cdk.sources.declarative.requesters.request_options.request_options_provider import (
12
12
  RequestOptionsProvider,
13
13
  )
14
- from airbyte_cdk.sources.types import Record
14
+ from airbyte_cdk.sources.types import Record, StreamSlice
15
15
 
16
16
 
17
17
  @dataclass
@@ -49,7 +49,12 @@ class Paginator(ABC, RequestOptionsProvider):
49
49
  pass
50
50
 
51
51
  @abstractmethod
52
- def path(self, next_page_token: Optional[Mapping[str, Any]]) -> Optional[str]:
52
+ def path(
53
+ self,
54
+ next_page_token: Optional[Mapping[str, Any]],
55
+ stream_state: Optional[Mapping[str, Any]] = None,
56
+ stream_slice: Optional[StreamSlice] = None,
57
+ ) -> Optional[str]:
53
58
  """
54
59
  Returns the URL path to hit to fetch the next page of records
55
60
 
@@ -35,7 +35,13 @@ class Requester(RequestOptionsProvider):
35
35
  pass
36
36
 
37
37
  @abstractmethod
38
- def get_url_base(self) -> str:
38
+ def get_url_base(
39
+ self,
40
+ *,
41
+ stream_state: Optional[StreamState],
42
+ stream_slice: Optional[StreamSlice],
43
+ next_page_token: Optional[Mapping[str, Any]],
44
+ ) -> str:
39
45
  """
40
46
  :return: URL base for the API endpoint e.g: if you wanted to hit https://myapi.com/v1/some_entity then this should return "https://myapi.com/v1/"
41
47
  """
@@ -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)
@@ -234,13 +234,22 @@ class SimpleRetriever(Retriever):
234
234
  raise ValueError("Request body json cannot be a string")
235
235
  return body_json
236
236
 
237
- def _paginator_path(self, next_page_token: Optional[Mapping[str, Any]] = None) -> Optional[str]:
237
+ def _paginator_path(
238
+ self,
239
+ next_page_token: Optional[Mapping[str, Any]] = None,
240
+ stream_state: Optional[Mapping[str, Any]] = None,
241
+ stream_slice: Optional[StreamSlice] = None,
242
+ ) -> Optional[str]:
238
243
  """
239
244
  If the paginator points to a path, follow it, else return nothing so the requester is used.
240
245
  :param next_page_token:
241
246
  :return:
242
247
  """
243
- return self._paginator.path(next_page_token=next_page_token)
248
+ return self._paginator.path(
249
+ next_page_token=next_page_token,
250
+ stream_state=stream_state,
251
+ stream_slice=stream_slice,
252
+ )
244
253
 
245
254
  def _parse_response(
246
255
  self,
@@ -299,7 +308,11 @@ class SimpleRetriever(Retriever):
299
308
  next_page_token: Optional[Mapping[str, Any]] = None,
300
309
  ) -> Optional[requests.Response]:
301
310
  return self.requester.send_request(
302
- path=self._paginator_path(next_page_token=next_page_token),
311
+ path=self._paginator_path(
312
+ next_page_token=next_page_token,
313
+ stream_state=stream_state,
314
+ stream_slice=stream_slice,
315
+ ),
303
316
  stream_state=stream_state,
304
317
  stream_slice=stream_slice,
305
318
  next_page_token=next_page_token,
@@ -570,7 +583,11 @@ class SimpleRetrieverTestReadDecorator(SimpleRetriever):
570
583
  next_page_token: Optional[Mapping[str, Any]] = None,
571
584
  ) -> Optional[requests.Response]:
572
585
  return self.requester.send_request(
573
- path=self._paginator_path(next_page_token=next_page_token),
586
+ path=self._paginator_path(
587
+ next_page_token=next_page_token,
588
+ stream_state=stream_state,
589
+ stream_slice=stream_slice,
590
+ ),
574
591
  stream_state=stream_state,
575
592
  stream_slice=stream_slice,
576
593
  next_page_token=next_page_token,
@@ -6,7 +6,7 @@ import re
6
6
  from dataclasses import dataclass
7
7
  from typing import Any, Dict, List, Optional
8
8
 
9
- import unidecode
9
+ import anyascii
10
10
 
11
11
  from airbyte_cdk.sources.declarative.transformations import RecordTransformation
12
12
  from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
@@ -48,7 +48,7 @@ class KeysToSnakeCaseTransformation(RecordTransformation):
48
48
  return self.tokens_to_snake_case(tokens)
49
49
 
50
50
  def normalize_key(self, key: str) -> str:
51
- return unidecode.unidecode(key)
51
+ return str(anyascii.anyascii(key))
52
52
 
53
53
  def tokenize_key(self, key: str) -> List[str]:
54
54
  tokens = []
@@ -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,
@@ -4,7 +4,7 @@
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  from enum import Enum
7
- from typing import TYPE_CHECKING, Any, List, MutableMapping, Optional, Tuple
7
+ from typing import TYPE_CHECKING, Any, Callable, List, MutableMapping, Optional, Tuple
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from airbyte_cdk.sources.streams.concurrent.cursor import CursorField
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
12
12
 
13
13
  class ConcurrencyCompatibleStateType(Enum):
14
14
  date_range = "date-range"
15
+ integer = "integer"
15
16
 
16
17
 
17
18
  class AbstractStreamStateConverter(ABC):
@@ -0,0 +1,92 @@
1
+ #
2
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ from typing import Any, Callable, MutableMapping, Optional, Tuple
6
+
7
+ from airbyte_cdk.sources.streams.concurrent.cursor import CursorField
8
+ from airbyte_cdk.sources.streams.concurrent.state_converters.abstract_stream_state_converter import (
9
+ AbstractStreamStateConverter,
10
+ ConcurrencyCompatibleStateType,
11
+ )
12
+
13
+
14
+ class IncrementingCountStreamStateConverter(AbstractStreamStateConverter):
15
+ def _from_state_message(self, value: Any) -> Any:
16
+ return value
17
+
18
+ def _to_state_message(self, value: Any) -> Any:
19
+ return value
20
+
21
+ @classmethod
22
+ def get_end_provider(cls) -> Callable[[], float]:
23
+ return lambda: float("inf")
24
+
25
+ def convert_from_sequential_state(
26
+ self,
27
+ cursor_field: "CursorField", # to deprecate as it is only needed for sequential state
28
+ stream_state: MutableMapping[str, Any],
29
+ start: Optional[Any],
30
+ ) -> Tuple[Any, MutableMapping[str, Any]]:
31
+ """
32
+ Convert the state message to the format required by the ConcurrentCursor.
33
+
34
+ e.g.
35
+ {
36
+ "state_type": ConcurrencyCompatibleStateType.date_range.value,
37
+ "metadata": { … },
38
+ "slices": [
39
+ {"start": "10", "end": "2021-01-18T21:18:20.000+00:00"},
40
+ ]
41
+ }
42
+ """
43
+ sync_start = self._get_sync_start(cursor_field, stream_state, start)
44
+ if self.is_state_message_compatible(stream_state):
45
+ return sync_start, stream_state
46
+
47
+ # Create a slice to represent the records synced during prior syncs.
48
+ # The start and end are the same to avoid confusion as to whether the records for this slice
49
+ # were actually synced
50
+ slices = [
51
+ {
52
+ self.START_KEY: start if start is not None else sync_start,
53
+ self.END_KEY: sync_start, # this may not be relevant anymore
54
+ self.MOST_RECENT_RECORD_KEY: sync_start,
55
+ }
56
+ ]
57
+
58
+ return sync_start, {
59
+ "state_type": ConcurrencyCompatibleStateType.integer.value,
60
+ "slices": slices,
61
+ "legacy": stream_state,
62
+ }
63
+
64
+ def parse_value(self, value: int) -> int:
65
+ return value
66
+
67
+ @property
68
+ def zero_value(self) -> int:
69
+ return 0
70
+
71
+ def increment(self, value: int) -> int:
72
+ return value + 1
73
+
74
+ def output_format(self, value: int) -> int:
75
+ return value
76
+
77
+ def _get_sync_start(
78
+ self,
79
+ cursor_field: CursorField,
80
+ stream_state: MutableMapping[str, Any],
81
+ start: Optional[int],
82
+ ) -> int:
83
+ sync_start = start if start is not None else self.zero_value
84
+ prev_sync_low_water_mark: Optional[int] = (
85
+ stream_state[cursor_field.cursor_field_key]
86
+ if cursor_field.cursor_field_key in stream_state
87
+ else None
88
+ )
89
+ if prev_sync_low_water_mark and prev_sync_low_water_mark >= sync_start:
90
+ return prev_sync_low_water_mark
91
+ else:
92
+ return sync_start
@@ -19,9 +19,9 @@ DEFAULT_ERROR_MAPPING: Mapping[Union[int, str, Type[Exception]], ErrorResolution
19
19
  error_message="Invalid Protocol Schema: The endpoint that data is being requested from is using an invalid or insecure. Exception: requests.exceptions.InvalidSchema",
20
20
  ),
21
21
  InvalidURL: ErrorResolution(
22
- response_action=ResponseAction.FAIL,
23
- failure_type=FailureType.config_error,
24
- error_message="Invalid URL specified: The endpoint that data is being requested from is not a valid URL. Exception: requests.exceptions.InvalidURL",
22
+ response_action=ResponseAction.RETRY,
23
+ failure_type=FailureType.transient_error,
24
+ error_message="Invalid URL specified or DNS error occurred: The endpoint that data is being requested from is not a valid URL. Exception: requests.exceptions.InvalidURL",
25
25
  ),
26
26
  RequestException: ErrorResolution(
27
27
  response_action=ResponseAction.RETRY,
@@ -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
 
@@ -14,6 +14,7 @@ FieldPointer = List[str]
14
14
  Config = Mapping[str, Any]
15
15
  ConnectionDefinition = Mapping[str, Any]
16
16
  StreamState = Mapping[str, Any]
17
+ EmptyString = str()
17
18
 
18
19
 
19
20
  class Record(Mapping[str, Any]):
@@ -10,7 +10,7 @@ from airbyte_cdk.sources.declarative.requesters.request_option import (
10
10
  RequestOption,
11
11
  RequestOptionType,
12
12
  )
13
- from airbyte_cdk.sources.types import Config
13
+ from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
14
14
 
15
15
 
16
16
  def _merge_mappings(
@@ -143,3 +143,20 @@ def _validate_component_request_option_paths(
143
143
  )
144
144
  except ValueError as error:
145
145
  raise ValueError(error)
146
+
147
+
148
+ def get_interpolation_context(
149
+ stream_state: Optional[StreamState] = None,
150
+ stream_slice: Optional[StreamSlice] = None,
151
+ next_page_token: Optional[Mapping[str, Any]] = None,
152
+ ) -> Mapping[str, Any]:
153
+ return {
154
+ "stream_slice": stream_slice,
155
+ "next_page_token": next_page_token,
156
+ # update the context with extra fields, if passed.
157
+ **(
158
+ stream_slice.extra_fields
159
+ if stream_slice is not None and hasattr(stream_slice, "extra_fields")
160
+ else {}
161
+ ),
162
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 6.37.0.dev1
3
+ Version: 6.37.2
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -22,13 +22,13 @@ Provides-Extra: sql
22
22
  Provides-Extra: vector-db-based
23
23
  Requires-Dist: Jinja2 (>=3.1.2,<3.2.0)
24
24
  Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
25
- Requires-Dist: Unidecode (>=1.3,<2.0)
26
25
  Requires-Dist: airbyte-protocol-models-dataclasses (>=0.14,<0.15)
26
+ Requires-Dist: anyascii (>=0.3.2,<0.4.0)
27
27
  Requires-Dist: avro (>=1.11.2,<1.13.0) ; extra == "file-based"
28
28
  Requires-Dist: backoff
29
29
  Requires-Dist: cachetools
30
30
  Requires-Dist: cohere (==4.21) ; extra == "vector-db-based"
31
- Requires-Dist: cryptography (>=42.0.5,<44.0.0)
31
+ Requires-Dist: cryptography (>=44.0.0,<45.0.0)
32
32
  Requires-Dist: dpath (>=2.1.6,<3.0.0)
33
33
  Requires-Dist: dunamai (>=1.22.0,<2.0.0)
34
34
  Requires-Dist: fastavro (>=1.8.0,<1.9.0) ; extra == "file-based"
@@ -47,7 +47,7 @@ Requires-Dist: pandas (==2.2.2)
47
47
  Requires-Dist: pdf2image (==1.16.3) ; extra == "file-based"
48
48
  Requires-Dist: pdfminer.six (==20221105) ; extra == "file-based"
49
49
  Requires-Dist: psutil (==6.1.0)
50
- Requires-Dist: pyarrow (>=15.0.0,<15.1.0) ; extra == "file-based"
50
+ Requires-Dist: pyarrow (>=19.0.0,<20.0.0) ; extra == "file-based"
51
51
  Requires-Dist: pydantic (>=2.7,<3.0)
52
52
  Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
53
53
  Requires-Dist: pyrate-limiter (>=3.1.0,<3.2.0)