airbyte-cdk 6.5.3rc2__py3-none-any.whl → 6.6.0__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 (200) hide show
  1. airbyte_cdk/__init__.py +17 -2
  2. airbyte_cdk/config_observation.py +10 -3
  3. airbyte_cdk/connector.py +19 -9
  4. airbyte_cdk/connector_builder/connector_builder_handler.py +28 -8
  5. airbyte_cdk/connector_builder/main.py +26 -6
  6. airbyte_cdk/connector_builder/message_grouper.py +95 -25
  7. airbyte_cdk/destinations/destination.py +47 -14
  8. airbyte_cdk/destinations/vector_db_based/config.py +36 -14
  9. airbyte_cdk/destinations/vector_db_based/document_processor.py +49 -11
  10. airbyte_cdk/destinations/vector_db_based/embedder.py +52 -11
  11. airbyte_cdk/destinations/vector_db_based/test_utils.py +14 -4
  12. airbyte_cdk/destinations/vector_db_based/utils.py +8 -2
  13. airbyte_cdk/destinations/vector_db_based/writer.py +15 -4
  14. airbyte_cdk/entrypoint.py +82 -26
  15. airbyte_cdk/exception_handler.py +13 -3
  16. airbyte_cdk/logger.py +10 -2
  17. airbyte_cdk/models/airbyte_protocol.py +11 -5
  18. airbyte_cdk/models/airbyte_protocol_serializers.py +9 -3
  19. airbyte_cdk/models/well_known_types.py +1 -1
  20. airbyte_cdk/sources/abstract_source.py +63 -17
  21. airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +47 -14
  22. airbyte_cdk/sources/concurrent_source/concurrent_source.py +25 -7
  23. airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +27 -6
  24. airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +9 -3
  25. airbyte_cdk/sources/connector_state_manager.py +32 -10
  26. airbyte_cdk/sources/declarative/async_job/job.py +3 -1
  27. airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +68 -14
  28. airbyte_cdk/sources/declarative/async_job/job_tracker.py +24 -6
  29. airbyte_cdk/sources/declarative/async_job/repository.py +3 -1
  30. airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
  31. airbyte_cdk/sources/declarative/auth/jwt.py +27 -7
  32. airbyte_cdk/sources/declarative/auth/oauth.py +35 -11
  33. airbyte_cdk/sources/declarative/auth/selective_authenticator.py +3 -1
  34. airbyte_cdk/sources/declarative/auth/token.py +25 -8
  35. airbyte_cdk/sources/declarative/checks/check_stream.py +12 -4
  36. airbyte_cdk/sources/declarative/checks/connection_checker.py +3 -1
  37. airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +11 -3
  38. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +106 -50
  39. airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +20 -6
  40. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +43 -0
  41. airbyte_cdk/sources/declarative/declarative_source.py +3 -1
  42. airbyte_cdk/sources/declarative/declarative_stream.py +27 -6
  43. airbyte_cdk/sources/declarative/decoders/__init__.py +2 -2
  44. airbyte_cdk/sources/declarative/decoders/decoder.py +3 -1
  45. airbyte_cdk/sources/declarative/decoders/json_decoder.py +48 -13
  46. airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +3 -1
  47. airbyte_cdk/sources/declarative/decoders/xml_decoder.py +6 -2
  48. airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +6 -2
  49. airbyte_cdk/sources/declarative/extractors/record_filter.py +24 -7
  50. airbyte_cdk/sources/declarative/extractors/record_selector.py +10 -3
  51. airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +15 -5
  52. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +96 -31
  53. airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +22 -8
  54. airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +46 -15
  55. airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +19 -5
  56. airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +3 -1
  57. airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +20 -2
  58. airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +5 -1
  59. airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +10 -3
  60. airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +6 -2
  61. airbyte_cdk/sources/declarative/interpolation/interpolation.py +7 -1
  62. airbyte_cdk/sources/declarative/interpolation/jinja.py +6 -2
  63. airbyte_cdk/sources/declarative/interpolation/macros.py +19 -4
  64. airbyte_cdk/sources/declarative/manifest_declarative_source.py +106 -24
  65. airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +14 -5
  66. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +697 -678
  67. airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +13 -4
  68. airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +9 -2
  69. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +802 -232
  70. airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +29 -7
  71. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +25 -7
  72. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +54 -15
  73. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +6 -2
  74. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +3 -1
  75. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +17 -5
  76. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +15 -5
  77. airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +3 -1
  78. airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +18 -8
  79. airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +16 -7
  80. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +51 -14
  81. airbyte_cdk/sources/declarative/requesters/http_job_repository.py +29 -8
  82. airbyte_cdk/sources/declarative/requesters/http_requester.py +58 -16
  83. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +49 -14
  84. airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +3 -1
  85. airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +3 -1
  86. airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +17 -5
  87. airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +24 -7
  88. airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +9 -3
  89. airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +3 -1
  90. airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +6 -2
  91. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +19 -6
  92. airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +3 -1
  93. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +21 -7
  94. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +18 -6
  95. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +27 -8
  96. airbyte_cdk/sources/declarative/requesters/requester.py +3 -1
  97. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +12 -5
  98. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +105 -24
  99. airbyte_cdk/sources/declarative/schema/default_schema_loader.py +3 -1
  100. airbyte_cdk/sources/declarative/spec/spec.py +8 -2
  101. airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +3 -1
  102. airbyte_cdk/sources/declarative/transformations/add_fields.py +12 -3
  103. airbyte_cdk/sources/declarative/transformations/remove_fields.py +6 -2
  104. airbyte_cdk/sources/declarative/types.py +8 -1
  105. airbyte_cdk/sources/declarative/yaml_declarative_source.py +3 -1
  106. airbyte_cdk/sources/embedded/base_integration.py +14 -4
  107. airbyte_cdk/sources/embedded/catalog.py +16 -4
  108. airbyte_cdk/sources/embedded/runner.py +19 -3
  109. airbyte_cdk/sources/embedded/tools.py +3 -1
  110. airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +12 -4
  111. airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +27 -7
  112. airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +12 -6
  113. airbyte_cdk/sources/file_based/config/csv_format.py +21 -9
  114. airbyte_cdk/sources/file_based/config/file_based_stream_config.py +6 -2
  115. airbyte_cdk/sources/file_based/config/unstructured_format.py +10 -3
  116. airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +2 -4
  117. airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +7 -2
  118. airbyte_cdk/sources/file_based/exceptions.py +13 -15
  119. airbyte_cdk/sources/file_based/file_based_source.py +82 -24
  120. airbyte_cdk/sources/file_based/file_based_stream_reader.py +16 -5
  121. airbyte_cdk/sources/file_based/file_types/avro_parser.py +58 -17
  122. airbyte_cdk/sources/file_based/file_types/csv_parser.py +89 -26
  123. airbyte_cdk/sources/file_based/file_types/excel_parser.py +25 -7
  124. airbyte_cdk/sources/file_based/file_types/file_transfer.py +8 -2
  125. airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
  126. airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +20 -6
  127. airbyte_cdk/sources/file_based/file_types/parquet_parser.py +57 -16
  128. airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +64 -15
  129. airbyte_cdk/sources/file_based/schema_helpers.py +33 -10
  130. airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +3 -1
  131. airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +16 -5
  132. airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +33 -10
  133. airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +47 -11
  134. airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +13 -22
  135. airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +53 -17
  136. airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +17 -5
  137. airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +3 -1
  138. airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +26 -9
  139. airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +67 -21
  140. airbyte_cdk/sources/http_logger.py +5 -1
  141. airbyte_cdk/sources/message/repository.py +18 -4
  142. airbyte_cdk/sources/source.py +17 -7
  143. airbyte_cdk/sources/streams/availability_strategy.py +9 -3
  144. airbyte_cdk/sources/streams/call_rate.py +63 -19
  145. airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +31 -7
  146. airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +6 -2
  147. airbyte_cdk/sources/streams/concurrent/adapters.py +77 -22
  148. airbyte_cdk/sources/streams/concurrent/cursor.py +56 -20
  149. airbyte_cdk/sources/streams/concurrent/default_stream.py +9 -2
  150. airbyte_cdk/sources/streams/concurrent/helpers.py +6 -2
  151. airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +9 -2
  152. airbyte_cdk/sources/streams/concurrent/partition_reader.py +4 -1
  153. airbyte_cdk/sources/streams/concurrent/partitions/record.py +10 -2
  154. airbyte_cdk/sources/streams/concurrent/partitions/types.py +6 -2
  155. airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +25 -10
  156. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +32 -16
  157. airbyte_cdk/sources/streams/core.py +77 -22
  158. airbyte_cdk/sources/streams/http/availability_strategy.py +3 -1
  159. airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +4 -1
  160. airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +3 -1
  161. airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +16 -5
  162. airbyte_cdk/sources/streams/http/error_handlers/response_models.py +9 -3
  163. airbyte_cdk/sources/streams/http/exceptions.py +2 -2
  164. airbyte_cdk/sources/streams/http/http.py +133 -33
  165. airbyte_cdk/sources/streams/http/http_client.py +91 -29
  166. airbyte_cdk/sources/streams/http/rate_limiting.py +23 -7
  167. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +19 -6
  168. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +38 -11
  169. airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
  170. airbyte_cdk/sources/types.py +5 -1
  171. airbyte_cdk/sources/utils/record_helper.py +12 -3
  172. airbyte_cdk/sources/utils/schema_helpers.py +9 -3
  173. airbyte_cdk/sources/utils/slice_logger.py +4 -1
  174. airbyte_cdk/sources/utils/transform.py +24 -9
  175. airbyte_cdk/sql/exceptions.py +19 -6
  176. airbyte_cdk/sql/secrets.py +3 -1
  177. airbyte_cdk/sql/shared/catalog_providers.py +13 -4
  178. airbyte_cdk/sql/shared/sql_processor.py +44 -14
  179. airbyte_cdk/test/catalog_builder.py +19 -8
  180. airbyte_cdk/test/entrypoint_wrapper.py +27 -8
  181. airbyte_cdk/test/mock_http/mocker.py +41 -11
  182. airbyte_cdk/test/mock_http/request.py +9 -3
  183. airbyte_cdk/test/mock_http/response.py +3 -1
  184. airbyte_cdk/test/mock_http/response_builder.py +29 -7
  185. airbyte_cdk/test/state_builder.py +10 -2
  186. airbyte_cdk/test/utils/data.py +6 -2
  187. airbyte_cdk/test/utils/http_mocking.py +3 -1
  188. airbyte_cdk/utils/airbyte_secrets_utils.py +3 -1
  189. airbyte_cdk/utils/analytics_message.py +10 -2
  190. airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
  191. airbyte_cdk/utils/mapping_helpers.py +3 -1
  192. airbyte_cdk/utils/message_utils.py +11 -4
  193. airbyte_cdk/utils/print_buffer.py +6 -1
  194. airbyte_cdk/utils/schema_inferrer.py +30 -9
  195. airbyte_cdk/utils/spec_schema_transformations.py +3 -1
  196. airbyte_cdk/utils/traced_exception.py +35 -9
  197. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/METADATA +8 -7
  198. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/RECORD +200 -200
  199. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/LICENSE.txt +0 -0
  200. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/WHEEL +0 -0
