airbyte-cdk 6.38.0.dev0__py3-none-any.whl → 6.38.1__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 (30) hide show
  1. airbyte_cdk/entrypoint.py +6 -6
  2. airbyte_cdk/logger.py +1 -4
  3. airbyte_cdk/sources/declarative/datetime/__init__.py +0 -4
  4. airbyte_cdk/sources/declarative/datetime/datetime_parser.py +4 -0
  5. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +10 -2
  6. airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +4 -0
  7. airbyte_cdk/sources/declarative/interpolation/macros.py +3 -5
  8. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +7 -5
  9. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +1 -1
  10. airbyte_cdk/sources/declarative/requesters/http_requester.py +48 -22
  11. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +25 -4
  12. airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +6 -1
  13. airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +7 -2
  14. airbyte_cdk/sources/declarative/requesters/requester.py +7 -1
  15. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +21 -4
  16. airbyte_cdk/sources/declarative/yaml_declarative_source.py +0 -1
  17. airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +3 -3
  18. airbyte_cdk/sources/types.py +1 -0
  19. airbyte_cdk/utils/mapping_helpers.py +18 -1
  20. {airbyte_cdk-6.38.0.dev0.dist-info → airbyte_cdk-6.38.1.dist-info}/METADATA +3 -3
  21. {airbyte_cdk-6.38.0.dev0.dist-info → airbyte_cdk-6.38.1.dist-info}/RECORD +25 -30
  22. airbyte_cdk/sources/embedded/__init__.py +0 -3
  23. airbyte_cdk/sources/embedded/base_integration.py +0 -61
  24. airbyte_cdk/sources/embedded/catalog.py +0 -57
  25. airbyte_cdk/sources/embedded/runner.py +0 -57
  26. airbyte_cdk/sources/embedded/tools.py +0 -27
  27. {airbyte_cdk-6.38.0.dev0.dist-info → airbyte_cdk-6.38.1.dist-info}/LICENSE.txt +0 -0
  28. {airbyte_cdk-6.38.0.dev0.dist-info → airbyte_cdk-6.38.1.dist-info}/LICENSE_SHORT +0 -0
  29. {airbyte_cdk-6.38.0.dev0.dist-info → airbyte_cdk-6.38.1.dist-info}/WHEEL +0 -0
  30. {airbyte_cdk-6.38.0.dev0.dist-info → airbyte_cdk-6.38.1.dist-info}/entry_points.txt +0 -0
airbyte_cdk/entrypoint.py CHANGED
@@ -22,7 +22,7 @@ from requests import PreparedRequest, Response, Session
22
22
 
23
23
  from airbyte_cdk.connector import TConfig
24
24
  from airbyte_cdk.exception_handler import init_uncaught_exception_handler
