Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Commonly useful validators. 

3""" 

4 

5from __future__ import absolute_import, division, print_function 

6 

7import re 

8 

9from ._make import _AndValidator, and_, attrib, attrs 

10from .exceptions import NotCallableError 

11 

12 

13__all__ = [ 

14 "and_", 

15 "deep_iterable", 

16 "deep_mapping", 

17 "in_", 

18 "instance_of", 

19 "is_callable", 

20 "matches_re", 

21 "optional", 

22 "provides", 

23] 

24 

25 

26@attrs(repr=False, slots=True, hash=True) 

27class _InstanceOfValidator(object): 

28 type = attrib() 

29 

30 def __call__(self, inst, attr, value): 

31 """ 

32 We use a callable class to be able to change the ``__repr__``. 

33 """ 

34 if not isinstance(value, self.type): 

35 raise TypeError( 

36 "'{name}' must be {type!r} (got {value!r} that is a " 

37 "{actual!r}).".format( 

38 name=attr.name, 

39 type=self.type, 

40 actual=value.__class__, 

41 value=value, 

42 ), 

43 attr, 

44 self.type, 

45 value, 

46 ) 

47 

48 def __repr__(self): 

49 return "<instance_of validator for type {type!r}>".format( 

50 type=self.type 

51 ) 

52 

53 

54def instance_of(type): 

55 """ 

56 A validator that raises a `TypeError` if the initializer is called 

