22import  sys 
33import  weakref 
44from  collections  import  deque 
5+ from  dataclasses  import  dataclass 
56from  typing  import  Any , Callable , Union , cast 
67
78import  pytest 
1617    ValidationError ,
1718    core_schema ,
1819)
20+ from  pydantic_core ._pydantic_core  import  SchemaSerializer 
1921
2022from  ..conftest  import  PyAndJson , assert_gc 
2123
@@ -822,31 +824,46 @@ def _raise(ex: Exception) -> None:
822824    assert  exc_info .value .errors (include_url = False , include_context = False ) ==  expected 
823825
824826
825- def  test_default_factory_not_called_if_existing_error (pydantic_version ) ->  None :
826-     class  Test :
827-         def  __init__ (self , a : int , b : int ):
828-             self .a  =  a 
829-             self .b  =  b 
827+ @pytest .fixture (params = ['model' , 'typed_dict' , 'dataclass' , 'arguments_v3' ]) 
828+ def  container_schema_builder (
829+     request : pytest .FixtureRequest ,
830+ ) ->  Callable [[dict [str , core_schema .CoreSchema ]], core_schema .CoreSchema ]:
831+     if  request .param  ==  'model' :
832+         return  lambda  fields : core_schema .model_schema (
833+             cls = type ('Test' , (), {}),
834+             schema = core_schema .model_fields_schema (
835+                 fields = {k : core_schema .model_field (schema = v ) for  k , v  in  fields .items ()},
836+             ),
837+         )
838+     elif  request .param  ==  'typed_dict' :
839+         return  lambda  fields : core_schema .typed_dict_schema (
840+             fields = {k : core_schema .typed_dict_field (schema = v ) for  k , v  in  fields .items ()}
841+         )
842+     elif  request .param  ==  'dataclass' :
843+         return  lambda  fields : core_schema .dataclass_schema (
844+             cls = dataclass (type ('Test' , (), {})),
845+             schema = core_schema .dataclass_args_schema (
846+                 'Test' ,
847+                 fields = [core_schema .dataclass_field (name = k , schema = v ) for  k , v  in  fields .items ()],
848+             ),
849+             fields = [k  for  k  in  fields .keys ()],
850+         )
851+     elif  request .param  ==  'arguments_v3' :
852+         # TODO: open an issue for this 
853+         raise  pytest .xfail ('arguments v3 does not yet support default_factory_takes_data properly' )
854+     else :
855+         raise  ValueError (f'Unknown container type { request .param }  )
830856
831-     schema  =  core_schema .model_schema (
832-         cls = Test ,
833-         schema = core_schema .model_fields_schema (
834-             computed_fields = [],
835-             fields = {
836-                 'a' : core_schema .model_field (
837-                     schema = core_schema .int_schema (),
838-                 ),
839-                 'b' : core_schema .model_field (
840-                     schema = core_schema .with_default_schema (
841-                         schema = core_schema .int_schema (),
842-                         default_factory = lambda  data : data ['a' ],
843-                         default_factory_takes_data = True ,
844-                     ),
845-                 ),
846-             },
847-         ),
848-     )
849857
858+ def  test_default_factory_not_called_if_existing_error (container_schema_builder , pydantic_version ) ->  None :
859+     schema  =  container_schema_builder (
860+         {
861+             'a' : core_schema .int_schema (),
862+             'b' : core_schema .with_default_schema (
863+                 schema = core_schema .int_schema (), default_factory = lambda  data : data ['a' ], default_factory_takes_data = True 
864+             ),
865+         }
866+     )
850867    v  =  SchemaValidator (schema )
851868    with  pytest .raises (ValidationError ) as  e :
852869        v .validate_python ({'a' : 'not_an_int' })
@@ -868,11 +885,85 @@ def __init__(self, a: int, b: int):
868885
869886    assert  (
870887        str (e .value )
871-         ==  f"""2 validation errors for Test 
888+         ==  f"""2 validation errors for { v .title }  
889+ a 
890+   Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not_an_int', input_type=str] 
891+     For further information visit https://errors.pydantic.dev/{ pydantic_version }  
892+ b 
893+   The default factory uses validated data, but at least one validation error occurred [type=default_factory_not_called] 
894+     For further information visit https://errors.pydantic.dev/{ pydantic_version }  
895+     )
896+ 
897+     # repeat with the first field being a default which validates incorrectly 
898+ 
899+     schema  =  container_schema_builder (
900+         {
901+             'a' : core_schema .with_default_schema (
902+                 schema = core_schema .int_schema (), default = 'not_an_int' , validate_default = True 
903+             ),
904+             'b' : core_schema .with_default_schema (
905+                 schema = core_schema .int_schema (), default_factory = lambda  data : data ['a' ], default_factory_takes_data = True 
906+             ),
907+         }
908+     )
909+     v  =  SchemaValidator (schema )
910+     with  pytest .raises (ValidationError ) as  e :
911+         v .validate_python ({})
912+ 
913+     assert  e .value .errors (include_url = False ) ==  [
914+         {
915+             'type' : 'int_parsing' ,
916+             'loc' : ('a' ,),
917+             'msg' : 'Input should be a valid integer, unable to parse string as an integer' ,
918+             'input' : 'not_an_int' ,
919+         },
920+         {
921+             'input' : PydanticUndefined ,
922+             'loc' : ('b' ,),
923+             'msg' : 'The default factory uses validated data, but at least one validation error occurred' ,
924+             'type' : 'default_factory_not_called' ,
925+         },
926+     ]
927+ 
928+     assert  (
929+         str (e .value )
930+         ==  f"""2 validation errors for { v .title }  
872931a 
873932  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not_an_int', input_type=str] 
874933    For further information visit https://errors.pydantic.dev/{ pydantic_version }  
875934b 
876935  The default factory uses validated data, but at least one validation error occurred [type=default_factory_not_called] 
877936    For further information visit https://errors.pydantic.dev/{ pydantic_version }  
878937    )
938+ 
939+ 
940+ def  test_default_factory_not_called_union_ok (container_schema_builder ) ->  None :
941+     schema_fail  =  container_schema_builder (
942+         {
943+             'a' : core_schema .none_schema (),
944+             'b' : core_schema .with_default_schema (
945+                 schema = core_schema .int_schema (),
946+                 default_factory = lambda  data : data ['a' ],
947+                 default_factory_takes_data = True ,
948+             ),
949+         }
950+     )
951+ 
952+     schema_ok  =  container_schema_builder (
953+         {
954+             'a' : core_schema .int_schema (),
955+             'b' : core_schema .with_default_schema (
956+                 schema = core_schema .int_schema (),
957+                 default_factory = lambda  data : data ['a' ] +  1 ,
958+                 default_factory_takes_data = True ,
959+             ),
960+             # this is used to show that this union member was selected 
961+             'c' : core_schema .with_default_schema (schema = core_schema .int_schema (), default = 3 ),
962+         }
963+     )
964+ 
965+     schema  =  core_schema .union_schema ([schema_fail , schema_ok ])
966+ 
967+     v  =  SchemaValidator (schema )
968+     s  =  SchemaSerializer (schema )
969+     assert  s .to_python (v .validate_python ({'a' : 1 }), mode = 'json' ) ==  {'a' : 1 , 'b' : 2 , 'c' : 3 }
0 commit comments