@@ -3281,6 +3281,126 @@ def test_create_concurrent_cursor_from_datetime_based_cursor(
32813281 assert getattr (concurrent_cursor , assertion_field ) == expected_value
32823282
32833283
3284+ def test_create_concurrent_cursor_from_datetime_based_cursor_runs_state_migrations ():
3285+ class DummyStateMigration :
3286+ def should_migrate (self , stream_state : Mapping [str , Any ]) -> bool :
3287+ return True
3288+
3289+ def migrate (self , stream_state : Mapping [str , Any ]) -> Mapping [str , Any ]:
3290+ updated_at = stream_state ["updated_at" ]
3291+ return {
3292+ "states" : [
3293+ {
3294+ "partition" : {"type" : "type_1" },
3295+ "cursor" : {"updated_at" : updated_at },
3296+ },
3297+ {
3298+ "partition" : {"type" : "type_2" },
3299+ "cursor" : {"updated_at" : updated_at },
3300+ },
3301+ ]
3302+ }
3303+
3304+ stream_name = "test"
3305+ config = {
3306+ "start_time" : "2024-08-01T00:00:00.000000Z" ,
3307+ "end_time" : "2024-09-01T00:00:00.000000Z" ,
3308+ }
3309+ stream_state = {"updated_at" : "2025-01-01T00:00:00.000000Z" }
3310+ connector_builder_factory = ModelToComponentFactory (emit_connector_builder_messages = True )
3311+ connector_state_manager = ConnectorStateManager ()
3312+ cursor_component_definition = {
3313+ "type" : "DatetimeBasedCursor" ,
3314+ "cursor_field" : "updated_at" ,
3315+ "datetime_format" : "%Y-%m-%dT%H:%M:%S.%fZ" ,
3316+ "start_datetime" : "{{ config['start_time'] }}" ,
3317+ "end_datetime" : "{{ config['end_time'] }}" ,
3318+ "partition_field_start" : "custom_start" ,
3319+ "partition_field_end" : "custom_end" ,
3320+ "step" : "P10D" ,
3321+ "cursor_granularity" : "PT0.000001S" ,
3322+ "lookback_window" : "P3D" ,
3323+ }
3324+ concurrent_cursor = (
3325+ connector_builder_factory .create_concurrent_cursor_from_datetime_based_cursor (
3326+ state_manager = connector_state_manager ,
3327+ model_type = DatetimeBasedCursorModel ,
3328+ component_definition = cursor_component_definition ,
3329+ stream_name = stream_name ,
3330+ stream_namespace = None ,
3331+ config = config ,
3332+ stream_state = stream_state ,
3333+ stream_state_migrations = [DummyStateMigration ()],
3334+ )
3335+ )
3336+ assert concurrent_cursor .state ["states" ] == [
3337+ {"cursor" : {"updated_at" : stream_state ["updated_at" ]}, "partition" : {"type" : "type_1" }},
3338+ {"cursor" : {"updated_at" : stream_state ["updated_at" ]}, "partition" : {"type" : "type_2" }},
3339+ ]
3340+
3341+
3342+ def test_create_concurrent_cursor_from_perpartition_cursor_runs_state_migrations ():
3343+ class DummyStateMigration :
3344+ def should_migrate (self , stream_state : Mapping [str , Any ]) -> bool :
3345+ return True
3346+
3347+ def migrate (self , stream_state : Mapping [str , Any ]) -> Mapping [str , Any ]:
3348+ stream_state ["lookback_window" ] = 10 * 2
3349+ return stream_state
3350+
3351+ state = {
3352+ "states" : [
3353+ {
3354+ "partition" : {"type" : "typ_1" },
3355+ "cursor" : {"updated_at" : "2024-08-01T00:00:00.000000Z" },
3356+ }
3357+ ],
3358+ "state" : {"updated_at" : "2024-08-01T00:00:00.000000Z" },
3359+ "lookback_window" : 10 ,
3360+ "parent_state" : {"parent_test" : {"last_updated" : "2024-08-01T00:00:00.000000Z" }},
3361+ }
3362+ config = {
3363+ "start_time" : "2024-08-01T00:00:00.000000Z" ,
3364+ "end_time" : "2024-09-01T00:00:00.000000Z" ,
3365+ }
3366+ list_partition_router = ListPartitionRouter (
3367+ cursor_field = "id" ,
3368+ values = ["type_1" , "type_2" , "type_3" ],
3369+ config = config ,
3370+ parameters = {},
3371+ )
3372+ connector_state_manager = ConnectorStateManager ()
3373+ stream_name = "test"
3374+ cursor_component_definition = {
3375+ "type" : "DatetimeBasedCursor" ,
3376+ "cursor_field" : "updated_at" ,
3377+ "datetime_format" : "%Y-%m-%dT%H:%M:%S.%fZ" ,
3378+ "start_datetime" : "{{ config['start_time'] }}" ,
3379+ "end_datetime" : "{{ config['end_time'] }}" ,
3380+ "partition_field_start" : "custom_start" ,
3381+ "partition_field_end" : "custom_end" ,
3382+ "step" : "P10D" ,
3383+ "cursor_granularity" : "PT0.000001S" ,
3384+ "lookback_window" : "P3D" ,
3385+ }
3386+ connector_builder_factory = ModelToComponentFactory (emit_connector_builder_messages = True )
3387+ cursor = connector_builder_factory .create_concurrent_cursor_from_perpartition_cursor (
3388+ state_manager = connector_state_manager ,
3389+ model_type = DatetimeBasedCursorModel ,
3390+ component_definition = cursor_component_definition ,
3391+ stream_name = stream_name ,
3392+ stream_namespace = None ,
3393+ config = config ,
3394+ stream_state = state ,
3395+ partition_router = list_partition_router ,
3396+ stream_state_migrations = [DummyStateMigration ()],
3397+ )
3398+ assert cursor .state ["lookback_window" ] != 10 , "State migration wasn't called"
3399+ assert (
3400+ cursor .state ["lookback_window" ] == 20
3401+ ), "State migration was called, but actual state don't match expected"
3402+
3403+
32843404def test_create_concurrent_cursor_uses_min_max_datetime_format_if_defined ():
32853405 """
32863406 Validates a special case for when the start_time.datetime_format and end_time.datetime_format are defined, the date to
0 commit comments