airbyte-cdk 0.58.8__py3-none-any.whl → 0.59.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +20 -21
  2. airbyte_cdk/sources/concurrent_source/concurrent_source.py +4 -3
  3. airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +15 -18
  4. airbyte_cdk/sources/concurrent_source/throttler.py +25 -0
  5. airbyte_cdk/sources/streams/concurrent/cursor.py +29 -8
  6. airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +3 -5
  7. airbyte_cdk/sources/streams/concurrent/partition_reader.py +3 -4
  8. airbyte_cdk/sources/streams/concurrent/partitions/throttled_queue.py +41 -0
  9. airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +6 -12
  10. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +36 -30
  11. {airbyte_cdk-0.58.8.dist-info → airbyte_cdk-0.59.0.dist-info}/METADATA +1 -1
  12. {airbyte_cdk-0.58.8.dist-info → airbyte_cdk-0.59.0.dist-info}/RECORD +23 -19
  13. unit_tests/sources/streams/concurrent/scenarios/stream_facade_builder.py +2 -2
  14. unit_tests/sources/streams/concurrent/test_concurrent_partition_generator.py +4 -10
  15. unit_tests/sources/streams/concurrent/test_concurrent_read_processor.py +82 -12
  16. unit_tests/sources/streams/concurrent/test_cursor.py +20 -3
  17. unit_tests/sources/streams/concurrent/test_datetime_state_converter.py +166 -268
  18. unit_tests/sources/streams/concurrent/test_thread_pool_manager.py +2 -15
  19. unit_tests/sources/streams/concurrent/test_throttled_queue.py +65 -0
  20. unit_tests/sources/streams/concurrent/test_throttler.py +13 -0
  21. {airbyte_cdk-0.58.8.dist-info → airbyte_cdk-0.59.0.dist-info}/LICENSE.txt +0 -0
  22. {airbyte_cdk-0.58.8.dist-info → airbyte_cdk-0.59.0.dist-info}/WHEEL +0 -0
  23. {airbyte_cdk-0.58.8.dist-info → airbyte_cdk-0.59.0.dist-info}/top_level.txt +0 -0
@@ -31,11 +31,12 @@ airbyte_cdk/sources/http_config.py,sha256=OBZeuyFilm6NlDlBhFQvHhTWabEvZww6OHDIlZ
31
31
  airbyte_cdk/sources/http_logger.py,sha256=v0kkpDtA0GUOgj6_3AayrYaBrSHBqG4t3MGbrtxaNmU,1437
32
32
  airbyte_cdk/sources/source.py,sha256=dk50z8Roc28MJ8FxWe652B-GwItO__bTZqFm7WOtHnw,4412
33
33
  airbyte_cdk/sources/concurrent_source/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
34
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py,sha256=7A8bdOqg9Hpw37QgqoGO6eYBx8_pKikt_AJUcImp-x4,9715
35
- airbyte_cdk/sources/concurrent_source/concurrent_source.py,sha256=qdxeuZJh5cW1THyASfCBJ_H92C42bQz0vv17IhXp7l0,7836
34
+ airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py,sha256=TIqzf-_fYfCe4n77UwREiXLnFfSlTT-zsHct760KZw8,10052
35
+ airbyte_cdk/sources/concurrent_source/concurrent_source.py,sha256=Rg8_62UfOIQd1IE8ILpj_z4xNAD9vtIrNRVvMgHMfdA,7957
36
36
  airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py,sha256=si5ipxvzCE2Pdusg19evr8ziG7eqBBuBuDPww6i3Amg,3223
37
37
  airbyte_cdk/sources/concurrent_source/partition_generation_completed_sentinel.py,sha256=oExaUlnDepGZjNmauIkFDCbWtxZvkBCFo1K0wAr4sRA,493
