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, slots=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 interface: The interface to check for. 

175 :type interface: ``zope.interface.Interface`` 

176 

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

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

179 value it got. 

180 """ 

181 return _ProvidesValidator(interface) 

182 

183 

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

185class _OptionalValidator(object): 

186 validator = attrib() 

187 

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

189 if value is None: 

190 return 

191 

192 self.validator(inst, attr, value) 

193 

194 def __repr__(self): 

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

196 what=repr(self.validator) 

197 ) 

198 

199 

200def optional(validator): 

201 """ 

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

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

204 the sub-validator. 

205 

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

207 non-``None`` values. 

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

209 

210 .. versionadded:: 15.1.0 

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

212 """ 

213 if isinstance(validator, list): 

214 return _OptionalValidator(_AndValidator(validator)) 

215 return _OptionalValidator(validator) 

216 

217 

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

219class _InValidator(object): 

220 options = attrib() 

221 

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

223 try: 

224 in_options = value in self.options 

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

226 in_options = False 

227 

228 if not in_options: 

229 raise ValueError( 

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

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

232 ) 

233 ) 

234 

235 def __repr__(self): 

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

237 options=self.options 

238 ) 

239 

240 

241def in_(options): 

242 """ 

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

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

245 performed using ``value in options``. 

246 

247 :param options: Allowed options. 

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

249 

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

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

252 got. 

253 

254 .. versionadded:: 17.1.0 

255 """ 

256 return _InValidator(options) 

257 

258 

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

260class _IsCallableValidator(object): 

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

262 """ 

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

264 """ 

265 if not callable(value): 

266 message = ( 

267 "'{name}' must be callable " 

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

269 ) 

270 raise NotCallableError( 

271 msg=message.format( 

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

273 ), 

274 value=value, 

275 ) 

276 

277 def __repr__(self): 

278 return "<is_callable validator>" 

279 

280 

281def is_callable(): 

282 """ 

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

284 initializer is called with a value for this particular attribute 

285 that is not callable. 

286 

287 .. versionadded:: 19.1.0 

288 

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

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

291 and the value it got. 

292 """ 

293 return _IsCallableValidator() 

294 

295 

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

297class _DeepIterable(object): 

298 member_validator = attrib(validator=is_callable()) 

299 iterable_validator = attrib( 

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

301 ) 

302 

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

304 """ 

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

306 """ 

307 if self.iterable_validator is not None: 

308 self.iterable_validator(inst, attr, value) 

309 

310 for member in value: 

311 self.member_validator(inst, attr, member) 

312 

313 def __repr__(self): 

314 iterable_identifier = ( 

315 "" 

316 if self.iterable_validator is None 

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

318 ) 

319 return ( 

320 "<deep_iterable validator for{iterable_identifier}" 

321 " iterables of {member!r}>" 

322 ).format( 

323 iterable_identifier=iterable_identifier, 

324 member=self.member_validator, 

325 ) 

326 

327 

328def deep_iterable(member_validator, iterable_validator=None): 

329 """ 

330 A validator that performs deep validation of an iterable. 

331 

332 :param member_validator: Validator to apply to iterable members 

333 :param iterable_validator: Validator to apply to iterable itself 

334 (optional) 

335 

336 .. versionadded:: 19.1.0 

337 

338 :raises TypeError: if any sub-validators fail 

339 """ 

340 return _DeepIterable(member_validator, iterable_validator) 

341 

342 

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

344class _DeepMapping(object): 

345 key_validator = attrib(validator=is_callable()) 

346 value_validator = attrib(validator=is_callable()) 

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

348 

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

350 """ 

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

352 """ 

353 if self.mapping_validator is not None: 

354 self.mapping_validator(inst, attr, value) 

355 

356 for key in value: 

357 self.key_validator(inst, attr, key) 

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

359 

360 def __repr__(self): 

361 return ( 

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

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

364 

365 

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

367 """ 

368 A validator that performs deep validation of a dictionary. 

369 

370 :param key_validator: Validator to apply to dictionary keys 

371 :param value_validator: Validator to apply to dictionary values 

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

373 attribute (optional) 

374 

375 .. versionadded:: 19.1.0 

376 

377 :raises TypeError: if any sub-validators fail 

378 """ 

379 return _DeepMapping(key_validator, value_validator, mapping_validator)