airbyte_cdk/__init__.py CHANGED
@@ -46,7 +46,7 @@ API Reference
46
46
  # Imports should also be placed in `if TYPE_CHECKING` blocks if they are only used as type
47
47
  # hints - again, to avoid circular dependencies.
48
48
  # Once those issues are resolved, the below can be sorted with isort.
49
- from importlib import metadata
49
+ import dunamai as _dunamai
50
50
 
51
51
  from .destinations import Destination
52
52
  from .models import AirbyteConnectionStatus, AirbyteMessage, ConfiguredAirbyteCatalog, Status, Type, FailureType, AirbyteStream, AdvancedAuth, DestinationSyncMode, ConnectorSpecification, OAuthConfigSpecification, OrchestratorType, ConfiguredAirbyteStream, SyncMode, AirbyteLogMessage, Level, AirbyteRecordMessage
@@ -281,4 +281,19 @@ __all__ = [
281
281
  "Source",
282
282
  "StreamSlice",
283
283
  ]
284
- __version__ = metadata.version("airbyte_cdk")
284
+
285
+ __version__: str
286
+ """Version generated by poetry dynamic versioning during publish.
287
+
288
+ When running in development, dunamai will calculate a new prerelease version
289
+ from existing git release tag info.
290
+ """
291
+
292
+ try:
293
+ __version__ = _dunamai.get_version(
294
+ "airbyte-cdk",
295
+ third_choice=_dunamai.Version.from_any_vcs,
296
+ fallback=_dunamai.Version("0.0.0+dev"),
297
+ ).serialize()
298
+ except:
299
+ __version__ = "0.0.0+dev"
@@ -23,7 +23,10 @@ from orjson import orjson
23
23
 