38
- airbyte_cdk/sources/concurrent_source/thread_pool_manager.py,sha256=huFp0uuG2kZAnbHY8oeDYuX0hfmP-rLOnJMa72ZuWt0,3905
38
+ airbyte_cdk/sources/concurrent_source/thread_pool_manager.py,sha256=M-IcXJwMBK31Luk-SLQSFwTV8PelDdaSFMLlB6K1T4w,3908
39
+ airbyte_cdk/sources/concurrent_source/throttler.py,sha256=gAdwWTk5iveSRUceXIYAms8v1qGSJytzUD07lkR1I9k,926
39
40
  airbyte_cdk/sources/declarative/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
40
41
  airbyte_cdk/sources/declarative/create_partial.py,sha256=sUJOwD8hBzW4pxw2XhYlSTMgl-WMc5WpP5Oq_jo3fHw,3371
41
42
  airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=4KtVA54eDmZt8JAFuHLwQv8PmhOmiiaGRBNUgcAQBzQ,87107
@@ -201,19 +202,20 @@ airbyte_cdk/sources/streams/concurrent/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuY
201
202
  airbyte_cdk/sources/streams/concurrent/abstract_stream.py,sha256=W7WEz6FrfAjb0o_msnMBIESSVO1qJC2_A8ocYg55Rw4,3579
202
203
  airbyte_cdk/sources/streams/concurrent/adapters.py,sha256=f48kLzOHYNeD7Tfsdy7WaZ__hB24SfCTcW5WpQedqTc,18648
203
204
  airbyte_cdk/sources/streams/concurrent/availability_strategy.py,sha256=8xDRpfktnARBbRi_RwznvKuoGrpPF2b6tQyloMwogkM,2013
204
- airbyte_cdk/sources/streams/concurrent/cursor.py,sha256=GnSRDkEEvg2GNy_fEc9cWFoYI1oEfvzwg1vhRrusqWg,7105
205
+ airbyte_cdk/sources/streams/concurrent/cursor.py,sha256=_mAbnJILeiGOGBNXeeXrSSoz7rveEBMoL97569EPEBY,8106
205
206
  airbyte_cdk/sources/streams/concurrent/default_stream.py,sha256=w83pvFbw9vjfhbovw-LrCFiwQMO8hfo1Vm-1CB1SeXQ,2777
206
207
  airbyte_cdk/sources/streams/concurrent/exceptions.py,sha256=-WETGIY5_QFmVeDFiqm4WhRJ_nNCkfcDwOQqx6cSqrI,365
207
- airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py,sha256=Mmn0hYq2xWe2a0WOpZPF3iZNozfmv7vY37LgfdY7DVo,1570
208
- airbyte_cdk/sources/streams/concurrent/partition_reader.py,sha256=H8sGVVGx6uKMSUehRaqmVbE19DE3cx3NivQ4sFj8wbk,1303
208
+ airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py,sha256=TicEVRyLt5Y85xa8bXsrGzjmNMJAulBLqe1LfBLnHmk,1540
209
+ airbyte_cdk/sources/streams/concurrent/partition_reader.py,sha256=KHFYK4Yn_6L1gmt31N62K1z28evPKtgI_tkhKQ5oWCg,1358
209
210
  airbyte_cdk/sources/streams/concurrent/partitions/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
210
211
  airbyte_cdk/sources/streams/concurrent/partitions/partition.py,sha256=o2QvDYZF3Tn9NbC5jc1UkDwMiCWq9fNGj493u2WFoko,1795
211
212
  airbyte_cdk/sources/streams/concurrent/partitions/partition_generator.py,sha256=_ymkkBr71_qt1fW0_MUqw96OfNBkeJngXQ09yolEDHw,441
212
213
  airbyte_cdk/sources/streams/concurrent/partitions/record.py,sha256=-Q3zLex3CHOXiB-KOZLbBZaPiQ_BLFJdknr6yoRz9I0,600
214
+ airbyte_cdk/sources/streams/concurrent/partitions/throttled_queue.py,sha256=P6KrMb4GtcDUbMcx7pVb7dfF_igeW9Utn2MFoVHkH6o,1589
213
215
  airbyte_cdk/sources/streams/concurrent/partitions/types.py,sha256=iVARnsGOSdvlSCqAf-yxc4_PUT3oOR9B6cyVNcLTjY8,932
