2
2
import sys
3
3
import weakref
4
4
from collections import deque
5
+ from dataclasses import dataclass
5
6
from typing import Any , Callable , Union , cast
6
7
7
8
import pytest
16
17
ValidationError ,
17
18
core_schema ,
18
19
)
20
+ from pydantic_core ._pydantic_core import SchemaSerializer
19
21
20
22
from ..conftest import PyAndJson , assert_gc
21
23
@@ -822,31 +824,46 @@ def _raise(ex: Exception) -> None:
822
824
assert exc_info .value .errors (include_url = False , include_context = False ) == expected
823
825
824
826
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 } ' )
830
856
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
- )
849
857
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
+ )
850
867
v = SchemaValidator (schema )
851
868
with pytest .raises (ValidationError ) as e :
852
869
v .validate_python ({'a' : 'not_an_int' })
@@ -868,11 +885,85 @@ def __init__(self, a: int, b: int):
868
885
869
886
assert (
870
887
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 }
872
931
a
873
932
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not_an_int', input_type=str]
874
933
For further information visit https://errors.pydantic.dev/{ pydantic_version } /v/int_parsing
875
934
b
876
935
The default factory uses validated data, but at least one validation error occurred [type=default_factory_not_called]
877
936
For further information visit https://errors.pydantic.dev/{ pydantic_version } /v/default_factory_not_called"""
878
937
)
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