24
24
  class ObservedDict(dict): # type: ignore # disallow_any_generics is set to True, and dict is equivalent to dict[Any]
25
25
  def __init__(
26
- self, non_observed_mapping: MutableMapping[Any, Any], observer: ConfigObserver, update_on_unchanged_value: bool = True
26
+ self,
27
+ non_observed_mapping: MutableMapping[Any, Any],
28
+ observer: ConfigObserver,
29
+ update_on_unchanged_value: bool = True,
27
30
  ) -> None:
28
31
  non_observed_mapping = copy(non_observed_mapping)
29
32
  self.observer = observer
@@ -69,11 +72,15 @@ class ConfigObserver:
69
72
  emit_configuration_as_airbyte_control_message(self.config)
70
73
 
71
74
 
72
- def observe_connector_config(non_observed_connector_config: MutableMapping[str, Any]) -> ObservedDict:
75
+ def observe_connector_config(
76
+ non_observed_connector_config: MutableMapping[str, Any],
77
+ ) -> ObservedDict:
73
78
  if isinstance(non_observed_connector_config, ObservedDict):
74
79
  raise ValueError("This connector configuration is already observed")
75
80
  connector_config_observer = ConfigObserver()
76
- observed_connector_config = ObservedDict(non_observed_connector_config, connector_config_observer)
81
+ observed_connector_config = ObservedDict(
82
+ non_observed_connector_config, connector_config_observer
83
+ )
77
84
  connector_config_observer.set_config(observed_connector_config)
78
85
  return observed_connector_config
79
86
 
airbyte_cdk/connector.py CHANGED
@@ -11,7 +11,11 @@ from abc import ABC, abstractmethod
11
11
  from typing import Any, Generic, Mapping, Optional, Protocol, TypeVar
12
12
 
13
13
  import yaml
14
- from airbyte_cdk.models import AirbyteConnectionStatus, ConnectorSpecification, ConnectorSpecificationSerializer
14
+ from airbyte_cdk.models import (
15
+ AirbyteConnectionStatus,
16
+ ConnectorSpecification,
17
+ ConnectorSpecificationSerializer,
18
+ )
15
19
 
16
20
 
17
21
  def load_optional_package_file(package: str, filename: str) -> Optional[bytes]:
@@ -53,7 +57,9 @@ class BaseConnector(ABC, Generic[TConfig]):
53
57
  try:
54
58
  return json.loads(contents)
55
59
  except json.JSONDecodeError as error:
56
- raise ValueError(f"Could not read json file {file_path}: {error}. Please ensure that it is a valid JSON.")
60
+ raise ValueError(
61
+ f"Could not read json file {file_path}: {error}. Please ensure that it is a valid JSON."
62
+ )
57
63
 
58
64
  @staticmethod
59
65
  def write_config(config: TConfig, config_path: str) -> None:
@@ -72,7 +78,9 @@ class BaseConnector(ABC, Generic[TConfig]):
72
78
  json_spec = load_optional_package_file(package, "spec.json")
73
79
 
74
80
  if yaml_spec and json_spec:
75
- raise RuntimeError("Found multiple spec files in the package. Only one of spec.yaml or spec.json should be provided.")
81
+ raise RuntimeError(
82
+ "Found multiple spec files in the package. Only one of spec.yaml or spec.json should be provided."
83
+ )
76
84
 
77
85
  if yaml_spec:
78
86
  spec_obj = yaml.load(yaml_spec, Loader=yaml.SafeLoader)
@@ -80,7 +88,9 @@ class BaseConnector(ABC, Generic[TConfig]):
80
88
  try:
81
89
  spec_obj = json.loads(json_spec)
82
90
  except json.JSONDecodeError as error:
83
- raise ValueError(f"Could not read json spec file: {error}. Please ensure that it is a valid JSON.")
91
+ raise ValueError(
92
+ f"Could not read json spec file: {error}. Please ensure that it is a valid JSON."
93
+ )
84
94
  else:
85
95
  raise FileNotFoundError("Unable to find spec.yaml or spec.json in the package.")
86
96
 
@@ -96,17 +106,17 @@ class BaseConnector(ABC, Generic[TConfig]):
96
106
 
