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

Sign up to get free protection for your applications and to get access to all the features.
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,