@@ -1358,3 +1358,83 @@ class Model:
13581358
13591359 assert isinstance (validator .validate_python ({'x' : {'foo' : 'foo' }}).x , Foo )
13601360 assert isinstance (validator .validate_python ({'x' : {'bar' : 'bar' }}).x , Bar )
1361+
1362+
1363+ def test_smart_union_wrap_validator_should_not_change_nested_model_field_counts () -> None :
1364+ """Adding a wrap validator on a union member should not affect smart union behavior"""
1365+
1366+ class SubModel :
1367+ x : str = 'x'
1368+
1369+ class ModelA :
1370+ type : str = 'A'
1371+ sub : SubModel
1372+
1373+ class ModelB :
1374+ type : str = 'B'
1375+ sub : SubModel
1376+
1377+ submodel_schema = core_schema .model_schema (
1378+ SubModel ,
1379+ core_schema .model_fields_schema (fields = {'x' : core_schema .model_field (core_schema .str_schema ())}),
1380+ )
1381+
1382+ wrapped_submodel_schema = core_schema .no_info_wrap_validator_function (
1383+ lambda v , handler : handler (v ), submodel_schema
1384+ )
1385+
1386+ model_a_schema = core_schema .model_schema (
1387+ ModelA ,
1388+ core_schema .model_fields_schema (
1389+ fields = {
1390+ 'type' : core_schema .model_field (
1391+ core_schema .with_default_schema (core_schema .literal_schema (['A' ]), default = 'A' ),
1392+ ),
1393+ 'sub' : core_schema .model_field (wrapped_submodel_schema ),
1394+ },
1395+ ),
1396+ )
1397+
1398+ model_b_schema = core_schema .model_schema (
1399+ ModelB ,
1400+ core_schema .model_fields_schema (
1401+ fields = {
1402+ 'type' : core_schema .model_field (
1403+ core_schema .with_default_schema (core_schema .literal_schema (['B' ]), default = 'B' ),
1404+ ),
1405+ 'sub' : core_schema .model_field (submodel_schema ),
1406+ },
1407+ ),
1408+ )
1409+
1410+ for choices in permute_choices ([model_a_schema , model_b_schema ]):
1411+ schema = core_schema .union_schema (choices )
1412+ validator = SchemaValidator (schema )
1413+
1414+ assert isinstance (validator .validate_python ({'type' : 'A' , 'sub' : {'x' : 'x' }}), ModelA )
1415+ assert isinstance (validator .validate_python ({'type' : 'B' , 'sub' : {'x' : 'x' }}), ModelB )
1416+
1417+ # defaults to leftmost choice if there's a tie
1418+ assert isinstance (validator .validate_python ({'sub' : {'x' : 'x' }}), choices [0 ]['cls' ])
1419+
1420+ # test validate_assignment
1421+ class RootModel :
1422+ ab : Union [ModelA , ModelB ]
1423+
1424+ root_model = core_schema .model_schema (
1425+ RootModel ,
1426+ core_schema .model_fields_schema (
1427+ fields = {'ab' : core_schema .model_field (core_schema .union_schema ([model_a_schema , model_b_schema ]))}
1428+ ),
1429+ )
1430+
1431+ validator = SchemaValidator (root_model )
1432+ m = validator .validate_python ({'ab' : {'type' : 'B' , 'sub' : {'x' : 'x' }}})
1433+ assert isinstance (m , RootModel )
1434+ assert isinstance (m .ab , ModelB )
1435+ assert m .ab .sub .x == 'x'
1436+
1437+ m = validator .validate_assignment (m , 'ab' , {'sub' : {'x' : 'y' }})
1438+ assert isinstance (m , RootModel )
1439+ assert isinstance (m .ab , ModelA )
1440+ assert m .ab .sub .x == 'y'
0 commit comments