97
107
  class _WriteConfigProtocol(Protocol):
98
108
  @staticmethod
99
- def write_config(config: Mapping[str, Any], config_path: str) -> None:
100
- ...
109
+ def write_config(config: Mapping[str, Any], config_path: str) -> None: ...
101
110
 
102
111
 
103
112
  class DefaultConnectorMixin:
104
113
  # can be overridden to change an input config
105
- def configure(self: _WriteConfigProtocol, config: Mapping[str, Any], temp_dir: str) -> Mapping[str, Any]:
114
+ def configure(
115
+ self: _WriteConfigProtocol, config: Mapping[str, Any], temp_dir: str
116
+ ) -> Mapping[str, Any]:
106
117
  config_path = os.path.join(temp_dir, "config.json")
107
118
  self.write_config(config, config_path)
108
119
  return config
109
120
 
110
121
 
111
- class Connector(DefaultConnectorMixin, BaseConnector[Mapping[str, Any]], ABC):
112
- ...
122
+ class Connector(DefaultConnectorMixin, BaseConnector[Mapping[str, Any]], ABC): ...
@@ -7,12 +7,19 @@ from datetime import datetime
7
7
  from typing import Any, List, Mapping
8
8
 
9
9
  from airbyte_cdk.connector_builder.message_grouper import MessageGrouper
10
- from airbyte_cdk.models import AirbyteMessage, AirbyteRecordMessage, AirbyteStateMessage, ConfiguredAirbyteCatalog
10
+ from airbyte_cdk.models import (
11
+ AirbyteMessage,
12
+ AirbyteRecordMessage,
13
+ AirbyteStateMessage,
14
+ ConfiguredAirbyteCatalog,
15
+ )
11
16
  from airbyte_cdk.models import Type
12
17
  from airbyte_cdk.models import Type as MessageType
13
18
  from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
14
19
  from airbyte_cdk.sources.declarative.manifest_declarative_source import ManifestDeclarativeSource
15
- from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import ModelToComponentFactory
20
+ from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import (
21
+ ModelToComponentFactory,
22
+ )
16
23
  from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
17
24
  from airbyte_cdk.utils.traced_exception import AirbyteTracedException
18
25
 
@@ -34,7 +41,9 @@ class TestReadLimits:
34
41
 
35
42
  def get_limits(config: Mapping[str, Any]) -> TestReadLimits:
36
43
  command_config = config.get("__test_read_config", {})
