Coverage for pydantic/_internal/_validators.py: 99.15%
167 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"""Validator functions for standard library types.
3Import of this module is deferred since it contains imports of many standard library modules.
4"""
6from __future__ import annotations as _annotations 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
8import math 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
9import re 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
10import typing 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
11from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
12from typing import Any, Callable 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
14from pydantic_core import PydanticCustomError, core_schema 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
15from pydantic_core._pydantic_core import PydanticKnownError 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
18def sequence_validator( 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
19 input_value: typing.Sequence[Any],
20 /,
21 validator: core_schema.ValidatorFunctionWrapHandler,
22) -> typing.Sequence[Any]:
23 """Validator for `Sequence` types, isinstance(v, Sequence) has already been called."""
24 value_type = type(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
26 # We don't accept any plain string as a sequence
27 # Relevant issue: https://github.com/pydantic/pydantic/issues/5595
28 if issubclass(value_type, (str, bytes)): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
29 raise PydanticCustomError( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
30 'sequence_str',
31 "'{type_name}' instances are not allowed as a Sequence value",
32 {'type_name': value_type.__name__},
33 )
35 # TODO: refactor sequence validation to validate with either a list or a tuple
36 # schema, depending on the type of the value.
37 # Additionally, we should be able to remove one of either this validator or the
38 # SequenceValidator in _std_types_schema.py (preferably this one, while porting over some logic).
39 # Effectively, a refactor for sequence validation is needed.
40 if value_type is tuple: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
41 input_value = list(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
43 v_list = validator(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
45 # the rest of the logic is just re-creating the original type from `v_list`
46 if value_type is list: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
47 return v_list 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
48 elif issubclass(value_type, range): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
49 # return the list as we probably can't re-create the range
50 return v_list 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
51 elif value_type is tuple: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
52 return tuple(v_list) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
53 else:
54 # best guess at how to re-create the original type, more custom construction logic might be required
55 return value_type(v_list) # type: ignore[call-arg] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
58def import_string(value: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
59 if isinstance(value, str): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
60 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
61 return _import_string_logic(value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
62 except ImportError as e: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
63 raise PydanticCustomError('import_error', 'Invalid python path: {error}', {'error': str(e)}) from e 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
64 else:
65 # otherwise we just return the value and let the next validator do the rest of the work
66 return value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
69def _import_string_logic(dotted_path: str) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
70 """Inspired by uvicorn — dotted paths should include a colon before the final item if that item is not a module.
71 (This is necessary to distinguish between a submodule and an attribute when there is a conflict.).
73 If the dotted path does not include a colon and the final item is not a valid module, importing as an attribute
74 rather than a submodule will be attempted automatically.
76 So, for example, the following values of `dotted_path` result in the following returned values:
77 * 'collections': <module 'collections'>
78 * 'collections.abc': <module 'collections.abc'>
79 * 'collections.abc:Mapping': <class 'collections.abc.Mapping'>
80 * `collections.abc.Mapping`: <class 'collections.abc.Mapping'> (though this is a bit slower than the previous line)
82 An error will be raised under any of the following scenarios:
83 * `dotted_path` contains more than one colon (e.g., 'collections:abc:Mapping')
84 * the substring of `dotted_path` before the colon is not a valid module in the environment (e.g., '123:Mapping')
85 * the substring of `dotted_path` after the colon is not an attribute of the module (e.g., 'collections:abc123')
86 """
87 from importlib import import_module 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
89 components = dotted_path.strip().split(':') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
90 if len(components) > 2: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
91 raise ImportError(f"Import strings should have at most one ':'; received {dotted_path!r}") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
93 module_path = components[0] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
94 if not module_path: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
95 raise ImportError(f'Import strings should have a nonempty module name; received {dotted_path!r}') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
97 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
98 module = import_module(module_path) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
99 except ModuleNotFoundError as e: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
100 if '.' in module_path: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
101 # Check if it would be valid if the final item was separated from its module with a `:`
102 maybe_module_path, maybe_attribute = dotted_path.strip().rsplit('.', 1) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
103 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
104 return _import_string_logic(f'{maybe_module_path}:{maybe_attribute}') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
105 except ImportError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
106 pass 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
107 raise ImportError(f'No module named {module_path!r}') from e 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
108 raise e 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
110 if len(components) > 1: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
111 attribute = components[1] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
112 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
113 return getattr(module, attribute) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
114 except AttributeError as e: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
115 raise ImportError(f'cannot import name {attribute!r} from {module_path!r}') from e 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
116 else:
117 return module 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
120def pattern_either_validator(input_value: Any, /) -> typing.Pattern[Any]: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
121 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
122 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
123 elif isinstance(input_value, (str, bytes)): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
124 # todo strict mode
125 return compile_pattern(input_value) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
126 else:
127 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
130def pattern_str_validator(input_value: Any, /) -> typing.Pattern[str]: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
131 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
132 if isinstance(input_value.pattern, str): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
133 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
134 else:
135 raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
136 elif isinstance(input_value, str): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
137 return compile_pattern(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
138 elif isinstance(input_value, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
139 raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
140 else:
141 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
144def pattern_bytes_validator(input_value: Any, /) -> typing.Pattern[bytes]: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
145 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
146 if isinstance(input_value.pattern, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
147 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
148 else:
149 raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
150 elif isinstance(input_value, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
151 return compile_pattern(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
152 elif isinstance(input_value, str): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
153 raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
154 else:
155 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
158PatternType = typing.TypeVar('PatternType', str, bytes) 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
161def compile_pattern(pattern: PatternType) -> typing.Pattern[PatternType]: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
162 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
163 return re.compile(pattern) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
164 except re.error: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
165 raise PydanticCustomError('pattern_regex', 'Input should be a valid regular expression') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
168def ip_v4_address_validator(input_value: Any, /) -> IPv4Address: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
169 if isinstance(input_value, IPv4Address): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
170 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
172 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
173 return IPv4Address(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
174 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
175 raise PydanticCustomError('ip_v4_address', 'Input is not a valid IPv4 address') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
178def ip_v6_address_validator(input_value: Any, /) -> IPv6Address: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
179 if isinstance(input_value, IPv6Address): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
180 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
182 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
183 return IPv6Address(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
184 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
185 raise PydanticCustomError('ip_v6_address', 'Input is not a valid IPv6 address') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
188def ip_v4_network_validator(input_value: Any, /) -> IPv4Network: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
189 """Assume IPv4Network initialised with a default `strict` argument.
191 See more:
192 https://docs.python.org/library/ipaddress.html#ipaddress.IPv4Network
193 """
194 if isinstance(input_value, IPv4Network): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
195 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
197 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
198 return IPv4Network(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
199 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
200 raise PydanticCustomError('ip_v4_network', 'Input is not a valid IPv4 network') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
203def ip_v6_network_validator(input_value: Any, /) -> IPv6Network: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
204 """Assume IPv6Network initialised with a default `strict` argument.
206 See more:
207 https://docs.python.org/library/ipaddress.html#ipaddress.IPv6Network
208 """
209 if isinstance(input_value, IPv6Network): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
210 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
212 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
213 return IPv6Network(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
214 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
215 raise PydanticCustomError('ip_v6_network', 'Input is not a valid IPv6 network') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
218def ip_v4_interface_validator(input_value: Any, /) -> IPv4Interface: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
219 if isinstance(input_value, IPv4Interface): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
220 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
222 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
223 return IPv4Interface(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
224 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
225 raise PydanticCustomError('ip_v4_interface', 'Input is not a valid IPv4 interface') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
228def ip_v6_interface_validator(input_value: Any, /) -> IPv6Interface: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
229 if isinstance(input_value, IPv6Interface): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
230 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
232 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
233 return IPv6Interface(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
234 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
235 raise PydanticCustomError('ip_v6_interface', 'Input is not a valid IPv6 interface') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
238def greater_than_validator(x: Any, gt: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
239 if not (x > gt): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
240 raise PydanticKnownError('greater_than', {'gt': gt}) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
241 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
244def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
245 if not (x >= ge): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
246 raise PydanticKnownError('greater_than_equal', {'ge': ge}) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
247 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
250def less_than_validator(x: Any, lt: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
251 if not (x < lt): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
252 raise PydanticKnownError('less_than', {'lt': lt}) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
253 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
256def less_than_or_equal_validator(x: Any, le: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
257 if not (x <= le): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
258 raise PydanticKnownError('less_than_equal', {'le': le}) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
259 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
262def multiple_of_validator(x: Any, multiple_of: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
263 if not (x % multiple_of == 0): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
264 raise PydanticKnownError('multiple_of', {'multiple_of': multiple_of}) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
265 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
268def min_length_validator(x: Any, min_length: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
269 if not (len(x) >= min_length): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
270 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
271 'too_short',
272 {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)},
273 )
274 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
277def max_length_validator(x: Any, max_length: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
278 if len(x) > max_length: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
279 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
280 'too_long',
281 {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)},
282 )
283 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
286def forbid_inf_nan_check(x: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
287 if not math.isfinite(x): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
288 raise PydanticKnownError('finite_number') 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
289 return x 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
292_CONSTRAINT_TO_VALIDATOR_MAP: dict[str, Callable] = { 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
293 'gt': greater_than_validator,
294 'ge': greater_than_or_equal_validator,
295 'lt': less_than_validator,
296 'le': less_than_or_equal_validator,
297 'multiple_of': multiple_of_validator,
298 'min_length': min_length_validator,
299 'max_length': max_length_validator,
300}
303def get_constraint_validator(constraint: str) -> Callable: 1abcdefghijklmnopqrstuvwxyzMNOPQRSTUVABCDEFGHIJKL
304 """Fetch the validator function for the given constraint."""
305 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
306 return _CONSTRAINT_TO_VALIDATOR_MAP[constraint] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
307 except KeyError:
308 raise TypeError(f'Unknown constraint {constraint}')