@@ -3281,6 +3281,126 @@ def test_create_concurrent_cursor_from_datetime_based_cursor(
3281
3281
assert getattr (concurrent_cursor , assertion_field ) == expected_value
3282
3282
3283
3283
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
+
3284
3404
def test_create_concurrent_cursor_uses_min_max_datetime_format_if_defined ():
3285
3405
"""
3286
3406
Validates a special case for when the start_time.datetime_format and end_time.datetime_format are defined, the date to
0 commit comments