37
- max_pages_per_slice = command_config.get(MAX_PAGES_PER_SLICE_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE
44
+ max_pages_per_slice = (
45
+ command_config.get(MAX_PAGES_PER_SLICE_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE
46
+ )
38
47
  max_slices = command_config.get(MAX_SLICES_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_SLICES
39
48
  max_records = command_config.get(MAX_RECORDS_KEY) or DEFAULT_MAXIMUM_RECORDS
40
49
  return TestReadLimits(max_records, max_pages_per_slice, max_slices)
@@ -64,15 +73,24 @@ def read_stream(
64
73
  ) -> AirbyteMessage:
65
74
  try:
66
75
  handler = MessageGrouper(limits.max_pages_per_slice, limits.max_slices, limits.max_records)
67
- stream_name = configured_catalog.streams[0].stream.name # The connector builder only supports a single stream
68
- stream_read = handler.get_message_groups(source, config, configured_catalog, state, limits.max_records)
76
+ stream_name = configured_catalog.streams[
77
+ 0
78
+ ].stream.name # The connector builder only supports a single stream
79
+ stream_read = handler.get_message_groups(
80
+ source, config, configured_catalog, state, limits.max_records
81
+ )
69
82
  return AirbyteMessage(
70
83
  type=MessageType.RECORD,
71
- record=AirbyteRecordMessage(data=dataclasses.asdict(stream_read), stream=stream_name, emitted_at=_emitted_at()),
84
+ record=AirbyteRecordMessage(
85
+ data=dataclasses.asdict(stream_read), stream=stream_name, emitted_at=_emitted_at()
86
+ ),
72
87
  )
73
88
  except Exception as exc:
74
89
  error = AirbyteTracedException.from_exception(
75
- exc, message=filter_secrets(f"Error reading stream with config={config} and catalog={configured_catalog}: {str(exc)}")
90
+ exc,
91
+ message=filter_secrets(
92
+ f"Error reading stream with config={config} and catalog={configured_catalog}: {str(exc)}"
93
+ ),
76
94
  )
77
95
  return error.as_airbyte_message()
78
96
 
@@ -88,7 +106,9 @@ def resolve_manifest(source: ManifestDeclarativeSource) -> AirbyteMessage:
88
106
  ),
89
107
  )
90
108
  except Exception as exc:
91
- error = AirbyteTracedException.from_exception(exc, message=f"Error resolving manifest: {str(exc)}")
109
+ error = AirbyteTracedException.from_exception(
110
+ exc, message=f"Error resolving manifest: {str(exc)}"
111
+ )
92
112
  return error.as_airbyte_message()
93
113
 
94
114
 
@@ -7,7 +7,13 @@ import sys
7
7
  from typing import Any, List, Mapping, Optional, Tuple
8
8
 
9
9
  from airbyte_cdk.connector import BaseConnector
10
- from airbyte_cdk.connector_builder.connector_builder_handler import TestReadLimits, create_source, get_limits, read_stream, resolve_manifest
10
+ from airbyte_cdk.connector_builder.connector_builder_handler import (
11
+ TestReadLimits,
12
+ create_source,
13
+ get_limits,
14
+ read_stream,
15
+ resolve_manifest,
16
+ )
11
17
  from airbyte_cdk.entrypoint import AirbyteEntrypoint
12
18
  from airbyte_cdk.models import (
13
19
  AirbyteMessage,
@@ -22,11 +28,17 @@ from airbyte_cdk.utils.traced_exception import AirbyteTracedException
22
28
  from orjson import orjson
23
29
 
24
30
 
25
- def get_config_and_catalog_from_args(args: List[str]) -> Tuple[str, Mapping[str, Any], Optional[ConfiguredAirbyteCatalog], Any]:
31
+ def get_config_and_catalog_from_args(
32
+ args: List[str],
33
+ ) -> Tuple[str, Mapping[str, Any], Optional[ConfiguredAirbyteCatalog], Any]:
26
34
  # TODO: Add functionality for the `debug` logger.
27
35
  # Currently, no one `debug` level log will be displayed during `read` a stream for a connector created through `connector-builder`.
28
36
  parsed_args = AirbyteEntrypoint.parse_args(args)
29
- config_path, catalog_path, state_path = parsed_args.config, parsed_args.catalog, parsed_args.state
37
+ config_path, catalog_path, state_path = (
38
+ parsed_args.config,
39
+ parsed_args.catalog,
40
+ parsed_args.state,
41
+ )
30
42
  if parsed_args.command != "read":
31
43
  raise ValueError("Only read commands are allowed for Connector Builder requests.")
32
44
 
@@ -64,7 +76,9 @@ def handle_connector_builder_request(
64
76
  if command == "resolve_manifest":
65
77
  return resolve_manifest(source)
66
78
  elif command == "test_read":
67
- assert catalog is not None, "`test_read` requires a valid `ConfiguredAirbyteCatalog`, got None."
79
+ assert (
80
+ catalog is not None
81
+ ), "`test_read` requires a valid `ConfiguredAirbyteCatalog`, got None."
68
82
  return read_stream(source, config, catalog, state, limits)
69
83
  else:
70
84
  raise ValueError(f"Unrecognized command {command}.")
@@ -74,13 +88,19 @@ def handle_request(args: List[str]) -> str:
74
88
  command, config, catalog, state = get_config_and_catalog_from_args(args)
75
89
  limits = get_limits(config)
76
90
  source = create_source(config, limits)
77
- return orjson.dumps(AirbyteMessageSerializer.dump(handle_connector_builder_request(source, command, config, catalog, state, limits))).decode() # type: ignore[no-any-return] # Serializer.dump() always returns AirbyteMessage
91
+ return orjson.dumps(
92
+ AirbyteMessageSerializer.dump(
93
+ handle_connector_builder_request(source, command, config, catalog, state, limits)
94
+ )
95
+ ).decode() # type: ignore[no-any-return] # Serializer.dump() always returns AirbyteMessage
78
96
 
79
97
 
80
98
  if __name__ == "__main__":
81
99
  try:
82
100
  print(handle_request(sys.argv[1:]))
83
101
  except Exception as exc:
84
- error = AirbyteTracedException.from_exception(exc, message=f"Error handling request: {str(exc)}")
102
+ error = AirbyteTracedException.from_exception(
103
+ exc, message=f"Error handling request: {str(exc)}"
104
+ )
85
105
  m = error.as_airbyte_message()
86
106
  print(orjson.dumps(AirbyteMessageSerializer.dump(m)).decode())
@@ -45,7 +45,9 @@ class MessageGrouper:
45
45
  self._max_slices = max_slices
46
46
  self._max_record_limit = max_record_limit
47
47
 
48
- def _pk_to_nested_and_composite_field(self, field: Optional[Union[str, List[str], List[List[str]]]]) -> List[List[str]]:
48
+ def _pk_to_nested_and_composite_field(
49
+ self, field: Optional[Union[str, List[str], List[List[str]]]]
50
+ ) -> List[List[str]]:
49
51
  if not field:
50
52
  return [[]]
51
53
 
@@ -58,7 +60,9 @@ class MessageGrouper:
58
60
 
59
61
  return field # type: ignore # the type of field is expected to be List[List[str]] here
60
62
 
61
- def _cursor_field_to_nested_and_composite_field(self, field: Union[str, List[str]]) -> List[List[str]]:
63
+ def _cursor_field_to_nested_and_composite_field(
64
+ self, field: Union[str, List[str]]
65
+ ) -> List[List[str]]:
62
66
  if not field:
63
67
  return [[]]
64
68
 
@@ -80,8 +84,12 @@ class MessageGrouper:
80
84
  record_limit: Optional[int] = None,
81
85
  ) -> StreamRead:
82
86
  if record_limit is not None and not (1 <= record_limit <= self._max_record_limit):
83
- raise ValueError(f"Record limit must be between 1 and {self._max_record_limit}. Got {record_limit}")
84
- stream = source.streams(config)[0] # The connector builder currently only supports reading from a single stream at a time
87
+ raise ValueError(
88
+ f"Record limit must be between 1 and {self._max_record_limit}. Got {record_limit}"
89
+ )
90
+ stream = source.streams(config)[
91
+ 0
92
+ ] # The connector builder currently only supports reading from a single stream at a time
85
93
  schema_inferrer = SchemaInferrer(
86
94
  self._pk_to_nested_and_composite_field(stream.primary_key),
87
95
  self._cursor_field_to_nested_and_composite_field(stream.cursor_field),
@@ -104,7 +112,11 @@ class MessageGrouper:
104
112
  record_limit,
105
113
  ):
106
114
  if isinstance(message_group, AirbyteLogMessage):
107
- log_messages.append(LogMessage(**{"message": message_group.message, "level": message_group.level.value}))
115
+ log_messages.append(
116
+ LogMessage(
117
+ **{"message": message_group.message, "level": message_group.level.value}
118
+ )
119
+ )
108
120
  elif isinstance(message_group, AirbyteTraceMessage):
109
121
  if message_group.type == TraceType.ERROR:
110
122
  log_messages.append(
@@ -118,7 +130,10 @@ class MessageGrouper:
118
130
  )
119
131
  )
120
132
  elif isinstance(message_group, AirbyteControlMessage):
121
- if not latest_config_update or latest_config_update.emitted_at <= message_group.emitted_at:
133
+ if (
134
+ not latest_config_update
135
+ or latest_config_update.emitted_at <= message_group.emitted_at
136
+ ):
122
137
  latest_config_update = message_group
123
138
  elif isinstance(message_group, AuxiliaryRequest):
124
139
  auxiliary_requests.append(message_group)
@@ -142,7 +157,9 @@ class MessageGrouper:
142
157
  test_read_limit_reached=self._has_reached_limit(slices),
143
158
  auxiliary_requests=auxiliary_requests,
144
159
  inferred_schema=schema,
145
- latest_config_update=self._clean_config(latest_config_update.connectorConfig.config) if latest_config_update else None,
160
+ latest_config_update=self._clean_config(latest_config_update.connectorConfig.config)
161
+ if latest_config_update
162
+ else None,
146
163
  inferred_datetime_formats=datetime_format_inferrer.get_inferred_datetime_formats(),
147
164
  )
