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 } /v/int_parsing
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 } /v/default_factory_not_called"""
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 } /v/int_parsing
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 } /v/default_factory_not_called"""
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