44from unittest import TestCase
55from unittest .mock import Mock
66
7- # This allows for the global total_record_counter to be reset between tests
8- import airbyte_cdk .sources .declarative .stream_slicers .declarative_partition_generator as declarative_partition_generator
97from airbyte_cdk .models import AirbyteLogMessage , AirbyteMessage , Level , Type
108from airbyte_cdk .sources .declarative .retrievers import Retriever
119from airbyte_cdk .sources .declarative .schema import InlineSchemaLoader
@@ -35,7 +33,7 @@ class StreamSlicerPartitionGeneratorTest(TestCase):
3533 def test_given_multiple_slices_partition_generator_uses_the_same_retriever (self ) -> None :
3634 retriever = self ._mock_retriever ([])
3735 message_repository = Mock (spec = MessageRepository )
38- partition_factory = declarative_partition_generator . DeclarativePartitionFactory (
36+ partition_factory = DeclarativePartitionFactory (
3937 _STREAM_NAME ,
4038 _SCHEMA_LOADER ,
4139 retriever ,
@@ -50,7 +48,7 @@ def test_given_multiple_slices_partition_generator_uses_the_same_retriever(self)
5048 def test_given_a_mapping_when_read_then_yield_record (self ) -> None :
5149 retriever = self ._mock_retriever ([_A_RECORD ])
5250 message_repository = Mock (spec = MessageRepository )
53- partition_factory = declarative_partition_generator . DeclarativePartitionFactory (
51+ partition_factory = DeclarativePartitionFactory (
5452 _STREAM_NAME ,
5553 _SCHEMA_LOADER ,
5654 retriever ,
@@ -68,7 +66,7 @@ def test_given_a_mapping_when_read_then_yield_record(self) -> None:
6866 def test_given_not_a_record_when_read_then_send_to_message_repository (self ) -> None :
6967 retriever = self ._mock_retriever ([_AIRBYTE_LOG_MESSAGE ])
7068 message_repository = Mock (spec = MessageRepository )
71- partition_factory = declarative_partition_generator . DeclarativePartitionFactory (
69+ partition_factory = DeclarativePartitionFactory (
7270 _STREAM_NAME ,
7371 _SCHEMA_LOADER ,
7472 retriever ,
@@ -80,8 +78,6 @@ def test_given_not_a_record_when_read_then_send_to_message_repository(self) -> N
8078 message_repository .emit_message .assert_called_once_with (_AIRBYTE_LOG_MESSAGE )
8179
8280 def test_max_records_reached_stops_reading (self ) -> None :
83- declarative_partition_generator .total_record_counter = 0
84-
8581 expected_records = [
8682 Record (data = {"id" : 1 , "name" : "Max" }, stream_name = "stream_name" ),
8783 Record (data = {"id" : 1 , "name" : "Oscar" }, stream_name = "stream_name" ),
@@ -97,7 +93,7 @@ def test_max_records_reached_stops_reading(self) -> None:
9793
9894 retriever = self ._mock_retriever (mock_records )
9995 message_repository = Mock (spec = MessageRepository )
100- partition_factory = declarative_partition_generator . DeclarativePartitionFactory (
96+ partition_factory = DeclarativePartitionFactory (
10197 _STREAM_NAME ,
10298 _SCHEMA_LOADER ,
10399 retriever ,
@@ -113,8 +109,6 @@ def test_max_records_reached_stops_reading(self) -> None:
113109 assert actual_records == expected_records
114110
115111 def test_max_records_reached_on_previous_partition (self ) -> None :
116- declarative_partition_generator .total_record_counter = 0
117-
118112 expected_records = [
119113 Record (data = {"id" : 1 , "name" : "Max" }, stream_name = "stream_name" ),
120114 Record (data = {"id" : 1 , "name" : "Oscar" }, stream_name = "stream_name" ),
@@ -128,7 +122,7 @@ def test_max_records_reached_on_previous_partition(self) -> None:
128122
129123 retriever = self ._mock_retriever (mock_records )
130124 message_repository = Mock (spec = MessageRepository )
131- partition_factory = declarative_partition_generator . DeclarativePartitionFactory (
125+ partition_factory = DeclarativePartitionFactory (
132126 _STREAM_NAME ,
133127 _SCHEMA_LOADER ,
134128 retriever ,
@@ -151,6 +145,55 @@ def test_max_records_reached_on_previous_partition(self) -> None:
151145 # called for the first partition read and not the second
152146 retriever .read_records .assert_called_once ()
153147
148+ def test_record_counter_isolation_between_different_factories (self ) -> None :
149+ """Test that record counters are isolated between different DeclarativePartitionFactory instances."""
150+
151+ # Create mock records that exceed the limit
152+ records = [
153+ Record (data = {"id" : 1 , "name" : "Record1" }, stream_name = "stream_name" ),
154+ Record (data = {"id" : 2 , "name" : "Record2" }, stream_name = "stream_name" ),
155+ Record (
156+ data = {"id" : 3 , "name" : "Record3" }, stream_name = "stream_name"
157+ ), # Should be blocked by limit
158+ ]
159+
160+ # Create first factory with record limit of 2
161+ retriever1 = self ._mock_retriever (records )
162+ message_repository1 = Mock (spec = MessageRepository )
163+ factory1 = DeclarativePartitionFactory (
164+ _STREAM_NAME ,
165+ _SCHEMA_LOADER ,
166+ retriever1 ,
167+ message_repository1 ,
168+ max_records_limit = 2 ,
169+ )
170+
171+ # First factory should read up to limit (2 records)
172+ partition1 = factory1 .create (_A_STREAM_SLICE )
173+ first_factory_records = list (partition1 .read ())
174+ assert len (first_factory_records ) == 2
175+
176+ # Create second factory with same limit - should be independent
177+ retriever2 = self ._mock_retriever (records )
178+ message_repository2 = Mock (spec = MessageRepository )
179+ factory2 = DeclarativePartitionFactory (
180+ _STREAM_NAME ,
181+ _SCHEMA_LOADER ,
182+ retriever2 ,
183+ message_repository2 ,
184+ max_records_limit = 2 ,
185+ )
186+
187+ # Second factory should also be able to read up to limit (2 records)
188+ # This would fail before the fix because record counter was global
189+ partition2 = factory2 .create (_A_STREAM_SLICE )
190+ second_factory_records = list (partition2 .read ())
191+ assert len (second_factory_records ) == 2
192+
193+ # Verify both retrievers were called (confirming isolation)
194+ retriever1 .read_records .assert_called_once ()
195+ retriever2 .read_records .assert_called_once ()
196+
154197 @staticmethod
155198 def _mock_retriever (read_return_value : List [StreamData ]) -> Mock :
156199 retriever = Mock (spec = Retriever )
0 commit comments