148
165
 
@@ -152,7 +169,15 @@ class MessageGrouper:
152
169
  schema_inferrer: SchemaInferrer,
153
170
  datetime_format_inferrer: DatetimeFormatInferrer,
154
171
  limit: int,
155
- ) -> Iterable[Union[StreamReadPages, AirbyteControlMessage, AirbyteLogMessage, AirbyteTraceMessage, AuxiliaryRequest]]:
172
+ ) -> Iterable[
173
+ Union[
174
+ StreamReadPages,
175
+ AirbyteControlMessage,
176
+ AirbyteLogMessage,
177
+ AirbyteTraceMessage,
178
+ AuxiliaryRequest,
179
+ ]
180
+ ]:
156
181
  """
157
182
  Message groups are partitioned according to when request log messages are received. Subsequent response log messages
158
183
  and record messages belong to the prior request log message and when we encounter another request, append the latest
@@ -180,10 +205,17 @@ class MessageGrouper:
180
205
  while records_count < limit and (message := next(messages, None)):
181
206
  json_object = self._parse_json(message.log) if message.type == MessageType.LOG else None
182
207
  if json_object is not None and not isinstance(json_object, dict):
183
- raise ValueError(f"Expected log message to be a dict, got {json_object} of type {type(json_object)}")
208
+ raise ValueError(
209
+ f"Expected log message to be a dict, got {json_object} of type {type(json_object)}"
210
+ )
184
211
  json_message: Optional[Dict[str, JsonType]] = json_object
185
212
  if self._need_to_close_page(at_least_one_page_in_group, message, json_message):
186
- self._close_page(current_page_request, current_page_response, current_slice_pages, current_page_records)
213
+ self._close_page(
214
+ current_page_request,
215
+ current_page_response,
216
+ current_slice_pages,
217
+ current_page_records,
218
+ )
187
219
  current_page_request = None
188
220
  current_page_response = None
189
221
 
@@ -200,7 +232,9 @@ class MessageGrouper:
200
232
  current_slice_descriptor = self._parse_slice_description(message.log.message) # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
201
233
  current_slice_pages = []
202
234
  at_least_one_page_in_group = False
203
- elif message.type == MessageType.LOG and message.log.message.startswith(SliceLogger.SLICE_LOG_PREFIX): # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
235
+ elif message.type == MessageType.LOG and message.log.message.startswith(
236
+ SliceLogger.SLICE_LOG_PREFIX
237
+ ): # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
204
238
  # parsing the first slice
205
239
  current_slice_descriptor = self._parse_slice_description(message.log.message) # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
206
240
  elif message.type == MessageType.LOG:
@@ -208,14 +242,22 @@ class MessageGrouper:
208
242
  if self._is_auxiliary_http_request(json_message):
209
243
  airbyte_cdk = json_message.get("airbyte_cdk", {})
210
244
  if not isinstance(airbyte_cdk, dict):
211
- raise ValueError(f"Expected airbyte_cdk to be a dict, got {airbyte_cdk} of type {type(airbyte_cdk)}")
245
+ raise ValueError(
246
+ f"Expected airbyte_cdk to be a dict, got {airbyte_cdk} of type {type(airbyte_cdk)}"
247
+ )
212
248
  stream = airbyte_cdk.get("stream", {})
213
249
  if not isinstance(stream, dict):
214
- raise ValueError(f"Expected stream to be a dict, got {stream} of type {type(stream)}")
215
- title_prefix = "Parent stream: " if stream.get("is_substream", False) else ""
250
+ raise ValueError(
251
+ f"Expected stream to be a dict, got {stream} of type {type(stream)}"
252
+ )
253
+ title_prefix = (
254
+ "Parent stream: " if stream.get("is_substream", False) else ""
255
+ )
216
256
  http = json_message.get("http", {})
217
257
  if not isinstance(http, dict):
218
- raise ValueError(f"Expected http to be a dict, got {http} of type {type(http)}")
258
+ raise ValueError(
259
+ f"Expected http to be a dict, got {http} of type {type(http)}"
260
+ )
219
261
  yield AuxiliaryRequest(
220
262
  title=title_prefix + str(http.get("title", None)),
221
263
  description=str(http.get("description", None)),
@@ -236,13 +278,21 @@ class MessageGrouper:
236
278
  records_count += 1
237
279
  schema_inferrer.accumulate(message.record)
238
280
  datetime_format_inferrer.accumulate(message.record)
239
- elif message.type == MessageType.CONTROL and message.control.type == OrchestratorType.CONNECTOR_CONFIG: # type: ignore[union-attr] # AirbyteMessage with MessageType.CONTROL has control.type
281
+ elif (
282
+ message.type == MessageType.CONTROL
283
+ and message.control.type == OrchestratorType.CONNECTOR_CONFIG
284
+ ): # type: ignore[union-attr] # AirbyteMessage with MessageType.CONTROL has control.type
240
285
  yield message.control
241
286
  elif message.type == MessageType.STATE:
242
287
  latest_state_message = message.state # type: ignore[assignment]
243
288
  else:
244
289
  if current_page_request or current_page_response or current_page_records:
245
- self._close_page(current_page_request, current_page_response, current_slice_pages, current_page_records)
290
+ self._close_page(
291
+ current_page_request,
292
+ current_page_response,
293
+ current_slice_pages,
294
+ current_page_records,
295
+ )
246
296
  yield StreamReadSlices(
247
297
  pages=current_slice_pages,
248
298
  slice_descriptor=current_slice_descriptor,
@@ -250,11 +300,18 @@ class MessageGrouper:
250
300
  )
251
301
 
252
302
  @staticmethod
253
- def _need_to_close_page(at_least_one_page_in_group: bool, message: AirbyteMessage, json_message: Optional[Dict[str, Any]]) -> bool:
303
+ def _need_to_close_page(
304
+ at_least_one_page_in_group: bool,
305
+ message: AirbyteMessage,
306
+ json_message: Optional[Dict[str, Any]],
307
+ ) -> bool:
254
308
  return (
255
309
  at_least_one_page_in_group
256
310
  and message.type == MessageType.LOG
257
- and (MessageGrouper._is_page_http_request(json_message) or message.log.message.startswith("slice:")) # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
311
+ and (
312
+ MessageGrouper._is_page_http_request(json_message)
313
+ or message.log.message.startswith("slice:")
314
+ ) # type: ignore[union-attr] # AirbyteMessage with MessageType.LOG has log.message
258
315
  )
259
316
 
260
317
  @staticmethod
@@ -262,7 +319,9 @@ class MessageGrouper:
262
319
  if not json_message:
263
320
  return False
264
321
  else:
265
- return MessageGrouper._is_http_log(json_message) and not MessageGrouper._is_auxiliary_http_request(json_message)
322
+ return MessageGrouper._is_http_log(
323
+ json_message
324
+ ) and not MessageGrouper._is_auxiliary_http_request(json_message)
266
325
 
267
326
  @staticmethod
268
327
  def _is_http_log(message: Dict[str, JsonType]) -> bool:
@@ -293,7 +352,11 @@ class MessageGrouper:
293
352
  Close a page when parsing message groups
294
353
  """
