@@ -1188,18 +1188,16 @@ async def test_schedule_create_limited_actions_validation(
11881188 assert "are remaining actions set" in str (err .value )
11891189
11901190
1191- async def test_schedule_search_attribute_update (
1191+ async def test_schedule_workflow_search_attribute_update (
11921192 client : Client , env : WorkflowEnvironment
11931193):
11941194 if env .supports_time_skipping :
11951195 pytest .skip ("Java test server doesn't support schedules" )
11961196 await assert_no_schedules (client )
11971197
11981198 # Put search attribute on server
1199- text_attr_key = SearchAttributeKey .for_text (f"python-test-schedule-text" )
1200- untyped_keyword_key = SearchAttributeKey .for_keyword (
1201- f"python-test-schedule-keyword"
1202- )
1199+ text_attr_key = SearchAttributeKey .for_text ("python-test-schedule-text" )
1200+ untyped_keyword_key = SearchAttributeKey .for_keyword ("python-test-schedule-keyword" )
12031201 await ensure_search_attributes_present (client , text_attr_key , untyped_keyword_key )
12041202
12051203 # Create a schedule with search attributes on the schedule and on the
@@ -1273,6 +1271,7 @@ def update_schedule_typed_attrs(
12731271 # Check that it changed
12741272 desc = await handle .describe ()
12751273 assert isinstance (desc .schedule .action , ScheduleActionStartWorkflow )
1274+ # Check that the workflow search attributes were changed
12761275 # This assertion has changed since server 1.24. Now, even untyped search
12771276 # attributes are given a type server side
12781277 assert (
@@ -1283,6 +1282,148 @@ def update_schedule_typed_attrs(
12831282 and desc .schedule .action .typed_search_attributes [untyped_keyword_key ]
12841283 == "some-untyped-attr1"
12851284 )
1285+ # Check that the schedule search attributes were not changed
1286+ assert desc .search_attributes [text_attr_key .name ] == ["some-schedule-attr1" ]
1287+ assert desc .typed_search_attributes [text_attr_key ] == "some-schedule-attr1"
1288+
1289+ await handle .delete ()
1290+ await assert_no_schedules (client )
1291+
1292+
1293+ @pytest .mark .parametrize (
1294+ "test_case" ,
1295+ [
1296+ "none-is-noop" ,
1297+ "empty-but-non-none-clears" ,
1298+ "all-new-values-overwrites" ,
1299+ "partial-new-values-overwrites-and-drops" ,
1300+ ],
1301+ )
1302+ async def test_schedule_search_attribute_update (
1303+ client : Client , env : WorkflowEnvironment , test_case : str
1304+ ):
1305+ if env .supports_time_skipping :
1306+ pytest .skip ("Java test server doesn't support schedules" )
1307+ await assert_no_schedules (client )
1308+
1309+ # Put search attributes on server
1310+ key_1 = SearchAttributeKey .for_text ("python-test-schedule-sa-update-key-1" )
1311+ key_2 = SearchAttributeKey .for_keyword ("python-test-schedule-sa-update-key-2" )
1312+ await ensure_search_attributes_present (client , key_1 , key_2 )
1313+ val_1 = "val-1"
1314+ val_2 = "val-2"
1315+
1316+ # Create a schedule with search attributes
1317+ create_action = ScheduleActionStartWorkflow (
1318+ "some workflow" ,
1319+ [],
1320+ id = f"workflow-{ uuid .uuid4 ()} " ,
1321+ task_queue = f"tq-{ uuid .uuid4 ()} " ,
1322+ )
1323+ handle = await client .create_schedule (
1324+ f"schedule-{ uuid .uuid4 ()} " ,
1325+ Schedule (action = create_action , spec = ScheduleSpec ()),
1326+ search_attributes = TypedSearchAttributes (
1327+ [
1328+ SearchAttributePair (key_1 , val_1 ),
1329+ SearchAttributePair (key_2 , val_2 ),
1330+ ]
1331+ ),
1332+ )
1333+
1334+ def update_search_attributes (
1335+ input : ScheduleUpdateInput ,
1336+ ) -> Optional [ScheduleUpdate ]:
1337+ # Make sure the initial search attributes are present
1338+ assert input .description .search_attributes [key_1 .name ] == [val_1 ]
1339+ assert input .description .search_attributes [key_2 .name ] == [val_2 ]
1340+ assert input .description .typed_search_attributes [key_1 ] == val_1
1341+ assert input .description .typed_search_attributes [key_2 ] == val_2
1342+
1343+ if test_case == "none-is-noop" :
1344+ # Passing None makes no changes
1345+ return ScheduleUpdate (input .description .schedule , search_attributes = None )
1346+ elif test_case == "empty-but-non-none-clears" :
1347+ # Pass empty but non-None to clear all attributes
1348+ return ScheduleUpdate (
1349+ input .description .schedule ,
1350+ search_attributes = TypedSearchAttributes .empty ,
1351+ )
1352+ elif test_case == "all-new-values-overwrites" :
1353+ # Pass all new values to overwrite existing
1354+ return ScheduleUpdate (
1355+ input .description .schedule ,
1356+ search_attributes = input .description .typed_search_attributes .updated (
1357+ SearchAttributePair (key_1 , val_1 + "-new" ),
1358+ SearchAttributePair (key_2 , val_2 + "-new" ),
1359+ ),
1360+ )
1361+ elif test_case == "partial-new-values-overwrites-and-drops" :
1362+ # Only update key_1, which should drop key_2
1363+ return ScheduleUpdate (
1364+ input .description .schedule ,
1365+ search_attributes = TypedSearchAttributes (
1366+ [
1367+ SearchAttributePair (key_1 , val_1 + "-new" ),
1368+ ]
1369+ ),
1370+ )
1371+ else :
1372+ raise ValueError (f"Invalid test case: { test_case } " )
1373+
1374+ await handle .update (update_search_attributes )
1375+
1376+ if test_case == "none-is-noop" :
1377+
1378+ async def expectation () -> bool :
1379+ desc = await handle .describe ()
1380+ return (
1381+ desc .search_attributes [key_1 .name ] == [val_1 ]
1382+ and desc .search_attributes [key_2 .name ] == [val_2 ]
1383+ and desc .typed_search_attributes [key_1 ] == val_1
1384+ and desc .typed_search_attributes [key_2 ] == val_2
1385+ )
1386+
1387+ await assert_eq_eventually (True , expectation )
1388+ elif test_case == "empty-but-non-none-clears" :
1389+
1390+ async def expectation () -> bool :
1391+ desc = await handle .describe ()
1392+ return (
1393+ len (desc .typed_search_attributes ) == 0
1394+ and len (desc .search_attributes ) == 0
1395+ )
1396+
1397+ await assert_eq_eventually (True , expectation )
1398+ elif test_case == "all-new-values-overwrites" :
1399+
1400+ async def expectation () -> bool :
1401+ desc = await handle .describe ()
1402+ return (
1403+ desc .search_attributes [key_1 .name ] == [val_1 + "-new" ]
1404+ and desc .search_attributes [key_2 .name ] == [val_2 + "-new" ]
1405+ and desc .typed_search_attributes [key_1 ] == val_1 + "-new"
1406+ and desc .typed_search_attributes [key_2 ] == val_2 + "-new"
1407+ )
1408+
1409+ await assert_eq_eventually (True , expectation )
1410+ elif test_case == "partial-new-values-overwrites-and-drops" :
1411+
1412+ async def expectation () -> bool :
1413+ desc = await handle .describe ()
1414+ return (
1415+ desc .search_attributes [key_1 .name ] == [val_1 + "-new" ]
1416+ and desc .typed_search_attributes [key_1 ] == val_1 + "-new"
1417+ and key_2 .name not in desc .search_attributes
1418+ and key_2 not in desc .typed_search_attributes
1419+ )
1420+
1421+ await assert_eq_eventually (True , expectation )
1422+ else :
1423+ raise ValueError (f"Invalid test case: { test_case } " )
1424+
1425+ await handle .delete ()
1426+ await assert_no_schedules (client )
12861427
12871428
12881429async def assert_no_schedules (client : Client ) -> None :
0 commit comments