214
216
  airbyte_cdk/sources/streams/concurrent/state_converters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
215
- airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py,sha256=8_97TvqOSn5jxmHsNMmnFi_A4XxGtMrX5GG6eNOyLtA,2954
216
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py,sha256=4K_Fa0nviG3L3Mu4YX3oEs1cAfE0QqzrKkkdJsL21M4,7002
217
+ airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py,sha256=jeVP3V9uDM_aCMh1G3kZNKafjopy1rZzIAJ8sU-69KU,2613
218
+ airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py,sha256=BvVwPiVHE-R7mWDjcqhsKnZyD6OcpmAYaih7pcmOUvo,7589
217
219
  airbyte_cdk/sources/streams/http/__init__.py,sha256=cTP2d7Wf0hYXaN20U0dtxKa1pFZ9rI-lBbkQ0UM1apQ,261
218
220
  airbyte_cdk/sources/streams/http/availability_strategy.py,sha256=MHgW42gwaevaCVnNLrUSE6WJHT4reeZ417nMWrmbC7o,6884
219
221
  airbyte_cdk/sources/streams/http/exceptions.py,sha256=OokLDI7W8hZvq9e15sL3em2AdwmzmcAl72Ms-i5l0Nw,1334
@@ -407,16 +409,18 @@ unit_tests/sources/streams/test_stream_read.py,sha256=xxyYV5jPsAptmI0awPO_VGWMaE
407
409
  unit_tests/sources/streams/test_streams_core.py,sha256=YOC7XqWFJ13Z4YuO9Nh4AR4AwpJ-s111vqPplFfpxk4,5059
408
410
  unit_tests/sources/streams/concurrent/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
409
411
  unit_tests/sources/streams/concurrent/test_adapters.py,sha256=Y_c1vKCtGKEzrUSncmpgp0lgFnArmBrIrmLFaOIAxRg,15439
410
- unit_tests/sources/streams/concurrent/test_concurrent_partition_generator.py,sha256=v8yf19_sDgVcWop6WKotahlQiO6B8MwxhGi3AL4vHm8,1375
411
- unit_tests/sources/streams/concurrent/test_concurrent_read_processor.py,sha256=1578s0CtFVQALrv2slo12RIgNmNwwobJAFWfBre8jdc,23822
412
- unit_tests/sources/streams/concurrent/test_cursor.py,sha256=ere6x4nhhtSmEdvDoksLvLJTFkcl29N7VI7Mj6jin-I,5385
413
- unit_tests/sources/streams/concurrent/test_datetime_state_converter.py,sha256=mzlm8JL5AuYmNX4r9VViLKNaU1zUyBvBQ3lIdChW9pU,19153
412
+ unit_tests/sources/streams/concurrent/test_concurrent_partition_generator.py,sha256=a5JWWWc20zeEGmMzYzzk-_6XwfDEZOGgW287dVgft_8,1339
413
+ unit_tests/sources/streams/concurrent/test_concurrent_read_processor.py,sha256=Dc7PGfQge1ymxnaTlPKGMVOLCV81JCXoK1ciJPwHIhg,26347
414
+ unit_tests/sources/streams/concurrent/test_cursor.py,sha256=0nFp9xauWxDVxJmFQLedvnxWKDoAlk9ChDkd0Mmrhm4,5951
415
+ unit_tests/sources/streams/concurrent/test_datetime_state_converter.py,sha256=vx-oPmGSzSfBM37ZXN_wXLeTOlozErqQoa5sc5zP42o,13287
414
416
  unit_tests/sources/streams/concurrent/test_default_stream.py,sha256=VLF46ESoRqcoALYCdrdZ2NDl5s2T1fRRWsYAy2-IwYw,6502
415
417
  unit_tests/sources/streams/concurrent/test_partition_reader.py,sha256=2uj7uV3ie0BMb--aa3MUru-f4jLiYUR-Nl0r3EhwxLQ,951