295
354
  current_slice_pages.append(
296
- StreamReadPages(request=current_page_request, response=current_page_response, records=deepcopy(current_page_records)) # type: ignore
355
+ StreamReadPages(
356
+ request=current_page_request,
357
+ response=current_page_response,
358
+ records=deepcopy(current_page_records),
359
+ ) # type: ignore
297
360
  )
298
361
  current_page_records.clear()
299
362
 
@@ -307,7 +370,9 @@ class MessageGrouper:
307
370
  # the generator can raise an exception
308
371
  # iterate over the generated messages. if next raise an exception, catch it and yield it as an AirbyteLogMessage
309
372
  try:
310
- yield from AirbyteEntrypoint(source).read(source.spec(self.logger), config, configured_catalog, state)
373
+ yield from AirbyteEntrypoint(source).read(
374
+ source.spec(self.logger), config, configured_catalog, state
375
+ )
311
376
  except AirbyteTracedException as traced_exception:
312
377
  # Look for this message which indicates that it is the "final exception" raised by AbstractSource.
313
378
  # If it matches, don't yield this as we don't need to show this in the Builder.
@@ -315,13 +380,16 @@ class MessageGrouper:
315
380
  # is that this message will be shown in the Builder.
316
381
  if (
317
382
  traced_exception.message is not None
318
- and "During the sync, the following streams did not sync successfully" in traced_exception.message
383
+ and "During the sync, the following streams did not sync successfully"
384
+ in traced_exception.message
319
385
  ):
320
386
  return
321
387
  yield traced_exception.as_airbyte_message()
322
388
  except Exception as e:
323
389
  error_message = f"{e.args[0] if len(e.args) > 0 else str(e)}"
324
- yield AirbyteTracedException.from_exception(e, message=error_message).as_airbyte_message()
390
+ yield AirbyteTracedException.from_exception(
391
+ e, message=error_message
392
+ ).as_airbyte_message()
325
393
 
326
394
  @staticmethod
327
395
  def _parse_json(log_message: AirbyteLogMessage) -> JsonType:
@@ -349,7 +417,9 @@ class MessageGrouper:
349
417
  def _create_response_from_log_message(json_http_message: Dict[str, Any]) -> HttpResponse:
350
418
  response = json_http_message.get("http", {}).get("response", {})
351
419
  body = response.get("body", {}).get("content", "")
352
- return HttpResponse(status=response.get("status_code"), body=body, headers=response.get("headers"))
420
+ return HttpResponse(
421
+ status=response.get("status_code"), body=body, headers=response.get("headers")
422
+ )
353
423
 
354
424
  def _has_reached_limit(self, slices: List[StreamReadSlices]) -> bool:
355
425
  if len(slices) >= self._max_slices:
@@ -11,7 +11,13 @@ from typing import Any, Iterable, List, Mapping
11
11
 
12
12
  from airbyte_cdk.connector import Connector
13
13
  from airbyte_cdk.exception_handler import init_uncaught_exception_handler
