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.
- airbyte_cdk/__init__.py +17 -2
- airbyte_cdk/config_observation.py +10 -3
- airbyte_cdk/connector.py +19 -9
- airbyte_cdk/connector_builder/connector_builder_handler.py +28 -8
- airbyte_cdk/connector_builder/main.py +26 -6
- airbyte_cdk/connector_builder/message_grouper.py +95 -25
- airbyte_cdk/destinations/destination.py +47 -14
- airbyte_cdk/destinations/vector_db_based/config.py +36 -14
- airbyte_cdk/destinations/vector_db_based/document_processor.py +49 -11
- airbyte_cdk/destinations/vector_db_based/embedder.py +52 -11
- airbyte_cdk/destinations/vector_db_based/test_utils.py +14 -4
- airbyte_cdk/destinations/vector_db_based/utils.py +8 -2
- airbyte_cdk/destinations/vector_db_based/writer.py +15 -4
- airbyte_cdk/entrypoint.py +82 -26
- airbyte_cdk/exception_handler.py +13 -3
- airbyte_cdk/logger.py +10 -2
- airbyte_cdk/models/airbyte_protocol.py +11 -5
- airbyte_cdk/models/airbyte_protocol_serializers.py +9 -3
- airbyte_cdk/models/well_known_types.py +1 -1
- airbyte_cdk/sources/abstract_source.py +63 -17
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +47 -14
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +25 -7
- airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +27 -6
- airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +9 -3
- airbyte_cdk/sources/connector_state_manager.py +32 -10
- airbyte_cdk/sources/declarative/async_job/job.py +3 -1
- airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +68 -14
- airbyte_cdk/sources/declarative/async_job/job_tracker.py +24 -6
- airbyte_cdk/sources/declarative/async_job/repository.py +3 -1
- airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
- airbyte_cdk/sources/declarative/auth/jwt.py +27 -7
- airbyte_cdk/sources/declarative/auth/oauth.py +35 -11
- airbyte_cdk/sources/declarative/auth/selective_authenticator.py +3 -1
- airbyte_cdk/sources/declarative/auth/token.py +25 -8
- airbyte_cdk/sources/declarative/checks/check_stream.py +12 -4
- airbyte_cdk/sources/declarative/checks/connection_checker.py +3 -1
- airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +11 -3
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +106 -50
- airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +20 -6
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +43 -0
- airbyte_cdk/sources/declarative/declarative_source.py +3 -1
- airbyte_cdk/sources/declarative/declarative_stream.py +27 -6
- airbyte_cdk/sources/declarative/decoders/__init__.py +2 -2
- airbyte_cdk/sources/declarative/decoders/decoder.py +3 -1
- airbyte_cdk/sources/declarative/decoders/json_decoder.py +48 -13
- airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +3 -1
- airbyte_cdk/sources/declarative/decoders/xml_decoder.py +6 -2
- airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +6 -2
- airbyte_cdk/sources/declarative/extractors/record_filter.py +24 -7
- airbyte_cdk/sources/declarative/extractors/record_selector.py +10 -3
- airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +15 -5
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +96 -31
- airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +22 -8
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +46 -15
- airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +19 -5
- airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +3 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +20 -2
- airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +5 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +10 -3
- airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +6 -2
- airbyte_cdk/sources/declarative/interpolation/interpolation.py +7 -1
- airbyte_cdk/sources/declarative/interpolation/jinja.py +6 -2
- airbyte_cdk/sources/declarative/interpolation/macros.py +19 -4
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +106 -24
- airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +14 -5
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +697 -678
- airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +13 -4
- airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +9 -2
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +802 -232
- airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +29 -7
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +25 -7
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +54 -15
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +6 -2
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +3 -1
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +17 -5
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +15 -5
- airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +3 -1
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +18 -8
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +16 -7
- airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +51 -14
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py +29 -8
- airbyte_cdk/sources/declarative/requesters/http_requester.py +58 -16
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +49 -14
- airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +3 -1
- airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +3 -1
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +17 -5
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +24 -7
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +9 -3
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +3 -1
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +6 -2
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +19 -6
- airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +3 -1
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +21 -7
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +18 -6
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +27 -8
- airbyte_cdk/sources/declarative/requesters/requester.py +3 -1
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py +12 -5
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +105 -24
- airbyte_cdk/sources/declarative/schema/default_schema_loader.py +3 -1
- airbyte_cdk/sources/declarative/spec/spec.py +8 -2
- airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +3 -1
- airbyte_cdk/sources/declarative/transformations/add_fields.py +12 -3
- airbyte_cdk/sources/declarative/transformations/remove_fields.py +6 -2
- airbyte_cdk/sources/declarative/types.py +8 -1
- airbyte_cdk/sources/declarative/yaml_declarative_source.py +3 -1
- airbyte_cdk/sources/embedded/base_integration.py +14 -4
- airbyte_cdk/sources/embedded/catalog.py +16 -4
- airbyte_cdk/sources/embedded/runner.py +19 -3
- airbyte_cdk/sources/embedded/tools.py +3 -1
- airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +12 -4
- airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +27 -7
- airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +12 -6
- airbyte_cdk/sources/file_based/config/csv_format.py +21 -9
- airbyte_cdk/sources/file_based/config/file_based_stream_config.py +6 -2
- airbyte_cdk/sources/file_based/config/unstructured_format.py +10 -3
- airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +2 -4
- airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +7 -2
- airbyte_cdk/sources/file_based/exceptions.py +13 -15
- airbyte_cdk/sources/file_based/file_based_source.py +82 -24
- airbyte_cdk/sources/file_based/file_based_stream_reader.py +16 -5
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +58 -17
- airbyte_cdk/sources/file_based/file_types/csv_parser.py +89 -26
- airbyte_cdk/sources/file_based/file_types/excel_parser.py +25 -7
- airbyte_cdk/sources/file_based/file_types/file_transfer.py +8 -2
- airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
- airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +20 -6
- airbyte_cdk/sources/file_based/file_types/parquet_parser.py +57 -16
- airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +64 -15
- airbyte_cdk/sources/file_based/schema_helpers.py +33 -10
- airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +3 -1
- airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +16 -5
- airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +33 -10
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +47 -11
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +13 -22
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +53 -17
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +17 -5
- airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +3 -1
- airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +26 -9
- airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +67 -21
- airbyte_cdk/sources/http_logger.py +5 -1
- airbyte_cdk/sources/message/repository.py +18 -4
- airbyte_cdk/sources/source.py +17 -7
- airbyte_cdk/sources/streams/availability_strategy.py +9 -3
- airbyte_cdk/sources/streams/call_rate.py +63 -19
- airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +31 -7
- airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +6 -2
- airbyte_cdk/sources/streams/concurrent/adapters.py +77 -22
- airbyte_cdk/sources/streams/concurrent/cursor.py +56 -20
- airbyte_cdk/sources/streams/concurrent/default_stream.py +9 -2
- airbyte_cdk/sources/streams/concurrent/helpers.py +6 -2
- airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +9 -2
- airbyte_cdk/sources/streams/concurrent/partition_reader.py +4 -1
- airbyte_cdk/sources/streams/concurrent/partitions/record.py +10 -2
- airbyte_cdk/sources/streams/concurrent/partitions/types.py +6 -2
- airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +25 -10
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +32 -16
- airbyte_cdk/sources/streams/core.py +77 -22
- airbyte_cdk/sources/streams/http/availability_strategy.py +3 -1
- airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +4 -1
- airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +3 -1
- airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +16 -5
- airbyte_cdk/sources/streams/http/error_handlers/response_models.py +9 -3
- airbyte_cdk/sources/streams/http/exceptions.py +2 -2
- airbyte_cdk/sources/streams/http/http.py +133 -33
- airbyte_cdk/sources/streams/http/http_client.py +91 -29
- airbyte_cdk/sources/streams/http/rate_limiting.py +23 -7
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +19 -6
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +38 -11
- airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
- airbyte_cdk/sources/types.py +5 -1
- airbyte_cdk/sources/utils/record_helper.py +12 -3
- airbyte_cdk/sources/utils/schema_helpers.py +9 -3
- airbyte_cdk/sources/utils/slice_logger.py +4 -1
- airbyte_cdk/sources/utils/transform.py +24 -9
- airbyte_cdk/sql/exceptions.py +19 -6
- airbyte_cdk/sql/secrets.py +3 -1
- airbyte_cdk/sql/shared/catalog_providers.py +13 -4
- airbyte_cdk/sql/shared/sql_processor.py +44 -14
- airbyte_cdk/test/catalog_builder.py +19 -8
- airbyte_cdk/test/entrypoint_wrapper.py +27 -8
- airbyte_cdk/test/mock_http/mocker.py +41 -11
- airbyte_cdk/test/mock_http/request.py +9 -3
- airbyte_cdk/test/mock_http/response.py +3 -1
- airbyte_cdk/test/mock_http/response_builder.py +29 -7
- airbyte_cdk/test/state_builder.py +10 -2
- airbyte_cdk/test/utils/data.py +6 -2
- airbyte_cdk/test/utils/http_mocking.py +3 -1
- airbyte_cdk/utils/airbyte_secrets_utils.py +3 -1
- airbyte_cdk/utils/analytics_message.py +10 -2
- airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
- airbyte_cdk/utils/mapping_helpers.py +3 -1
- airbyte_cdk/utils/message_utils.py +11 -4
- airbyte_cdk/utils/print_buffer.py +6 -1
- airbyte_cdk/utils/schema_inferrer.py +30 -9
- airbyte_cdk/utils/spec_schema_transformations.py +3 -1
- airbyte_cdk/utils/traced_exception.py +35 -9
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/METADATA +8 -7
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/RECORD +200 -200
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/LICENSE.txt +0 -0
- {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
|
-
|
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
|
-
|
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,
|
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(
|
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(
|
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
|
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(
|
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(
|
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(
|
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(
|
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
|
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
|
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 =
|
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[
|
68
|
-
|
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(
|
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,
|
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(
|
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
|
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(
|
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 =
|
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
|
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(
|
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(
|
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(
|
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(
|
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(
|
84
|
-
|
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(
|
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
|
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)
|
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[
|
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(
|
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(
|
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(
|
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(
|
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(
|
215
|
-
|
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(
|
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
|
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(
|
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(
|
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 (
|
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(
|
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(
|
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(
|
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"
|
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(
|
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(
|
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
|
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,
|
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(
|
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,
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
74
|
-
|
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(
|
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)
|