416
- unit_tests/sources/streams/concurrent/test_thread_pool_manager.py,sha256=7L9Sv7VXULOHx3-KSFwFtzAY1X96wcPiPKGq38BQEVg,3699
418
+ unit_tests/sources/streams/concurrent/test_thread_pool_manager.py,sha256=UzlMhXTgXAuqqPrESGjkDG9JLj4UdPo2bx3T9oLCFpA,3140
419
+ unit_tests/sources/streams/concurrent/test_throttled_queue.py,sha256=05NgNkx5c5rL8RE2ViOdJz8jty5-67BQPvJNLpVjuMo,1747
420
+ unit_tests/sources/streams/concurrent/test_throttler.py,sha256=y1cWUdKZP5iy4FKWmGdgEk4e_0WY0VNgRvzmlU114NY,448
417
421
  unit_tests/sources/streams/concurrent/scenarios/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
418
422
  unit_tests/sources/streams/concurrent/scenarios/incremental_scenarios.py,sha256=TH4vzdHNWvw4JsF0v4n6wrR1Rnr-WfU3R6nnOwGLNwg,9751
419
- unit_tests/sources/streams/concurrent/scenarios/stream_facade_builder.py,sha256=cFqcfrk-P4Gz-5yUFjTXIZ5U0zhzg3jayZiF6W79oPc,5880
423
+ unit_tests/sources/streams/concurrent/scenarios/stream_facade_builder.py,sha256=BGqaYgU_ow4PsuhDjFwAFkU1VCkUuromvTAUV5tOaJ8,5816
420
424
  unit_tests/sources/streams/concurrent/scenarios/stream_facade_scenarios.py,sha256=kDKKV0ApASyS5c2HYkKvYohSkT--46TqALirqU8POjg,13804
421
425
  unit_tests/sources/streams/concurrent/scenarios/test_concurrent_scenarios.py,sha256=Z_4-ClsxBupmN7Pbl8lF9bkSA9wnjLtrgA9WR_8VRi8,3757
422
426
  unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_scenarios.py,sha256=KqCLsXB_9rV4hNdSPrNynK3G-UIsipqsZT6X0Z-iM5E,13175
@@ -444,8 +448,8 @@ unit_tests/utils/test_schema_inferrer.py,sha256=Z2jHBZ540wnYkylIdV_2xr75Vtwlxuyg
444
448
  unit_tests/utils/test_secret_utils.py,sha256=XKe0f1RHYii8iwE6ATmBr5JGDI1pzzrnZUGdUSMJQP4,4886
445
449
  unit_tests/utils/test_stream_status_utils.py,sha256=Xr8MZ2HWgTVIyMbywDvuYkRaUF4RZLQOT8-JjvcfR24,2970
446
450
  unit_tests/utils/test_traced_exception.py,sha256=bDFP5zMBizFenz6V2WvEZTRCKGB5ijh3DBezjbfoYIs,4198
447
- airbyte_cdk-0.58.8.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
448
- airbyte_cdk-0.58.8.dist-info/METADATA,sha256=dc4NGd9LcXAFFyYyc-NiTyDUkWjJsvz9iztzMveJZUA,11073
449
- airbyte_cdk-0.58.8.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
450
- airbyte_cdk-0.58.8.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
451
- airbyte_cdk-0.58.8.dist-info/RECORD,,
451
+ airbyte_cdk-0.59.0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
452
+ airbyte_cdk-0.59.0.dist-info/METADATA,sha256=5wiAdalwK0NUdE--8UKtiGbRy9ccsaY1T255M_Ei850,11073
453
+ airbyte_cdk-0.59.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
454
+ airbyte_cdk-0.59.0.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
455
+ airbyte_cdk-0.59.0.dist-info/RECORD,,
@@ -52,8 +52,7 @@ class StreamFacadeSource(ConcurrentSourceAdapter):
52
52
  def streams(self, config: Mapping[str, Any]) -> List[Stream]:
