Coverage for pydantic/_internal/_validators.py: 100.00%

161 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2024-06-21 17:00 +0000

1"""Validator functions for standard library types. 

2 

3Import of this module is deferred since it contains imports of many standard library modules. 

4""" 

5 

6from __future__ import annotations as _annotations 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

7 

8import math 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

9import re 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

10import typing 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

11from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

12from typing import Any 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

13 

14from pydantic_core import PydanticCustomError, core_schema 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

15from pydantic_core._pydantic_core import PydanticKnownError 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

16 

17 

18def sequence_validator( 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

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) 1abcdefghijklmnopqrstuvwxyzABCDEF

25 

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)): 1abcdefghijklmnopqrstuvwxyzABCDEF

29 raise PydanticCustomError( 1abcdefghijklmnopqrstuvwxyzABCDEF

30 'sequence_str', 

31 "'{type_name}' instances are not allowed as a Sequence value", 

32 {'type_name': value_type.__name__}, 

33 ) 

34 

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 == tuple: 1abcdefghijklmnopqrstuvwxyzABCDEF

41 input_value = list(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

42 

43 v_list = validator(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

44 

45 # the rest of the logic is just re-creating the original type from `v_list` 

46 if value_type == list: 1abcdefghijklmnopqrstuvwxyzABCDEF

47 return v_list 1abcdefghijklmnopqrstuvwxyzABCDEF

48 elif issubclass(value_type, range): 1abcdefghijklmnopqrstuvwxyzABCDEF

49 # return the list as we probably can't re-create the range 

50 return v_list 1abcdefghijklmnopqrstuvwxyzABCDEF

51 elif value_type == tuple: 1abcdefghijklmnopqrstuvwxyzABCDEF

52 return tuple(v_list) 1abcdefghijklmnopqrstuvwxyzABCDEF

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] 1abcdefghijklmnopqrstuvwxyzABCDEF

56 

57 

58def import_string(value: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

59 if isinstance(value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF

60 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

61 return _import_string_logic(value) 1abcdefghijklmnopqrstuvwxyzABCDEF

62 except ImportError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF

63 raise PydanticCustomError('import_error', 'Invalid python path: {error}', {'error': str(e)}) from e 1abcdefghijklmnopqrstuvwxyzABCDEF

64 else: 

65 # otherwise we just return the value and let the next validator do the rest of the work 

66 return value 1abcdefghijklmnopqrstuvwxyzABCDEF

67 

68 

69def _import_string_logic(dotted_path: str) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

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.). 

72 

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. 

75 

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) 

81 

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 1abcdefghijklmnopqrstuvwxyzABCDEF

88 

89 components = dotted_path.strip().split(':') 1abcdefghijklmnopqrstuvwxyzABCDEF

90 if len(components) > 2: 1abcdefghijklmnopqrstuvwxyzABCDEF

91 raise ImportError(f"Import strings should have at most one ':'; received {dotted_path!r}") 1abcdefghijklmnopqrstuvwxyzABCDEF

92 

93 module_path = components[0] 1abcdefghijklmnopqrstuvwxyzABCDEF

94 if not module_path: 1abcdefghijklmnopqrstuvwxyzABCDEF

95 raise ImportError(f'Import strings should have a nonempty module name; received {dotted_path!r}') 1abcdefghijklmnopqrstuvwxyzABCDEF

96 

97 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

98 module = import_module(module_path) 1abcdefghijklmnopqrstuvwxyzABCDEF

99 except ModuleNotFoundError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF

100 if '.' in module_path: 1abcdefghijklmnopqrstuvwxyzABCDEF

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) 1abcdefghijklmnopqrstuvwxyzABCDEF

103 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

104 return _import_string_logic(f'{maybe_module_path}:{maybe_attribute}') 1abcdefghijklmnopqrstuvwxyzABCDEF

105 except ImportError: 1abcdefghijklmnopqrstuvwxyzABCDEF

106 pass 1abcdefghijklmnopqrstuvwxyzABCDEF

107 raise ImportError(f'No module named {module_path!r}') from e 1abcdefghijklmnopqrstuvwxyzABCDEF

108 raise e 1abcdefghijklmnopqrstuvwxyzABCDEF