57 with a wrong type for this particular attribute (checks are performed using 

58 `isinstance` therefore it's also valid to pass a tuple of types). 

59 

60 :param type: The type to check for. 

61 :type type: type or tuple of types 

62 

63 :raises TypeError: With a human readable error message, the attribute 

64 (of type `attr.Attribute`), the expected type, and the value it 

65 got. 

66 """ 

67 return _InstanceOfValidator(type) 

68 

69 

70@attrs(repr=False, frozen=True) 

71class _MatchesReValidator(object): 

72 regex = attrib() 

73 flags = attrib() 

74 match_func = attrib() 

75 

76 def __call__(self, inst, attr, value): 

77 """ 

78 We use a callable class to be able to change the ``__repr__``. 

79 """ 

80 if not self.match_func(value): 

81 raise ValueError( 

82 "'{name}' must match regex {regex!r}" 

83 " ({value!r} doesn't)".format( 

84 name=attr.name, regex=self.regex.pattern, value=value 

85 ), 

86 attr, 

87 self.regex, 

88 value, 

89 ) 

90 

91 def __repr__(self): 

92 return "<matches_re validator for pattern {regex!r}>".format( 

93 regex=self.regex 

94 ) 

95 

96 

97def matches_re(regex, flags=0, func=None): 

98 r""" 

99 A validator that raises `ValueError` if the initializer is called 

100 with a string that doesn't match *regex*. 

101 

102 :param str regex: a regex string to match against 

103 :param int flags: flags that will be passed to the underlying re function 

104 (default 0) 

105 :param callable func: which underlying `re` function to call (options 

106 are `re.fullmatch`, `re.search`, `re.match`, default 

107 is ``None`` which means either `re.fullmatch` or an emulation of 

108 it on Python 2). For performance reasons, they won't be used directly 

109 but on a pre-`re.compile`\ ed pattern. 

110 

111 .. versionadded:: 19.2.0 

112 """ 

113 fullmatch = getattr(re, "fullmatch", None) 

114 valid_funcs = (fullmatch, None, re.search, re.match) 

115 if func not in valid_funcs: 

116 raise ValueError( 

117 "'func' must be one of %s." 

118 % ( 

119 ", ".join( 

120 sorted( 

121 e and e.__name__ or "None" for e in set(valid_funcs) 

122 ) 

123 ), 

124 ) 

125 ) 

126 

127 pattern = re.compile(regex, flags) 

128 if func is re.match: 

129 match_func = pattern.match 

130 elif func is re.search: 

131 match_func = pattern.search 

132 else: 

133 if fullmatch: 

134 match_func = pattern.fullmatch 

135 else: 

136 pattern = re.compile(r"(?:{})\Z".format(regex), flags) 

137 match_func = pattern.match 

138 

139 return _MatchesReValidator(pattern, flags, match_func) 

140 

141 

142@attrs(repr=False, slots=True, hash=True) 

143class _ProvidesValidator(object): 

144 interface = attrib() 

145 

146 def __call__(self, inst, attr, value): 

147 """ 

148 We use a callable class to be able to change the ``__repr__``. 

149 """ 

150 if not self.interface.providedBy(value): 

151 raise TypeError( 

152 "'{name}' must provide {interface!r} which {value!r} " 

153 "doesn't.".format( 

154 name=attr.name, interface=self.interface, value=value 

155 ), 

156 attr, 

157 self.interface, 

158 value, 

159 ) 

160 

161 def __repr__(self): 

162 return "<provides validator for interface {interface!r}>".format( 

163 interface=self.interface 

164 ) 

165 

166 

167def provides(interface): 

168 """ 

169 A validator that raises a `TypeError` if the initializer is called 

170 with an object that does not provide the requested *interface* (checks are 

171 performed using ``interface.providedBy(value)`` (see `zope.interface 

172 <https://zopeinterface.readthedocs.io/en/latest/>`_). 

173 

174 :param zope.interface.Interface interface: The interface to check for. 

175 

176 :raises TypeError: With a human readable error message, the attribute 

177 (of type `attr.Attribute`), the expected interface, and the 

178 value it got. 

179 """ 

180 return _ProvidesValidator(interface) 

181 

182 

183@attrs(repr=False, slots=True, hash=True) 

184class _OptionalValidator(object): 

185 validator = attrib() 

186 

187 def __call__(self, inst, attr, value): 

188 if value is None: 

189 return 

190 

191 self.validator(inst, attr, value) 

192 

193 def __repr__(self): 

194 return "<optional validator for {what} or None>".format( 

195 what=repr(self.validator) 

196 ) 

197 

198 

199def optional(validator): 

200 """ 

201 A validator that makes an attribute optional. An optional attribute is one 

202 which can be set to ``None`` in addition to satisfying the requirements of 

203 the sub-validator. 

204 

205 :param validator: A validator (or a list of validators) that is used for 

206 non-``None`` values. 

207 :type validator: callable or `list` of callables. 

208 

209 .. versionadded:: 15.1.0 

210 .. versionchanged:: 17.1.0 *validator* can be a list of validators. 

211 """ 

212 if isinstance(validator, list): 

213 return _OptionalValidator(_AndValidator(validator)) 

214 return _OptionalValidator(validator) 

215 

216 

217@attrs(repr=False, slots=True, hash=True) 

218class _InValidator(object): 

219 options = attrib() 

220 

221 def __call__(self, inst, attr, value): 

222 try: 

223 in_options = value in self.options 

224 except TypeError: # e.g. `1 in "abc"` 

225 in_options = False 

226 

227 if not in_options: 

228 raise ValueError( 

229 "'{name}' must be in {options!r} (got {value!r})".format( 

230 name=attr.name, options=self.options, value=value 

231 ) 

232 ) 

233 

234 def __repr__(self): 

235 return "<in_ validator with options {options!r}>".format( 

236 options=self.options 

237 ) 

238 

239 

240def in_(options): 

241 """ 

242 A validator that raises a `ValueError` if the initializer is called 

243 with a value that does not belong in the options provided. The check is 

244 performed using ``value in options``. 

245 

246 :param options: Allowed options. 

247 :type options: list, tuple, `enum.Enum`, ... 

248 

249 :raises ValueError: With a human readable error message, the attribute (of 

250 type `attr.Attribute`), the expected options, and the value it 

251 got. 

252 

253 .. versionadded:: 17.1.0 

254 """ 

255 return _InValidator(options) 

256 

257 

258@attrs(repr=False, slots=False, hash=True) 

259class _IsCallableValidator(object): 

260 def __call__(self, inst, attr, value): 

261 """ 

262 We use a callable class to be able to change the ``__repr__``. 

263 """ 

264 if not callable(value): 

265 message = ( 

266 "'{name}' must be callable " 

267 "(got {value!r} that is a {actual!r})." 

268 ) 

269 raise NotCallableError( 

270 msg=message.format( 

271 name=attr.name, value=value, actual=value.__class__ 

272 ), 

273 value=value, 

274 ) 

275 

276 def __repr__(self): 

277 return "<is_callable validator>" 

278 

279 

280def is_callable(): 

281 """ 

282 A validator that raises a `attr.exceptions.NotCallableError` if the 

283 initializer is called with a value for this particular attribute 

284 that is not callable. 

285 

286 .. versionadded:: 19.1.0 

287 

288 :raises `attr.exceptions.NotCallableError`: With a human readable error 

289 message containing the attribute (`attr.Attribute`) name, 

290 and the value it got. 

291 """ 

292 return _IsCallableValidator() 

293 

294 

295@attrs(repr=False, slots=True, hash=True) 

296class _DeepIterable(object): 

297 member_validator = attrib(validator=is_callable()) 

298 iterable_validator = attrib( 

299 default=None, validator=optional(is_callable()) 

300 ) 

301 

302 def __call__(self, inst, attr, value): 

303 """ 

304 We use a callable class to be able to change the ``__repr__``. 

305 """ 

306 if self.iterable_validator is not None: 

307 self.iterable_validator(inst, attr, value) 

308 

309 for member in value: 

310 self.member_validator(inst, attr, member) 

311 

312 def __repr__(self): 

313 iterable_identifier = ( 

314 "" 

315 if self.iterable_validator is None 

316 else " {iterable!r}".format(iterable=self.iterable_validator) 

317 ) 

318 return ( 

319 "<deep_iterable validator for{iterable_identifier}" 

320 " iterables of {member!r}>" 

321 ).format( 

322 iterable_identifier=iterable_identifier, 

323 member=self.member_validator, 

324 ) 

325 

326 

327def deep_iterable(member_validator, iterable_validator=None): 

328 """ 

329 A validator that performs deep validation of an iterable. 

330 

331 :param member_validator: Validator to apply to iterable members 

332 :param iterable_validator: Validator to apply to iterable itself 

333 (optional) 

334 

335 .. versionadded:: 19.1.0 

336 

337 :raises TypeError: if any sub-validators fail 

338 """ 

339 return _DeepIterable(member_validator, iterable_validator) 

340 

341 

342@attrs(repr=False, slots=True, hash=True) 

343class _DeepMapping(object): 

344 key_validator = attrib(validator=is_callable()) 

345 value_validator = attrib(validator=is_callable()) 

346 mapping_validator = attrib(default=None, validator=optional(is_callable())) 

347 

348 def __call__(self, inst, attr, value): 

349 """ 

350 We use a callable class to be able to change the ``__repr__``. 

351 """ 

352 if self.mapping_validator is not None: 

353 self.mapping_validator(inst, attr, value) 

354 

355 for key in value: 

356 self.key_validator(inst, attr, key) 

357 self.value_validator(inst, attr, value[key]) 

358 

359 def __repr__(self): 

360 return ( 

361 "<deep_mapping validator for objects mapping {key!r} to {value!r}>" 

362 ).format(key=self.key_validator, value=self.value_validator) 

363 

364 

365def deep_mapping(key_validator, value_validator, mapping_validator=None): 

366 """ 

367 A validator that performs deep validation of a dictionary. 

368 

369 :param key_validator: Validator to apply to dictionary keys 

370 :param value_validator: Validator to apply to dictionary values 

371 :param mapping_validator: Validator to apply to top-level mapping 

372 attribute (optional) 

373 

374 .. versionadded:: 19.1.0 

375 

376 :raises TypeError: if any sub-validators fail 

377 """ 

378 return _DeepMapping(key_validator, value_validator, mapping_validator)