-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
class_validators.py
253 lines (213 loc) · 9.64 KB
/
class_validators.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
"""Old `@validator` and `@root_validator` function validators from V1."""
from __future__ import annotations as _annotations
from functools import partial, partialmethod
from types import FunctionType
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, overload
from warnings import warn
from typing_extensions import Literal, Protocol, TypeAlias
from .._internal import _decorators, _decorators_v1
from ..errors import PydanticUserError
from ..warnings import PydanticDeprecatedSince20
_ALLOW_REUSE_WARNING_MESSAGE = '`allow_reuse` is deprecated and will be ignored; it should no longer be necessary'
if TYPE_CHECKING:
class _OnlyValueValidatorClsMethod(Protocol):
def __call__(self, __cls: Any, __value: Any) -> Any:
...
class _V1ValidatorWithValuesClsMethod(Protocol):
def __call__(self, __cls: Any, __value: Any, values: dict[str, Any]) -> Any:
...
class _V1ValidatorWithValuesKwOnlyClsMethod(Protocol):
def __call__(self, __cls: Any, __value: Any, *, values: dict[str, Any]) -> Any:
...
class _V1ValidatorWithKwargsClsMethod(Protocol):
def __call__(self, __cls: Any, **kwargs: Any) -> Any:
...
class _V1ValidatorWithValuesAndKwargsClsMethod(Protocol):
def __call__(self, __cls: Any, values: dict[str, Any], **kwargs: Any) -> Any:
...
class _V1RootValidatorClsMethod(Protocol):
def __call__(
self, __cls: Any, __values: _decorators_v1.RootValidatorValues
) -> _decorators_v1.RootValidatorValues:
...
V1Validator = Union[
_OnlyValueValidatorClsMethod,
_V1ValidatorWithValuesClsMethod,
_V1ValidatorWithValuesKwOnlyClsMethod,
_V1ValidatorWithKwargsClsMethod,
_V1ValidatorWithValuesAndKwargsClsMethod,
_decorators_v1.V1ValidatorWithValues,
_decorators_v1.V1ValidatorWithValuesKwOnly,
_decorators_v1.V1ValidatorWithKwargs,
_decorators_v1.V1ValidatorWithValuesAndKwargs,
]
V1RootValidator = Union[
_V1RootValidatorClsMethod,
_decorators_v1.V1RootValidatorFunction,
]
_PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]]
# Allow both a V1 (assumed pre=False) or V2 (assumed mode='after') validator
# We lie to type checkers and say we return the same thing we get
# but in reality we return a proxy object that _mostly_ behaves like the wrapped thing
_V1ValidatorType = TypeVar('_V1ValidatorType', V1Validator, _PartialClsOrStaticMethod)
_V1RootValidatorFunctionType = TypeVar(
'_V1RootValidatorFunctionType',
_decorators_v1.V1RootValidatorFunction,
_V1RootValidatorClsMethod,
_PartialClsOrStaticMethod,
)
else:
# See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915
# and https://youtrack.jetbrains.com/issue/PY-51428
DeprecationWarning = PydanticDeprecatedSince20
def validator(
__field: str,
*fields: str,
pre: bool = False,
each_item: bool = False,
always: bool = False,
check_fields: bool | None = None,
allow_reuse: bool = False,
) -> Callable[[_V1ValidatorType], _V1ValidatorType]:
"""Decorate methods on the class indicating that they should be used to validate fields.
Args:
__field (str): The first field the validator should be called on; this is separate
from `fields` to ensure an error is raised if you don't pass at least one.
*fields (str): Additional field(s) the validator should be called on.
pre (bool, optional): Whether this validator should be called before the standard
validators (else after). Defaults to False.
each_item (bool, optional): For complex objects (sets, lists etc.) whether to validate
individual elements rather than the whole object. Defaults to False.
always (bool, optional): Whether this method and other validators should be called even if
the value is missing. Defaults to False.
check_fields (bool | None, optional): Whether to check that the fields actually exist on the model.
Defaults to None.
allow_reuse (bool, optional): Whether to track and raise an error if another validator refers to
the decorated function. Defaults to False.
Returns:
Callable: A decorator that can be used to decorate a
function to be used as a validator.
"""
if allow_reuse is True: # pragma: no cover
warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning)
fields = tuple((__field, *fields))
if isinstance(fields[0], FunctionType):
raise PydanticUserError(
'`@validator` should be used with fields and keyword arguments, not bare. '
"E.g. usage should be `@validator('<field_name>', ...)`",
code='validator-no-fields',
)
elif not all(isinstance(field, str) for field in fields):
raise PydanticUserError(
'`@validator` fields should be passed as separate string args. '
"E.g. usage should be `@validator('<field_name_1>', '<field_name_2>', ...)`",
code='validator-invalid-fields',
)
warn(
'Pydantic V1 style `@validator` validators are deprecated.'
' You should migrate to Pydantic V2 style `@field_validator` validators,'
' see the migration guide for more details',
DeprecationWarning,
stacklevel=2,
)
mode: Literal['before', 'after'] = 'before' if pre is True else 'after'
def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]:
if _decorators.is_instance_method_from_sig(f):
raise PydanticUserError(
'`@validator` cannot be applied to instance methods', code='validator-instance-method'
)
# auto apply the @classmethod decorator
f = _decorators.ensure_classmethod_based_on_signature(f)
wrap = _decorators_v1.make_generic_v1_field_validator
validator_wrapper_info = _decorators.ValidatorDecoratorInfo(
fields=fields,
mode=mode,
each_item=each_item,
always=always,
check_fields=check_fields,
)
return _decorators.PydanticDescriptorProxy(f, validator_wrapper_info, shim=wrap)
return dec # type: ignore[return-value]
@overload
def root_validator(
*,
# if you don't specify `pre` the default is `pre=False`
# which means you need to specify `skip_on_failure=True`
skip_on_failure: Literal[True],
allow_reuse: bool = ...,
) -> Callable[
[_V1RootValidatorFunctionType],
_V1RootValidatorFunctionType,
]:
...
@overload
def root_validator(
*,
# if you specify `pre=True` then you don't need to specify
# `skip_on_failure`, in fact it is not allowed as an argument!
pre: Literal[True],
allow_reuse: bool = ...,
) -> Callable[
[_V1RootValidatorFunctionType],
_V1RootValidatorFunctionType,
]:
...
@overload
def root_validator(
*,
# if you explicitly specify `pre=False` then you
# MUST specify `skip_on_failure=True`
pre: Literal[False],
skip_on_failure: Literal[True],
allow_reuse: bool = ...,
) -> Callable[
[_V1RootValidatorFunctionType],
_V1RootValidatorFunctionType,
]:
...
def root_validator(
*__args,
pre: bool = False,
skip_on_failure: bool = False,
allow_reuse: bool = False,
) -> Any:
"""Decorate methods on a model indicating that they should be used to validate (and perhaps
modify) data either before or after standard model parsing/validation is performed.
Args:
pre (bool, optional): Whether this validator should be called before the standard
validators (else after). Defaults to False.
skip_on_failure (bool, optional): Whether to stop validation and return as soon as a
failure is encountered. Defaults to False.
allow_reuse (bool, optional): Whether to track and raise an error if another validator
refers to the decorated function. Defaults to False.
Returns:
Any: A decorator that can be used to decorate a function to be used as a root_validator.
"""
warn(
'Pydantic V1 style `@root_validator` validators are deprecated.'
' You should migrate to Pydantic V2 style `@model_validator` validators,'
' see the migration guide for more details',
DeprecationWarning,
stacklevel=2,
)
if __args:
# Ensure a nice error is raised if someone attempts to use the bare decorator
return root_validator()(*__args) # type: ignore
if allow_reuse is True: # pragma: no cover
warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning)
mode: Literal['before', 'after'] = 'before' if pre is True else 'after'
if pre is False and skip_on_failure is not True:
raise PydanticUserError(
'If you use `@root_validator` with pre=False (the default) you MUST specify `skip_on_failure=True`.'
' Note that `@root_validator` is deprecated and should be replaced with `@model_validator`.',
code='root-validator-pre-skip',
)
wrap = partial(_decorators_v1.make_v1_generic_root_validator, pre=pre)
def dec(f: Callable[..., Any] | classmethod[Any, Any, Any] | staticmethod[Any, Any]) -> Any:
if _decorators.is_instance_method_from_sig(f):
raise TypeError('`@root_validator` cannot be applied to instance methods')
# auto apply the @classmethod decorator
res = _decorators.ensure_classmethod_based_on_signature(f)
dec_info = _decorators.RootValidatorDecoratorInfo(mode=mode)
return _decorators.PydanticDescriptorProxy(res, dec_info, shim=wrap)
return dec