airbyte-cdk 0.35.4__py3-none-any.whl → 0.36.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|