109 

110 if len(components) > 1: 1abcdefghijklmnopqrstuvwxyzABCDEF

111 attribute = components[1] 1abcdefghijklmnopqrstuvwxyzABCDEF

112 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

113 return getattr(module, attribute) 1abcdefghijklmnopqrstuvwxyzABCDEF

114 except AttributeError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF

115 raise ImportError(f'cannot import name {attribute!r} from {module_path!r}') from e 1abcdefghijklmnopqrstuvwxyzABCDEF

116 else: 

117 return module 1abcdefghijklmnopqrstuvwxyzABCDEF

118 

119 

120def pattern_either_validator(input_value: Any, /) -> typing.Pattern[Any]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

121 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEF

122 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

123 elif isinstance(input_value, (str, bytes)): 1abcdefghijklmnopqrstuvwxyzABCDEF

124 # todo strict mode 

125 return compile_pattern(input_value) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEF

126 else: 

127 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

128 

129 

130def pattern_str_validator(input_value: Any, /) -> typing.Pattern[str]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

131 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEF

132 if isinstance(input_value.pattern, str): 1abcdefghijklmnopqrstuvwxyzABCDEF

133 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

134 else: 

135 raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

136 elif isinstance(input_value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF

137 return compile_pattern(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

138 elif isinstance(input_value, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEF

139 raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

140 else: 

141 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

142 

143 

144def pattern_bytes_validator(input_value: Any, /) -> typing.Pattern[bytes]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

145 if isinstance(input_value, typing.Pattern): 1abcdefghijklmnopqrstuvwxyzABCDEF

146 if isinstance(input_value.pattern, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEF

147 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

148 else: 

149 raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

150 elif isinstance(input_value, bytes): 1abcdefghijklmnopqrstuvwxyzABCDEF

151 return compile_pattern(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

152 elif isinstance(input_value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF

153 raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

154 else: 

155 raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') 1abcdefghijklmnopqrstuvwxyzABCDEF

156 

157 

158PatternType = typing.TypeVar('PatternType', str, bytes) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

159 

160 

161def compile_pattern(pattern: PatternType) -> typing.Pattern[PatternType]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

162 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

163 return re.compile(pattern) 1abcdefghijklmnopqrstuvwxyzABCDEF

164 except re.error: 1abcdefghijklmnopqrstuvwxyzABCDEF

165 raise PydanticCustomError('pattern_regex', 'Input should be a valid regular expression') 1abcdefghijklmnopqrstuvwxyzABCDEF

166 

167 

168def ip_v4_address_validator(input_value: Any, /) -> IPv4Address: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

169 if isinstance(input_value, IPv4Address): 1abcdefghijklmnopqrstuvwxyzABCDEF

170 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

171 

172 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

173 return IPv4Address(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

174 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF

175 raise PydanticCustomError('ip_v4_address', 'Input is not a valid IPv4 address') 1abcdefghijklmnopqrstuvwxyzABCDEF

176 

177 

178def ip_v6_address_validator(input_value: Any, /) -> IPv6Address: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

179 if isinstance(input_value, IPv6Address): 1abcdefghijklmnopqrstuvwxyzABCDEF

180 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

181 

182 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

183 return IPv6Address(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

184 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF

185 raise PydanticCustomError('ip_v6_address', 'Input is not a valid IPv6 address') 1abcdefghijklmnopqrstuvwxyzABCDEF

186 

187 

188def ip_v4_network_validator(input_value: Any, /) -> IPv4Network: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

189 """Assume IPv4Network initialised with a default `strict` argument. 

190 

191 See more: 

192 https://docs.python.org/library/ipaddress.html#ipaddress.IPv4Network 

193 """ 

194 if isinstance(input_value, IPv4Network): 1abcdefghijklmnopqrstuvwxyzABCDEF

195 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

196 

197 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

198 return IPv4Network(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

199 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF

200 raise PydanticCustomError('ip_v4_network', 'Input is not a valid IPv4 network') 1abcdefghijklmnopqrstuvwxyzABCDEF

201 

202 

203def ip_v6_network_validator(input_value: Any, /) -> IPv6Network: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

204 """Assume IPv6Network initialised with a default `strict` argument. 

205 

206 See more: 

207 https://docs.python.org/library/ipaddress.html#ipaddress.IPv6Network 

208 """ 

209 if isinstance(input_value, IPv6Network): 1abcdefghijklmnopqrstuvwxyzABCDEF

210 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

211 

212 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

213 return IPv6Network(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

214 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF

215 raise PydanticCustomError('ip_v6_network', 'Input is not a valid IPv6 network') 1abcdefghijklmnopqrstuvwxyzABCDEF

216 

217 

218def ip_v4_interface_validator(input_value: Any, /) -> IPv4Interface: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

219 if isinstance(input_value, IPv4Interface): 1abcdefghijklmnopqrstuvwxyzABCDEF

220 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

221 

222 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

223 return IPv4Interface(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

224 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF

225 raise PydanticCustomError('ip_v4_interface', 'Input is not a valid IPv4 interface') 1abcdefghijklmnopqrstuvwxyzABCDEF

226 

227 

228def ip_v6_interface_validator(input_value: Any, /) -> IPv6Interface: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

229 if isinstance(input_value, IPv6Interface): 1abcdefghijklmnopqrstuvwxyzABCDEF

230 return input_value 1abcdefghijklmnopqrstuvwxyzABCDEF

231 

232 try: 1abcdefghijklmnopqrstuvwxyzABCDEF

233 return IPv6Interface(input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

234 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF

235 raise PydanticCustomError('ip_v6_interface', 'Input is not a valid IPv6 interface') 1abcdefghijklmnopqrstuvwxyzABCDEF

236 

237 

238def greater_than_validator(x: Any, gt: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

239 if not (x > gt): 1abcdefghijklmnopqrstuvwxyzABCDEF

240 raise PydanticKnownError('greater_than', {'gt': gt}) 1abcdefghijklmnopqrstuvwxyzABCDEF

241 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

242 

243 

244def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

245 if not (x >= ge): 1abcdefghijklmnopqrstuvwxyzABCDEF

246 raise PydanticKnownError('greater_than_equal', {'ge': ge}) 1abcdefghijklmnopqrstuvwxyzABCDEF

247 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

248 

249 

250def less_than_validator(x: Any, lt: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

251 if not (x < lt): 1abcdefghijklmnopqrstuvwxyzABCDEF

252 raise PydanticKnownError('less_than', {'lt': lt}) 1abcdefghijklmnopqrstuvwxyzABCDEF

253 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

254 

255 

256def less_than_or_equal_validator(x: Any, le: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

257 if not (x <= le): 1abcdefghijklmnopqrstuvwxyzABCDEF

258 raise PydanticKnownError('less_than_equal', {'le': le}) 1abcdefghijklmnopqrstuvwxyzABCDEF

259 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

260 

261 

262def multiple_of_validator(x: Any, multiple_of: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

263 if not (x % multiple_of == 0): 1abcdefghijklmnopqrstuvwxyzABCDEF

264 raise PydanticKnownError('multiple_of', {'multiple_of': multiple_of}) 1abcdefghijklmnopqrstuvwxyzABCDEF

265 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

266 

267 

268def min_length_validator(x: Any, min_length: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

269 if not (len(x) >= min_length): 1abcdefghijklmnopqrstuvwxyzABCDEF

270 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEF

271 'too_short', 

272 {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)}, 

273 ) 

274 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

275 

276 

277def max_length_validator(x: Any, max_length: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

278 if len(x) > max_length: 1abcdefghijklmnopqrstuvwxyzABCDEF

279 raise PydanticKnownError( 1abcdefghijklmnopqrstuvwxyzABCDEF

280 'too_long', 

281 {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)}, 

282 ) 

283 return x 1abcdefghijklmnopqrstuvwxyzABCDEF

284 

285 

286def forbid_inf_nan_check(x: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

287 if not math.isfinite(x): 1abcdefghijklmnopqrstuvwxyzABCDEF

288 raise PydanticKnownError('finite_number') 1abcdefghijklmnopqrstuvwxyzABCDEF

289 return x 1abcdefghijklmnopqrstuvwxyzABCDEF