Coverage for pydantic/_internal/_decorators_v1.py: 95.00%
56 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
1"""Logic for V1 validators, e.g. `@validator` and `@root_validator`."""
3from __future__ import annotations as _annotations 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
5from inspect import Parameter, signature 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
6from typing import Any, Dict, Tuple, Union, cast 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
8from pydantic_core import core_schema 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
9from typing_extensions import Protocol 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
11from ..errors import PydanticUserError 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
12from ._decorators import can_be_positional 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
15class V1OnlyValueValidator(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
16 """A simple validator, supported for V1 validators and V2 validators.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
18 def __call__(self, __value: Any) -> Any: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
21class V1ValidatorWithValues(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
22 """A validator with `values` argument, supported for V1 validators and V2 validators.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
24 def __call__(self, __value: Any, values: dict[str, Any]) -> Any: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
27class V1ValidatorWithValuesKwOnly(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
28 """A validator with keyword only `values` argument, supported for V1 validators and V2 validators.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
30 def __call__(self, __value: Any, *, values: dict[str, Any]) -> Any: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
33class V1ValidatorWithKwargs(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
34 """A validator with `kwargs` argument, supported for V1 validators and V2 validators.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
36 def __call__(self, __value: Any, **kwargs: Any) -> Any: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
39class V1ValidatorWithValuesAndKwargs(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
40 """A validator with `values` and `kwargs` arguments, supported for V1 validators and V2 validators.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
42 def __call__(self, __value: Any, values: dict[str, Any], **kwargs: Any) -> Any: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
45V1Validator = Union[ 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
46 V1ValidatorWithValues, V1ValidatorWithValuesKwOnly, V1ValidatorWithKwargs, V1ValidatorWithValuesAndKwargs
47]
50def can_be_keyword(param: Parameter) -> bool: 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
51 return param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
54def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.WithInfoValidatorFunction: 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
55 """Wrap a V1 style field validator for V2 compatibility.
57 Args:
58 validator: The V1 style field validator.
60 Returns:
61 A wrapped V2 style field validator.
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) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
69 needs_values_kw = False 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
71 for param_num, (param_name, parameter) in enumerate(sig.parameters.items()): 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
72 if can_be_keyword(parameter) and param_name in ('field', 'config'): 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
73 raise PydanticUserError( 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
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: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
79 needs_values_kw = True 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
80 elif can_be_keyword(parameter) and param_name == 'values': 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
81 needs_values_kw = True 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
82 elif can_be_positional(parameter) and param_num == 0: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
83 # value
84 continue 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
85 elif parameter.default is Parameter.empty: # ignore params with defaults e.g. bound by functools.partial 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
86 raise PydanticUserError( 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
87 f'Unsupported signature for V1 style validator {validator}: {sig} is not supported.',
88 code='validator-v1-signature',
89 )
91 if needs_values_kw: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
92 # (v, **kwargs), (v, values, **kwargs), (v, *, values, **kwargs) or (v, *, values)
93 val1 = cast(V1ValidatorWithValues, validator) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
95 def wrapper1(value: Any, info: core_schema.ValidationInfo) -> Any: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
96 return val1(value, values=info.data) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
98 return wrapper1 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
99 else:
100 val2 = cast(V1OnlyValueValidator, validator) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
102 def wrapper2(value: Any, _: core_schema.ValidationInfo) -> Any: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
103 return val2(value) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
105 return wrapper2 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
108RootValidatorValues = Dict[str, Any] 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
109# technically tuple[model_dict, model_extra, fields_set] | tuple[dataclass_dict, init_vars]
110RootValidatorFieldsTuple = Tuple[Any, ...] 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
113class V1RootValidatorFunction(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
114 """A simple root validator, supported for V1 validators and V2 validators.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
116 def __call__(self, __values: RootValidatorValues) -> RootValidatorValues: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
119class V2CoreBeforeRootValidator(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
120 """V2 validator with mode='before'.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
122 def __call__(self, __values: RootValidatorValues, __info: core_schema.ValidationInfo) -> RootValidatorValues: ... 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
125class V2CoreAfterRootValidator(Protocol): 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
126 """V2 validator with mode='after'.""" 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
128 def __call__( 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
129 self, __fields_tuple: RootValidatorFieldsTuple, __info: core_schema.ValidationInfo 1abcdefghijMNklmn
130 ) -> RootValidatorFieldsTuple: ... 1abcdefghijMNklmn
133def make_v1_generic_root_validator( 1opqrstuvabcdefwxyzABCDghijOPQRSTUVMNEFGHIJKLklmn
134 validator: V1RootValidatorFunction, pre: bool
135) -> V2CoreBeforeRootValidator | V2CoreAfterRootValidator:
136 """Wrap a V1 style root validator for V2 compatibility.
138 Args:
139 validator: The V1 style field validator.
140 pre: Whether the validator is a pre validator.
142 Returns:
143 A wrapped V2 style validator.
144 """
145 if pre is True: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
146 # mode='before' for pydantic-core
147 def _wrapper1(values: RootValidatorValues, _: core_schema.ValidationInfo) -> RootValidatorValues: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
148 return validator(values) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
150 return _wrapper1 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
152 # mode='after' for pydantic-core
153 def _wrapper2(fields_tuple: RootValidatorFieldsTuple, _: core_schema.ValidationInfo) -> RootValidatorFieldsTuple: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
154 if len(fields_tuple) == 2: 154 ↛ 156line 154 didn't jump to line 156 because the condition on line 154 was never true1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
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 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
163 if model_extra: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
164 fields = set(model_dict.keys()) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
165 model_dict.update(model_extra) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
166 model_dict_new = validator(model_dict) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
167 for k in list(model_dict_new.keys()): 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
168 if k not in fields: 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
169 model_extra[k] = model_dict_new.pop(k) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
170 else:
171 model_dict_new = validator(model_dict) 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
172 return model_dict_new, model_extra, fields_set 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn
174 return _wrapper2 1opqrstuvabcdefwxyzABCDghijEFGHIJKLklmn