53
53
  state_manager = ConnectorStateManager(stream_instance_map={s.name: s for s in self._streams}, state=self._state)
54
54
  state_converter = StreamFacadeConcurrentConnectorStateConverter()
55
- stream_states = [state_converter.get_concurrent_stream_state(self._cursor_field, state_manager.get_stream_state(stream.name, stream.namespace))
56
- for stream in self._streams]
55
+ stream_states = [state_manager.get_stream_state(stream.name, stream.namespace) for stream in self._streams]
57
56
  return [
58
57
  StreamFacade.create_from_stream(
59
58
  stream,
@@ -69,6 +68,7 @@ class StreamFacadeSource(ConcurrentSourceAdapter):
69
68
  state_converter,
70
69
  self._cursor_field,
71
70
  self._cursor_boundaries,
71
+ None,
72
72
  )
73
73
  if self._cursor_field
74
74
  else NoopCursor(),
@@ -2,21 +2,21 @@
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
- from queue import Queue
6
- from unittest.mock import Mock
5
+ from unittest.mock import Mock, call
7
6
 
8
7
  import pytest
9
8
  from airbyte_cdk.models import SyncMode
10
9
  from airbyte_cdk.sources.concurrent_source.partition_generation_completed_sentinel import PartitionGenerationCompletedSentinel
11
10
  from airbyte_cdk.sources.streams.concurrent.adapters import StreamPartition
12
11
  from airbyte_cdk.sources.streams.concurrent.partition_enqueuer import PartitionEnqueuer
12
+ from airbyte_cdk.sources.streams.concurrent.partitions.throttled_queue import ThrottledQueue
13
13
 
14
14
 
15
15
  @pytest.mark.parametrize(
16
16
  "slices", [pytest.param([], id="test_no_partitions"), pytest.param([{"partition": 1}, {"partition": 2}], id="test_two_partitions")]
17
17
  )
18
18
  def test_partition_generator(slices):
19
- queue = Queue()
19
+ queue = Mock(spec=ThrottledQueue)
20
20
  partition_generator = PartitionEnqueuer(queue)
21
21
 
22
22
  stream = Mock()
@@ -30,10 +30,4 @@ def test_partition_generator(slices):
30
30
 
31
31
  partition_generator.generate_partitions(stream)
32
32
 
33
- actual_partitions = []
34
- while partition := queue.get(False):
35
- if isinstance(partition, PartitionGenerationCompletedSentinel):
36
- break
37
- actual_partitions.append(partition)
38
-
39
- assert actual_partitions == partitions
33
+ assert queue.put.has_calls([call(p) for p in partitions] + [call(PartitionGenerationCompletedSentinel(stream))])
@@ -40,13 +40,11 @@ class TestConcurrentReadProcessor(unittest.TestCase):
40
40
  self._thread_pool_manager = Mock(spec=ThreadPoolManager)
41
41
 
42
42
  self._an_open_partition = Mock(spec=Partition)
43
- self._an_open_partition.is_closed.return_value = False
44
43
  self._log_message = Mock(spec=LogMessage)
45
44
  self._an_open_partition.to_slice.return_value = self._log_message
46
45
  self._an_open_partition.stream_name.return_value = _STREAM_NAME
47
46
 
48
47
  self._a_closed_partition = Mock(spec=Partition)
49
- self._a_closed_partition.is_closed.return_value = True
50
48
  self._a_closed_partition.stream_name.return_value = _ANOTHER_STREAM_NAME
51
49
 
52
50
  self._logger = Mock(spec=logging.Logger)
@@ -76,6 +74,19 @@ class TestConcurrentReadProcessor(unittest.TestCase):
76
74
  self._record.stream_name = _STREAM_NAME
77
75
  self._record.data = self._record_data
78
76
 
77
+ def test_stream_is_not_done_initially(self):
78
+ stream_instances_to_read_from = [self._stream]
79
+ handler = ConcurrentReadProcessor(
80
+ stream_instances_to_read_from,
81
+ self._partition_enqueuer,
82
+ self._thread_pool_manager,
83
+ self._logger,
84
+ self._slice_logger,
85
+ self._message_repository,
86
+ self._partition_reader,
87
+ )
88
+ assert not handler._is_stream_done(self._stream.name)
89
+
79
90
  def test_handle_partition_done_no_other_streams_to_generate_partitions_for(self):
80
91
  stream_instances_to_read_from = [self._stream]
81
92
 
@@ -111,7 +122,6 @@ class TestConcurrentReadProcessor(unittest.TestCase):
111
122
  self._partition_reader,
112
123
  )
