Coverage for pydantic/_internal/_decorators_v1.py: 95.00%

56 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-01 08:13 +0000

1"""Logic for V1 validators, e.g. `@validator` and `@root_validator`.""" 

2 

3from __future__ import annotations as _annotations 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

4 

5from inspect import Parameter, signature 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

6from typing import Any, Union, cast 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

7 

8from pydantic_core import core_schema 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

9from typing_extensions import Protocol 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

10 

11from ..errors import PydanticUserError 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

12from ._utils import can_be_positional 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

13 

14 

15class V1OnlyValueValidator(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

16 """A simple validator, supported for V1 validators and V2 validators.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

17 

18 def __call__(self, __value: Any) -> Any: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

19 

20 

21class V1ValidatorWithValues(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

22 """A validator with `values` argument, supported for V1 validators and V2 validators.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

23 

24 def __call__(self, __value: Any, values: dict[str, Any]) -> Any: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

25 

26 

27class V1ValidatorWithValuesKwOnly(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

28 """A validator with keyword only `values` argument, supported for V1 validators and V2 validators.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

29 

30 def __call__(self, __value: Any, *, values: dict[str, Any]) -> Any: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

31 

32 

33class V1ValidatorWithKwargs(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

34 """A validator with `kwargs` argument, supported for V1 validators and V2 validators.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

35 

36 def __call__(self, __value: Any, **kwargs: Any) -> Any: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

37 

38 

39class V1ValidatorWithValuesAndKwargs(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

40 """A validator with `values` and `kwargs` arguments, supported for V1 validators and V2 validators.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

41 

42 def __call__(self, __value: Any, values: dict[str, Any], **kwargs: Any) -> Any: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

43 

44 

45V1Validator = Union[ 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

46 V1ValidatorWithValues, V1ValidatorWithValuesKwOnly, V1ValidatorWithKwargs, V1ValidatorWithValuesAndKwargs 

47] 

48 

49 

50def can_be_keyword(param: Parameter) -> bool: 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

51 return param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

52 

53 

54def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.WithInfoValidatorFunction: 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

55 """Wrap a V1 style field validator for V2 compatibility. 

56 

57 Args: 

58 validator: The V1 style field validator. 

59 

60 Returns: 

61 A wrapped V2 style field validator. 

62 

63 Raises: 

64 PydanticUserError: If the signature is not supported or the parameters are 

65 not available in Pydantic V2. 

66 """ 

67 sig = signature(validator) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

68 

69 needs_values_kw = False 1rstuvwabcdefgxyzABChijklDEFGHImnopq

70 

71 for param_num, (param_name, parameter) in enumerate(sig.parameters.items()): 1rstuvwabcdefgxyzABChijklDEFGHImnopq

72 if can_be_keyword(parameter) and param_name in ('field', 'config'): 1rstuvwabcdefgxyzABChijklDEFGHImnopq

73 raise PydanticUserError( 1rstuvwabcdefgxyzABChijklDEFGHImnopq

74 'The `field` and `config` parameters are not available in Pydantic V2, ' 

75 'please use the `info` parameter instead.', 

76 code='validator-field-config-info', 

77 ) 

78 if parameter.kind is Parameter.VAR_KEYWORD: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

79 needs_values_kw = True 1rstuvwabcdefgxyzABChijklDEFGHImnopq

80 elif can_be_keyword(parameter) and param_name == 'values': 1rstuvwabcdefgxyzABChijklDEFGHImnopq

81 needs_values_kw = True 1rstuvwabcdefgxyzABChijklDEFGHImnopq

82 elif can_be_positional(parameter) and param_num == 0: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

83 # value 

84 continue 1rstuvwabcdefgxyzABChijklDEFGHImnopq

85 elif parameter.default is Parameter.empty: # ignore params with defaults e.g. bound by functools.partial 1rstuvwabcdefgxyzABChijklDEFGHImnopq

86 raise PydanticUserError( 1rstuvwabcdefgxyzABChijklDEFGHImnopq

87 f'Unsupported signature for V1 style validator {validator}: {sig} is not supported.', 

88 code='validator-v1-signature', 

89 ) 

90 

91 if needs_values_kw: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

92 # (v, **kwargs), (v, values, **kwargs), (v, *, values, **kwargs) or (v, *, values) 

93 val1 = cast(V1ValidatorWithValues, validator) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

94 

95 def wrapper1(value: Any, info: core_schema.ValidationInfo) -> Any: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

96 return val1(value, values=info.data) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

97 

98 return wrapper1 1rstuvwabcdefgxyzABChijklDEFGHImnopq

99 else: 

100 val2 = cast(V1OnlyValueValidator, validator) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

101 

102 def wrapper2(value: Any, _: core_schema.ValidationInfo) -> Any: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

103 return val2(value) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

104 

105 return wrapper2 1rstuvwabcdefgxyzABChijklDEFGHImnopq

106 

107 

108RootValidatorValues = dict[str, Any] 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

109# technically tuple[model_dict, model_extra, fields_set] | tuple[dataclass_dict, init_vars] 

110RootValidatorFieldsTuple = tuple[Any, ...] 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

111 

112 

113class V1RootValidatorFunction(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

114 """A simple root validator, supported for V1 validators and V2 validators.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

115 

116 def __call__(self, __values: RootValidatorValues) -> RootValidatorValues: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

117 

118 

119class V2CoreBeforeRootValidator(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

120 """V2 validator with mode='before'.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

121 

122 def __call__(self, __values: RootValidatorValues, __info: core_schema.ValidationInfo) -> RootValidatorValues: ... 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

123 

124 

125class V2CoreAfterRootValidator(Protocol): 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

126 """V2 validator with mode='after'.""" 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

127 

128 def __call__( 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

129 self, __fields_tuple: RootValidatorFieldsTuple, __info: core_schema.ValidationInfo 1abcdefghijklJKLMmnopq

130 ) -> RootValidatorFieldsTuple: ... 1abcdefghijklJKLMmnopq

131 

132 

133def make_v1_generic_root_validator( 1rstuvwabcdefgxyzABChijklNOPJKLMDEFGHImnopq

134 validator: V1RootValidatorFunction, pre: bool 

135) -> V2CoreBeforeRootValidator | V2CoreAfterRootValidator: 

136 """Wrap a V1 style root validator for V2 compatibility. 

137 

138 Args: 

139 validator: The V1 style field validator. 

140 pre: Whether the validator is a pre validator. 

141 

142 Returns: 

143 A wrapped V2 style validator. 

144 """ 

145 if pre is True: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

146 # mode='before' for pydantic-core 

147 def _wrapper1(values: RootValidatorValues, _: core_schema.ValidationInfo) -> RootValidatorValues: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

148 return validator(values) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

149 

150 return _wrapper1 1rstuvwabcdefgxyzABChijklDEFGHImnopq

151 

152 # mode='after' for pydantic-core 

153 def _wrapper2(fields_tuple: RootValidatorFieldsTuple, _: core_schema.ValidationInfo) -> RootValidatorFieldsTuple: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

154 if len(fields_tuple) == 2: 154 ↛ 156line 154 didn't jump to line 156 because the condition on line 154 was never true1rstuvwabcdefgxyzABChijklDEFGHImnopq

155 # dataclass, this is easy 

156 values, init_vars = fields_tuple 

157 values = validator(values) 

158 return values, init_vars 

159 else: 

160 # ugly hack: to match v1 behaviour, we merge values and model_extra, then split them up based on fields 

161 # afterwards 

162 model_dict, model_extra, fields_set = fields_tuple 1rstuvwabcdefgxyzABChijklDEFGHImnopq

163 if model_extra: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

164 fields = set(model_dict.keys()) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

165 model_dict.update(model_extra) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

166 model_dict_new = validator(model_dict) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

167 for k in list(model_dict_new.keys()): 1rstuvwabcdefgxyzABChijklDEFGHImnopq

168 if k not in fields: 1rstuvwabcdefgxyzABChijklDEFGHImnopq

169 model_extra[k] = model_dict_new.pop(k) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

170 else: 

171 model_dict_new = validator(model_dict) 1rstuvwabcdefgxyzABChijklDEFGHImnopq

172 return model_dict_new, model_extra, fields_set 1rstuvwabcdefgxyzABChijklDEFGHImnopq

173 

174 return _wrapper2 1rstuvwabcdefgxyzABChijklDEFGHImnopq