14
- from airbyte_cdk.models import AirbyteMessage, AirbyteMessageSerializer, ConfiguredAirbyteCatalog, ConfiguredAirbyteCatalogSerializer, Type
14
+ from airbyte_cdk.models import (
15
+ AirbyteMessage,
16
+ AirbyteMessageSerializer,
17
+ ConfiguredAirbyteCatalog,
18
+ ConfiguredAirbyteCatalogSerializer,
19
+ Type,
20
+ )
15
21
  from airbyte_cdk.sources.utils.schema_helpers import check_config_against_spec_or_exit
16
22
  from airbyte_cdk.utils.traced_exception import AirbyteTracedException
17
23
  from orjson import orjson
@@ -24,7 +30,10 @@ class Destination(Connector, ABC):
24
30
 
25
31
  @abstractmethod
26
32
  def write(
27
- self, config: Mapping[str, Any], configured_catalog: ConfiguredAirbyteCatalog, input_messages: Iterable[AirbyteMessage]
33
+ self,
34
+ config: Mapping[str, Any],
35
+ configured_catalog: ConfiguredAirbyteCatalog,
36
+ input_messages: Iterable[AirbyteMessage],
28
37
  ) -> Iterable[AirbyteMessage]:
29
38
  """Implement to define how the connector writes data to the destination"""
30
39
 
@@ -38,15 +47,24 @@ class Destination(Connector, ABC):
38
47
  try:
39
48
  yield AirbyteMessageSerializer.load(orjson.loads(line))
40
49
  except orjson.JSONDecodeError:
41
- logger.info(f"ignoring input which can't be deserialized as Airbyte Message: {line}")
50
+ logger.info(
51
+ f"ignoring input which can't be deserialized as Airbyte Message: {line}"
52
+ )
42
53
 
43
54
  def _run_write(
44
- self, config: Mapping[str, Any], configured_catalog_path: str, input_stream: io.TextIOWrapper
55
+ self,
56
+ config: Mapping[str, Any],
57
+ configured_catalog_path: str,
58
+ input_stream: io.TextIOWrapper,
45
59
  ) -> Iterable[AirbyteMessage]:
46
- catalog = ConfiguredAirbyteCatalogSerializer.load(orjson.loads(open(configured_catalog_path).read()))
60
+ catalog = ConfiguredAirbyteCatalogSerializer.load(
61
+ orjson.loads(open(configured_catalog_path).read())
62
+ )
47
63
  input_messages = self._parse_input_stream(input_stream)
48
64
  logger.info("Begin writing to the destination...")
49
- yield from self.write(config=config, configured_catalog=catalog, input_messages=input_messages)
65
+ yield from self.write(
66
+ config=config, configured_catalog=catalog, input_messages=input_messages
67
+ )
50
68
  logger.info("Writing complete.")
51
69
 
52
70
  def parse_args(self, args: List[str]) -> argparse.Namespace:
@@ -60,18 +78,30 @@ class Destination(Connector, ABC):
60
78
  subparsers = main_parser.add_subparsers(title="commands", dest="command")
61
79
 
62
80
  # spec
63
- subparsers.add_parser("spec", help="outputs the json configuration specification", parents=[parent_parser])
81
+ subparsers.add_parser(
82
+ "spec", help="outputs the json configuration specification", parents=[parent_parser]
83
+ )
64
84
 
65
85
  # check
66
- check_parser = subparsers.add_parser("check", help="checks the config can be used to connect", parents=[parent_parser])
86
+ check_parser = subparsers.add_parser(
87
+ "check", help="checks the config can be used to connect", parents=[parent_parser]
88
+ )
67
89
  required_check_parser = check_parser.add_argument_group("required named arguments")
68
- required_check_parser.add_argument("--config", type=str, required=True, help="path to the json configuration file")
90
+ required_check_parser.add_argument(
91
+ "--config", type=str, required=True, help="path to the json configuration file"
92
+ )
69
93
 
70
94
  # write
71
- write_parser = subparsers.add_parser("write", help="Writes data to the destination", parents=[parent_parser])
95
+ write_parser = subparsers.add_parser(
96
+ "write", help="Writes data to the destination", parents=[parent_parser]
97
+ )
72
98
  write_required = write_parser.add_argument_group("required named arguments")
73
- write_required.add_argument("--config", type=str, required=True, help="path to the JSON configuration file")
74
- write_required.add_argument("--catalog", type=str, required=True, help="path to the configured catalog JSON file")
99
+ write_required.add_argument(
100
+ "--config", type=str, required=True, help="path to the JSON configuration file"
101
+ )
102
+ write_required.add_argument(
103
+ "--catalog", type=str, required=True, help="path to the configured catalog JSON file"
104
+ )
75
105
 
76
106
  parsed_args = main_parser.parse_args(args)
77
107
  cmd = parsed_args.command
@@ -85,7 +115,6 @@ class Destination(Connector, ABC):
85
115
  return parsed_args
86
116
 
87
117
  def run_cmd(self, parsed_args: argparse.Namespace) -> Iterable[AirbyteMessage]:
88
-
89
118
  cmd = parsed_args.command
90
119
  if cmd not in self.VALID_CMDS:
91
120
  raise Exception(f"Unrecognized command: {cmd}")
@@ -110,7 +139,11 @@ class Destination(Connector, ABC):
110
139
  elif cmd == "write":
111
140
  # Wrap in UTF-8 to override any other input encodings
112
141
  wrapped_stdin = io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8")
113
- yield from self._run_write(config=config, configured_catalog_path=parsed_args.catalog, input_stream=wrapped_stdin)
142
+ yield from self._run_write(
143
+ config=config,
144
+ configured_catalog_path=parsed_args.catalog,
145
+ input_stream=wrapped_stdin,
146
+ )
114
147
 
115
148
  def run(self, args: List[str]) -> None:
116
149
  init_uncaught_exception_handler(logger)