1
1
from functools import wraps
2
- from inspect import Signature
2
+ from inspect import Signature , signature
3
3
from typing import Callable
4
4
5
- import pydantic
6
5
from pydantic import PydanticUserError
7
6
8
- _ASYNC_VALIDATOR_FUNCS : set [str ] = set ()
9
7
10
-
11
- def prepare_validator (function : Callable , allow_reuse : bool = False ) -> classmethod :
12
- """
13
- Return the function as classmethod and check for duplicate names.
14
-
15
- Avoid validators with duplicated names since without this,
16
- validators can be overwritten silently
17
- which generally isn't the intended behaviour,
18
- don't run in ipython (see #312) or if allow_reuse is False.
8
+ def make_generic_field_validator (validator_func : Callable ) -> Callable :
19
9
"""
20
- f_cls : classmethod = (
21
- function
22
- if isinstance (function , classmethod )
23
- else classmethod (function )
24
- )
25
- if not allow_reuse :
26
- ref = f_cls .__func__ .__module__ + '.' + f_cls .__func__ .__qualname__
27
- if ref in _ASYNC_VALIDATOR_FUNCS :
28
- # TODO: Does this still make sense? pydantic v2 seems to now need this?
29
- raise pydantic .PydanticUserError (
30
- f'Duplicate validator function "{ ref } "; if this is intended, '
31
- f'set `allow_reuse=True`' ,
32
- # TODO: Find correct code for this - if we keep this exception
33
- code = 'validator-reuse' , # type: ignore
34
- )
35
- _ASYNC_VALIDATOR_FUNCS .add (ref )
36
- return f_cls
37
-
38
-
39
- def make_generic_validator (validator : Callable ) -> Callable :
10
+ Make a generic function which calls a field validator with the right arguments.
40
11
"""
41
- Make a generic function which calls a validator with the right arguments.
42
-
43
- Unfortunately other approaches
44
- (eg. return a partial of a function that builds the arguments) is slow,
45
- hence this laborious way of doing things.
46
12
47
- It's done like this so validators don't all need **kwargs in
48
- their signature, eg. any combination of
49
- the arguments "values", "fields" and/or "config" are permitted.
50
- """
51
- from inspect import signature # noqa
52
-
53
- sig = signature (validator )
13
+ sig = signature (validator_func )
54
14
args = list (sig .parameters .keys ())
55
15
first_arg = args .pop (0 )
56
- if first_arg == 'self ' :
16
+ if first_arg == 'cls ' :
57
17
raise PydanticUserError (
58
- f'Invalid signature for validator { validator } : { sig } ,'
59
- f'"self " not permitted as first argument, '
60
- f'should be: (cls , value, instance , config, field ), '
61
- f'"instance", "config" and "field " are all optional.' ,
18
+ f'Invalid signature for validator { validator_func } : { sig } ,'
19
+ f'"cls " not permitted as first argument, '
20
+ f'should be: (self , value, field , config), '
21
+ f'"field" and "config " are all optional.' ,
62
22
code = 'validator-signature' ,
63
23
)
64
- return wraps (validator )(
65
- generic_validator_cls (validator , sig , set (args [1 :])),
24
+ return wraps (validator_func )(
25
+ generic_field_validator_wrapper (
26
+ validator_func ,
27
+ sig ,
28
+ set (args [1 :]),
29
+ ),
66
30
)
67
31
68
32
69
- all_kwargs = {'instance' , ' field' , 'config ' }
33
+ all_field_validator_kwargs = {'field' , 'validator ' }
70
34
71
35
72
- def generic_validator_cls (
73
- validator : Callable ,
36
+ def generic_field_validator_wrapper (
37
+ validator_func : Callable ,
74
38
sig : 'Signature' ,
75
39
args : set [str ],
76
40
) -> Callable :
@@ -83,47 +47,99 @@ def generic_validator_cls(
83
47
has_kwargs = True
84
48
args -= {'kwargs' }
85
49
86
- if not args .issubset (all_kwargs ):
50
+ if not args .issubset (all_field_validator_kwargs ):
87
51
raise PydanticUserError ( # noqa
88
- f'Invalid signature for validator { validator } : { sig } , '
52
+ f'Invalid signature for validator { validator_func } : { sig } , '
89
53
f'should be: '
90
- f'(cls , value, instance , config, field ), '
91
- f'"instance", "config" and "field " are all optional.' ,
54
+ f'(self , value, field , config), '
55
+ f'"field" and "config " are all optional.' ,
92
56
code = 'validator-signature' ,
93
57
)
94
58
95
59
if has_kwargs :
96
- return lambda cls , v , instance , field , config : validator (
97
- cls , v , instance = instance , field = field , config = config ,
60
+ return lambda self , v , field , config : validator_func (
61
+ self , v , field = field , config = config ,
98
62
)
99
63
if args == set ():
100
- return lambda cls , v , instance , field , config : validator (cls , v )
101
- if args == {'instance' }:
102
- return lambda cls , v , instance , field , config : validator (
103
- cls , v , instance = instance ,
64
+ return lambda self , v , field , config : validator_func (
65
+ self , v ,
104
66
)
105
67
if args == {'field' }:
106
- return lambda cls , v , instance , field , config : validator (
107
- cls , v , field = field ,
68
+ return lambda self , v , field , config : validator_func (
69
+ self , v , field = field ,
108
70
)
109
71
if args == {'config' }:
110
- return lambda cls , v , instance , field , config : validator (
111
- cls , v , config = config ,
72
+ return lambda self , v , field , config : validator_func (
73
+ self , v , config = config ,
112
74
)
113
- if args == {'instance' , 'field' }:
114
- return lambda cls , v , instance , field , config : validator (
115
- cls , v , instance = instance , field = field ,
75
+
76
+ # args == {'field', 'validator'}
77
+ return lambda self , v , field , config : validator_func (
78
+ self , v , field = field , config = config ,
79
+ )
80
+
81
+
82
+ def make_generic_model_validator (validator_func : Callable ) -> Callable :
83
+ """
84
+ Make a generic function which calls a model validator with the right arguments.
85
+ """
86
+
87
+ sig = signature (validator_func )
88
+ args = list (sig .parameters .keys ())
89
+ first_arg = args .pop (0 )
90
+ if first_arg == 'cls' :
91
+ raise PydanticUserError (
92
+ f'Invalid signature for validator { validator_func } : { sig } ,'
93
+ f'"cls" not permitted as first argument, '
94
+ f'should be: (self, config), '
95
+ f'"config" is optional.' ,
96
+ code = 'validator-signature' ,
97
+ )
98
+ return wraps (validator_func )(
99
+ generic_model_validator_wrapper (
100
+ validator_func ,
101
+ sig ,
102
+ set (args [1 :]),
103
+ ),
104
+ )
105
+
106
+
107
+ all_model_validator_kwargs = {'validator' }
108
+
109
+
110
+ def generic_model_validator_wrapper (
111
+ validator_func : Callable ,
112
+ sig : 'Signature' ,
113
+ args : set [str ],
114
+ ) -> Callable :
115
+ """
116
+ Return a helper function to wrap a method to be called with its defined parameters.
117
+ """
118
+ # assume the first argument is value
119
+ has_kwargs = False
120
+ if 'kwargs' in args :
121
+ has_kwargs = True
122
+ args -= {'kwargs' }
123
+
124
+ if not args .issubset (all_model_validator_kwargs ):
125
+ raise PydanticUserError ( # noqa
126
+ f'Invalid signature for validator { validator_func } : { sig } , '
127
+ f'should be: '
128
+ f'(self, config), '
129
+ f'"config" is optional.' ,
130
+ code = 'validator-signature' ,
116
131
)
117
- if args == {'instance' , 'config' }:
118
- return lambda cls , v , instance , field , config : validator (
119
- cls , v , instance = instance , config = config ,
132
+
133
+ if has_kwargs :
134
+ return lambda self , config : validator_func (
135
+ self , config = config ,
120
136
)
121
- if args == { 'field' , 'config' } :
122
- return lambda cls , v , instance , field , config : validator (
123
- cls , v , field = field , config = config ,
137
+ if args == set () :
138
+ return lambda self , config : validator_func (
139
+ self ,
124
140
)
125
141
126
- # args == {'instance', 'field', 'config '}
127
- return lambda cls , v , instance , field , config : validator (
128
- cls , v , instance = instance , field = field , config = config ,
142
+ # args == {'validator '}
143
+ return lambda self , config : validator_func (
144
+ self , config = config ,
129
145
)
0 commit comments