1
- from typing import ClassVar , List , Tuple
1
+ from typing import ClassVar , List , Tuple , Union , cast
2
2
3
3
import pydantic
4
- from pydantic_core import ErrorDetails , PydanticCustomError , ValidationError
4
+ from pydantic_core import InitErrorDetails , PydanticCustomError , ValidationError
5
5
6
6
from pydantic_async_validation .constants import (
7
7
ASYNC_FIELD_VALIDATOR_CONFIG_KEY ,
10
10
ASYNC_MODEL_VALIDATORS_KEY ,
11
11
)
12
12
from pydantic_async_validation .metaclasses import AsyncValidationModelMetaclass
13
- from pydantic_async_validation .validators import Validator
13
+ from pydantic_async_validation .utils import prefix_errors
14
+ from pydantic_async_validation .validators import ValidationInfo
14
15
15
16
16
17
class AsyncValidationModelMixin (
17
18
pydantic .BaseModel ,
18
19
metaclass = AsyncValidationModelMetaclass ,
19
20
):
20
21
# MUST match names defined in constants.py!
21
- pydantic_model_async_field_validators : ClassVar [List [Tuple [List [str ], Validator ]]]
22
- pydantic_model_async_model_validators : ClassVar [List [Validator ]]
22
+ pydantic_model_async_field_validators : ClassVar [List [Tuple [List [str ], ValidationInfo ]]]
23
+ pydantic_model_async_model_validators : ClassVar [List [ValidationInfo ]]
23
24
24
25
async def model_async_validate (self ) -> None :
25
26
"""
@@ -29,60 +30,99 @@ async def model_async_validate(self) -> None:
29
30
collected and raised as a `ValidationError` exception.
30
31
"""
31
32
field_names : list [str ]
32
- validator : Validator
33
+ field_validator : ValidationInfo
34
+ model_validator : ValidationInfo
33
35
34
36
validation_errors = []
35
- validators = getattr (self , ASYNC_FIELD_VALIDATORS_KEY , [])
36
- root_validators = getattr (self , ASYNC_MODEL_VALIDATORS_KEY , [])
37
+ field_validators = getattr (self , ASYNC_FIELD_VALIDATORS_KEY , [])
38
+ model_validators = getattr (self , ASYNC_MODEL_VALIDATORS_KEY , [])
37
39
38
- for validator_attr in validators :
39
- field_names , validator = getattr (
40
- validator_attr ,
40
+ # Call all field validators
41
+ for field_validator_attr in field_validators :
42
+ field_names , field_validator = getattr (
43
+ field_validator_attr ,
41
44
ASYNC_FIELD_VALIDATOR_CONFIG_KEY ,
42
45
)
43
46
for field_name in field_names :
44
47
try :
45
- await validator .func (
48
+ await field_validator .func (
46
49
self ,
47
50
getattr (self , field_name , None ),
48
51
field_name ,
49
- validator ,
52
+ field_validator ,
50
53
)
51
54
except (ValueError , TypeError , AssertionError ) as o_O :
52
55
validation_errors .append (
53
- ErrorDetails (
54
- type = PydanticCustomError ('value_error' , str (o_O )),
55
- msg = str (o_O ),
56
+ InitErrorDetails (
57
+ type = PydanticCustomError ('value_error' , str (o_O )), # type: ignore
56
58
loc = (field_name ,),
57
59
input = getattr (self , field_name , None ),
58
60
),
59
61
)
60
62
61
- for validator_attr in root_validators :
62
- validator = getattr (
63
- validator_attr ,
63
+ # Call all model validators
64
+ for model_validator_attr in model_validators :
65
+ model_validator = getattr (
66
+ model_validator_attr ,
64
67
ASYNC_MODEL_VALIDATOR_CONFIG_KEY ,
65
68
)
66
69
try :
67
- await validator .func (
70
+ await model_validator .func (
68
71
self ,
69
- validator ,
72
+ model_validator ,
70
73
)
71
74
except (ValueError , TypeError , AssertionError ) as o_O :
72
75
validation_errors .append (
73
- ErrorDetails (
74
- type = PydanticCustomError ('value_error' , str (o_O )),
75
- msg = str (o_O ),
76
+ InitErrorDetails (
77
+ type = PydanticCustomError ('value_error' , str (o_O )), # type: ignore
76
78
loc = ('__root__' ,),
77
79
input = self .__dict__ ,
78
80
),
79
81
)
80
82
81
- # TODO:
82
- # for attribute_name, attribute_value in self.__dict__.items():
83
- # if isinstance(attribute_value, AsyncValidationModelMixin):
84
- # await attribute_value.model_async_validate()
83
+ # Also call async validation on attribute values
84
+ async def extend_with_validation_errors_by (
85
+ prefix : Tuple [Union [int , str ], ...],
86
+ instance : AsyncValidationModelMixin ,
87
+ ) -> None :
88
+ try :
89
+ await instance .model_async_validate ()
90
+ except ValidationError as O_o :
91
+ validation_errors .extend (
92
+ prefix_errors (
93
+ prefix ,
94
+ cast (
95
+ List [InitErrorDetails ],
96
+ O_o .errors (),
97
+ ),
98
+ ),
99
+ )
100
+
101
+ for attribute_name , attribute_value in self .__dict__ .items ():
102
+ # Direct child instance
103
+ if isinstance (attribute_value , AsyncValidationModelMixin ):
104
+ await extend_with_validation_errors_by (
105
+ (attribute_name ,),
106
+ attribute_value ,
107
+ )
108
+ # List of child instances
109
+ if isinstance (attribute_value , list ):
110
+ for index , item in enumerate (attribute_value ):
111
+ if isinstance (item , AsyncValidationModelMixin ):
112
+ await extend_with_validation_errors_by (
113
+ (attribute_name , index ),
114
+ item ,
115
+ )
116
+ # Dict of child instances
117
+ if isinstance (attribute_value , dict ):
118
+ for key , item in attribute_value .items ():
119
+ if isinstance (item , AsyncValidationModelMixin ):
120
+ await extend_with_validation_errors_by (
121
+ (attribute_name , key ),
122
+ item ,
123
+ )
85
124
125
+ # If some errors did occur, raise them as a ValidationError
86
126
if len (validation_errors ) > 0 :
87
127
raise ValidationError .from_exception_data (
88
128
self .__class__ .__name__ ,
0 commit comments