airbyte-cdk 6.45.0.post24.dev14387467928__py3-none-any.whl → 6.45.1.post42.dev14452300548__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 (49) hide show
  1. airbyte_cdk/connector_builder/main.py +3 -3
  2. airbyte_cdk/models/__init__.py +1 -0
  3. airbyte_cdk/models/airbyte_protocol.py +1 -3
  4. airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +1 -1
  5. airbyte_cdk/sources/concurrent_source/concurrent_source.py +3 -3
  6. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +8 -0
  7. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +36 -0
  8. airbyte_cdk/sources/declarative/extractors/record_selector.py +6 -1
  9. airbyte_cdk/sources/declarative/interpolation/filters.py +49 -2
  10. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +31 -0
  11. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +39 -1
  12. airbyte_cdk/sources/declarative/retrievers/file_uploader.py +89 -0
  13. airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +9 -4
  14. airbyte_cdk/sources/file_based/file_based_source.py +3 -3
  15. airbyte_cdk/sources/file_based/file_based_stream_reader.py +38 -15
  16. airbyte_cdk/sources/file_based/file_record_data.py +22 -0
  17. airbyte_cdk/sources/file_based/file_types/avro_parser.py +1 -1
  18. airbyte_cdk/sources/file_based/file_types/file_transfer.py +8 -15
  19. airbyte_cdk/sources/file_based/schema_helpers.py +9 -1
  20. airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +6 -15
  21. airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +15 -38
  22. airbyte_cdk/sources/file_based/stream/permissions_file_based_stream.py +1 -3
  23. airbyte_cdk/sources/streams/concurrent/default_stream.py +3 -0
  24. airbyte_cdk/sources/types.py +11 -2
  25. airbyte_cdk/sources/utils/files_directory.py +15 -0
  26. airbyte_cdk/sources/utils/record_helper.py +8 -8
  27. airbyte_cdk/sql/shared/sql_processor.py +8 -9
  28. airbyte_cdk/test/entrypoint_wrapper.py +0 -4
  29. airbyte_cdk/test/mock_http/response_builder.py +8 -0
  30. {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/METADATA +2 -2
  31. {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/RECORD +35 -46
  32. airbyte_cdk/models/file_transfer_record_message.py +0 -13
  33. airbyte_cdk/test/declarative/__init__.py +0 -6
  34. airbyte_cdk/test/declarative/models/__init__.py +0 -7
  35. airbyte_cdk/test/declarative/models/scenario.py +0 -74
  36. airbyte_cdk/test/declarative/test_suites/__init__.py +0 -24
  37. airbyte_cdk/test/declarative/test_suites/connector_base.py +0 -202
  38. airbyte_cdk/test/declarative/test_suites/declarative_sources.py +0 -48
  39. airbyte_cdk/test/declarative/test_suites/destination_base.py +0 -12
  40. airbyte_cdk/test/declarative/test_suites/source_base.py +0 -129
  41. airbyte_cdk/test/declarative/utils/__init__.py +0 -0
  42. airbyte_cdk/test/declarative/utils/job_runner.py +0 -128
  43. airbyte_cdk/test/fixtures/__init__.py +0 -0
  44. airbyte_cdk/test/fixtures/auto.py +0 -14
  45. airbyte_cdk/test/pytest_config/plugin.py +0 -46
  46. {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/LICENSE.txt +0 -0
  47. {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/LICENSE_SHORT +0 -0
  48. {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/WHEEL +0 -0
  49. {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/entry_points.txt +0 -0
@@ -2,34 +2,27 @@
2
2
  # Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
  import logging
5
- import os
6
- from typing import Any, Dict, Iterable
5
+ from typing import Iterable, Tuple
7
6
 
8
- from airbyte_cdk.sources.file_based.config.file_based_stream_config import FileBasedStreamConfig
7
+ from airbyte_cdk.models import AirbyteRecordMessageFileReference
9
8
  from airbyte_cdk.sources.file_based.file_based_stream_reader import AbstractFileBasedStreamReader
9
+ from airbyte_cdk.sources.file_based.file_record_data import FileRecordData
10
10
  from airbyte_cdk.sources.file_based.remote_file import RemoteFile
11
-
12
- AIRBYTE_STAGING_DIRECTORY = os.getenv("AIRBYTE_STAGING_DIRECTORY", "/staging/files")
13
- DEFAULT_LOCAL_DIRECTORY = "/tmp/airbyte-file-transfer"
11
+ from airbyte_cdk.sources.utils.files_directory import get_files_directory
14
12
 
15
13
 
16
14
  class FileTransfer:
17
15
  def __init__(self) -> None:
18
- self._local_directory = (
19
- AIRBYTE_STAGING_DIRECTORY
20
- if os.path.exists(AIRBYTE_STAGING_DIRECTORY)
21
- else DEFAULT_LOCAL_DIRECTORY
22
- )
16
+ self._local_directory = get_files_directory()
23
17
 
24
- def get_file(
18
+ def upload(
25
19
  self,
26
- config: FileBasedStreamConfig,
27
20
  file: RemoteFile,
28
21
  stream_reader: AbstractFileBasedStreamReader,
29
22
  logger: logging.Logger,
30
- ) -> Iterable[Dict[str, Any]]:
23
+ ) -> Iterable[Tuple[FileRecordData, AirbyteRecordMessageFileReference]]:
31
24
  try:
32
- yield stream_reader.get_file(
25
+ yield stream_reader.upload(
33
26
  file=file, local_directory=self._local_directory, logger=logger
34
27
  )
35
28
  except Exception as ex:
@@ -18,9 +18,17 @@ JsonSchemaSupportedType = Union[List[str], Literal["string"], str]
18
18
  SchemaType = Mapping[str, Mapping[str, JsonSchemaSupportedType]]
19
19
 
20
20
  schemaless_schema = {"type": "object", "properties": {"data": {"type": "object"}}}
21
+
21
22
  file_transfer_schema = {
22
23
  "type": "object",
23
- "properties": {"data": {"type": "object"}, "file": {"type": "object"}},
24
+ "properties": {
25
+ "folder": {"type": "string"},
26
+ "file_name": {"type": "string"},
27
+ "bytes": {"type": "integer"},
28
+ "id": {"type": ["null", "string"]},
29
+ "updated_at": {"type": ["null", "string"]},
30
+ "mime_type": {"type": ["null", "string"]},
31
+ },
24
32
  }
25
33
 
26
34
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  import copy
6
6
  import logging
7
- from functools import cache, lru_cache
7
+ from functools import lru_cache
8
8
  from typing import TYPE_CHECKING, Any, Iterable, List, Mapping, MutableMapping, Optional, Union
9
9
 
10
10
  from typing_extensions import deprecated
@@ -258,19 +258,14 @@ class FileBasedStreamPartition(Partition):
258
258
  and record_data.record is not None
259
259
  ):
260
260
  # `AirbyteMessage`s of type `Record` should also be yielded so they are enqueued
261
- # If stream is flagged for file_transfer the record should data in file key
262
- record_message_data = (
263
- record_data.record.file
264
- if self._use_file_transfer()
265
- else record_data.record.data
266
- )
261
+ record_message_data = record_data.record.data
267
262
  if not record_message_data:
268
263
  raise ExceptionWithDisplayMessage("A record without data was found")
269
264
  else:
270
265
  yield Record(
271
266
  data=record_message_data,
272
267
  stream_name=self.stream_name(),
273
- is_file_transfer_message=self._use_file_transfer(),
268
+ file_reference=record_data.record.file_reference,
274
269
  )
275
270
  else:
276
271
  self._message_repository.emit_message(record_data)
@@ -284,9 +279,9 @@ class FileBasedStreamPartition(Partition):
284
279
  def to_slice(self) -> Optional[Mapping[str, Any]]:
285
280
  if self._slice is None:
286
281
  return None
287
- assert (
288
- len(self._slice["files"]) == 1
289
- ), f"Expected 1 file per partition but got {len(self._slice['files'])} for stream {self.stream_name()}"
282
+ assert len(self._slice["files"]) == 1, (
283
+ f"Expected 1 file per partition but got {len(self._slice['files'])} for stream {self.stream_name()}"
284
+ )
290
285
  file = self._slice["files"][0]
291
286
  return {"files": [file]}
292
287
 
@@ -306,10 +301,6 @@ class FileBasedStreamPartition(Partition):
306
301
  def stream_name(self) -> str:
307
302
  return self._stream.name
308
303
 
309
- @cache
310
- def _use_file_transfer(self) -> bool:
311
- return hasattr(self._stream, "use_file_transfer") and self._stream.use_file_transfer
312
-
313
304
  def __repr__(self) -> str:
314
305
  return f"FileBasedStreamPartition({self._stream.name}, {self._slice})"
315
306
 
@@ -11,7 +11,7 @@ from functools import cache
11
11
  from os import path
12
12
  from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Set, Tuple, Union
13
13
 
14
- from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, FailureType, Level
14
+ from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, AirbyteStream, FailureType, Level
15
15
  from airbyte_cdk.models import Type as MessageType
16
16
  from airbyte_cdk.sources.file_based.config.file_based_stream_config import PrimaryKeyType
17
17
  from airbyte_cdk.sources.file_based.exceptions import (
@@ -56,6 +56,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
56
56
  airbyte_columns = [ab_last_mod_col, ab_file_name_col]
57
57
  use_file_transfer = False
58
58
  preserve_directory_structure = True
59
+ _file_transfer = FileTransfer()
59
60
 
60
61
  def __init__(self, **kwargs: Any):
61
62
  if self.FILE_TRANSFER_KW in kwargs:
@@ -93,21 +94,6 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
93
94
  self.config
94
95
  )
95
96
 
96
- def _filter_schema_invalid_properties(
97
- self, configured_catalog_json_schema: Dict[str, Any]
98
- ) -> Dict[str, Any]:
99
- if self.use_file_transfer:
100
- return {
101
- "type": "object",
102
- "properties": {
103
- "file_path": {"type": "string"},
104
- "file_size": {"type": "string"},
105
- self.ab_file_name_col: {"type": "string"},
106
- },
107
- }
108
- else:
109
- return super()._filter_schema_invalid_properties(configured_catalog_json_schema)
110
-
111
97
  def _duplicated_files_names(
112
98
  self, slices: List[dict[str, List[RemoteFile]]]
113
99
  ) -> List[dict[str, List[str]]]:
@@ -145,14 +131,6 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
145
131
  record[self.ab_file_name_col] = file.uri
146
132
  return record
147
133
 
148
- def transform_record_for_file_transfer(
149
- self, record: dict[str, Any], file: RemoteFile
150
- ) -> dict[str, Any]:
151
- # timstamp() returns a float representing the number of seconds since the unix epoch
152
- record[self.modified] = int(file.last_modified.timestamp()) * 1000
153
- record[self.source_file_url] = file.uri
154
- return record
155
-
156
134
  def read_records_from_slice(self, stream_slice: StreamSlice) -> Iterable[AirbyteMessage]:
157
135
  """
158
136
  Yield all records from all remote files in `list_files_for_this_sync`.
@@ -173,19 +151,13 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
173
151
 
174
152
  try:
175
153
  if self.use_file_transfer:
176
- self.logger.info(f"{self.name}: {file} file-based syncing")
177
- # todo: complete here the code to not rely on local parser
178
- file_transfer = FileTransfer()
179
- for record in file_transfer.get_file(
180
- self.config, file, self.stream_reader, self.logger
154
+ for file_record_data, file_reference in self._file_transfer.upload(
155
+ file=file, stream_reader=self.stream_reader, logger=self.logger
181
156
  ):
182
- line_no += 1
183
- if not self.record_passes_validation_policy(record):
184
- n_skipped += 1
185
- continue
186
- record = self.transform_record_for_file_transfer(record, file)
187
157
  yield stream_data_to_airbyte_message(
188
- self.name, record, is_file_transfer_message=True
158
+ self.name,
159
+ file_record_data.dict(exclude_none=True),
160
+ file_reference=file_reference,
189
161
  )
190
162
  else:
191
163
  for record in parser.parse_records(
@@ -259,6 +231,8 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
259
231
 
260
232
  @cache
261
233
  def get_json_schema(self) -> JsonSchema:
234
+ if self.use_file_transfer:
235
+ return file_transfer_schema
262
236
  extra_fields = {
263
237
  self.ab_last_mod_col: {"type": "string"},
264
238
  self.ab_file_name_col: {"type": "string"},
@@ -282,9 +256,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
282
256
  return {"type": "object", "properties": {**extra_fields, **schema["properties"]}}
283
257
 
284
258
  def _get_raw_json_schema(self) -> JsonSchema:
285
- if self.use_file_transfer:
286
- return file_transfer_schema
287
- elif self.config.input_schema:
259
+ if self.config.input_schema:
288
260
  return self.config.get_input_schema() # type: ignore
289
261
  elif self.config.schemaless:
290
262
  return schemaless_schema
@@ -341,6 +313,11 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
341
313
  self.config.globs or [], self.config.legacy_prefix, self.logger
342
314
  )
343
315
 
316
+ def as_airbyte_stream(self) -> AirbyteStream:
317
+ file_stream = super().as_airbyte_stream()
318
+ file_stream.is_file_based = self.use_file_transfer
319
+ return file_stream
320
+
344
321
  def infer_schema(self, files: List[RemoteFile]) -> Mapping[str, Any]:
345
322
  loop = asyncio.get_event_loop()
346
323
  schema = loop.run_until_complete(self._infer_schema(files))
@@ -61,9 +61,7 @@ class PermissionsFileBasedStream(DefaultFileBasedStream):
61
61
  permissions_record = self.transform_record(
62
62
  permissions_record, file, file_datetime_string
63
63
  )
64
- yield stream_data_to_airbyte_message(
65
- self.name, permissions_record, is_file_transfer_message=False
66
- )
64
+ yield stream_data_to_airbyte_message(self.name, permissions_record)
67
65
  except Exception as e:
68
66
  self.logger.error(f"Failed to retrieve permissions for file {file.uri}: {str(e)}")
69
67
  yield AirbyteMessage(
@@ -29,6 +29,7 @@ class DefaultStream(AbstractStream):
29
29
  logger: Logger,
30
30
  cursor: Cursor,
31
31
  namespace: Optional[str] = None,
32
+ supports_file_transfer: bool = False,
32
33
  ) -> None:
33
34
  self._stream_partition_generator = partition_generator
34
35
  self._name = name
@@ -39,6 +40,7 @@ class DefaultStream(AbstractStream):
39
40
  self._logger = logger
40
41
  self._cursor = cursor
41
42
  self._namespace = namespace
43
+ self._supports_file_transfer = supports_file_transfer
42
44
 
43
45
  def generate_partitions(self) -> Iterable[Partition]:
44
46
  yield from self._stream_partition_generator.generate()
@@ -68,6 +70,7 @@ class DefaultStream(AbstractStream):
68
70
  json_schema=dict(self._json_schema),
69
71
  supported_sync_modes=[SyncMode.full_refresh],
70
72
  is_resumable=False,
73
+ is_file_based=self._supports_file_transfer,
71
74
  )
72
75
 
73
76
  if self._namespace:
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from typing import Any, ItemsView, Iterator, KeysView, List, Mapping, Optional, ValuesView
8
8
 
9
+ from airbyte_cdk.models import AirbyteRecordMessageFileReference
9
10
  from airbyte_cdk.utils.slice_hasher import SliceHasher
10
11
 
11
12
  # A FieldPointer designates a path to a field inside a mapping. For example, retrieving ["k1", "k1.2"] in the object {"k1" :{"k1.2":
@@ -23,12 +24,12 @@ class Record(Mapping[str, Any]):
23
24
  data: Mapping[str, Any],
24
25
  stream_name: str,
25
26
  associated_slice: Optional[StreamSlice] = None,
26
- is_file_transfer_message: bool = False,
27
+ file_reference: Optional[AirbyteRecordMessageFileReference] = None,
27
28
  ):
28
29
  self._data = data
29
30
  self._associated_slice = associated_slice
30
31
  self.stream_name = stream_name
31
- self.is_file_transfer_message = is_file_transfer_message
32
+ self._file_reference = file_reference
32
33
 
33
34
  @property
34
35
  def data(self) -> Mapping[str, Any]:
@@ -38,6 +39,14 @@ class Record(Mapping[str, Any]):
38
39
  def associated_slice(self) -> Optional[StreamSlice]:
39
40
  return self._associated_slice
40
41
 
42
+ @property
43
+ def file_reference(self) -> AirbyteRecordMessageFileReference:
44
+ return self._file_reference
45
+
46
+ @file_reference.setter
47
+ def file_reference(self, value: AirbyteRecordMessageFileReference) -> None:
48
+ self._file_reference = value
49
+
41
50
  def __repr__(self) -> str:
42
51
  return repr(self._data)
43
52
 
@@ -0,0 +1,15 @@
1
+ #
2
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
3
+ #
4
+ import os
5
+
6
+ AIRBYTE_STAGING_DIRECTORY = os.getenv("AIRBYTE_STAGING_DIRECTORY", "/staging/files")
7
+ DEFAULT_LOCAL_DIRECTORY = "/tmp/airbyte-file-transfer"
8
+
9
+
10
+ def get_files_directory() -> str:
11
+ return (
12
+ AIRBYTE_STAGING_DIRECTORY
13
+ if os.path.exists(AIRBYTE_STAGING_DIRECTORY)
14
+ else DEFAULT_LOCAL_DIRECTORY
15
+ )
@@ -9,10 +9,10 @@ from airbyte_cdk.models import (
9
9
  AirbyteLogMessage,
10
10
  AirbyteMessage,
11
11
  AirbyteRecordMessage,
12
+ AirbyteRecordMessageFileReference,
12
13
  AirbyteTraceMessage,
13
14
  )
14
15
  from airbyte_cdk.models import Type as MessageType
15
- from airbyte_cdk.models.file_transfer_record_message import AirbyteFileTransferRecordMessage
16
16
  from airbyte_cdk.sources.streams.core import StreamData
17
17
  from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer
18
18
 
@@ -22,7 +22,7 @@ def stream_data_to_airbyte_message(
22
22
  data_or_message: StreamData,
23
23
  transformer: TypeTransformer = TypeTransformer(TransformConfig.NoTransform),
24
24
  schema: Optional[Mapping[str, Any]] = None,
25
- is_file_transfer_message: bool = False,
25
+ file_reference: Optional[AirbyteRecordMessageFileReference] = None,
26
26
  ) -> AirbyteMessage:
27
27
  if schema is None:
28
28
  schema = {}
@@ -36,12 +36,12 @@ def stream_data_to_airbyte_message(
36
36
  # taken unless configured. See
37
37
  # docs/connector-development/cdk-python/schemas.md for details.
38
38
  transformer.transform(data, schema)
39
- if is_file_transfer_message:
40
- message = AirbyteFileTransferRecordMessage(
41
- stream=stream_name, file=data, emitted_at=now_millis, data={}
42
- )
43
- else:
44
- message = AirbyteRecordMessage(stream=stream_name, data=data, emitted_at=now_millis)
39
+ message = AirbyteRecordMessage(
40
+ stream=stream_name,
41
+ data=data,
42
+ emitted_at=now_millis,
43
+ file_reference=file_reference,
44
+ )
45
45
  return AirbyteMessage(type=MessageType.RECORD, record=message)
46
46
  case AirbyteTraceMessage():
47
47
  return AirbyteMessage(type=MessageType.TRACE, trace=data_or_message)
@@ -326,9 +326,9 @@ class SqlProcessorBase(abc.ABC):
326
326
 
327
327
  if DEBUG_MODE:
328
328
  found_schemas = schemas_list
329
- assert (
330
- schema_name in found_schemas
331
- ), f"Schema {schema_name} was not created. Found: {found_schemas}"
329
+ assert schema_name in found_schemas, (
330
+ f"Schema {schema_name} was not created. Found: {found_schemas}"
331
+ )
332
332
 
333
333
  def _quote_identifier(self, identifier: str) -> str:
334
334
  """Return the given identifier, quoted."""
@@ -617,10 +617,10 @@ class SqlProcessorBase(abc.ABC):
617
617
  self._execute_sql(
618
618
  f"""
619
619
  INSERT INTO {self._fully_qualified(final_table_name)} (
620
- {f',{nl} '.join(columns)}
620
+ {f",{nl} ".join(columns)}
621
621
  )
622
622
  SELECT
623
- {f',{nl} '.join(columns)}
623
+ {f",{nl} ".join(columns)}
624
624
  FROM {self._fully_qualified(temp_table_name)}
625
625
  """,
626
626
  )
@@ -645,8 +645,7 @@ class SqlProcessorBase(abc.ABC):
645
645
  deletion_name = f"{final_table_name}_deleteme"
646
646
  commands = "\n".join(
647
647
  [
648
- f"ALTER TABLE {self._fully_qualified(final_table_name)} RENAME "
649
- f"TO {deletion_name};",
648
+ f"ALTER TABLE {self._fully_qualified(final_table_name)} RENAME TO {deletion_name};",
650
649
  f"ALTER TABLE {self._fully_qualified(temp_table_name)} RENAME "
651
650
  f"TO {final_table_name};",
652
651
  f"DROP TABLE {self._fully_qualified(deletion_name)};",
@@ -686,10 +685,10 @@ class SqlProcessorBase(abc.ABC):
686
685
  {set_clause}
687
686
  WHEN NOT MATCHED THEN INSERT
688
687
  (
689
- {f',{nl} '.join(columns)}
688
+ {f",{nl} ".join(columns)}
690
689
  )
691
690
  VALUES (
692
- tmp.{f',{nl} tmp.'.join(columns)}
691
+ tmp.{f",{nl} tmp.".join(columns)}
693
692
  );
694
693
  """,
695
694
  )
@@ -82,10 +82,6 @@ class EntrypointOutput:
82
82
  def state_messages(self) -> List[AirbyteMessage]:
83
83
  return self._get_message_by_types([Type.STATE])
84
84
 
85
- @property
86
- def connection_status_messages(self) -> List[AirbyteMessage]:
87
- return self._get_message_by_types([Type.CONNECTION_STATUS])
88
-
89
85
  @property
90
86
  def most_recent_state(self) -> Any:
91
87
  state_messages = self._get_message_by_types([Type.STATE])
@@ -198,6 +198,14 @@ def find_template(resource: str, execution_folder: str) -> Dict[str, Any]:
198
198
  return json.load(template_file) # type: ignore # we assume the dev correctly set up the resource file
199
199
 
200
200
 
201
+ def find_binary_response(resource: str, execution_folder: str) -> bytes:
202
+ response_filepath = str(
203
+ get_unit_test_folder(execution_folder) / "resource" / "http" / "response" / f"{resource}"
204
+ )
205
+ with open(response_filepath, "rb") as response_file:
206
+ return response_file.read() # type: ignore # we assume the dev correctly set up the resource file
207
+
208
+
201
209
  def create_record_builder(
202
210
  response_template: Dict[str, Any],
203
211
  records_path: Union[FieldPath, NestedPath],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 6.45.0.post24.dev14387467928
3
+ Version: 6.45.1.post42.dev14452300548
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -22,7 +22,7 @@ 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: airbyte-protocol-models-dataclasses (>=0.14,<0.15)
25
+ Requires-Dist: airbyte-protocol-models-dataclasses (>=0.15,<0.16)
26
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