airbyte-cdk 0.35.4__py3-none-any.whl → 0.36.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airbyte_cdk/connector_builder/connector_builder_handler.py +3 -1
- airbyte_cdk/sources/abstract_source.py +13 -0
- airbyte_cdk/sources/declarative/declarative_stream.py +18 -8
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +8 -1
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +11 -0
- airbyte_cdk/sources/streams/core.py +3 -5
- airbyte_cdk/utils/stream_status_utils.py +36 -0
- {airbyte_cdk-0.35.4.dist-info → airbyte_cdk-0.36.1.dist-info}/METADATA +2 -2
- {airbyte_cdk-0.35.4.dist-info → airbyte_cdk-0.36.1.dist-info}/RECORD +20 -18
- unit_tests/connector_builder/test_connector_builder_handler.py +1 -0
- unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +26 -0
- unit_tests/sources/declarative/retrievers/test_simple_retriever.py +42 -0
- unit_tests/sources/declarative/test_declarative_stream.py +18 -7
- unit_tests/sources/declarative/test_manifest_declarative_source.py +1 -1
- unit_tests/sources/test_abstract_source.py +95 -15
- unit_tests/sources/test_source.py +24 -18
- unit_tests/utils/test_stream_status_utils.py +70 -0
- {airbyte_cdk-0.35.4.dist-info → airbyte_cdk-0.36.1.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-0.35.4.dist-info → airbyte_cdk-0.36.1.dist-info}/WHEEL +0 -0
- {airbyte_cdk-0.35.4.dist-info → airbyte_cdk-0.36.1.dist-info}/top_level.txt +0 -0
@@ -50,7 +50,9 @@ def create_source(config: Mapping[str, Any], limits: TestReadLimits) -> Manifest
|
|
50
50
|
component_factory=ModelToComponentFactory(
|
51
51
|
emit_connector_builder_messages=True,
|
52
52
|
limit_pages_fetched_per_slice=limits.max_pages_per_slice,
|
53
|
-
limit_slices_fetched=limits.max_slices
|
53
|
+
limit_slices_fetched=limits.max_slices,
|
54
|
+
disable_retries=True
|
55
|
+
)
|
54
56
|
)
|
55
57
|
|
56
58
|
|
@@ -13,6 +13,7 @@ from airbyte_cdk.models import (
|
|
13
13
|
AirbyteLogMessage,
|
14
14
|
AirbyteMessage,
|
15
15
|
AirbyteStateMessage,
|
16
|
+
AirbyteStreamStatus,
|
16
17
|
ConfiguredAirbyteCatalog,
|
17
18
|
ConfiguredAirbyteStream,
|
18
19
|
Level,
|
@@ -28,6 +29,7 @@ from airbyte_cdk.sources.streams.http.http import HttpStream
|
|
28
29
|
from airbyte_cdk.sources.utils.record_helper import stream_data_to_airbyte_message
|
29
30
|
from airbyte_cdk.sources.utils.schema_helpers import InternalConfig, split_config
|
30
31
|
from airbyte_cdk.utils.event_timing import create_timer
|
32
|
+
from airbyte_cdk.utils.stream_status_utils import as_airbyte_message as stream_status_as_airbyte_message
|
31
33
|
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
|
32
34
|
|
33
35
|
|
@@ -113,6 +115,8 @@ class AbstractSource(Source, ABC):
|
|
113
115
|
continue
|
114
116
|
try:
|
115
117
|
timer.start_event(f"Syncing stream {configured_stream.stream.name}")
|
118
|
+
logger.info(f"Marking stream {configured_stream.stream.name} as STARTED")
|
119
|
+
yield stream_status_as_airbyte_message(configured_stream, AirbyteStreamStatus.STARTED)
|
116
120
|
yield from self._read_stream(
|
117
121
|
logger=logger,
|
118
122
|
stream_instance=stream_instance,
|
@@ -120,10 +124,15 @@ class AbstractSource(Source, ABC):
|
|
120
124
|
state_manager=state_manager,
|
121
125
|
internal_config=internal_config,
|
122
126
|
)
|
127
|
+
logger.info(f"Marking stream {configured_stream.stream.name} as STOPPED")
|
128
|
+
yield stream_status_as_airbyte_message(configured_stream, AirbyteStreamStatus.COMPLETE)
|
123
129
|
except AirbyteTracedException as e:
|
130
|
+
yield stream_status_as_airbyte_message(configured_stream, AirbyteStreamStatus.INCOMPLETE)
|
124
131
|
raise e
|
125
132
|
except Exception as e:
|
126
133
|
logger.exception(f"Encountered an exception while reading stream {configured_stream.stream.name}")
|
134
|
+
logger.info(f"Marking stream {configured_stream.stream.name} as STOPPED")
|
135
|
+
yield stream_status_as_airbyte_message(configured_stream, AirbyteStreamStatus.INCOMPLETE)
|
127
136
|
display_message = stream_instance.get_error_display_message(e)
|
128
137
|
if display_message:
|
129
138
|
raise AirbyteTracedException.from_exception(e, message=display_message) from e
|
@@ -185,6 +194,10 @@ class AbstractSource(Source, ABC):
|
|
185
194
|
for record in record_iterator:
|
186
195
|
if record.type == MessageType.RECORD:
|
187
196
|
record_counter += 1
|
197
|
+
if record_counter == 1:
|
198
|
+
logger.info(f"Marking stream {stream_name} as RUNNING")
|
199
|
+
# If we just read the first record of the stream, emit the transition to the RUNNING state
|
200
|
+
yield stream_status_as_airbyte_message(configured_stream, AirbyteStreamStatus.RUNNING)
|
188
201
|
yield record
|
189
202
|
|
190
203
|
logger.info(f"Read {record_counter} records from {stream_name} stream")
|
@@ -5,14 +5,14 @@
|
|
5
5
|
from dataclasses import InitVar, dataclass, field
|
6
6
|
from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Union
|
7
7
|
|
8
|
-
from airbyte_cdk.models import
|
8
|
+
from airbyte_cdk.models import AirbyteMessage, SyncMode
|
9
9
|
from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
|
10
10
|
from airbyte_cdk.sources.declarative.retrievers.retriever import Retriever
|
11
11
|
from airbyte_cdk.sources.declarative.schema import DefaultSchemaLoader
|
12
12
|
from airbyte_cdk.sources.declarative.schema.schema_loader import SchemaLoader
|
13
13
|
from airbyte_cdk.sources.declarative.transformations import RecordTransformation
|
14
14
|
from airbyte_cdk.sources.declarative.types import Config, StreamSlice
|
15
|
-
from airbyte_cdk.sources.streams.core import Stream
|
15
|
+
from airbyte_cdk.sources.streams.core import Stream, StreamData
|
16
16
|
|
17
17
|
|
18
18
|
@dataclass
|
@@ -102,17 +102,27 @@ class DeclarativeStream(Stream):
|
|
102
102
|
|
103
103
|
def _apply_transformations(
|
104
104
|
self,
|
105
|
-
message_or_record_data:
|
105
|
+
message_or_record_data: StreamData,
|
106
106
|
config: Config,
|
107
107
|
stream_slice: StreamSlice,
|
108
108
|
):
|
109
|
-
# If the input is an
|
110
|
-
# If the input is another type of
|
109
|
+
# If the input is an AirbyteMessage with a record, transform the record's data
|
110
|
+
# If the input is another type of AirbyteMessage, return it as is
|
111
111
|
# If the input is a dict, transform it
|
112
|
-
if isinstance(message_or_record_data,
|
113
|
-
|
112
|
+
if isinstance(message_or_record_data, AirbyteMessage):
|
113
|
+
if message_or_record_data.record:
|
114
|
+
record = message_or_record_data.record.data
|
115
|
+
else:
|
116
|
+
return message_or_record_data
|
117
|
+
elif isinstance(message_or_record_data, dict):
|
118
|
+
record = message_or_record_data
|
119
|
+
else:
|
120
|
+
# Raise an error because this is unexpected and indicative of a typing problem in the CDK
|
121
|
+
raise ValueError(
|
122
|
+
f"Unexpected record type. Expected {StreamData}. Got {type(message_or_record_data)}. This is probably due to a bug in the CDK."
|
123
|
+
)
|
114
124
|
for transformation in self.transformations:
|
115
|
-
transformation.transform(
|
125
|
+
transformation.transform(record, config=config, stream_state=self.state, stream_slice=stream_slice)
|
116
126
|
|
117
127
|
return message_or_record_data
|
118
128
|
|
@@ -112,12 +112,17 @@ DEFAULT_BACKOFF_STRATEGY = ExponentialBackoffStrategy
|
|
112
112
|
|
113
113
|
class ModelToComponentFactory:
|
114
114
|
def __init__(
|
115
|
-
self,
|
115
|
+
self,
|
116
|
+
limit_pages_fetched_per_slice: int = None,
|
117
|
+
limit_slices_fetched: int = None,
|
118
|
+
emit_connector_builder_messages: bool = False,
|
119
|
+
disable_retries=False,
|
116
120
|
):
|
117
121
|
self._init_mappings()
|
118
122
|
self._limit_pages_fetched_per_slice = limit_pages_fetched_per_slice
|
119
123
|
self._limit_slices_fetched = limit_slices_fetched
|
120
124
|
self._emit_connector_builder_messages = emit_connector_builder_messages
|
125
|
+
self._disable_retries = disable_retries
|
121
126
|
|
122
127
|
def _init_mappings(self):
|
123
128
|
self.PYDANTIC_MODEL_TO_CONSTRUCTOR: [Type[BaseModel], Callable] = {
|
@@ -779,6 +784,7 @@ class ModelToComponentFactory:
|
|
779
784
|
config=config,
|
780
785
|
maximum_number_of_slices=self._limit_slices_fetched,
|
781
786
|
parameters=model.parameters,
|
787
|
+
disable_retries=self._disable_retries,
|
782
788
|
)
|
783
789
|
return SimpleRetriever(
|
784
790
|
name=name,
|
@@ -789,6 +795,7 @@ class ModelToComponentFactory:
|
|
789
795
|
stream_slicer=stream_slicer or SinglePartitionRouter(parameters={}),
|
790
796
|
config=config,
|
791
797
|
parameters=model.parameters,
|
798
|
+
disable_retries=self._disable_retries,
|
792
799
|
)
|
793
800
|
|
794
801
|
@staticmethod
|
@@ -50,6 +50,8 @@ class SimpleRetriever(Retriever, HttpStream):
|
|
50
50
|
parameters (Mapping[str, Any]): Additional runtime parameters to be used for string interpolation
|
51
51
|
"""
|
52
52
|
|
53
|
+
_DEFAULT_MAX_RETRY = 5
|
54
|
+
|
53
55
|
requester: Requester
|
54
56
|
record_selector: HttpSelector
|
55
57
|
config: Config
|
@@ -61,6 +63,7 @@ class SimpleRetriever(Retriever, HttpStream):
|
|
61
63
|
paginator: Optional[Paginator] = None
|
62
64
|
stream_slicer: Optional[StreamSlicer] = SinglePartitionRouter(parameters={})
|
63
65
|
emit_connector_builder_messages: bool = False
|
66
|
+
disable_retries: bool = False
|
64
67
|
|
65
68
|
def __post_init__(self, parameters: Mapping[str, Any]):
|
66
69
|
self.paginator = self.paginator or NoPagination(parameters=parameters)
|
@@ -95,6 +98,14 @@ class SimpleRetriever(Retriever, HttpStream):
|
|
95
98
|
# never raise on http_errors because this overrides the error handler logic...
|
96
99
|
return False
|
97
100
|
|
101
|
+
@property
|
102
|
+
def max_retries(self) -> Union[int, None]:
|
103
|
+
if self.disable_retries:
|
104
|
+
return 0
|
105
|
+
if hasattr(self.requester.error_handler, "max_retries"):
|
106
|
+
return self.requester.error_handler.max_retries
|
107
|
+
return self._DEFAULT_MAX_RETRY
|
108
|
+
|
98
109
|
def should_retry(self, response: requests.Response) -> bool:
|
99
110
|
"""
|
100
111
|
Specifies conditions for backoff based on the response from the server.
|
@@ -11,7 +11,7 @@ from functools import lru_cache
|
|
11
11
|
from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Tuple, Union
|
12
12
|
|
13
13
|
import airbyte_cdk.sources.utils.casing as casing
|
14
|
-
from airbyte_cdk.models import
|
14
|
+
from airbyte_cdk.models import AirbyteMessage, AirbyteStream, SyncMode
|
15
15
|
|
16
16
|
# list of all possible HTTP methods which can be used for sending of request bodies
|
17
17
|
from airbyte_cdk.sources.utils.schema_helpers import ResourceSchemaLoader
|
@@ -24,10 +24,8 @@ if typing.TYPE_CHECKING:
|
|
24
24
|
|
25
25
|
# A stream's read method can return one of the following types:
|
26
26
|
# Mapping[str, Any]: The content of an AirbyteRecordMessage
|
27
|
-
#
|
28
|
-
|
29
|
-
# AirbyteTraceMessage: A trace message
|
30
|
-
StreamData = Union[Mapping[str, Any], AirbyteLogMessage, AirbyteTraceMessage]
|
27
|
+
# AirbyteMessage: An AirbyteMessage. Could be of any type
|
28
|
+
StreamData = Union[Mapping[str, Any], AirbyteMessage]
|
31
29
|
|
32
30
|
|
33
31
|
def package_name_from_class(cls: object) -> str:
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
from datetime import datetime
|
7
|
+
|
8
|
+
from airbyte_cdk.models import (
|
9
|
+
AirbyteMessage,
|
10
|
+
AirbyteStreamStatus,
|
11
|
+
AirbyteStreamStatusTraceMessage,
|
12
|
+
AirbyteTraceMessage,
|
13
|
+
ConfiguredAirbyteStream,
|
14
|
+
StreamDescriptor,
|
15
|
+
TraceType,
|
16
|
+
)
|
17
|
+
from airbyte_cdk.models import Type as MessageType
|
18
|
+
|
19
|
+
|
20
|
+
def as_airbyte_message(stream: ConfiguredAirbyteStream, current_status: AirbyteStreamStatus) -> AirbyteMessage:
|
21
|
+
"""
|
22
|
+
Builds an AirbyteStreamStatusTraceMessage for the provided stream
|
23
|
+
"""
|
24
|
+
|
25
|
+
now_millis = datetime.now().timestamp() * 1000.0
|
26
|
+
|
27
|
+
trace_message = AirbyteTraceMessage(
|
28
|
+
type=TraceType.STREAM_STATUS,
|
29
|
+
emitted_at=now_millis,
|
30
|
+
stream_status=AirbyteStreamStatusTraceMessage(
|
31
|
+
stream_descriptor=StreamDescriptor(name=stream.stream.name, namespace=stream.stream.namespace),
|
32
|
+
status=current_status,
|
33
|
+
),
|
34
|
+
)
|
35
|
+
|
36
|
+
return AirbyteMessage(type=MessageType.TRACE, trace=trace_message)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: airbyte-cdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.36.1
|
4
4
|
Summary: A framework for writing Airbyte Connectors.
|
5
5
|
Home-page: https://github.com/airbytehq/airbyte
|
6
6
|
Author: Airbyte
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
19
19
|
Requires-Python: >=3.8
|
20
20
|
Description-Content-Type: text/markdown
|
21
21
|
License-File: LICENSE.txt
|
22
|
-
Requires-Dist: airbyte-protocol-models (==
|
22
|
+
Requires-Dist: airbyte-protocol-models (==0.3.6)
|
23
23
|
Requires-Dist: backoff
|
24
24
|
Requires-Dist: dpath (~=2.0.1)
|
25
25
|
Requires-Dist: isodate (~=0.6.1)
|
@@ -6,7 +6,7 @@ airbyte_cdk/exception_handler.py,sha256=CwkiPdZ1WMOr3CBkvKFyHiyLerXGRqBrVlB4p0OI
|
|
6
6
|
airbyte_cdk/logger.py,sha256=4Mi2MEQi1uh59BP9Dxw_UEbZuxaJewqK_jvEU2b10nk,3985
|
7
7
|
airbyte_cdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
airbyte_cdk/connector_builder/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
9
|
-
airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=
|
9
|
+
airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=KYfc3yyR9hWoj06E1-j2Z0OZtjeWw3YxdRda5z-dnq4,5407
|
10
10
|
airbyte_cdk/connector_builder/main.py,sha256=eTXmkL1GaK1SCaN4jnVLh0MBok0KvM17QzQQ6qpsTTw,2982
|
11
11
|
airbyte_cdk/connector_builder/message_grouper.py,sha256=swO_B0FovZMEliyjeYZ7Y7DRnTTwzrexy7rmqk7VnGU,11206
|
12
12
|
airbyte_cdk/connector_builder/models.py,sha256=y0PJ-LwJk3e1RzRmMfjQSBP9ENx_a0wBcWNCjlW72Ks,1832
|
@@ -16,7 +16,7 @@ airbyte_cdk/models/__init__.py,sha256=LPQcYdDPwrCXiBPe_jexO4UAcbovIb1V9tHB6I7Un3
|
|
16
16
|
airbyte_cdk/models/airbyte_protocol.py,sha256=wKXV_4sCzmUyPndiW7HWAj_A6EDRJyk9cA88xvXGQN0,117
|
17
17
|
airbyte_cdk/models/well_known_types.py,sha256=KKfNbow2gdLoC1Z4hcXy_JR8m_acsB2ol7gQuEgjobw,117
|
18
18
|
airbyte_cdk/sources/__init__.py,sha256=4j6fLtoRCjcZnojpise4EMmQtV1RepBxoGTBgpz80JA,218
|
19
|
-
airbyte_cdk/sources/abstract_source.py,sha256=
|
19
|
+
airbyte_cdk/sources/abstract_source.py,sha256=svXe29SUHKA6WJwKlQlyKkZax2ybaG0wSvIFzaibl24,17262
|
20
20
|
airbyte_cdk/sources/config.py,sha256=PYsY7y2u3EUwxLiEb96JnuKwH_E8CuxKggsRO2ZPSRc,856
|
21
21
|
airbyte_cdk/sources/connector_state_manager.py,sha256=_R-2QnMGimKL0t5aV4f6P1dgd--TB3abY5Seg1xddXk,10469
|
22
22
|
airbyte_cdk/sources/source.py,sha256=N3vHZzdUsBETFsql-YpO-LcgjolT_jcnAuHBhGD6Hqk,4278
|
@@ -24,7 +24,7 @@ airbyte_cdk/sources/declarative/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4G
|
|
24
24
|
airbyte_cdk/sources/declarative/create_partial.py,sha256=sUJOwD8hBzW4pxw2XhYlSTMgl-WMc5WpP5Oq_jo3fHw,3371
|
25
25
|
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=DJIV9HgHfBgv4w8zg6DhLIqXjZe235UVTe79IT7O51Q,74761
|
26
26
|
airbyte_cdk/sources/declarative/declarative_source.py,sha256=U2As9PDKmcWDgbsWUo-RetJ9fxQOBlwntWZ0NOgs5Ac,1453
|
27
|
-
airbyte_cdk/sources/declarative/declarative_stream.py,sha256=
|
27
|
+
airbyte_cdk/sources/declarative/declarative_stream.py,sha256=0iZSpypxt8bhO3Lmf3BpGRTO7Fp0Q2GI8m8xyJJUjeM,6580
|
28
28
|
airbyte_cdk/sources/declarative/exceptions.py,sha256=kTPUA4I2NV4J6HDz-mKPGMrfuc592akJnOyYx38l_QM,176
|
29
29
|
airbyte_cdk/sources/declarative/manifest_declarative_source.py,sha256=vTbRNM8D9P_ChOu1GNvtNRt-PM2L9N5Y0pNRyfVFuZg,9759
|
30
30
|
airbyte_cdk/sources/declarative/types.py,sha256=b_RJpL9TyAgxJIRYZx5BxpC39p-WccHKxbAqxWrn9oE,482
|
@@ -66,7 +66,7 @@ airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=y7_G5mM07zxT
|
|
66
66
|
airbyte_cdk/sources/declarative/parsers/default_implementation_registry.py,sha256=W8BcK4KOg4ifNXgsdeIoV4oneHjXBKcPHEZHIC4r-hM,3801
|
67
67
|
airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=H23H3nURCxsvjq66Gn9naffp0HJ1fU03wLFu-5F0AhQ,7701
|
68
68
|
airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=6ukHx0bBrCJm9rek1l_MEfS3U_gdJcM4pJRyifJEOp0,6412
|
69
|
-
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=
|
69
|
+
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=AzSjGeLahAA1FWvzbjIOk4iLiwHcaeS-tUhjs7bnyBk,46588
|
70
70
|
airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=27sOWhw2LBQs62HchURakHQ2M_mtnOatNgU6q8RUtpU,476
|
71
71
|
airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha256=fa6VtTwSoIkDI3SBoRtVx79opVtJX80_gU9bt31lspc,4785
|
72
72
|
airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py,sha256=Fi3ocNZZoYkr0uvRgwoVSqne6enxRvi8DOHrASVK2PQ,1851
|
@@ -105,7 +105,7 @@ airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_
|
|
105
105
|
airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py,sha256=tXxM0OPop49gw_EJuhJR5vyayXcg_XORrJlp5X8KydU,2625
|
106
106
|
airbyte_cdk/sources/declarative/retrievers/__init__.py,sha256=IiHXDeKtibRqeWcRUckmSiXfk--u-sFMw3APWK8PCGQ,339
|
107
107
|
airbyte_cdk/sources/declarative/retrievers/retriever.py,sha256=LBxg5r2QBPY8TSyQ7FxeG-FjLYFCV3Kq5MQM4SkCbUw,2030
|
108
|
-
airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=
|
108
|
+
airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=Yc5c1NQ0Jpgy5isrA6ltWIUW-Y1p9ih0aUMXNJUve0k,21057
|
109
109
|
airbyte_cdk/sources/declarative/schema/__init__.py,sha256=ul8L9S0-__AMEdbCLHBq-PMEeA928NVp8BB83BMotfU,517
|
110
110
|
airbyte_cdk/sources/declarative/schema/default_schema_loader.py,sha256=t0ll098cIG2Wr1rq1rZ3QDZ9WnScUuqAh42YVoTRWrU,1794
|
111
111
|
airbyte_cdk/sources/declarative/schema/inline_schema_loader.py,sha256=bVETE10hRsatRJq3R3BeyRR0wIoK3gcP1gcpVRQ_P5U,464
|
@@ -128,7 +128,7 @@ airbyte_cdk/sources/singer/singer_helpers.py,sha256=q1LmgjFxSnN-dobMy7nikUwcK-9F
|
|
128
128
|
airbyte_cdk/sources/singer/source.py,sha256=3YY8UTOXmctvMVUnYmIegmL3_IxF55iGP_bc_s2MZdY,8530
|
129
129
|
airbyte_cdk/sources/streams/__init__.py,sha256=XGrzYjIkqItvnMshsOUzYhi4lC4M9kFHhxG0oCAoAyE,176
|
130
130
|
airbyte_cdk/sources/streams/availability_strategy.py,sha256=7BM0qLvXS0QrlKvnVkBEw4Cw8i7PCENCBLcIAcuD3nY,1007
|
131
|
-
airbyte_cdk/sources/streams/core.py,sha256=
|
131
|
+
airbyte_cdk/sources/streams/core.py,sha256=G0MxhVkrF1DAPoPgmRBd_wnhrI7S6ZAdtOG0h9N_zHU,11021
|
132
132
|
airbyte_cdk/sources/streams/http/__init__.py,sha256=6hRmA0P_RhB7X54xQbtj2RTz1RGlP52AG9V4pPWLaEQ,261
|
133
133
|
airbyte_cdk/sources/streams/http/availability_strategy.py,sha256=uYYjanBG0f-CIrUYn1SnMyJqHoQQHnTggeJiB0m4i-Y,6344
|
134
134
|
airbyte_cdk/sources/streams/http/exceptions.py,sha256=OokLDI7W8hZvq9e15sL3em2AdwmzmcAl72Ms-i5l0Nw,1334
|
@@ -156,11 +156,12 @@ airbyte_cdk/utils/__init__.py,sha256=kFLcs2P-tbPyeVOJS9rOv1jZdnSpjG24ro0CHgt_CIk
|
|
156
156
|
airbyte_cdk/utils/airbyte_secrets_utils.py,sha256=q3aDl8T10ufGbeqnUPqbZLxQcHdkf2kDfQK_upWzBbI,2894
|
157
157
|
airbyte_cdk/utils/event_timing.py,sha256=Hn5kCc9xGKLcV5EYpJCZwNiz9neKKu2WG8FJF_hy278,2377
|
158
158
|
airbyte_cdk/utils/schema_inferrer.py,sha256=LQLOlraFksg7_sqpJNhy9pS_K42GVxG634ogM_P2s5E,2361
|
159
|
+
airbyte_cdk/utils/stream_status_utils.py,sha256=X1Vy7BhglycjdIWpfKDfwJussNCxYffelKt6Utjx-qY,1005
|
159
160
|
airbyte_cdk/utils/traced_exception.py,sha256=9G2sG9eYkvn6Aa7rMuUW_KIRszRaTc_xdnTQNDKyKGI,3216
|
160
161
|
source_declarative_manifest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
161
162
|
source_declarative_manifest/main.py,sha256=HXzuRsRyhHwPrGU-hc4S7RrgoOoHImqkdfbmO2geBeE,1027
|
162
163
|
unit_tests/connector_builder/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
163
|
-
unit_tests/connector_builder/test_connector_builder_handler.py,sha256
|
164
|
+
unit_tests/connector_builder/test_connector_builder_handler.py,sha256=tFcMzGHO2eLmbVtRPqLI3TKIo88tOY_ECEuG1D939p0,26796
|
164
165
|
unit_tests/connector_builder/test_message_grouper.py,sha256=m4Iod2AwgKaRTkH8ndAC7Uq276BdzSg7SGsj1a4Jbhg,24086
|
165
166
|
unit_tests/connector_builder/utils.py,sha256=AAggdGWP-mNuWOZUHLAVIbjTeIcdPo-3pbMm5zdYpS0,796
|
166
167
|
unit_tests/destinations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -169,15 +170,15 @@ unit_tests/singer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
169
170
|
unit_tests/singer/test_singer_helpers.py,sha256=pZV6VxJuK-3-FICNGmoGbokrA_zkaFZEd4rYZCVpSRU,1762
|
170
171
|
unit_tests/singer/test_singer_source.py,sha256=edN_kv7dnYAdBveWdUYOs74ak0dK6p8uaX225h_ZILA,4442
|
171
172
|
unit_tests/sources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
172
|
-
unit_tests/sources/test_abstract_source.py,sha256=
|
173
|
+
unit_tests/sources/test_abstract_source.py,sha256=eHZjhfSN-fzqbvdZtGqa5FVwggoFDXxi5SBA1-LQi70,44194
|
173
174
|
unit_tests/sources/test_config.py,sha256=gFXqU_6OjwHXkV4JHMqQUznxmvTWN8nAv0w0-FFpugc,2477
|
174
175
|
unit_tests/sources/test_connector_state_manager.py,sha256=ynFxA63Cxe6t-wMMh9C6ByTlMAuk8W7H2FikDhnUEQ0,24264
|
175
|
-
unit_tests/sources/test_source.py,sha256=
|
176
|
+
unit_tests/sources/test_source.py,sha256=eVtU9Zuc9gBsg11Pb5xjDtyU0gVrbYqbZ4RmzPvDw_M,24695
|
176
177
|
unit_tests/sources/declarative/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
|
177
178
|
unit_tests/sources/declarative/external_component.py,sha256=lU2gL736bLEWtmrGm1B2k83RXt_3XkROimLIahZd5dg,293
|
178
179
|
unit_tests/sources/declarative/test_create_partial.py,sha256=s_KIywQqt8RlauOCWNJVk3HC3KBTAtSwFTN6JVQgu80,2636
|
179
|
-
unit_tests/sources/declarative/test_declarative_stream.py,sha256=
|
180
|
-
unit_tests/sources/declarative/test_manifest_declarative_source.py,sha256=
|
180
|
+
unit_tests/sources/declarative/test_declarative_stream.py,sha256=3leJnZIYHiFq8XI4jb3TjPXTubGJmvNGzABt4c01EkQ,5436
|
181
|
+
unit_tests/sources/declarative/test_manifest_declarative_source.py,sha256=GckUc3nepzZkD1UM24woHlYCVZb5DP4IAQC3IeMyZF0,58924
|
181
182
|
unit_tests/sources/declarative/test_yaml_declarative_source.py,sha256=6HhsUFgB7ueN0yOUHWb4gpPYLng5jasxN_plvz3x37g,5097
|
182
183
|
unit_tests/sources/declarative/auth/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
183
184
|
unit_tests/sources/declarative/auth/test_oauth.py,sha256=mqXE_mQBcM78-ZaDX5GCWFOkbXPCvYeCj81aKyPZ3D8,5204
|
@@ -203,7 +204,7 @@ unit_tests/sources/declarative/interpolation/test_macros.py,sha256=q6kNuNfsrpupm
|
|
203
204
|
unit_tests/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
|
204
205
|
unit_tests/sources/declarative/parsers/test_manifest_component_transformer.py,sha256=5lHUFv2n32b6h5IRh65S7EfqPkP5-IrGE3VUxDoPflI,12483
|
205
206
|
unit_tests/sources/declarative/parsers/test_manifest_reference_resolver.py,sha256=K3q9eyx-sJFQ8nGYjAgS7fxau4sX_FlNreEAjiCYOeE,5306
|
206
|
-
unit_tests/sources/declarative/parsers/test_model_to_component_factory.py,sha256=
|
207
|
+
unit_tests/sources/declarative/parsers/test_model_to_component_factory.py,sha256=OKYCRk4_gK7x_9tvqUq2Yd1OXdHdPQRJcBqUj06jomA,58950
|
207
208
|
unit_tests/sources/declarative/parsers/testing_components.py,sha256=_yUijmYRM-yYHPGDB2JsfEiOuVrgexGW9QwHf1xxNW8,1326
|
208
209
|
unit_tests/sources/declarative/partition_routers/__init__.py,sha256=O8MZg4Bv_DghdRy9BoJCPIqdV75VtiUrhEkExQgb2nE,61
|
209
210
|
unit_tests/sources/declarative/partition_routers/test_list_partition_router.py,sha256=gyivHDJ7iA6dslrI886GQnT_if0BfGmLPfiviVGiSqo,5118
|
@@ -233,7 +234,7 @@ unit_tests/sources/declarative/requesters/paginators/test_request_option.py,sha2
|
|
233
234
|
unit_tests/sources/declarative/requesters/request_options/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
234
235
|
unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py,sha256=Hl7b59ix1OwCJ5c34wn83d3D_l1dccE_nXlXWUR7Zos,5607
|
235
236
|
unit_tests/sources/declarative/retrievers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
|
236
|
-
unit_tests/sources/declarative/retrievers/test_simple_retriever.py,sha256=
|
237
|
+
unit_tests/sources/declarative/retrievers/test_simple_retriever.py,sha256=EhaUAo5ElmWW8pDRCD8K_SNhN-ct0ix7f-3DGTFp8Ag,29002
|
237
238
|
unit_tests/sources/declarative/schema/__init__.py,sha256=i-iWyCqXPVgY-4miy16FH8U06gW_1_49AVq_8S8rVWY,134
|
238
239
|
unit_tests/sources/declarative/schema/test_default_schema_loader.py,sha256=cWOFJnT9fhcEU6XLHkoe3E83mCjWc8lEttT0PFcvAm8,1091
|
239
240
|
unit_tests/sources/declarative/schema/test_inline_schema_loader.py,sha256=vDJauhZ8og8M9ZqKDbf12SSYSfhUZ0_LmH7zjJHCHwI,517
|
@@ -256,9 +257,10 @@ unit_tests/sources/streams/http/requests_native_auth/test_requests_native_auth.p
|
|
256
257
|
unit_tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
257
258
|
unit_tests/utils/test_schema_inferrer.py,sha256=ckl17GlNOZInqgxni7Z2A0bg_p6JDy0GVFAG8ph67pw,3288
|
258
259
|
unit_tests/utils/test_secret_utils.py,sha256=XKe0f1RHYii8iwE6ATmBr5JGDI1pzzrnZUGdUSMJQP4,4886
|
260
|
+
unit_tests/utils/test_stream_status_utils.py,sha256=NpV155JMXA6CG-2Zvofa14lItobyh3Onttc59X4m5DI,3382
|
259
261
|
unit_tests/utils/test_traced_exception.py,sha256=bDFP5zMBizFenz6V2WvEZTRCKGB5ijh3DBezjbfoYIs,4198
|
260
|
-
airbyte_cdk-0.
|
261
|
-
airbyte_cdk-0.
|
262
|
-
airbyte_cdk-0.
|
263
|
-
airbyte_cdk-0.
|
264
|
-
airbyte_cdk-0.
|
262
|
+
airbyte_cdk-0.36.1.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
263
|
+
airbyte_cdk-0.36.1.dist-info/METADATA,sha256=julG0JMgo00RRk6h3nZf_h-2-wVJffJ3e_fEEsV3dhQ,8902
|
264
|
+
airbyte_cdk-0.36.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
265
|
+
airbyte_cdk-0.36.1.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
|
266
|
+
airbyte_cdk-0.36.1.dist-info/RECORD,,
|
@@ -566,6 +566,7 @@ def test_create_source():
|
|
566
566
|
assert isinstance(source, ManifestDeclarativeSource)
|
567
567
|
assert source._constructor._limit_pages_fetched_per_slice == limits.max_pages_per_slice
|
568
568
|
assert source._constructor._limit_slices_fetched == limits.max_slices
|
569
|
+
assert source.streams(config={})[0].retriever.max_retries == 0
|
569
570
|
|
570
571
|
|
571
572
|
def request_log_message(request: dict) -> AirbyteMessage:
|
@@ -1360,3 +1360,29 @@ def test_simple_retriever_emit_log_messages():
|
|
1360
1360
|
)
|
1361
1361
|
|
1362
1362
|
assert isinstance(retriever, SimpleRetrieverTestReadDecorator)
|
1363
|
+
|
1364
|
+
|
1365
|
+
def test_ignore_retry():
|
1366
|
+
requester_model = {
|
1367
|
+
"type": "SimpleRetriever",
|
1368
|
+
"record_selector": {
|
1369
|
+
"type": "RecordSelector",
|
1370
|
+
"extractor": {
|
1371
|
+
"type": "DpathExtractor",
|
1372
|
+
"field_path": [],
|
1373
|
+
},
|
1374
|
+
},
|
1375
|
+
"requester": {"type": "HttpRequester", "name": "list", "url_base": "orange.com", "path": "/v1/api"},
|
1376
|
+
}
|
1377
|
+
|
1378
|
+
connector_builder_factory = ModelToComponentFactory(disable_retries=True)
|
1379
|
+
retriever = connector_builder_factory.create_component(
|
1380
|
+
model_type=SimpleRetrieverModel,
|
1381
|
+
component_definition=requester_model,
|
1382
|
+
config={},
|
1383
|
+
name="Test",
|
1384
|
+
primary_key="id",
|
1385
|
+
stream_slicer=None,
|
1386
|
+
)
|
1387
|
+
|
1388
|
+
assert retriever.max_retries == 0
|
@@ -256,6 +256,48 @@ def test_parse_response(test_name, status_code, response_status, len_expected_re
|
|
256
256
|
assert len(records) == len_expected_records
|
257
257
|
|
258
258
|
|
259
|
+
def test_max_retries_given_error_handler_has_max_retries():
|
260
|
+
requester = MagicMock()
|
261
|
+
requester.error_handler = MagicMock()
|
262
|
+
requester.error_handler.max_retries = 10
|
263
|
+
retriever = SimpleRetriever(
|
264
|
+
name="stream_name",
|
265
|
+
primary_key=primary_key,
|
266
|
+
requester=requester,
|
267
|
+
record_selector=MagicMock(),
|
268
|
+
parameters={},
|
269
|
+
config={}
|
270
|
+
)
|
271
|
+
assert retriever.max_retries == 10
|
272
|
+
|
273
|
+
|
274
|
+
def test_max_retries_given_error_handler_without_max_retries():
|
275
|
+
requester = MagicMock()
|
276
|
+
requester.error_handler = MagicMock(spec=[u'without_max_retries_attribute'])
|
277
|
+
retriever = SimpleRetriever(
|
278
|
+
name="stream_name",
|
279
|
+
primary_key=primary_key,
|
280
|
+
requester=requester,
|
281
|
+
record_selector=MagicMock(),
|
282
|
+
parameters={},
|
283
|
+
config={}
|
284
|
+
)
|
285
|
+
assert retriever.max_retries == 5
|
286
|
+
|
287
|
+
|
288
|
+
def test_max_retries_given_disable_retries():
|
289
|
+
retriever = SimpleRetriever(
|
290
|
+
name="stream_name",
|
291
|
+
primary_key=primary_key,
|
292
|
+
requester=MagicMock(),
|
293
|
+
record_selector=MagicMock(),
|
294
|
+
disable_retries=True,
|
295
|
+
parameters={},
|
296
|
+
config={}
|
297
|
+
)
|
298
|
+
assert retriever.max_retries == 0
|
299
|
+
|
300
|
+
|
259
301
|
@pytest.mark.parametrize(
|
260
302
|
"test_name, response_action, retry_in, expected_backoff_time",
|
261
303
|
[
|
@@ -5,7 +5,16 @@
|
|
5
5
|
from unittest import mock
|
6
6
|
from unittest.mock import MagicMock, call
|
7
7
|
|
8
|
-
from airbyte_cdk.models import
|
8
|
+
from airbyte_cdk.models import (
|
9
|
+
AirbyteLogMessage,
|
10
|
+
AirbyteMessage,
|
11
|
+
AirbyteRecordMessage,
|
12
|
+
AirbyteTraceMessage,
|
13
|
+
Level,
|
14
|
+
SyncMode,
|
15
|
+
TraceType,
|
16
|
+
Type,
|
17
|
+
)
|
9
18
|
from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
|
10
19
|
from airbyte_cdk.sources.declarative.transformations import AddFields, RecordTransformation
|
11
20
|
from airbyte_cdk.sources.declarative.transformations.add_fields import AddedFieldDefinition
|
@@ -24,8 +33,8 @@ def test_declarative_stream():
|
|
24
33
|
records = [
|
25
34
|
{"pk": 1234, "field": "value"},
|
26
35
|
{"pk": 4567, "field": "different_value"},
|
27
|
-
AirbyteLogMessage(level=Level.INFO, message="This is a log message"),
|
28
|
-
AirbyteTraceMessage(type=TraceType.ERROR, emitted_at=12345),
|
36
|
+
AirbyteMessage(type=Type.LOG, log=AirbyteLogMessage(level=Level.INFO, message="This is a log message")),
|
37
|
+
AirbyteMessage(type=Type.TRACE, trace=AirbyteTraceMessage(type=TraceType.ERROR, emitted_at=12345)),
|
29
38
|
]
|
30
39
|
stream_slices = [
|
31
40
|
{"date": "2021-01-01"},
|
@@ -84,15 +93,17 @@ def test_declarative_stream_with_add_fields_transform():
|
|
84
93
|
retriever_records = [
|
85
94
|
{"pk": 1234, "field": "value"},
|
86
95
|
{"pk": 4567, "field": "different_value"},
|
87
|
-
|
88
|
-
|
96
|
+
AirbyteMessage(type=Type.RECORD, record=AirbyteRecordMessage(data={"pk": 1357, "field": "a_value"}, emitted_at=12344, stream="stream")),
|
97
|
+
AirbyteMessage(type=Type.LOG, log=AirbyteLogMessage(level=Level.INFO, message="This is a log message")),
|
98
|
+
AirbyteMessage(type=Type.TRACE, trace=AirbyteTraceMessage(type=TraceType.ERROR, emitted_at=12345)),
|
89
99
|
]
|
90
100
|
|
91
101
|
expected_records = [
|
92
102
|
{"pk": 1234, "field": "value", "added_key": "added_value"},
|
93
103
|
{"pk": 4567, "field": "different_value", "added_key": "added_value"},
|
94
|
-
|
95
|
-
|
104
|
+
AirbyteMessage(type=Type.RECORD, record=AirbyteRecordMessage(data={"pk": 1357, "field": "a_value", "added_key": "added_value"}, emitted_at=12344, stream="stream")),
|
105
|
+
AirbyteMessage(type=Type.LOG, log=AirbyteLogMessage(level=Level.INFO, message="This is a log message")),
|
106
|
+
AirbyteMessage(type=Type.TRACE, trace=AirbyteTraceMessage(type=TraceType.ERROR, emitted_at=12345)),
|
96
107
|
]
|
97
108
|
stream_slices = [
|
98
109
|
{"date": "2021-01-01"},
|
@@ -1242,7 +1242,7 @@ def _create_page(response_body):
|
|
1242
1242
|
def test_read_manifest_declarative_source(test_name, manifest, pages, expected_records, expected_calls):
|
1243
1243
|
_stream_name = "Rates"
|
1244
1244
|
with patch.object(HttpStream, "_fetch_next_page", side_effect=pages) as mock_http_stream:
|
1245
|
-
output_data = [message.record.data for message in _run_read(manifest, _stream_name)]
|
1245
|
+
output_data = [message.record.data for message in _run_read(manifest, _stream_name) if message.record]
|
1246
1246
|
assert expected_records == output_data
|
1247
1247
|
mock_http_stream.assert_has_calls(expected_calls)
|
1248
1248
|
|
@@ -21,6 +21,9 @@ from airbyte_cdk.models import (
|
|
21
21
|
AirbyteStateType,
|
22
22
|
AirbyteStream,
|
23
23
|
AirbyteStreamState,
|
24
|
+
AirbyteStreamStatus,
|
25
|
+
AirbyteStreamStatusTraceMessage,
|
26
|
+
AirbyteTraceMessage,
|
24
27
|
ConfiguredAirbyteCatalog,
|
25
28
|
ConfiguredAirbyteStream,
|
26
29
|
DestinationSyncMode,
|
@@ -28,8 +31,10 @@ from airbyte_cdk.models import (
|
|
28
31
|
Status,
|
29
32
|
StreamDescriptor,
|
30
33
|
SyncMode,
|
31
|
-
|
34
|
+
TraceType,
|
32
35
|
)
|
36
|
+
from airbyte_cdk.models import Type
|
37
|
+
from airbyte_cdk.models import Type as MessageType
|
33
38
|
from airbyte_cdk.sources import AbstractSource
|
34
39
|
from airbyte_cdk.sources.connector_state_manager import ConnectorStateManager
|
35
40
|
from airbyte_cdk.sources.streams import IncrementalMixin, Stream
|
@@ -250,6 +255,19 @@ def _as_records(stream: str, data: List[Dict[str, Any]]) -> List[AirbyteMessage]
|
|
250
255
|
return [_as_record(stream, datum) for datum in data]
|
251
256
|
|
252
257
|
|
258
|
+
def _as_stream_status(stream: str, status: AirbyteStreamStatus) -> AirbyteMessage:
|
259
|
+
trace_message = AirbyteTraceMessage(
|
260
|
+
emitted_at=datetime.datetime.now().timestamp() * 1000.0,
|
261
|
+
type=TraceType.STREAM_STATUS,
|
262
|
+
stream_status=AirbyteStreamStatusTraceMessage(
|
263
|
+
stream_descriptor=StreamDescriptor(name=stream),
|
264
|
+
status=status,
|
265
|
+
),
|
266
|
+
)
|
267
|
+
|
268
|
+
return AirbyteMessage(type=MessageType.TRACE, trace=trace_message)
|
269
|
+
|
270
|
+
|
253
271
|
def _as_state(state_data: Dict[str, Any], stream_name: str = "", per_stream_state: Dict[str, Any] = None):
|
254
272
|
if per_stream_state:
|
255
273
|
return AirbyteMessage(
|
@@ -277,6 +295,8 @@ def _fix_emitted_at(messages: List[AirbyteMessage]) -> List[AirbyteMessage]:
|
|
277
295
|
for msg in messages:
|
278
296
|
if msg.type == Type.RECORD and msg.record:
|
279
297
|
msg.record.emitted_at = GLOBAL_EMITTED_AT
|
298
|
+
if msg.type == Type.TRACE and msg.trace:
|
299
|
+
msg.trace.emitted_at = GLOBAL_EMITTED_AT
|
280
300
|
return messages
|
281
301
|
|
282
302
|
|
@@ -296,7 +316,17 @@ def test_valid_full_refresh_read_no_slices(mocker):
|
|
296
316
|
]
|
297
317
|
)
|
298
318
|
|
299
|
-
expected =
|
319
|
+
expected = _fix_emitted_at(
|
320
|
+
[
|
321
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
322
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
323
|
+
*_as_records("s1", stream_output),
|
324
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
325
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
326
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
327
|
+
*_as_records("s2", stream_output),
|
328
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE)
|
329
|
+
])
|
300
330
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog)))
|
301
331
|
|
302
332
|
assert expected == messages
|
@@ -326,7 +356,17 @@ def test_valid_full_refresh_read_with_slices(mocker):
|
|
326
356
|
]
|
327
357
|
)
|
328
358
|
|
329
|
-
expected =
|
359
|
+
expected = _fix_emitted_at(
|
360
|
+
[
|
361
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
362
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
363
|
+
*_as_records("s1", slices),
|
364
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
365
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
366
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
367
|
+
*_as_records("s2", slices),
|
368
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE)
|
369
|
+
])
|
330
370
|
|
331
371
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog)))
|
332
372
|
|
@@ -448,18 +488,24 @@ class TestIncrementalRead:
|
|
448
488
|
]
|
449
489
|
)
|
450
490
|
|
451
|
-
expected = [
|
491
|
+
expected = _fix_emitted_at([
|
492
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
493
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
452
494
|
_as_record("s1", stream_output[0]),
|
453
495
|
_as_record("s1", stream_output[1]),
|
454
496
|
_as_state({"s1": new_state_from_connector}, "s1", new_state_from_connector)
|
455
497
|
if per_stream_enabled
|
456
498
|
else _as_state({"s1": new_state_from_connector}),
|
499
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
500
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
501
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
457
502
|
_as_record("s2", stream_output[0]),
|
458
503
|
_as_record("s2", stream_output[1]),
|
459
504
|
_as_state({"s1": new_state_from_connector, "s2": new_state_from_connector}, "s2", new_state_from_connector)
|
460
505
|
if per_stream_enabled
|
461
506
|
else _as_state({"s1": new_state_from_connector, "s2": new_state_from_connector}),
|
462
|
-
|
507
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
508
|
+
])
|
463
509
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog, state=input_state)))
|
464
510
|
|
465
511
|
assert messages == expected
|
@@ -521,18 +567,24 @@ class TestIncrementalRead:
|
|
521
567
|
]
|
522
568
|
)
|
523
569
|
|
524
|
-
expected = [
|
570
|
+
expected = _fix_emitted_at([
|
571
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
572
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
525
573
|
_as_record("s1", stream_output[0]),
|
526
574
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
527
575
|
_as_record("s1", stream_output[1]),
|
528
576
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
529
577
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
578
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
579
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
580
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
530
581
|
_as_record("s2", stream_output[0]),
|
531
582
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
532
583
|
_as_record("s2", stream_output[1]),
|
533
584
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
534
585
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
535
|
-
|
586
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
587
|
+
])
|
536
588
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog, state=input_state)))
|
537
589
|
|
538
590
|
assert expected == messages
|
@@ -582,12 +634,18 @@ class TestIncrementalRead:
|
|
582
634
|
]
|
583
635
|
)
|
584
636
|
|
585
|
-
expected = [
|
637
|
+
expected = _fix_emitted_at([
|
638
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
639
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
586
640
|
*_as_records("s1", stream_output),
|
587
641
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
642
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
643
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
644
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
588
645
|
*_as_records("s2", stream_output),
|
589
646
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
590
|
-
|
647
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
648
|
+
])
|
591
649
|
|
592
650
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog, state=input_state)))
|
593
651
|
|
@@ -658,20 +716,26 @@ class TestIncrementalRead:
|
|
658
716
|
]
|
659
717
|
)
|
660
718
|
|
661
|
-
expected = [
|
719
|
+
expected = _fix_emitted_at([
|
720
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
721
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
662
722
|
# stream 1 slice 1
|
663
723
|
*_as_records("s1", stream_output),
|
664
724
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
665
725
|
# stream 1 slice 2
|
666
726
|
*_as_records("s1", stream_output),
|
667
727
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
728
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
729
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
730
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
668
731
|
# stream 2 slice 1
|
669
732
|
*_as_records("s2", stream_output),
|
670
733
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
671
734
|
# stream 2 slice 2
|
672
735
|
*_as_records("s2", stream_output),
|
673
736
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
674
|
-
|
737
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
738
|
+
])
|
675
739
|
|
676
740
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog, state=input_state)))
|
677
741
|
|
@@ -753,10 +817,14 @@ class TestIncrementalRead:
|
|
753
817
|
]
|
754
818
|
)
|
755
819
|
|
756
|
-
expected = [
|
820
|
+
expected = _fix_emitted_at([
|
821
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
757
822
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
823
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
824
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
758
825
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
759
|
-
|
826
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
827
|
+
])
|
760
828
|
|
761
829
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog, state=input_state)))
|
762
830
|
|
@@ -837,8 +905,10 @@ class TestIncrementalRead:
|
|
837
905
|
]
|
838
906
|
)
|
839
907
|
|
840
|
-
expected = [
|
908
|
+
expected = _fix_emitted_at([
|
841
909
|
# stream 1 slice 1
|
910
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
911
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
842
912
|
_as_record("s1", stream_output[0]),
|
843
913
|
_as_record("s1", stream_output[1]),
|
844
914
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
@@ -850,7 +920,10 @@ class TestIncrementalRead:
|
|
850
920
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
851
921
|
_as_record("s1", stream_output[2]),
|
852
922
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
923
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
853
924
|
# stream 2 slice 1
|
925
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
926
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
854
927
|
_as_record("s2", stream_output[0]),
|
855
928
|
_as_record("s2", stream_output[1]),
|
856
929
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
@@ -862,7 +935,8 @@ class TestIncrementalRead:
|
|
862
935
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
863
936
|
_as_record("s2", stream_output[2]),
|
864
937
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
865
|
-
|
938
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
939
|
+
])
|
866
940
|
|
867
941
|
messages = _fix_emitted_at(list(src.read(logger, {}, catalog, state=input_state)))
|
868
942
|
|
@@ -942,6 +1016,8 @@ class TestIncrementalRead:
|
|
942
1016
|
|
943
1017
|
expected = _fix_emitted_at(
|
944
1018
|
[
|
1019
|
+
_as_stream_status("s1", AirbyteStreamStatus.STARTED),
|
1020
|
+
_as_stream_status("s1", AirbyteStreamStatus.RUNNING),
|
945
1021
|
# stream 1 slice 1
|
946
1022
|
stream_data_to_airbyte_message("s1", stream_output[0]),
|
947
1023
|
stream_data_to_airbyte_message("s1", stream_output[1]),
|
@@ -956,7 +1032,10 @@ class TestIncrementalRead:
|
|
956
1032
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
957
1033
|
stream_data_to_airbyte_message("s1", stream_output[3]),
|
958
1034
|
_as_state({"s1": state}, "s1", state) if per_stream_enabled else _as_state({"s1": state}),
|
1035
|
+
_as_stream_status("s1", AirbyteStreamStatus.COMPLETE),
|
959
1036
|
# stream 2 slice 1
|
1037
|
+
_as_stream_status("s2", AirbyteStreamStatus.STARTED),
|
1038
|
+
_as_stream_status("s2", AirbyteStreamStatus.RUNNING),
|
960
1039
|
stream_data_to_airbyte_message("s2", stream_output[0]),
|
961
1040
|
stream_data_to_airbyte_message("s2", stream_output[1]),
|
962
1041
|
stream_data_to_airbyte_message("s2", stream_output[2]),
|
@@ -970,6 +1049,7 @@ class TestIncrementalRead:
|
|
970
1049
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
971
1050
|
stream_data_to_airbyte_message("s2", stream_output[3]),
|
972
1051
|
_as_state({"s1": state, "s2": state}, "s2", state) if per_stream_enabled else _as_state({"s1": state, "s2": state}),
|
1052
|
+
_as_stream_status("s2", AirbyteStreamStatus.COMPLETE),
|
973
1053
|
]
|
974
1054
|
)
|
975
1055
|
|
@@ -365,8 +365,8 @@ def test_internal_config(abstract_source, catalog):
|
|
365
365
|
# Test with empty config
|
366
366
|
logger = logging.getLogger(f"airbyte.{getattr(abstract_source, 'name', '')}")
|
367
367
|
records = [r for r in abstract_source.read(logger=logger, config={}, catalog=catalog, state={})]
|
368
|
-
# 3 for http stream
|
369
|
-
assert len(records) == 3 + 3
|
368
|
+
# 3 for http stream, 3 for non http stream and 3 for stream status messages for each stream (2x)
|
369
|
+
assert len(records) == 3 + 3 + 3 + 3
|
370
370
|
assert http_stream.read_records.called
|
371
371
|
assert non_http_stream.read_records.called
|
372
372
|
# Make sure page_size havent been set
|
@@ -375,21 +375,21 @@ def test_internal_config(abstract_source, catalog):
|
|
375
375
|
# Test with records limit set to 1
|
376
376
|
internal_config = {"some_config": 100, "_limit": 1}
|
377
377
|
records = [r for r in abstract_source.read(logger=logger, config=internal_config, catalog=catalog, state={})]
|
378
|
-
# 1 from http stream + 1 from non http stream
|
379
|
-
assert len(records) == 1 + 1
|
378
|
+
# 1 from http stream + 1 from non http stream and 3 for stream status messages for each stream (2x)
|
379
|
+
assert len(records) == 1 + 1 + 3 + 3
|
380
380
|
assert "_limit" not in abstract_source.streams_config
|
381
381
|
assert "some_config" in abstract_source.streams_config
|
382
382
|
# Test with records limit set to number that exceeds expceted records
|
383
383
|
internal_config = {"some_config": 100, "_limit": 20}
|
384
384
|
records = [r for r in abstract_source.read(logger=logger, config=internal_config, catalog=catalog, state={})]
|
385
|
-
assert len(records) == 3 + 3
|
385
|
+
assert len(records) == 3 + 3 + 3 + 3
|
386
386
|
|
387
387
|
# Check if page_size paramter is set to http instance only
|
388
388
|
internal_config = {"some_config": 100, "_page_size": 2}
|
389
389
|
records = [r for r in abstract_source.read(logger=logger, config=internal_config, catalog=catalog, state={})]
|
390
390
|
assert "_page_size" not in abstract_source.streams_config
|
391
391
|
assert "some_config" in abstract_source.streams_config
|
392
|
-
assert len(records) == 3 + 3
|
392
|
+
assert len(records) == 3 + 3 + 3 + 3
|
393
393
|
assert http_stream.page_size == 2
|
394
394
|
# Make sure page_size havent been set for non http streams
|
395
395
|
assert not non_http_stream.page_size
|
@@ -402,6 +402,7 @@ def test_internal_config_limit(mocker, abstract_source, catalog):
|
|
402
402
|
STREAM_LIMIT = 2
|
403
403
|
SLICE_DEBUG_LOG_COUNT = 1
|
404
404
|
FULL_RECORDS_NUMBER = 3
|
405
|
+
TRACE_STATUS_COUNT = 3
|
405
406
|
streams = abstract_source.streams(None)
|
406
407
|
http_stream = streams[0]
|
407
408
|
http_stream.read_records.return_value = [{}] * FULL_RECORDS_NUMBER
|
@@ -409,7 +410,7 @@ def test_internal_config_limit(mocker, abstract_source, catalog):
|
|
409
410
|
|
410
411
|
catalog.streams[0].sync_mode = SyncMode.full_refresh
|
411
412
|
records = [r for r in abstract_source.read(logger=logger_mock, config=internal_config, catalog=catalog, state={})]
|
412
|
-
assert len(records) == STREAM_LIMIT + SLICE_DEBUG_LOG_COUNT
|
413
|
+
assert len(records) == STREAM_LIMIT + SLICE_DEBUG_LOG_COUNT + TRACE_STATUS_COUNT
|
413
414
|
logger_info_args = [call[0][0] for call in logger_mock.info.call_args_list]
|
414
415
|
# Check if log line matches number of limit
|
415
416
|
read_log_record = [_l for _l in logger_info_args if _l.startswith("Read")]
|
@@ -418,14 +419,16 @@ def test_internal_config_limit(mocker, abstract_source, catalog):
|
|
418
419
|
# No limit, check if state record produced for incremental stream
|
419
420
|
catalog.streams[0].sync_mode = SyncMode.incremental
|
420
421
|
records = [r for r in abstract_source.read(logger=logger_mock, config={}, catalog=catalog, state={})]
|
421
|
-
assert len(records) == FULL_RECORDS_NUMBER + SLICE_DEBUG_LOG_COUNT + 1
|
422
|
-
assert records[-
|
422
|
+
assert len(records) == FULL_RECORDS_NUMBER + SLICE_DEBUG_LOG_COUNT + TRACE_STATUS_COUNT + 1
|
423
|
+
assert records[-2].type == Type.STATE
|
424
|
+
assert records[-1].type == Type.TRACE
|
423
425
|
|
424
426
|
# Set limit and check if state is produced when limit is set for incremental stream
|
425
427
|
logger_mock.reset_mock()
|
426
428
|
records = [r for r in abstract_source.read(logger=logger_mock, config=internal_config, catalog=catalog, state={})]
|
427
|
-
assert len(records) == STREAM_LIMIT + SLICE_DEBUG_LOG_COUNT + 1
|
428
|
-
assert records[-
|
429
|
+
assert len(records) == STREAM_LIMIT + SLICE_DEBUG_LOG_COUNT + TRACE_STATUS_COUNT + 1
|
430
|
+
assert records[-2].type == Type.STATE
|
431
|
+
assert records[-1].type == Type.TRACE
|
429
432
|
logger_info_args = [call[0][0] for call in logger_mock.info.call_args_list]
|
430
433
|
read_log_record = [_l for _l in logger_info_args if _l.startswith("Read")]
|
431
434
|
assert read_log_record[0].startswith(f"Read {STREAM_LIMIT} ")
|
@@ -436,6 +439,7 @@ SCHEMA = {"type": "object", "properties": {"value": {"type": "string"}}}
|
|
436
439
|
|
437
440
|
def test_source_config_no_transform(mocker, abstract_source, catalog):
|
438
441
|
SLICE_DEBUG_LOG_COUNT = 1
|
442
|
+
TRACE_STATUS_COUNT = 3
|
439
443
|
logger_mock = mocker.MagicMock()
|
440
444
|
logger_mock.level = logging.DEBUG
|
441
445
|
streams = abstract_source.streams(None)
|
@@ -443,7 +447,7 @@ def test_source_config_no_transform(mocker, abstract_source, catalog):
|
|
443
447
|
http_stream.get_json_schema.return_value = non_http_stream.get_json_schema.return_value = SCHEMA
|
444
448
|
http_stream.read_records.return_value, non_http_stream.read_records.return_value = [[{"value": 23}] * 5] * 2
|
445
449
|
records = [r for r in abstract_source.read(logger=logger_mock, config={}, catalog=catalog, state={})]
|
446
|
-
assert len(records) == 2 * (5 + SLICE_DEBUG_LOG_COUNT)
|
450
|
+
assert len(records) == 2 * (5 + SLICE_DEBUG_LOG_COUNT + TRACE_STATUS_COUNT)
|
447
451
|
assert [r.record.data for r in records if r.type == Type.RECORD] == [{"value": 23}] * 2 * 5
|
448
452
|
assert http_stream.get_json_schema.call_count == 5
|
449
453
|
assert non_http_stream.get_json_schema.call_count == 5
|
@@ -453,6 +457,7 @@ def test_source_config_transform(mocker, abstract_source, catalog):
|
|
453
457
|
logger_mock = mocker.MagicMock()
|
454
458
|
logger_mock.level = logging.DEBUG
|
455
459
|
SLICE_DEBUG_LOG_COUNT = 2
|
460
|
+
TRACE_STATUS_COUNT = 6
|
456
461
|
streams = abstract_source.streams(None)
|
457
462
|
http_stream, non_http_stream = streams
|
458
463
|
http_stream.transformer = TypeTransformer(TransformConfig.DefaultSchemaNormalization)
|
@@ -460,7 +465,7 @@ def test_source_config_transform(mocker, abstract_source, catalog):
|
|
460
465
|
http_stream.get_json_schema.return_value = non_http_stream.get_json_schema.return_value = SCHEMA
|
461
466
|
http_stream.read_records.return_value, non_http_stream.read_records.return_value = [{"value": 23}], [{"value": 23}]
|
462
467
|
records = [r for r in abstract_source.read(logger=logger_mock, config={}, catalog=catalog, state={})]
|
463
|
-
assert len(records) == 2 + SLICE_DEBUG_LOG_COUNT
|
468
|
+
assert len(records) == 2 + SLICE_DEBUG_LOG_COUNT + TRACE_STATUS_COUNT
|
464
469
|
assert [r.record.data for r in records if r.type == Type.RECORD] == [{"value": "23"}] * 2
|
465
470
|
|
466
471
|
|
@@ -468,13 +473,14 @@ def test_source_config_transform_and_no_transform(mocker, abstract_source, catal
|
|
468
473
|
logger_mock = mocker.MagicMock()
|
469
474
|
logger_mock.level = logging.DEBUG
|
470
475
|
SLICE_DEBUG_LOG_COUNT = 2
|
476
|
+
TRACE_STATUS_COUNT = 6
|
471
477
|
streams = abstract_source.streams(None)
|
472
478
|
http_stream, non_http_stream = streams
|
473
479
|
http_stream.transformer = TypeTransformer(TransformConfig.DefaultSchemaNormalization)
|
474
480
|
http_stream.get_json_schema.return_value = non_http_stream.get_json_schema.return_value = SCHEMA
|
475
481
|
http_stream.read_records.return_value, non_http_stream.read_records.return_value = [{"value": 23}], [{"value": 23}]
|
476
482
|
records = [r for r in abstract_source.read(logger=logger_mock, config={}, catalog=catalog, state={})]
|
477
|
-
assert len(records) == 2 + SLICE_DEBUG_LOG_COUNT
|
483
|
+
assert len(records) == 2 + SLICE_DEBUG_LOG_COUNT + TRACE_STATUS_COUNT
|
478
484
|
assert [r.record.data for r in records if r.type == Type.RECORD] == [{"value": "23"}, {"value": 23}]
|
479
485
|
|
480
486
|
|
@@ -520,8 +526,8 @@ def test_read_default_http_availability_strategy_stream_available(catalog, mocke
|
|
520
526
|
source = MockAbstractSource(streams=streams)
|
521
527
|
logger = logging.getLogger(f"airbyte.{getattr(abstract_source, 'name', '')}")
|
522
528
|
records = [r for r in source.read(logger=logger, config={}, catalog=catalog, state={})]
|
523
|
-
# 3 for http stream
|
524
|
-
assert len(records) == 3 + 3
|
529
|
+
# 3 for http stream, 3 for non http stream and 3 for stream status messages for each stream (2x)
|
530
|
+
assert len(records) == 3 + 3 + 3 + 3
|
525
531
|
assert http_stream.read_records.called
|
526
532
|
assert non_http_stream.read_records.called
|
527
533
|
|
@@ -578,8 +584,8 @@ def test_read_default_http_availability_strategy_stream_unavailable(catalog, moc
|
|
578
584
|
with caplog.at_level(logging.WARNING):
|
579
585
|
records = [r for r in source.read(logger=logger, config={}, catalog=catalog, state={})]
|
580
586
|
|
581
|
-
# 0 for http stream
|
582
|
-
assert len(records) == 0 + 3
|
587
|
+
# 0 for http stream, 3 for non http stream and 3 status trace meessages
|
588
|
+
assert len(records) == 0 + 3 + 3
|
583
589
|
assert non_http_stream.read_records.called
|
584
590
|
expected_logs = [
|
585
591
|
f"Skipped syncing stream '{http_stream.name}' because it was unavailable.",
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
from airbyte_cdk.models import (
|
6
|
+
AirbyteMessage,
|
7
|
+
AirbyteStream,
|
8
|
+
AirbyteStreamStatus,
|
9
|
+
ConfiguredAirbyteStream,
|
10
|
+
DestinationSyncMode,
|
11
|
+
SyncMode,
|
12
|
+
TraceType,
|
13
|
+
)
|
14
|
+
from airbyte_cdk.models import Type as MessageType
|
15
|
+
from airbyte_cdk.utils.stream_status_utils import as_airbyte_message as stream_status_as_airbyte_message
|
16
|
+
|
17
|
+
stream = AirbyteStream(name="name", namespace="namespace", json_schema={}, supported_sync_modes=[SyncMode.full_refresh])
|
18
|
+
configured_stream = ConfiguredAirbyteStream(stream=stream, sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.overwrite)
|
19
|
+
|
20
|
+
|
21
|
+
def test_started_as_message():
|
22
|
+
stream_status = AirbyteStreamStatus.STARTED
|
23
|
+
airbyte_message = stream_status_as_airbyte_message(configured_stream, stream_status)
|
24
|
+
|
25
|
+
assert type(airbyte_message) == AirbyteMessage
|
26
|
+
assert airbyte_message.type == MessageType.TRACE
|
27
|
+
assert airbyte_message.trace.type == TraceType.STREAM_STATUS
|
28
|
+
assert airbyte_message.trace.emitted_at > 0
|
29
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.name == configured_stream.stream.name
|
30
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.namespace == configured_stream.stream.namespace
|
31
|
+
assert airbyte_message.trace.stream_status.status == stream_status
|
32
|
+
|
33
|
+
|
34
|
+
def test_running_as_message():
|
35
|
+
stream_status = AirbyteStreamStatus.RUNNING
|
36
|
+
airbyte_message = stream_status_as_airbyte_message(configured_stream, stream_status)
|
37
|
+
|
38
|
+
assert type(airbyte_message) == AirbyteMessage
|
39
|
+
assert airbyte_message.type == MessageType.TRACE
|
40
|
+
assert airbyte_message.trace.type == TraceType.STREAM_STATUS
|
41
|
+
assert airbyte_message.trace.emitted_at > 0
|
42
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.name == configured_stream.stream.name
|
43
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.namespace == configured_stream.stream.namespace
|
44
|
+
assert airbyte_message.trace.stream_status.status == stream_status
|
45
|
+
|
46
|
+
|
47
|
+
def test_complete_as_message():
|
48
|
+
stream_status = AirbyteStreamStatus.COMPLETE
|
49
|
+
airbyte_message = stream_status_as_airbyte_message(configured_stream, stream_status)
|
50
|
+
|
51
|
+
assert type(airbyte_message) == AirbyteMessage
|
52
|
+
assert airbyte_message.type == MessageType.TRACE
|
53
|
+
assert airbyte_message.trace.type == TraceType.STREAM_STATUS
|
54
|
+
assert airbyte_message.trace.emitted_at > 0
|
55
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.name == configured_stream.stream.name
|
56
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.namespace == configured_stream.stream.namespace
|
57
|
+
assert airbyte_message.trace.stream_status.status == stream_status
|
58
|
+
|
59
|
+
|
60
|
+
def test_incomplete_failed_as_message():
|
61
|
+
stream_status = AirbyteStreamStatus.INCOMPLETE
|
62
|
+
airbyte_message = stream_status_as_airbyte_message(configured_stream, stream_status)
|
63
|
+
|
64
|
+
assert type(airbyte_message) == AirbyteMessage
|
65
|
+
assert airbyte_message.type == MessageType.TRACE
|
66
|
+
assert airbyte_message.trace.type == TraceType.STREAM_STATUS
|
67
|
+
assert airbyte_message.trace.emitted_at > 0
|
68
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.name == configured_stream.stream.name
|
69
|
+
assert airbyte_message.trace.stream_status.stream_descriptor.namespace == configured_stream.stream.namespace
|
70
|
+
assert airbyte_message.trace.stream_status.status == stream_status
|
File without changes
|
File without changes
|
File without changes
|