113
124
  handler.start_next_partition_generator()
114
- handler.on_partition(self._a_closed_partition)
115
125
 
116
126
  sentinel = PartitionGenerationCompletedSentinel(self._another_stream)
117
127
  messages = handler.on_partition_generation_completed(sentinel)
@@ -147,7 +157,7 @@ class TestConcurrentReadProcessor(unittest.TestCase):
147
157
  handler.on_partition(self._a_closed_partition)
148
158
 
149
159
  self._thread_pool_manager.submit.assert_called_with(self._partition_reader.process_partition, self._a_closed_partition)
150
- assert self._a_closed_partition in handler._streams_to_partitions[_ANOTHER_STREAM_NAME]
160
+ assert self._a_closed_partition in handler._streams_to_running_partitions[_ANOTHER_STREAM_NAME]
151
161
 
152
162
  def test_handle_partition_emits_log_message_if_it_should_be_logged(self):
153
163
  stream_instances_to_read_from = [self._stream]
@@ -169,15 +179,16 @@ class TestConcurrentReadProcessor(unittest.TestCase):
169
179
 
170
180
  self._thread_pool_manager.submit.assert_called_with(self._partition_reader.process_partition, self._an_open_partition)
171
181
  self._message_repository.emit_message.assert_called_with(self._log_message)
172
- assert self._an_open_partition in handler._streams_to_partitions[_STREAM_NAME]
173
182
 
183
+ assert self._an_open_partition in handler._streams_to_running_partitions[_STREAM_NAME]
184
+
185
+ @freezegun.freeze_time("2020-01-01T00:00:00")
174
186
  def test_handle_on_partition_complete_sentinel_with_messages_from_repository(self):
175
187
  stream_instances_to_read_from = [self._stream]
176
188
  partition = Mock(spec=Partition)
177
189
  log_message = Mock(spec=LogMessage)
178
190
  partition.to_slice.return_value = log_message
179
191
  partition.stream_name.return_value = _STREAM_NAME
180
- partition.is_closed.return_value = True
181
192
 
182
193
  handler = ConcurrentReadProcessor(
183
194
  stream_instances_to_read_from,
@@ -189,6 +200,7 @@ class TestConcurrentReadProcessor(unittest.TestCase):
189
200
  self._partition_reader,
190
201
  )
191
202
  handler.start_next_partition_generator()
203
+ handler.on_partition(partition)
192
204
 
193
205
  sentinel = PartitionCompleteSentinel(partition)
194
206
 
@@ -223,6 +235,7 @@ class TestConcurrentReadProcessor(unittest.TestCase):
223
235
  self._partition_reader,
224
236
  )
225
237
  handler.start_next_partition_generator()
238
+ handler.on_partition(self._a_closed_partition)
226
239
  handler.on_partition_generation_completed(PartitionGenerationCompletedSentinel(self._another_stream))
227
240
 
228
241
  sentinel = PartitionCompleteSentinel(self._a_closed_partition)
@@ -254,7 +267,6 @@ class TestConcurrentReadProcessor(unittest.TestCase):
254
267
  log_message = Mock(spec=LogMessage)
255
268
  partition.to_slice.return_value = log_message
256
269
  partition.stream_name.return_value = _STREAM_NAME
257
- partition.is_closed.return_value = True
258
270
 
