Coverage for pydantic/_internal/_validators.py: 99.45%
273 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-13 19:35 +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 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
8import collections.abc 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
9import math 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
10import re 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
11import typing 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
12from decimal import Decimal 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
13from fractions import Fraction 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
14from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
15from typing import Any, Callable, TypeVar, Union, cast, get_origin 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
16from zoneinfo import ZoneInfo, ZoneInfoNotFoundError 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
18import typing_extensions 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
19from pydantic_core import PydanticCustomError, core_schema 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
20from pydantic_core._pydantic_core import PydanticKnownError 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
22from pydantic._internal import _typing_extra 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
23from pydantic._internal._import_utils import import_cached_field_info 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
24from pydantic.errors import PydanticSchemaGenerationError 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
27def sequence_validator( 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
28 input_value: typing.Sequence[Any],
29 /,
30 validator: core_schema.ValidatorFunctionWrapHandler,
31) -> typing.Sequence[Any]:
32 """Validator for `Sequence` types, isinstance(v, Sequence) has already been called."""
33 value_type = type(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
35 # We don't accept any plain string as a sequence
36 # Relevant issue: https://github.com/pydantic/pydantic/issues/5595
37 if issubclass(value_type, (str, bytes)): 1abcdefghijklmnopqrstuvwxyzABCDEF
38 raise PydanticCustomError( 1abcdefghijklmnopqrstuvwxyzABCDEF
39 'sequence_str',
40 "'{type_name}' instances are not allowed as a Sequence value",
41 {'type_name': value_type.__name__},
42 )
44 # TODO: refactor sequence validation to validate with either a list or a tuple
45 # schema, depending on the type of the value.
46 # Additionally, we should be able to remove one of either this validator or the
47 # SequenceValidator in _std_types_schema.py (preferably this one, while porting over some logic).
48 # Effectively, a refactor for sequence validation is needed.
49 if value_type is tuple: 1abcdefghijklmnopqrstuvwxyzABCDEF
50 input_value = list(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
52 v_list = validator(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
54 # the rest of the logic is just re-creating the original type from `v_list`
55 if value_type is list: 1abcdefghijklmnopqrstuvwxyzABCDEF
56 return v_list 1abcdefghijklmnopqrstuvwxyzABCDEF
57 elif issubclass(value_type, range): 1abcdefghijklmnopqrstuvwxyzABCDEF
58 # return the list as we probably can't re-create the range
59 return v_list 1abcdefghijklmnopqrstuvwxyzABCDEF
60 elif value_type is tuple: 1abcdefghijklmnopqrstuvwxyzABCDEF
61 return tuple(v_list) 1abcdefghijklmnopqrstuvwxyzABCDEF
62 else:
63 # best guess at how to re-create the original type, more custom construction logic might be required
64 return value_type(v_list) # type: ignore[call-arg] 1abcdefghijklmnopqrstuvwxyzABCDEF
67def import_string(value: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
68 if isinstance(value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF
69 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
70 return _import_string_logic(value) 1abcdefghijklmnopqrstuvwxyzABCDEF
71 except ImportError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF
72 raise PydanticCustomError('import_error', 'Invalid python path: {error}', {'error': str(e)}) from e 1abcdefghijklmnopqrstuvwxyzABCDEF
73 else:
74 # otherwise we just return the value and let the next validator do the rest of the work
75 return value 1abcdefghijklmnopqrstuvwxyzABCDEF
78def _import_string_logic(dotted_path: str) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
79 """Inspired by uvicorn — dotted paths should include a colon before the final item if that item is not a module.
80 (This is necessary to distinguish between a submodule and an attribute when there is a conflict.).
82 If the dotted path does not include a colon and the final item is not a valid module, importing as an attribute
83 rather than a submodule will be attempted automatically.
85 So, for example, the following values of `dotted_path` result in the following returned values:
86 * 'collections': <module 'collections'>
87 * 'collections.abc': <module 'collections.abc'>
88 * 'collections.abc:Mapping': <class 'collections.abc.Mapping'>
89 * `collections.abc.Mapping`: <class 'collections.abc.Mapping'> (though this is a bit slower than the previous line)
91 An error will be raised under any of the following scenarios:
92 * `dotted_path` contains more than one colon (e.g., 'collections:abc:Mapping')
93 * the substring of `dotted_path` before the colon is not a valid module in the environment (e.g., '123:Mapping')
94 * the substring of `dotted_path` after the colon is not an attribute of the module (e.g., 'collections:abc123')
95 """
96 from importlib import import_module 1abcdefghijklmnopqrstuvwxyzABCDEF
98 components = dotted_path.strip().split(':') 1abcdefghijklmnopqrstuvwxyzABCDEF
99 if len(components) > 2: 1abcdefghijklmnopqrstuvwxyzABCDEF
100 raise ImportError(f"Import strings should have at most one ':'; received {dotted_path!r}") 1abcdefghijklmnopqrstuvwxyzABCDEF
102 module_path = components[0] 1abcdefghijklmnopqrstuvwxyzABCDEF
103 if not module_path: 1abcdefghijklmnopqrstuvwxyzABCDEF
104 raise ImportError(f'Import strings should have a nonempty module name; received {dotted_path!r}') 1abcdefghijklmnopqrstuvwxyzABCDEF
106 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
107 module = import_module(module_path) 1abcdefghijklmnopqrstuvwxyzABCDEF
108 except ModuleNotFoundError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF
109 if '.' in module_path: 1abcdefghijklmnopqrstuvwxyzABCDEF
110 # Check if it would be valid if the final item was separated from its module with a `:`
111 maybe_module_path, maybe_attribute = dotted_path.strip().rsplit('.', 1) 1abcdefghijklmnopqrstuvwxyzABCDEF
112 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
113 return _import_string_logic(f'{maybe_module_path}:{maybe_attribute}') 1abcdefghijklmnopqrstuvwxyzABCDEF
114 except ImportError: 1abcdefghijklmnopqrstuvwxyzABCDEF
115 pass 1abcdefghijklmnopqrstuvwxyzABCDEF
116 raise ImportError(f'No module named {module_path!r}') from e 1abcdefghijklmnopqrstuvwxyzABCDEF
117 raise e 1abcdefghijklmnopqrstuvwxyzABCDEF
119 if len(components) > 1: 1abcdefghijklmnopqrstuvwxyzABCDEF
120 attribute = components[1] 1abcdefghijklmnopqrstuvwxyzABCDEF
121 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
122 return getattr(module, attribute) 1abcdefghijklmnopqrstuvwxyzABCDEF
123 except AttributeError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF
124 raise ImportError(f'cannot import name {attribute!r} from {module_path!r}') from e 1abcdefghijklmnopqrstuvwxyzABCDEF
125 else:
126 return module 1abcdefghijklmnopqrstuvwxyzABCDEF
129def pattern_either_validator(input_value: Any, /) -> typing.Pattern[Any]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
130 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEF
131 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
132 elif isinstance(input_value, (str, bytes)): 1abcdefghijklmnopqrstuvwxyzABCDEF
133 # todo strict mode
134 return compile_pattern(input_value) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEF
135 else:
136 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
139def pattern_str_validator(input_value: Any, /) -> typing.Pattern[str]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
140 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEF
141 if isinstance(input_value.pattern, str): 1abcdefghijklmnopqrstuvwxyzABCDEF
142 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
143 else:
144 raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
145 elif isinstance(input_value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF
146 return compile_pattern(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
147 elif isinstance(input_value, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEF
148 raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
149 else:
150 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
153def pattern_bytes_validator(input_value: Any, /) -> typing.Pattern[bytes]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
154 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEF
155 if isinstance(input_value.pattern, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEF
156 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
157 else:
158 raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
159 elif isinstance(input_value, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEF
160 return compile_pattern(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
161 elif isinstance(input_value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF
162 raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
163 else:
164 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF
167PatternType = typing.TypeVar('PatternType', str, bytes) 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
170def compile_pattern(pattern: PatternType) -> typing.Pattern[PatternType]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
171 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
172 return re.compile(pattern) 1abcdefghijklmnopqrstuvwxyzABCDEF
173 except re.error: 1abcdefghijklmnopqrstuvwxyzABCDEF
174 raise PydanticCustomError('pattern_regex', 'Input should be a valid regular expression') 1abcdefghijklmnopqrstuvwxyzABCDEF
177def ip_v4_address_validator(input_value: Any, /) -> IPv4Address: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
178 if isinstance(input_value, IPv4Address): 1abcdefghijklmnopqrstuvwxyzABCDEF
179 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
181 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
182 return IPv4Address(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
183 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
184 raise PydanticCustomError('ip_v4_address', 'Input is not a valid IPv4 address') 1abcdefghijklmnopqrstuvwxyzABCDEF
187def ip_v6_address_validator(input_value: Any, /) -> IPv6Address: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
188 if isinstance(input_value, IPv6Address): 1abcdefghijklmnopqrstuvwxyzABCDEF
189 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
191 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
192 return IPv6Address(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
193 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
194 raise PydanticCustomError('ip_v6_address', 'Input is not a valid IPv6 address') 1abcdefghijklmnopqrstuvwxyzABCDEF
197def ip_v4_network_validator(input_value: Any, /) -> IPv4Network: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
198 """Assume IPv4Network initialised with a default `strict` argument.
200 See more:
201 https://docs.python.org/library/ipaddress.html#ipaddress.IPv4Network
202 """
203 if isinstance(input_value, IPv4Network): 1abcdefghijklmnopqrstuvwxyzABCDEF
204 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
206 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
207 return IPv4Network(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
208 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
209 raise PydanticCustomError('ip_v4_network', 'Input is not a valid IPv4 network') 1abcdefghijklmnopqrstuvwxyzABCDEF
212def ip_v6_network_validator(input_value: Any, /) -> IPv6Network: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
213 """Assume IPv6Network initialised with a default `strict` argument.
215 See more:
216 https://docs.python.org/library/ipaddress.html#ipaddress.IPv6Network
217 """
218 if isinstance(input_value, IPv6Network): 1abcdefghijklmnopqrstuvwxyzABCDEF
219 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
221 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
222 return IPv6Network(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
223 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
224 raise PydanticCustomError('ip_v6_network', 'Input is not a valid IPv6 network') 1abcdefghijklmnopqrstuvwxyzABCDEF
227def ip_v4_interface_validator(input_value: Any, /) -> IPv4Interface: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
228 if isinstance(input_value, IPv4Interface): 1abcdefghijklmnopqrstuvwxyzABCDEF
229 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
231 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
232 return IPv4Interface(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
233 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
234 raise PydanticCustomError('ip_v4_interface', 'Input is not a valid IPv4 interface') 1abcdefghijklmnopqrstuvwxyzABCDEF
237def ip_v6_interface_validator(input_value: Any, /) -> IPv6Interface: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
238 if isinstance(input_value, IPv6Interface): 1abcdefghijklmnopqrstuvwxyzABCDEF
239 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
241 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
242 return IPv6Interface(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
243 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
244 raise PydanticCustomError('ip_v6_interface', 'Input is not a valid IPv6 interface') 1abcdefghijklmnopqrstuvwxyzABCDEF
247def fraction_validator(input_value: Any, /) -> Fraction: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
248 if isinstance(input_value, Fraction): 1abcdefghijklmnopqrstuvwxyzABCDEF
249 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF
251 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
252 return Fraction(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
253 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
254 raise PydanticCustomError('fraction_parsing', 'Input is not a valid fraction') 1abcdefghijklmnopqrstuvwxyzABCDEF
257def forbid_inf_nan_check(x: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
258 if not math.isfinite(x): 1abcdefghijklmnopqrstuvwxyzABCDEF
259 raise PydanticKnownError('finite_number') 1abcdefghijklmnopqrstuvwxyzABCDEF
260 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
263def _safe_repr(v: Any) -> int | float | str: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
264 """The context argument for `PydanticKnownError` requires a number or str type, so we do a simple repr() coercion for types like timedelta.
266 See tests/test_types.py::test_annotated_metadata_any_order for some context.
267 """
268 if isinstance(v, (int, float, str)): 1abcdefghijklmnopqrstuvwxyzABCDEF
269 return v 1abcdefghijklmnopqrstuvwxyzABCDEF
270 return repr(v) 1abcdefghijklmnopqrstuvwxyzABCDEF
273def greater_than_validator(x: Any, gt: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
274 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
275 if not (x > gt): 1abcdefghijklmnopqrstuvwxyzABCDEF
276 raise PydanticKnownError('greater_than', {'gt': _safe_repr(gt)}) 1abcdefghijklmnopqrstuvwxyzABCDEF
277 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
278 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
279 raise TypeError(f"Unable to apply constraint 'gt' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
282def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
283 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
284 if not (x >= ge): 1abcdefghijklmnopqrstuvwxyzABCDEF
285 raise PydanticKnownError('greater_than_equal', {'ge': _safe_repr(ge)}) 1abcdefghijklmnopqrstuvwxyzABCDEF
286 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
287 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
288 raise TypeError(f"Unable to apply constraint 'ge' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
291def less_than_validator(x: Any, lt: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
292 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
293 if not (x < lt): 1abcdefghijklmnopqrstuvwxyzABCDEF
294 raise PydanticKnownError('less_than', {'lt': _safe_repr(lt)}) 1abcdefghijklmnopqrstuvwxyzABCDEF
295 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
296 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
297 raise TypeError(f"Unable to apply constraint 'lt' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
300def less_than_or_equal_validator(x: Any, le: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
301 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
302 if not (x <= le): 1abcdefghijklmnopqrstuvwxyzABCDEF
303 raise PydanticKnownError('less_than_equal', {'le': _safe_repr(le)}) 1abcdefghijklmnopqrstuvwxyzABCDEF
304 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
305 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
306 raise TypeError(f"Unable to apply constraint 'le' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
309def multiple_of_validator(x: Any, multiple_of: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
310 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
311 if x % multiple_of: 1abcdefghijklmnopqrstuvwxyzABCDEF
312 raise PydanticKnownError('multiple_of', {'multiple_of': _safe_repr(multiple_of)}) 1abcdefghijklmnopqrstuvwxyzABCDEF
313 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
314 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
315 raise TypeError(f"Unable to apply constraint 'multiple_of' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
318def min_length_validator(x: Any, min_length: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
319 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
320 if not (len(x) >= min_length): 1abcdefghijklmnopqrstuvwxyzABCDEF
321 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEF
322 'too_short', {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)}
323 )
324 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
325 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
326 raise TypeError(f"Unable to apply constraint 'min_length' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
329def max_length_validator(x: Any, max_length: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
330 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
331 if len(x) > max_length: 1abcdefghijklmnopqrstuvwxyzABCDEF
332 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEF
333 'too_long',
334 {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)},
335 )
336 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
337 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
338 raise TypeError(f"Unable to apply constraint 'max_length' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
341def _extract_decimal_digits_info(decimal: Decimal) -> tuple[int, int]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
342 """Compute the total number of digits and decimal places for a given [`Decimal`][decimal.Decimal] instance.
344 This function handles both normalized and non-normalized Decimal instances.
345 Example: Decimal('1.230') -> 4 digits, 3 decimal places
347 Args:
348 decimal (Decimal): The decimal number to analyze.
350 Returns:
351 tuple[int, int]: A tuple containing the number of decimal places and total digits.
353 Though this could be divided into two separate functions, the logic is easier to follow if we couple the computation
354 of the number of decimals and digits together.
355 """
356 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
357 decimal_tuple = decimal.as_tuple() 1abcdefghijklmnopqrstuvwxyzABCDEF
359 assert isinstance(decimal_tuple.exponent, int) 1abcdefghijklmnopqrstuvwxyzABCDEF
361 exponent = decimal_tuple.exponent 1abcdefghijklmnopqrstuvwxyzABCDEF
362 num_digits = len(decimal_tuple.digits) 1abcdefghijklmnopqrstuvwxyzABCDEF
364 if exponent >= 0: 1abcdefghijklmnopqrstuvwxyzABCDEF
365 # A positive exponent adds that many trailing zeros
366 # Ex: digit_tuple=(1, 2, 3), exponent=2 -> 12300 -> 0 decimal places, 5 digits
367 num_digits += exponent 1abcdefghijklmnopqrstuvwxyzABCDEF
368 decimal_places = 0 1abcdefghijklmnopqrstuvwxyzABCDEF
369 else:
370 # If the absolute value of the negative exponent is larger than the
371 # number of digits, then it's the same as the number of digits,
372 # because it'll consume all the digits in digit_tuple and then
373 # add abs(exponent) - len(digit_tuple) leading zeros after the decimal point.
374 # Ex: digit_tuple=(1, 2, 3), exponent=-2 -> 1.23 -> 2 decimal places, 3 digits
375 # Ex: digit_tuple=(1, 2, 3), exponent=-4 -> 0.0123 -> 4 decimal places, 4 digits
376 decimal_places = abs(exponent) 1abcdefghijklmnopqrstuvwxyzABCDEF
377 num_digits = max(num_digits, decimal_places) 1abcdefghijklmnopqrstuvwxyzABCDEF
379 return decimal_places, num_digits 1abcdefghijklmnopqrstuvwxyzABCDEF
380 except (AssertionError, AttributeError): 1abcdefghijklmnopqrstuvwxyzABCDEF
381 raise TypeError(f'Unable to extract decimal digits info from supplied value {decimal}') 1abcdefghijklmnopqrstuvwxyzABCDEF
384def max_digits_validator(x: Any, max_digits: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
385 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
386 _, num_digits = _extract_decimal_digits_info(x) 1abcdefghijklmnopqrstuvwxyzABCDEF
387 _, normalized_num_digits = _extract_decimal_digits_info(x.normalize()) 1abcdefghijklmnopqrstuvwxyzABCDEF
388 if (num_digits > max_digits) and (normalized_num_digits > max_digits): 1abcdefghijklmnopqrstuvwxyzABCDEF
389 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEF
390 'decimal_max_digits',
391 {'max_digits': max_digits},
392 )
393 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
394 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
395 raise TypeError(f"Unable to apply constraint 'max_digits' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
398def decimal_places_validator(x: Any, decimal_places: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
399 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
400 decimal_places_, _ = _extract_decimal_digits_info(x) 1abcdefghijklmnopqrstuvwxyzABCDEF
401 if decimal_places_ > decimal_places: 1abcdefghijklmnopqrstuvwxyzABCDEF
402 normalized_decimal_places, _ = _extract_decimal_digits_info(x.normalize()) 1abcdefghijklmnopqrstuvwxyzABCDEF
403 if normalized_decimal_places > decimal_places: 403 ↛ 408line 403 didn't jump to line 408 because the condition on line 403 was always true1abcdefghijklmnopqrstuvwxyzABCDEF
404 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEF
405 'decimal_max_places',
406 {'decimal_places': decimal_places},
407 )
408 return x 1abcdefghijklmnopqrstuvwxyzABCDEF
409 except TypeError: 1abcdefghijklmnopqrstuvwxyzABCDEF
410 raise TypeError(f"Unable to apply constraint 'decimal_places' to supplied value {x}") 1abcdefghijklmnopqrstuvwxyzABCDEF
413def deque_validator(input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> collections.deque[Any]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
414 return collections.deque(handler(input_value), maxlen=getattr(input_value, 'maxlen', None)) 1abcdefghijklmnopqrstuvwxyzABCDEF
417def defaultdict_validator( 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
418 input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler, default_default_factory: Callable[[], Any]
419) -> collections.defaultdict[Any, Any]:
420 if isinstance(input_value, collections.defaultdict): 1abcdefghijklmnopqrstuvwxyzABCDEF
421 default_factory = input_value.default_factory 1abcdefghijklmnopqrstuvwxyzABCDEF
422 return collections.defaultdict(default_factory, handler(input_value)) 1abcdefghijklmnopqrstuvwxyzABCDEF
423 else:
424 return collections.defaultdict(default_default_factory, handler(input_value)) 1abcdefghijklmnopqrstuvwxyzABCDEF
427def get_defaultdict_default_default_factory(values_source_type: Any) -> Callable[[], Any]: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
428 FieldInfo = import_cached_field_info() 1abcdefghijklmnopqrstuvwxyzABCDEF
430 def infer_default() -> Callable[[], Any]: 1abcdefghijklmnopqrstuvwxyzABCDEF
431 allowed_default_types: dict[Any, Any] = { 1abcdefghijklmnopqrstuvwxyzABCDEF
432 tuple: tuple,
433 collections.abc.Sequence: tuple,
434 collections.abc.MutableSequence: list,
435 list: list,
436 typing.Sequence: list,
437 set: set,
438 typing.MutableSet: set,
439 collections.abc.MutableSet: set,
440 collections.abc.Set: frozenset,
441 typing.MutableMapping: dict,
442 typing.Mapping: dict,
443 collections.abc.Mapping: dict,
444 collections.abc.MutableMapping: dict,
445 float: float,
446 int: int,
447 str: str,
448 bool: bool,
449 }
450 values_type_origin = get_origin(values_source_type) or values_source_type 1abcdefghijklmnopqrstuvwxyzABCDEF
451 instructions = 'set using `DefaultDict[..., Annotated[..., Field(default_factory=...)]]`' 1abcdefghijklmnopqrstuvwxyzABCDEF
452 if isinstance(values_type_origin, TypeVar): 1abcdefghijklmnopqrstuvwxyzABCDEF
454 def type_var_default_factory() -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF
455 raise RuntimeError(
456 'Generic defaultdict cannot be used without a concrete value type or an'
457 ' explicit default factory, ' + instructions
458 )
460 return type_var_default_factory 1abcdefghijklmnopqrstuvwxyzABCDEF
461 elif values_type_origin not in allowed_default_types: 1abcdefghijklmnopqrstuvwxyzABCDEF
462 # a somewhat subjective set of types that have reasonable default values
463 allowed_msg = ', '.join([t.__name__ for t in set(allowed_default_types.values())]) 1abcdefghijklmnopqrstuvwxyzABCDEF
464 raise PydanticSchemaGenerationError( 1abcdefghijklmnopqrstuvwxyzABCDEF
465 f'Unable to infer a default factory for keys of type {values_source_type}.'
466 f' Only {allowed_msg} are supported, other types require an explicit default factory'
467 ' ' + instructions
468 )
469 return allowed_default_types[values_type_origin] 1abcdefghijklmnopqrstuvwxyzABCDEF
471 # Assume Annotated[..., Field(...)]
472 if _typing_extra.is_annotated(values_source_type): 1abcdefghijklmnopqrstuvwxyzABCDEF
473 field_info = next((v for v in typing_extensions.get_args(values_source_type) if isinstance(v, FieldInfo)), None) 1abcdefghijklmnopqrstuvwxyzABCDEF
474 else:
475 field_info = None 1abcdefghijklmnopqrstuvwxyzABCDEF
476 if field_info and field_info.default_factory: 1abcdefghijklmnopqrstuvwxyzABCDEF
477 # Assume the default factory does not take any argument:
478 default_default_factory = cast(Callable[[], Any], field_info.default_factory) 1abcdefghijklmnopqrstuvwxyzABCDEF
479 else:
480 default_default_factory = infer_default() 1abcdefghijklmnopqrstuvwxyzABCDEF
481 return default_default_factory 1abcdefghijklmnopqrstuvwxyzABCDEF
484def validate_str_is_valid_iana_tz(value: Any, /) -> ZoneInfo: 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
485 if isinstance(value, ZoneInfo): 1abcdefghijklmnopqrstuvwxyzABCDEF
486 return value 1abcdefghijklmnopqrstuvwxyzABCDEF
487 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
488 return ZoneInfo(value) 1abcdefghijklmnopqrstuvwxyzABCDEF
489 except (ZoneInfoNotFoundError, ValueError, TypeError): 1abcdefghijklmnopqrstuvwxyzABCDEF
490 raise PydanticCustomError('zoneinfo_str', 'invalid timezone: {value}', {'value': value}) 1abcdefghijklmnopqrstuvwxyzABCDEF
493NUMERIC_VALIDATOR_LOOKUP: dict[str, Callable] = { 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
494 'gt': greater_than_validator,
495 'ge': greater_than_or_equal_validator,
496 'lt': less_than_validator,
497 'le': less_than_or_equal_validator,
498 'multiple_of': multiple_of_validator,
499 'min_length': min_length_validator,
500 'max_length': max_length_validator,
501 'max_digits': max_digits_validator,
502 'decimal_places': decimal_places_validator,
503}
505IpType = Union[IPv4Address, IPv6Address, IPv4Network, IPv6Network, IPv4Interface, IPv6Interface] 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
507IP_VALIDATOR_LOOKUP: dict[type[IpType], Callable] = { 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
508 IPv4Address: ip_v4_address_validator,
509 IPv6Address: ip_v6_address_validator,
510 IPv4Network: ip_v4_network_validator,
511 IPv6Network: ip_v6_network_validator,
512 IPv4Interface: ip_v4_interface_validator,
513 IPv6Interface: ip_v6_interface_validator,
514}
516MAPPING_ORIGIN_MAP: dict[Any, Any] = { 1abcdefghijklmnopqrstuvGHIJKLMwxyzABCDEF
517 typing.DefaultDict: collections.defaultdict, # noqa: UP006
518 collections.defaultdict: collections.defaultdict,
519 typing.OrderedDict: collections.OrderedDict, # noqa: UP006
520 collections.OrderedDict: collections.OrderedDict,
521 typing_extensions.OrderedDict: collections.OrderedDict,
522 typing.Counter: collections.Counter,
523 collections.Counter: collections.Counter,
524 # this doesn't handle subclasses of these
525 typing.Mapping: dict,
526 typing.MutableMapping: dict,
527 # parametrized typing.{Mutable}Mapping creates one of these
528 collections.abc.Mapping: dict,
529 collections.abc.MutableMapping: dict,
530}