25
- from airbyte_cdk.logger import PRINT_BUFFER, init_logger
25
+ from airbyte_cdk.logger import init_logger
26
26
  from airbyte_cdk.models import (
27
27
  AirbyteConnectionStatus,
28
28
  AirbyteMessage,
@@ -337,11 +337,11 @@ def launch(source: Source, args: List[str]) -> None:
337
337
  parsed_args = source_entrypoint.parse_args(args)
338
338
  # temporarily removes the PrintBuffer because we're seeing weird print behavior for concurrent syncs
339
339
  # Refer to: https://github.com/airbytehq/oncall/issues/6235
340
- with PRINT_BUFFER:
341
- for message in source_entrypoint.run(parsed_args):
342
- # simply printing is creating issues for concurrent CDK as Python uses different two instructions to print: one for the message and
343
- # the other for the break line. Adding `\n` to the message ensure that both are printed at the same time
344
- print(f"{message}\n", end="")
340
+ # with PrintBuffer():
341
+ for message in source_entrypoint.run(parsed_args):
342
+ # simply printing is creating issues for concurrent CDK as Python uses different two instructions to print: one for the message and
343
+ # the other for the break line. Adding `\n` to the message ensure that both are printed at the same time
344
+ print(f"{message}\n", end="", flush=True)
345
345
 
346
346
 
347
347
  def _init_internal_request_filter() -> None:
airbyte_cdk/logger.py CHANGED
@@ -16,11 +16,8 @@ from airbyte_cdk.models import (
16
16
  Level,
17
17
  Type,
18
18
  )
19
- from airbyte_cdk.utils import PrintBuffer
20
19
  from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
21
20
 
22
- PRINT_BUFFER = PrintBuffer(flush_interval=0.1)
23
-
24
21
  LOGGING_CONFIG = {
25
22
  "version": 1,
26
23
  "disable_existing_loggers": False,
@@ -30,7 +27,7 @@ LOGGING_CONFIG = {
30
27
  "handlers": {
31
28
  "console": {
32
29
  "class": "logging.StreamHandler",
33
- "stream": PRINT_BUFFER,
30
+ "stream": "ext://sys.stdout",
34
31
  "formatter": "airbyte",
35
32
  },
36
33
  },
@@ -1,7 +1,3 @@
1
1
  #
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
-
5
- from airbyte_cdk.sources.declarative.datetime.min_max_datetime import MinMaxDatetime
6
-
7
- __all__ = ["MinMaxDatetime"]
@@ -29,6 +29,8 @@ class DatetimeParser:
29
29
  return datetime.datetime.fromtimestamp(int(date), tz=datetime.timezone.utc)
30
30
  elif format == "%s_as_float":
31
31
  return datetime.datetime.fromtimestamp(float(date), tz=datetime.timezone.utc)
32
+ elif format == "%epoch_microseconds":
33
+ return self._UNIX_EPOCH + datetime.timedelta(microseconds=int(date))
32
34
  elif format == "%ms":
33
35
  return self._UNIX_EPOCH + datetime.timedelta(milliseconds=int(date))
34
36
  elif "%_ms" in format:
@@ -46,6 +48,8 @@ class DatetimeParser:
46
48
  return str(int(dt.timestamp()))
47
49
  if format == "%s_as_float":
48
50
  return str(float(dt.timestamp()))
51
+ if format == "%epoch_microseconds":
52
+ return str(int(dt.timestamp() * 1_000_000))
49
53
  if format == "%ms":
50
54
  # timstamp() returns a float representing the number of seconds since the unix epoch
51
55
  return str(int(dt.timestamp() * 1000))
@@ -1794,7 +1794,6 @@ definitions:
1794
1794
  type: object
1795
1795
  required:
1796
1796
  - type
1797
- - path
1798
1797
  - url_base
1799
1798
  properties:
1800
1799
  type:
@@ -1806,9 +1805,18 @@ definitions:
1806
1805
  type: string
1807
1806
  interpolation_context:
1808
1807
  - config
1808
+ - next_page_token
1809
+ - stream_interval
1810
+ - stream_partition
1811
+ - stream_slice
1812
+ - creation_response
1813
+ - polling_response
1814
+ - download_target
1809
1815
  examples:
1810
1816
  - "https://connect.squareup.com/v2"
1811
- - "{{ config['base_url'] or 'https://app.posthog.com'}}/api/"
1817
+ - "{{ config['base_url'] or 'https://app.posthog.com'}}/api"
1818
+ - "https://connect.squareup.com/v2/quotes/{{ stream_partition['id'] }}/quote_line_groups"
1819
+ - "https://example.com/api/v1/resource/{{ next_page_token['id'] }}"
1812
1820
  path:
1813
1821
  title: URL Path
1814
1822
  description: Path the specific API endpoint that this stream represents. Do not put sensitive information (e.g. API tokens) into this field - Use the Authentication component for this.
@@ -151,6 +151,10 @@ class CompositeRawDecoder(Decoder):
151
151
  self, response: requests.Response
152
152
  ) -> Generator[MutableMapping[str, Any], None, None]:
153
153
  if self.is_stream_response():
154
+ # urllib mentions that some interfaces don't play nice with auto_close [here](https://urllib3.readthedocs.io/en/stable/user-guide.html#using-io-wrappers-with-response-content)
155
+ # We have indeed observed some issues with CSV parsing. Hence, we will manage the closing of the file ourselves until we find a better solution.
156
+ response.raw.auto_close = False
154
157
  yield from self.parser.parse(data=response.raw) # type: ignore[arg-type]
158
+ response.raw.close()
155
159
  else:
156
160
  yield from self.parser.parse(data=io.BytesIO(response.content))
@@ -12,6 +12,8 @@ import pytz
12
12
  from dateutil import parser
13
13
  from isodate import parse_duration
14
14
 
15
+ from airbyte_cdk.sources.declarative.datetime.datetime_parser import DatetimeParser
16
+
15
17
  """
16
18
  This file contains macros that can be evaluated by a `JinjaInterpolation` object
17
19
  """
@@ -171,11 +173,7 @@ def format_datetime(
171
173
  dt_datetime = (
172
174
  datetime.datetime.strptime(dt, input_format) if input_format else str_to_datetime(dt)
173
175
  )
174
- if format == "%s":
175
- return str(int(dt_datetime.timestamp()))
176
- elif format == "%ms":
177
- return str(int(dt_datetime.timestamp() * 1_000_000))
178
- return dt_datetime.strftime(format)
176
+ return DatetimeParser().format(dt=dt_datetime, format=format)
179
177
 
180
178
 
181
179
  _macros_list = [
@@ -939,7 +939,7 @@ class MinMaxDatetime(BaseModel):
939
939
  )
940
940
  datetime_format: Optional[str] = Field(
941
941
  "",
942
- description='Format of the datetime value. Defaults to "%Y-%m-%dT%H:%M:%S.%f%z" if left empty. Use placeholders starting with "%" to describe the format the API is using. The following placeholders are available:\n * **%s**: Epoch unix timestamp - `1686218963`\n * **%s_as_float**: Epoch unix timestamp in seconds as float with microsecond precision - `1686218963.123456`\n * **%ms**: Epoch unix timestamp - `1686218963123`\n * **%a**: Weekday (abbreviated) - `Sun`\n * **%A**: Weekday (full) - `Sunday`\n * **%w**: Weekday (decimal) - `0` (Sunday), `6` (Saturday)\n * **%d**: Day of the month (zero-padded) - `01`, `02`, ..., `31`\n * **%b**: Month (abbreviated) - `Jan`\n * **%B**: Month (full) - `January`\n * **%m**: Month (zero-padded) - `01`, `02`, ..., `12`\n * **%y**: Year (without century, zero-padded) - `00`, `01`, ..., `99`\n * **%Y**: Year (with century) - `0001`, `0002`, ..., `9999`\n * **%H**: Hour (24-hour, zero-padded) - `00`, `01`, ..., `23`\n * **%I**: Hour (12-hour, zero-padded) - `01`, `02`, ..., `12`\n * **%p**: AM/PM indicator\n * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59`\n * **%S**: Second (zero-padded) - `00`, `01`, ..., `59`\n * **%f**: Microsecond (zero-padded to 6 digits) - `000000`, `000001`, ..., `999999`\n * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00`\n * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT`\n * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366`\n * **%U**: Week number of the year (Sunday as first day) - `00`, `01`, ..., `53`\n * **%W**: Week number of the year (Monday as first day) - `00`, `01`, ..., `53`\n * **%c**: Date and time representation - `Tue Aug 16 21:30:00 1988`\n * **%x**: Date representation - `08/16/1988`\n * **%X**: Time representation - `21:30:00`\n * **%%**: Literal \'%\' character\n\n Some placeholders depend on the locale of the underlying system - in most cases this locale is configured as en/US. For more information see the [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes).\n',
942
+ description='Format of the datetime value. Defaults to "%Y-%m-%dT%H:%M:%S.%f%z" if left empty. Use placeholders starting with "%" to describe the format the API is using. The following placeholders are available:\n * **%s**: Epoch unix timestamp - `1686218963`\n * **%s_as_float**: Epoch unix timestamp in seconds as float with microsecond precision - `1686218963.123456`\n * **%ms**: Epoch unix timestamp - `1686218963123`\n * **%a**: Weekday (abbreviated) - `Sun`\n * **%A**: Weekday (full) - `Sunday`\n * **%w**: Weekday (decimal) - `0` (Sunday), `6` (Saturday)\n * **%d**: Day of the month (zero-padded) - `01`, `02`, ..., `31`\n * **%b**: Month (abbreviated) - `Jan`\n * **%B**: Month (full) - `January`\n * **%m**: Month (zero-padded) - `01`, `02`, ..., `12`\n * **%y**: Year (without century, zero-padded) - `00`, `01`, ..., `99`\n * **%Y**: Year (with century) - `0001`, `0002`, ..., `9999`\n * **%H**: Hour (24-hour, zero-padded) - `00`, `01`, ..., `23`\n * **%I**: Hour (12-hour, zero-padded) - `01`, `02`, ..., `12`\n * **%p**: AM/PM indicator\n * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59`\n * **%S**: Second (zero-padded) - `00`, `01`, ..., `59`\n * **%f**: Microsecond (zero-padded to 6 digits) - `000000`, `000001`, ..., `999999`\n * **%_ms**: Millisecond (zero-padded to 3 digits) - `000`, `001`, ..., `999`\n * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00`\n * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT`\n * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366`\n * **%U**: Week number of the year (Sunday as first day) - `00`, `01`, ..., `53`\n * **%W**: Week number of the year (Monday as first day) - `00`, `01`, ..., `53`\n * **%c**: Date and time representation - `Tue Aug 16 21:30:00 1988`\n * **%x**: Date representation - `08/16/1988`\n * **%X**: Time representation - `21:30:00`\n * **%%**: Literal \'%\' character\n\n Some placeholders depend on the locale of the underlying system - in most cases this locale is configured as en/US. For more information see the [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes).\n',
943
943
  examples=["%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%d", "%s"],
944
944
  title="Datetime Format",
945
945
  )
@@ -1545,7 +1545,7 @@ class DatetimeBasedCursor(BaseModel):
1545
1545
  )
1546
1546
  datetime_format: str = Field(
1547
1547
  ...,
1548
- description="The datetime format used to format the datetime values that are sent in outgoing requests to the API. Use placeholders starting with \"%\" to describe the format the API is using. The following placeholders are available:\n * **%s**: Epoch unix timestamp - `1686218963`\n * **%s_as_float**: Epoch unix timestamp in seconds as float with microsecond precision - `1686218963.123456`\n * **%ms**: Epoch unix timestamp (milliseconds) - `1686218963123`\n * **%a**: Weekday (abbreviated) - `Sun`\n * **%A**: Weekday (full) - `Sunday`\n * **%w**: Weekday (decimal) - `0` (Sunday), `6` (Saturday)\n * **%d**: Day of the month (zero-padded) - `01`, `02`, ..., `31`\n * **%b**: Month (abbreviated) - `Jan`\n * **%B**: Month (full) - `January`\n * **%m**: Month (zero-padded) - `01`, `02`, ..., `12`\n * **%y**: Year (without century, zero-padded) - `00`, `01`, ..., `99`\n * **%Y**: Year (with century) - `0001`, `0002`, ..., `9999`\n * **%H**: Hour (24-hour, zero-padded) - `00`, `01`, ..., `23`\n * **%I**: Hour (12-hour, zero-padded) - `01`, `02`, ..., `12`\n * **%p**: AM/PM indicator\n * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59`\n * **%S**: Second (zero-padded) - `00`, `01`, ..., `59`\n * **%f**: Microsecond (zero-padded to 6 digits) - `000000`\n * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00`\n * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT`\n * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366`\n * **%U**: Week number of the year (starting Sunday) - `00`, ..., `53`\n * **%W**: Week number of the year (starting Monday) - `00`, ..., `53`\n * **%c**: Date and time - `Tue Aug 16 21:30:00 1988`\n * **%x**: Date standard format - `08/16/1988`\n * **%X**: Time standard format - `21:30:00`\n * **%%**: Literal '%' character\n\n Some placeholders depend on the locale of the underlying system - in most cases this locale is configured as en/US. For more information see the [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes).\n",
1548
+ description="The datetime format used to format the datetime values that are sent in outgoing requests to the API. Use placeholders starting with \"%\" to describe the format the API is using. The following placeholders are available:\n * **%s**: Epoch unix timestamp - `1686218963`\n * **%s_as_float**: Epoch unix timestamp in seconds as float with microsecond precision - `1686218963.123456`\n * **%ms**: Epoch unix timestamp (milliseconds) - `1686218963123`\n * **%a**: Weekday (abbreviated) - `Sun`\n * **%A**: Weekday (full) - `Sunday`\n * **%w**: Weekday (decimal) - `0` (Sunday), `6` (Saturday)\n * **%d**: Day of the month (zero-padded) - `01`, `02`, ..., `31`\n * **%b**: Month (abbreviated) - `Jan`\n * **%B**: Month (full) - `January`\n * **%m**: Month (zero-padded) - `01`, `02`, ..., `12`\n * **%y**: Year (without century, zero-padded) - `00`, `01`, ..., `99`\n * **%Y**: Year (with century) - `0001`, `0002`, ..., `9999`\n * **%H**: Hour (24-hour, zero-padded) - `00`, `01`, ..., `23`\n * **%I**: Hour (12-hour, zero-padded) - `01`, `02`, ..., `12`\n * **%p**: AM/PM indicator\n * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59`\n * **%S**: Second (zero-padded) - `00`, `01`, ..., `59`\n * **%f**: Microsecond (zero-padded to 6 digits) - `000000`\n * **%_ms**: Millisecond (zero-padded to 3 digits) - `000`\n * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00`\n * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT`\n * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366`\n * **%U**: Week number of the year (starting Sunday) - `00`, ..., `53`\n * **%W**: Week number of the year (starting Monday) - `00`, ..., `53`\n * **%c**: Date and time - `Tue Aug 16 21:30:00 1988`\n * **%x**: Date standard format - `08/16/1988`\n * **%X**: Time standard format - `21:30:00`\n * **%%**: Literal '%' character\n\n Some placeholders depend on the locale of the underlying system - in most cases this locale is configured as en/US. For more information see the [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes).\n",
1549
1549
  examples=["%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%d", "%s", "%ms", "%s_as_float"],
1550
1550
  title="Outgoing Datetime Format",
1551
1551
  )
@@ -2072,12 +2072,14 @@ class HttpRequester(BaseModel):
2072
2072
  description="Base URL of the API source. Do not put sensitive information (e.g. API tokens) into this field - Use the Authentication component for this.",
2073
2073
  examples=[
2074
2074
  "https://connect.squareup.com/v2",
2075
- "{{ config['base_url'] or 'https://app.posthog.com'}}/api/",
2075
+ "{{ config['base_url'] or 'https://app.posthog.com'}}/api",
2076
+ "https://connect.squareup.com/v2/quotes/{{ stream_partition['id'] }}/quote_line_groups",
2077
+ "https://example.com/api/v1/resource/{{ next_page_token['id'] }}",
2076
2078
  ],
2077
2079
  title="API Base URL",
2078
2080
  )
2079
- path: str = Field(
2080
- ...,
2081
+ path: Optional[str] = Field(
2082
+ None,
2081
2083
  description="Path the specific API endpoint that this stream represents. Do not put sensitive information (e.g. API tokens) into this field - Use the Authentication component for this.",
2082
2084
  examples=[
2083
2085
  "/products",
@@ -56,7 +56,7 @@ from airbyte_cdk.sources.declarative.auth.token_provider import (
56
56
  )
57
57
  from airbyte_cdk.sources.declarative.checks import CheckDynamicStream, CheckStream
58
58
  from airbyte_cdk.sources.declarative.concurrency_level import ConcurrencyLevel
59
- from airbyte_cdk.sources.declarative.datetime import MinMaxDatetime
59
+ from airbyte_cdk.sources.declarative.datetime.min_max_datetime import MinMaxDatetime
60
60
  from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
61
61
  from airbyte_cdk.sources.declarative.decoders import (
62
62
  Decoder,
@@ -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
@@ -112,27 +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
- # 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
- ),
134
- }
135
- 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))
136
145
  return path.lstrip("/")
137
146
 
138
147
  def get_method(self) -> HttpMethod:
@@ -330,7 +339,20 @@ class HttpRequester(Requester):
330
339
 
331
340
  @classmethod
332
341
  def _join_url(cls, url_base: str, path: str) -> str:
333
- 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("/")
334
356
 
335
357
  def send_request(
336
358
  self,
@@ -347,7 +369,11 @@ class HttpRequester(Requester):
347
369
  request, response = self._http_client.send_request(
348
370
  http_method=self.get_method().value,
349
371
  url=self._join_url(
350
- 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
+ ),
351
377
  path
352
378
  or self.get_path(
353
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
  """
@@ -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,
@@ -50,7 +50,6 @@ class YamlDeclarativeSource(ConcurrentDeclarativeSource[List[AirbyteStateMessage
50
50
 
51
51
  def _emit_manifest_debug_message(self, extra_args: dict[str, Any]) -> None:
52
52
  extra_args["path_to_yaml"] = self._path_to_yaml
53
- self.logger.debug("declarative source created from parsed YAML manifest", extra=extra_args)
54
53
 
55
54
  @staticmethod
56
55
  def _parse(connection_definition_str: str) -> ConnectionDefinition:
@@ -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,
@@ -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.38.0.dev0
3
+ Version: 6.38.1
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -28,7 +28,7 @@ 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)
@@ -26,9 +26,9 @@ airbyte_cdk/destinations/vector_db_based/indexer.py,sha256=beiSi2Uu67EoTr7yQSaCJ
26
26
  airbyte_cdk/destinations/vector_db_based/test_utils.py,sha256=MkqLiOJ5QyKbV4rNiJhe-BHM7FD-ADHQ4bQGf4c5lRY,1932
27
27
  airbyte_cdk/destinations/vector_db_based/utils.py,sha256=FOyEo8Lc-fY8UyhpCivhZtIqBRyxf3cUt6anmK03fUY,1127
28
28
  airbyte_cdk/destinations/vector_db_based/writer.py,sha256=nZ00xPiohElJmYktEZZIhr0m5EDETCHGhg0Lb2S7A20,5095
29
- airbyte_cdk/entrypoint.py,sha256=NRJv5BNZRSUEVTmNBa9N7ih6fW5sg4DwL0nkB9kI99Y,18570
29
+ airbyte_cdk/entrypoint.py,sha256=xFLY2PV8mKXUaeBAknczbK6plrs4_B1WdWA6K3iaRJI,18555
30
30
  airbyte_cdk/exception_handler.py,sha256=D_doVl3Dt60ASXlJsfviOCswxGyKF2q0RL6rif3fNks,2013
31
- airbyte_cdk/logger.py,sha256=1cURbvawbunCAV178q-XhTHcbAQZTSf07WhU7U9AXWU,3744
31
+ airbyte_cdk/logger.py,sha256=qi4UGuSYQQGaFaTVJlMD9lLppwqLXt1XBhwSXo-Q5IA,3660
32
32
  airbyte_cdk/models/__init__.py,sha256=MOTiuML2wShBaMSIwikdjyye2uUWBjo4J1QFSbnoiM4,2075
33
33
  airbyte_cdk/models/airbyte_protocol.py,sha256=MCmLir67-hF12YM5OKzeGbWrlxr7ChG_OQSE1xG8EIU,3748
34
34
  airbyte_cdk/models/airbyte_protocol_serializers.py,sha256=s6SaFB2CMrG_7jTQGn_fhFbQ1FUxhCxf5kq2RWGHMVI,1749
@@ -68,14 +68,14 @@ airbyte_cdk/sources/declarative/checks/connection_checker.py,sha256=MBRJo6WJlZQH
68
68
  airbyte_cdk/sources/declarative/concurrency_level/__init__.py,sha256=5XUqrmlstYlMM0j6crktlKQwALek0uiz2D3WdM46MyA,191
69
69
  airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py,sha256=YIwCTCpOr_QSNW4ltQK0yUGWInI8PKNY216HOOegYLk,2101
70
70
  airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=rAp-sgld4n8Tmybz-51m7VcYXqKwzKDpCJVr1elmkRc,26824
71
- airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=l9LG7Qm6e5r_qgqfVKnx3mXYtg1I9MmMjomVIPfU4XA,177
72
- airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=0qs4hhmh_XOy2B4MHCn2qVMM79C6MizIBqnvpZj1aSE,2923
71
+ airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
72
+ airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=_zGNGq31RNy_0QBLt_EcTvgPyhj7urPdx6oA3M5-r3o,3150
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=Vsem7b0YL_kaLeTwY_kX-EqHzuBDjik0lBN7e3srXT4,147126
74
+ airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=n8hJVquDj00_VS_I0B2QgwYNcNcfsVZdkajAKArcOHU,147487
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
78
- airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py,sha256=DJbWaaJ5LHCBpyWz-4bEw8rqtJYqabEYZtxnfRtWFE0,4946
78
+ airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py,sha256=vo3l7BfsHaPqK14JfF4ZAodxqyW6PKHPZCmn87sRF3w,5368
79
79
  airbyte_cdk/sources/declarative/decoders/decoder.py,sha256=sl-Gt8lXi7yD2Q-sD8je5QS2PbgrgsYjxRLWsay7DMc,826
80
80
  airbyte_cdk/sources/declarative/decoders/json_decoder.py,sha256=BdWpXXPhEGf_zknggJmhojLosmxuw51RBVTS0jvdCPc,2080
81
81
  airbyte_cdk/sources/declarative/decoders/noop_decoder.py,sha256=iZh0yKY_JzgBnJWiubEusf5c0o6Khd-8EWFWT-8EgFo,542
@@ -107,19 +107,19 @@ airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py,sha
107
107
  airbyte_cdk/sources/declarative/interpolation/interpolated_string.py,sha256=CQkHqGlfa87G6VYMtBAQWin7ECKpfMdrDcg0JO5_rhc,3212
108
108
  airbyte_cdk/sources/declarative/interpolation/interpolation.py,sha256=9IoeuWam3L6GyN10L6U8xNWXmkt9cnahSDNkez1OmFY,982
109
109
  airbyte_cdk/sources/declarative/interpolation/jinja.py,sha256=UQeuS4Vpyp4hlOn-R3tRyeBX0e9IoV6jQ6gH-Jz8lY0,7182
110
- airbyte_cdk/sources/declarative/interpolation/macros.py,sha256=uuXBZUWDWM-sPcUKjNSPRN657QhNQCx_hnhTuJj2zOA,5129
110
+ airbyte_cdk/sources/declarative/interpolation/macros.py,sha256=HQKHKnjE17zKoPn27ZpTpugRZZQSaof4GVzUUZaV2eE,5081
111
111
  airbyte_cdk/sources/declarative/manifest_declarative_source.py,sha256=TN6GCgLXaWDONTaJwQ3A5ELqC-sxwKz-UYSraJYB-dI,17078
112
112
  airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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=Kd8HvvXqvGWZBey99eQzbK5u2k1ItnRAi2h7C7UNwBQ,103225
116
+ airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=DfbPi512ovaBSWDICJfjIkC3pXDn2aNr1BP-eiLOLyA,103556
117
117
  airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
118
118
  airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=jDw_TttD3_hpfevXOH-0Ws0eRuqt6wvED0BqosGPRjI,5938
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=Mx0KJGbqIZeUWduKy-UvpVH-DRm0pzXDcz203r69oNY,140619
122
+ airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=Tq-T4iFS8Dh8cGEEN0KrowF_QqPxiXvcs-Djct1qlpA,140636
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
@@ -143,11 +143,11 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_
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
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
146
+ airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=KS2nEUKMnyow8HvvfJJbWdPjlglIWSOZIAxrwkmXCI4,16356
147
147
  airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
148
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=ZW4lwWNAzb4zL0jKc-HjowP5-y0Zg9xi0YlK6tkx_XY,12057
149
- airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py,sha256=j6j9QRPaTbKQ2N661RFVKthhkWiodEp6ut0tKeEd0Ng,2019
150
- airbyte_cdk/sources/declarative/requesters/paginators/paginator.py,sha256=OlN-y0PEOMzlUNUh3pzonoTpIJpGwkP4ibFengvpLVU,2230
148
+ airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=SB-Af3CRb4mJwhm4EKNxzl_PK2w5QS4tqrSNNMO2IV4,12760
149
+ airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py,sha256=b1-zKxYOUMHn7ahdWpzKEzfG4A7s_WQWy-vzRqZWzME,2152
150
+ airbyte_cdk/sources/declarative/requesters/paginators/paginator.py,sha256=TzJF1Q-CFlsHF9lMSfmnGCxRYm9_UQCmBcHYQpc7F30,2376
151
151
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py,sha256=2gly8fuZpDNwtu1Qg6oE2jBLGqQRdzSLJdnpk_iDV6I,767
152
152
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py,sha256=yLzzK5YIRTkXd2Z-BS__AZXuTd6HXjJIxq05K-lQoxI,3898
153
153
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py,sha256=WvGt_DTFcAgTR-NHrlrR7B71yG-L6jmfW-Gwm9iYzjY,3624
@@ -163,7 +163,7 @@ airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_
163
163
  airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py,sha256=vOsdHfWHiTFc89WENHPv1hcxLgdzycMXVT_IEtLuhfs,5012
164
164
  airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py,sha256=8YRiDzjYvqJ-aMmKFcjqzv_-e8OZ5QG_TbpZ-nuCu6s,2590
165
165
  airbyte_cdk/sources/declarative/requesters/request_path.py,sha256=S3MeFvcaQrMbOkSY2W2VbXLNomqt_3eXqVd9ZhgNwUs,299
166
- airbyte_cdk/sources/declarative/requesters/requester.py,sha256=iVVpXQ4KEd9OyZNwmOofMvx7_06i8ZRxGo3aNTrEQLM,4946
166
+ airbyte_cdk/sources/declarative/requesters/requester.py,sha256=OcDzuCBgD1owK_lBPG0KbRRHRn9kzbuRveU4HejDiv4,5116
167
167
  airbyte_cdk/sources/declarative/resolvers/__init__.py,sha256=NiDcz5qi8HPsfX94MUmnX0Rgs_kQXGvucOmJjNWlxKQ,1207
168
168
  airbyte_cdk/sources/declarative/resolvers/components_resolver.py,sha256=KPjKc0yb9artL4ZkeqN8RmEykHH6FJgqXD7fCEnh1X0,1936
169
169
  airbyte_cdk/sources/declarative/resolvers/config_components_resolver.py,sha256=dz4iJV9liD_LzY_Mn4XmAStoUll60R3MIGWV4aN3pgg,5223
@@ -171,7 +171,7 @@ airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py,sha256=Aio
171
171
  airbyte_cdk/sources/declarative/retrievers/__init__.py,sha256=ix9m1dkR69DcXCXUKC5RK_ZZM7ojTLBQ4IkWQTfmfCk,456
172
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
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=bOAKQLgMv1Vca-ozMPRVAg1V5nkyUoPwqC02lKpnLiM,24575
174
+ airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=fDhc6dMx75UImh1_TfLm4Le59tsHpqIUZnau7uIJyYw,25043
175
175
  airbyte_cdk/sources/declarative/schema/__init__.py,sha256=xU45UvM5O4c1PSM13UHpCdh5hpW3HXy9vRRGEiAC1rg,795
176
176
  airbyte_cdk/sources/declarative/schema/default_schema_loader.py,sha256=KTACrIE23a83wsm3Rd9Eb4K6-20lrGqYxTHNp9yxsso,1820
177
177
  airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py,sha256=J8Q_iJYhcSQLWyt0bTZCbDAGpxt9G8FCc6Q9jtGsNzw,10703
@@ -193,12 +193,7 @@ airbyte_cdk/sources/declarative/transformations/keys_to_snake_transformation.py,
193
193
  airbyte_cdk/sources/declarative/transformations/remove_fields.py,sha256=EwUP0SZ2p4GRJ6Q8CUzlz9dcUeEidEFDlI2IBye2tlc,2745
194
194
  airbyte_cdk/sources/declarative/transformations/transformation.py,sha256=4sXtx9cNY2EHUPq-xHvDs8GQEBUy3Eo6TkRLKHPXx68,1161
195
195
  airbyte_cdk/sources/declarative/types.py,sha256=yqx0xlZv_76tkC7fqJKefmvl4GJJ8mXbeddwVV8XRJU,778
196
- airbyte_cdk/sources/declarative/yaml_declarative_source.py,sha256=MsKSAqtpwIqJfYOiUX01RbqMeTy7pvBoguvyTWrL7pI,2390
197
- airbyte_cdk/sources/embedded/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
198
- airbyte_cdk/sources/embedded/base_integration.py,sha256=0LbtEtWlnVdkYlweA5OJU4BIoyS6d4le5w9FsLn25Zc,2417
199
- airbyte_cdk/sources/embedded/catalog.py,sha256=EAnLw9u5fXLNBLfWr_I0itA7OEHMWdqEaM_rWc_tCpI,1653
200
- airbyte_cdk/sources/embedded/runner.py,sha256=S6cz7SWRTFfT_ZhP-zZYdiT9XRijulV_bMABal2h0OI,1493
201
- airbyte_cdk/sources/embedded/tools.py,sha256=kp26Au9xH4hxsnDQoavxE_a418ZrRUAMBr2MeOU52VI,746
196
+ airbyte_cdk/sources/declarative/yaml_declarative_source.py,sha256=swZUN7g_AuLeE7n1yp_cquK7DBB082lZUSMIBNEHxgs,2290
202
197
  airbyte_cdk/sources/file_based/README.md,sha256=iMqww4VZ882jfNQIdljjDgqreKs-mkdtSrRKA94iX6A,11085
203
198
  airbyte_cdk/sources/file_based/__init__.py,sha256=EaxHv_9ot-eRlUCR47ZMZ0IOtB-n0HH24om7Bfn-uuQ,868
204
199
  airbyte_cdk/sources/file_based/availability_strategy/__init__.py,sha256=ddKQfUmk-Ls7LJaG8gtrqDybG3d8S7KXOAEjLeYLrTg,399
@@ -293,7 +288,7 @@ airbyte_cdk/sources/streams/http/availability_strategy.py,sha256=sovoGFThZr-doMN
293
288
  airbyte_cdk/sources/streams/http/error_handlers/__init__.py,sha256=nNO0bnD0ogDlR4AQrI-YmpcM911vWe83b7RJYgvjSYs,666
294
289
  airbyte_cdk/sources/streams/http/error_handlers/backoff_strategy.py,sha256=7fIkF00wD6bqIXtiG2QAgbje_xiQ5JTs99ibwR8LLXY,1030
295
290
  airbyte_cdk/sources/streams/http/error_handlers/default_backoff_strategy.py,sha256=1_1Gi2ImwTbh4SgIKCRh2P4kPaVeAeHc1ib6IrBfqKA,411
296
- airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py,sha256=Z0gHhbBSFNWuatzCtBOQt9C-BkiN1G76wr6yl0qodEU,3376
291
+ airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py,sha256=drMniPygapMfOigh_ShJQvX6oqJaN4UA3cbRMkOgZJ4,3402
297
292
  airbyte_cdk/sources/streams/http/error_handlers/error_handler.py,sha256=GuqP7U1eC9RPaiD4y4Mkf17vKcOXo2ENnMB-CUBLsbo,1164
298
293
  airbyte_cdk/sources/streams/http/error_handlers/error_message_parser.py,sha256=xC93uB5BJd3iOnAXCrYLJTitWeGZlqzwe55VtsZqNnE,456
299
294
  airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py,sha256=2gqececTxxUqO6aIkVNNXADg48Px5EHUwnXHL9KiPT8,4188
@@ -310,7 +305,7 @@ airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=C2j2uVfi9d
310
305
  airbyte_cdk/sources/streams/http/requests_native_auth/token.py,sha256=h5PTzcdH-RQLeCg7xZ45w_484OPUDSwNWl_iMJQmZoI,2526
311
306
  airbyte_cdk/sources/streams/permissions/identities_stream.py,sha256=9O9k6k18Xm3Zsiw_vnI_jsHXfMCQiek6V-jMkJJLxn8,2621
312
307
  airbyte_cdk/sources/streams/utils/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
313
- airbyte_cdk/sources/types.py,sha256=aFPGI4t2K1vHz2oFSUIYUyDN7kw-vcYq4D7aD2zgfAU,5128
308
+ airbyte_cdk/sources/types.py,sha256=x5Rkfpottg46qgr1-g_O4kN6v0Fd0sWHttdYsftyo7w,5148
314
309
  airbyte_cdk/sources/utils/__init__.py,sha256=TTN6VUxVy6Is8BhYQZR5pxJGQh8yH4duXh4O1TiMiEY,118
315
310
  airbyte_cdk/sources/utils/casing.py,sha256=QC-gV1O4e8DR4-bhdXieUPKm_JamzslVyfABLYYRSXA,256
316
311
  airbyte_cdk/sources/utils/record_helper.py,sha256=jeB0mucudzna7Zvj-pCBbwFrbLJ36SlAWZTh5O4Fb9Y,2168
@@ -352,7 +347,7 @@ airbyte_cdk/utils/datetime_format_inferrer.py,sha256=Ne2cpk7Tx3eZDEW2Q3O7jnNOY9g
352
347
  airbyte_cdk/utils/datetime_helpers.py,sha256=8mqzZ67Or2PBp7tLtrhh6XFv4wFzYsjCL_DOQJRaftI,17751
353
348
  airbyte_cdk/utils/event_timing.py,sha256=aiuFmPU80buLlNdKq4fDTEqqhEIelHPF6AalFGwY8as,2557
354
349
  airbyte_cdk/utils/is_cloud_environment.py,sha256=DayV32Irh-SdnJ0MnjvstwCJ66_l5oEsd8l85rZtHoc,574
355
- airbyte_cdk/utils/mapping_helpers.py,sha256=imUTULHmZ1Ks-MRMRLIVqHCX1eJi_j6tFQrYsKIKtM4,5967
350
+ airbyte_cdk/utils/mapping_helpers.py,sha256=nWjOpnz_5QE9tY9-GtSWMPvYQL95kDD6k8KwwjUmrh0,6526
356
351
  airbyte_cdk/utils/message_utils.py,sha256=OTzbkwN7AdMDA3iKYq1LKwfPFxpyEDfdgEF9BED3dkU,1366
357
352
  airbyte_cdk/utils/oneof_option_config.py,sha256=N8EmWdYdwt0FM7fuShh6H8nj_r4KEL9tb2DJJtwsPow,1180
358
353
  airbyte_cdk/utils/print_buffer.py,sha256=PhMOi0C4Z91kWKrSvCQXcp8qRh1uCimpIdvrg6voZIA,2810
@@ -361,9 +356,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
361
356
  airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
362
357
  airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
363
358
  airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
364
- airbyte_cdk-6.38.0.dev0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
365
- airbyte_cdk-6.38.0.dev0.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
366
- airbyte_cdk-6.38.0.dev0.dist-info/METADATA,sha256=1EhdJFpsvmQYoaGXbq_JPTxW9hAy5n81U-fuQsfp24A,6018
367
- airbyte_cdk-6.38.0.dev0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
368
- airbyte_cdk-6.38.0.dev0.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
369
- airbyte_cdk-6.38.0.dev0.dist-info/RECORD,,
359
+ airbyte_cdk-6.38.1.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
360
+ airbyte_cdk-6.38.1.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
361
+ airbyte_cdk-6.38.1.dist-info/METADATA,sha256=Dd1RjRaSaSaEJ5_BvSaJ5cZnJ6hRq6btlHXT4P_kOzQ,6013
362
+ airbyte_cdk-6.38.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
363
+ airbyte_cdk-6.38.1.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
364
+ airbyte_cdk-6.38.1.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- #
2
- # Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3
- #
@@ -1,61 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
- from abc import ABC, abstractmethod
6
- from typing import Generic, Iterable, Optional, TypeVar
7
-
8
- from airbyte_cdk.connector import TConfig
9
- from airbyte_cdk.models import AirbyteRecordMessage, AirbyteStateMessage, SyncMode, Type
10
- from airbyte_cdk.sources.embedded.catalog import (
11
- create_configured_catalog,
12
- get_stream,
13
- get_stream_names,
14
- )
15
- from airbyte_cdk.sources.embedded.runner import SourceRunner
16
- from airbyte_cdk.sources.embedded.tools import get_defined_id
17
- from airbyte_cdk.sources.utils.schema_helpers import check_config_against_spec_or_exit
18
-
19
- TOutput = TypeVar("TOutput")
20
-
21
-
22
- class BaseEmbeddedIntegration(ABC, Generic[TConfig, TOutput]):
23
- def __init__(self, runner: SourceRunner[TConfig], config: TConfig):
24
- check_config_against_spec_or_exit(config, runner.spec())
25
-
26
- self.source = runner
27
- self.config = config
28
-
29
- self.last_state: Optional[AirbyteStateMessage] = None
30
-
31
- @abstractmethod
32
- def _handle_record(self, record: AirbyteRecordMessage, id: Optional[str]) -> Optional[TOutput]:
33
- """
34
- Turn an Airbyte record into the appropriate output type for the integration.
35
- """
36
- pass
37
-
38
- def _load_data(
39
- self, stream_name: str, state: Optional[AirbyteStateMessage] = None
40
- ) -> Iterable[TOutput]:
41
- catalog = self.source.discover(self.config)
42
- stream = get_stream(catalog, stream_name)
43
- if not stream:
44
- raise ValueError(
45
- f"Stream {stream_name} not found, the following streams are available: {', '.join(get_stream_names(catalog))}"
46
- )
47
- if SyncMode.incremental not in stream.supported_sync_modes:
48
- configured_catalog = create_configured_catalog(stream, sync_mode=SyncMode.full_refresh)
49
- else:
50
- configured_catalog = create_configured_catalog(stream, sync_mode=SyncMode.incremental)
51
-
52
- for message in self.source.read(self.config, configured_catalog, state):
53
- if message.type == Type.RECORD:
54
- output = self._handle_record(
55
- message.record,
56
- get_defined_id(stream, message.record.data), # type: ignore[union-attr, arg-type]
57
- )
58
- if output:
59
- yield output
60
- elif message.type is Type.STATE and message.state:
61
- self.last_state = message.state
@@ -1,57 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
- from typing import List, Optional
6
-
7
- from airbyte_cdk.models import (
8
- AirbyteCatalog,
9
- AirbyteStream,
10
- ConfiguredAirbyteCatalog,
11
- ConfiguredAirbyteStream,
12
- DestinationSyncMode,
13
- SyncMode,
14
- )
15
- from airbyte_cdk.sources.embedded.tools import get_first
16
-
17
-
18
- def get_stream(catalog: AirbyteCatalog, stream_name: str) -> Optional[AirbyteStream]:
19
- return get_first(catalog.streams, lambda s: s.name == stream_name)
20
-
21
-
22
- def get_stream_names(catalog: AirbyteCatalog) -> List[str]:
23
- return [stream.name for stream in catalog.streams]
24
-
25
-
26
- def to_configured_stream(
27
- stream: AirbyteStream,
28
- sync_mode: SyncMode = SyncMode.full_refresh,
29
- destination_sync_mode: DestinationSyncMode = DestinationSyncMode.append,
30
- cursor_field: Optional[List[str]] = None,
31
- primary_key: Optional[List[List[str]]] = None,
32
- ) -> ConfiguredAirbyteStream:
33
- return ConfiguredAirbyteStream(
34
- stream=stream,
35
- sync_mode=sync_mode,
36
- destination_sync_mode=destination_sync_mode,
37
- cursor_field=cursor_field,
38
- primary_key=primary_key,
39
- )
40
-
41
-
42
- def to_configured_catalog(
43
- configured_streams: List[ConfiguredAirbyteStream],
44
- ) -> ConfiguredAirbyteCatalog:
45
- return ConfiguredAirbyteCatalog(streams=configured_streams)
46
-
47
-
48
- def create_configured_catalog(
49
- stream: AirbyteStream, sync_mode: SyncMode = SyncMode.full_refresh
50
- ) -> ConfiguredAirbyteCatalog:
51
- configured_streams = [
52
- to_configured_stream(
53
- stream, sync_mode=sync_mode, primary_key=stream.source_defined_primary_key
54
- )
55
- ]
56
-
57
- return to_configured_catalog(configured_streams)
@@ -1,57 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
-
6
- import logging
7
- from abc import ABC, abstractmethod
8
- from typing import Generic, Iterable, Optional
9
-
10
- from airbyte_cdk.connector import TConfig
11
- from airbyte_cdk.models import (
12
- AirbyteCatalog,
13
- AirbyteMessage,
14
- AirbyteStateMessage,
15
- ConfiguredAirbyteCatalog,
16
- ConnectorSpecification,
17
- )
18
- from airbyte_cdk.sources.source import Source
19
-
20
-
21
- class SourceRunner(ABC, Generic[TConfig]):
22
- @abstractmethod
23
- def spec(self) -> ConnectorSpecification:
24
- pass
25
-
26
- @abstractmethod
27
- def discover(self, config: TConfig) -> AirbyteCatalog:
28
- pass
29
-
30
- @abstractmethod
31
- def read(
32
- self,
33
- config: TConfig,
34
- catalog: ConfiguredAirbyteCatalog,
35
- state: Optional[AirbyteStateMessage],
36
- ) -> Iterable[AirbyteMessage]:
37
- pass
38
-
39
-
40
- class CDKRunner(SourceRunner[TConfig]):
41
- def __init__(self, source: Source, name: str):
42
- self._source = source
43
- self._logger = logging.getLogger(name)
44
-
45
- def spec(self) -> ConnectorSpecification:
46
- return self._source.spec(self._logger)
47
-
48
- def discover(self, config: TConfig) -> AirbyteCatalog:
49
- return self._source.discover(self._logger, config)
50
-
51
- def read(
52
- self,
53
- config: TConfig,
54
- catalog: ConfiguredAirbyteCatalog,
55
- state: Optional[AirbyteStateMessage],
56
- ) -> Iterable[AirbyteMessage]:
57
- return self._source.read(self._logger, config, catalog, state=[state] if state else [])
@@ -1,27 +0,0 @@
1
- #
2
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
- #
4
-
5
- from typing import Any, Callable, Dict, Iterable, Optional
6
-
7
- import dpath
8
-
9
- from airbyte_cdk.models import AirbyteStream
10
-
11
-
12
- def get_first(
13
- iterable: Iterable[Any], predicate: Callable[[Any], bool] = lambda m: True
14
- ) -> Optional[Any]:
15
- return next(filter(predicate, iterable), None)
16
-
17
-
18
- def get_defined_id(stream: AirbyteStream, data: Dict[str, Any]) -> Optional[str]:
19
- if not stream.source_defined_primary_key:
20
- return None
21
- primary_key = []
22
- for key in stream.source_defined_primary_key:
23
- try:
24
- primary_key.append(str(dpath.get(data, key)))
25
- except KeyError:
26
- primary_key.append("__not_found__")
27
- return "_".join(primary_key)