259
271
  handler = ConcurrentReadProcessor(
260
272
  stream_instances_to_read_from,
@@ -282,7 +294,6 @@ class TestConcurrentReadProcessor(unittest.TestCase):
282
294
  log_message = Mock(spec=LogMessage)
283
295
  partition.to_slice.return_value = log_message
284
296
  partition.stream_name.return_value = _STREAM_NAME
285
- partition.is_closed.return_value = True
286
297
  self._message_repository.consume_queue.return_value = []
287
298
 
288
299
  handler = ConcurrentReadProcessor(
@@ -319,7 +330,6 @@ class TestConcurrentReadProcessor(unittest.TestCase):
319
330
  log_message = Mock(spec=LogMessage)
320
331
  partition.to_slice.return_value = log_message
321
332
  partition.stream_name.return_value = _STREAM_NAME
322
- partition.is_closed.return_value = True
323
333
  slice_logger = Mock(spec=SliceLogger)
324
334
  slice_logger.should_log_slice_message.return_value = True
325
335
  slice_logger.create_slice_log_message.return_value = log_message
@@ -370,7 +380,6 @@ class TestConcurrentReadProcessor(unittest.TestCase):
370
380
  stream_instances_to_read_from = [self._stream]
371
381
  partition = Mock(spec=Partition)
372
382
  partition.stream_name.return_value = _STREAM_NAME
373
- partition.is_closed.return_value = True
374
383
 
375
384
  handler = ConcurrentReadProcessor(
376
385
  stream_instances_to_read_from,
@@ -413,7 +422,6 @@ class TestConcurrentReadProcessor(unittest.TestCase):
413
422
  log_message = Mock(spec=LogMessage)
414
423
  partition.to_slice.return_value = log_message
415
424
  partition.stream_name.return_value = _STREAM_NAME
416
- partition.is_closed.return_value = True
417
425
  self._message_repository.consume_queue.return_value = [
418
426
  AirbyteMessage(type=MessageType.LOG, log=AirbyteLogMessage(level=LogLevel.INFO, message="message emitted from the repository"))
419
427
  ]
@@ -474,7 +482,69 @@ class TestConcurrentReadProcessor(unittest.TestCase):
474
482
  self._message_repository,
475
483
  self._partition_reader,
476
484
  )
477
- handler._streams_to_partitions = {_STREAM_NAME: {self._an_open_partition}, _ANOTHER_STREAM_NAME: {self._a_closed_partition}}
485
+
486
+ handler.start_next_partition_generator()
487
+
488
+ another_stream = Mock(spec=AbstractStream)
489
+ another_stream.name = _STREAM_NAME
490
+ another_stream.as_airbyte_stream.return_value = AirbyteStream(
491
+ name=_ANOTHER_STREAM_NAME,
492
+ json_schema={},
493
+ supported_sync_modes=[SyncMode.full_refresh],
494
+ )
495
+
496
+ exception = RuntimeError("Something went wrong")
497
+
498
+ messages = []
499
+
500
+ with self.assertRaises(RuntimeError):
501
+ for m in handler.on_exception(exception):
502
+ messages.append(m)
503
+
504
+ expected_message = [
505
+ AirbyteMessage(
506
+ type=MessageType.TRACE,
507
+ trace=AirbyteTraceMessage(
508
+ type=TraceType.STREAM_STATUS,
509
+ emitted_at=1577836800000.0,
510
+ stream_status=AirbyteStreamStatusTraceMessage(
511
+ stream_descriptor=StreamDescriptor(name=_STREAM_NAME), status=AirbyteStreamStatus(AirbyteStreamStatus.INCOMPLETE)
512
+ ),
513
+ ),
514
+ ),
515
+ AirbyteMessage(
516
+ type=MessageType.TRACE,
517
+ trace=AirbyteTraceMessage(
518
+ type=TraceType.STREAM_STATUS,
519
+ emitted_at=1577836800000.0,
520
+ stream_status=AirbyteStreamStatusTraceMessage(
521
+ stream_descriptor=StreamDescriptor(name=_ANOTHER_STREAM_NAME), status=AirbyteStreamStatus(AirbyteStreamStatus.INCOMPLETE)
522
+ ),
523
+ ),
524
+ )
525
+ ]
526
+
527
+ assert messages == expected_message
528
+ self._thread_pool_manager.shutdown.assert_called_once()
529
+
530
+ @freezegun.freeze_time("2020-01-01T00:00:00")
531
+ def test_on_exception_does_not_stop_streams_that_are_already_done(self):
532
+ stream_instances_to_read_from = [self._stream, self._another_stream]
533
+
534
+ handler = ConcurrentReadProcessor(
535
+ stream_instances_to_read_from,
536
+ self._partition_enqueuer,
537
+ self._thread_pool_manager,
538
+ self._logger,
539
+ self._slice_logger,
540
+ self._message_repository,
541
+ self._partition_reader,
542
+ )
543
+
544
+ handler.start_next_partition_generator()
545
+ handler.on_partition(self._an_open_partition)
546
+ handler.on_partition_generation_completed(PartitionGenerationCompletedSentinel(self._stream))
547
+ handler.on_partition_generation_completed(PartitionGenerationCompletedSentinel(self._another_stream))
478
548
 
479
549
  another_stream = Mock(spec=AbstractStream)
480
550
  another_stream.name = _STREAM_NAME
@@ -45,33 +45,50 @@ class ConcurrentCursorTest(TestCase):
45
45
  return ConcurrentCursor(
46
46
  _A_STREAM_NAME,
47
47
  _A_STREAM_NAMESPACE,
48
- self._state_converter.get_concurrent_stream_state(CursorField(_A_CURSOR_FIELD_KEY), {}),
48
+ {},
49
49
  self._message_repository,
50
50
  self._state_manager,
51
51
  self._state_converter,
52
52
  CursorField(_A_CURSOR_FIELD_KEY),
53
53
  _SLICE_BOUNDARY_FIELDS,
54
+ None,
54
55
  )
55
56
 
56
57
  def _cursor_without_slice_boundary_fields(self) -> ConcurrentCursor:
57
58
  return ConcurrentCursor(
58
59
  _A_STREAM_NAME,
59
60
  _A_STREAM_NAMESPACE,
60
- self._state_converter.get_concurrent_stream_state(CursorField(_A_CURSOR_FIELD_KEY), {}),
61
+ {},
61
62
  self._message_repository,
62
63
  self._state_manager,
63
64
  self._state_converter,
64
65
  CursorField(_A_CURSOR_FIELD_KEY),
65
66
  None,
67
+ None,
66
68
  )
67
69
 
68
70
  def test_given_boundary_fields_when_close_partition_then_emit_state(self) -> None:
69
- self._cursor_with_slice_boundary_fields().close_partition(
71
+ cursor = self._cursor_with_slice_boundary_fields()
72
+ cursor.close_partition(
70
73
  _partition(
71
74
  {_LOWER_SLICE_BOUNDARY_FIELD: 12, _UPPER_SLICE_BOUNDARY_FIELD: 30},
72
75
  )
73
76
  )
74
77
 
78
+ self._message_repository.emit_message.assert_called_once_with(self._state_manager.create_state_message.return_value)
79
+ self._state_manager.update_state_for_stream.assert_called_once_with(
80
+ _A_STREAM_NAME,
81
+ _A_STREAM_NAMESPACE,
82
+ {_A_CURSOR_FIELD_KEY: 0}, # State message is updated to the legacy format before being emitted
83
+ )
84
+
85
+ def test_given_boundary_fields_when_close_partition_then_emit_updated_state(self) -> None:
86
+ self._cursor_with_slice_boundary_fields().close_partition(
87
+ _partition(
88
+ {_LOWER_SLICE_BOUNDARY_FIELD: 0, _UPPER_SLICE_BOUNDARY_FIELD: 30},
89
+ )
90
+ )
91
+
75
92
  self._message_repository.emit_message.assert_called_once_with(self._state_manager.create_state_message.return_value)
76
93
  self._state_manager.update_state_for_stream.assert_called_once_with(
77
94
  _A_STREAM_NAME,