Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/attr/validators.py: 53%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

211 statements  

1# SPDX-License-Identifier: MIT 

2 

3""" 

4Commonly useful validators. 

5""" 

6 

7 

8import operator 

9import re 

10 

11from contextlib import contextmanager 

12from re import Pattern 

13 

14from ._config import get_run_validators, set_run_validators 

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

16from .converters import default_if_none 

17from .exceptions import NotCallableError 

18 

19 

20__all__ = [ 

21 "and_", 

22 "deep_iterable", 

23 "deep_mapping", 

24 "disabled", 

25 "ge", 

26 "get_disabled", 

27 "gt", 

28 "in_", 

29 "instance_of", 

30 "is_callable", 

31 "le", 

32 "lt", 

33 "matches_re", 

34 "max_len", 

35 "min_len", 

36 "not_", 

37 "optional", 

38 "provides", 

39 "set_disabled", 

40] 

41 

42 

43def set_disabled(disabled): 

44 """ 

45 Globally disable or enable running validators. 

46 

47 By default, they are run. 

48 

49 :param disabled: If ``True``, disable running all validators. 

50 :type disabled: bool 

51 

52 .. warning:: 

53 

54 This function is not thread-safe! 

55 

56 .. versionadded:: 21.3.0 

57 """ 

58 set_run_validators(not disabled) 

59 

60 

61def get_disabled(): 

62 """ 

63 Return a bool indicating whether validators are currently disabled or not. 

64 

65 :return: ``True`` if validators are currently disabled. 

66 :rtype: bool 

67 

68 .. versionadded:: 21.3.0 

69 """ 

70 return not get_run_validators() 

71 

72 

73@contextmanager 

74def disabled(): 

75 """ 

76 Context manager that disables running validators within its context. 

77 

78 .. warning:: 

79 

80 This context manager is not thread-safe! 

81 

82 .. versionadded:: 21.3.0 

83 """ 

84 set_run_validators(False) 

85 try: 

86 yield 

87 finally: 

88 set_run_validators(True) 

89 

90 

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

92class _InstanceOfValidator: 

93 type = attrib() 

94 

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

96 """ 

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

98 """ 

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

100 msg = "'{name}' must be {type!r} (got {value!r} that is a {actual!r}).".format( 

101 name=attr.name, 

102 type=self.type, 

103 actual=value.__class__, 

104 value=value, 

105 ) 

106 raise TypeError( 

107 msg, 

108 attr, 

109 self.type, 

110 value, 

111 ) 

112 

113 def __repr__(self): 

114 return f"<instance_of validator for type {self.type!r}>" 

115 

116 

117def instance_of(type): 

118 """ 

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

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

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

122 

123 :param type: The type to check for. 

124 :type type: type or tuple of type 

125 

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

127 (of type `attrs.Attribute`), the expected type, and the value it 

128 got. 

129 """ 

130 return _InstanceOfValidator(type) 

131 

132 

133@attrs(repr=False, frozen=True, slots=True) 

134class _MatchesReValidator: 

135 pattern = attrib() 

136 match_func = attrib() 

137 

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

139 """ 

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

141 """ 

142 if not self.match_func(value): 

143 msg = "'{name}' must match regex {pattern!r} ({value!r} doesn't)".format( 

144 name=attr.name, pattern=self.pattern.pattern, value=value 

145 ) 

146 raise ValueError( 

147 msg, 

148 attr, 

149 self.pattern, 

150 value, 

151 ) 

152 

153 def __repr__(self): 

154 return f"<matches_re validator for pattern {self.pattern!r}>" 

155 

156 

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

158 r""" 

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

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

161 

162 :param regex: a regex string or precompiled pattern to match against 

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

164 (default 0) 

165 :param callable func: which underlying `re` function to call. Valid options 

166 are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` 

167 means `re.fullmatch`. For performance reasons, the pattern is always 

168 precompiled using `re.compile`. 

169 

170 .. versionadded:: 19.2.0 

171 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. 

172 """ 

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

174 if func not in valid_funcs: 

175 msg = "'func' must be one of {}.".format( 

176 ", ".join( 

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

178 ) 

179 ) 

180 raise ValueError(msg) 

181 

182 if isinstance(regex, Pattern): 

183 if flags: 

184 msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead" 

185 raise TypeError(msg) 

186 pattern = regex 

187 else: 

188 pattern = re.compile(regex, flags) 

189 

190 if func is re.match: 

191 match_func = pattern.match 

192 elif func is re.search: 

193 match_func = pattern.search 

194 else: 

195 match_func = pattern.fullmatch 

196 

197 return _MatchesReValidator(pattern, match_func) 

198 

199 

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

201class _ProvidesValidator: 

202 interface = attrib() 

203 

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

205 """ 

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

207 """ 

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

209 msg = "'{name}' must provide {interface!r} which {value!r} doesn't.".format( 

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

211 ) 

212 raise TypeError( 

213 msg, 

214 attr, 

215 self.interface, 

216 value, 

217 ) 

218 

219 def __repr__(self): 

220 return f"<provides validator for interface {self.interface!r}>" 

221 

222 

223def provides(interface): 

224 """ 

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

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

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

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

229 

230 :param interface: The interface to check for. 

231 :type interface: ``zope.interface.Interface`` 

232 

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

234 (of type `attrs.Attribute`), the expected interface, and the 

235 value it got. 

236 

237 .. deprecated:: 23.1.0 

238 """ 

239 import warnings 

240 

241 warnings.warn( 

242 "attrs's zope-interface support is deprecated and will be removed in, " 

243 "or after, April 2024.", 

244 DeprecationWarning, 

245 stacklevel=2, 

246 ) 

247 return _ProvidesValidator(interface) 

248 

249 

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

251class _OptionalValidator: 

252 validator = attrib() 

253 

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

255 if value is None: 

256 return 

257 

258 self.validator(inst, attr, value) 

259 

260 def __repr__(self): 

261 return f"<optional validator for {self.validator!r} or None>" 

262 

263 

264def optional(validator): 

265 """ 

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

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

268 the sub-validator. 

269 

270 :param Callable | tuple[Callable] | list[Callable] validator: A validator 

271 (or validators) that is used for non-``None`` values. 

272 

273 .. versionadded:: 15.1.0 

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

275 .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. 

276 """ 

277 if isinstance(validator, (list, tuple)): 

278 return _OptionalValidator(_AndValidator(validator)) 

279 

280 return _OptionalValidator(validator) 

281 

282 

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

284class _InValidator: 

285 options = attrib() 

286 

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

288 try: 

289 in_options = value in self.options 

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

291 in_options = False 

292 

293 if not in_options: 

294 msg = f"'{attr.name}' must be in {self.options!r} (got {value!r})" 

295 raise ValueError( 

296 msg, 

297 attr, 

298 self.options, 

299 value, 

300 ) 

301 

302 def __repr__(self): 

303 return f"<in_ validator with options {self.options!r}>" 

304 

305 

306def in_(options): 

307 """ 

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

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

310 performed using ``value in options``. 

311 

312 :param options: Allowed options. 

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

314 

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

316 type `attrs.Attribute`), the expected options, and the value it 

317 got. 

318 

319 .. versionadded:: 17.1.0 

320 .. versionchanged:: 22.1.0 

321 The ValueError was incomplete until now and only contained the human 

322 readable error message. Now it contains all the information that has 

323 been promised since 17.1.0. 

324 """ 

325 return _InValidator(options) 

326 

327 

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

329class _IsCallableValidator: 

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

331 """ 

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

333 """ 

334 if not callable(value): 

335 message = ( 

336 "'{name}' must be callable " 

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

338 ) 

339 raise NotCallableError( 

340 msg=message.format( 

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

342 ), 

343 value=value, 

344 ) 

345 

346 def __repr__(self): 

347 return "<is_callable validator>" 

348 

349 

350def is_callable(): 

351 """ 

352 A validator that raises a `attrs.exceptions.NotCallableError` if the 

353 initializer is called with a value for this particular attribute 

354 that is not callable. 

355 

356 .. versionadded:: 19.1.0 

357 

358 :raises attrs.exceptions.NotCallableError: With a human readable error 

359 message containing the attribute (`attrs.Attribute`) name, 

360 and the value it got. 

361 """ 

362 return _IsCallableValidator() 

363 

364 

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

366class _DeepIterable: 

367 member_validator = attrib(validator=is_callable()) 

368 iterable_validator = attrib( 

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

370 ) 

371 

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

373 """ 

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

375 """ 

376 if self.iterable_validator is not None: 

377 self.iterable_validator(inst, attr, value) 

378 

379 for member in value: 

380 self.member_validator(inst, attr, member) 

381 

382 def __repr__(self): 

383 iterable_identifier = ( 

384 "" 

385 if self.iterable_validator is None 

386 else f" {self.iterable_validator!r}" 

387 ) 

388 return ( 

389 f"<deep_iterable validator for{iterable_identifier}" 

390 f" iterables of {self.member_validator!r}>" 

391 ) 

392 

393 

394def deep_iterable(member_validator, iterable_validator=None): 

395 """ 

396 A validator that performs deep validation of an iterable. 

397 

398 :param member_validator: Validator(s) to apply to iterable members 

399 :param iterable_validator: Validator to apply to iterable itself 

400 (optional) 

401 

402 .. versionadded:: 19.1.0 

403 

404 :raises TypeError: if any sub-validators fail 

405 """ 

406 if isinstance(member_validator, (list, tuple)): 

407 member_validator = and_(*member_validator) 

408 return _DeepIterable(member_validator, iterable_validator) 

409 

410 

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

412class _DeepMapping: 

413 key_validator = attrib(validator=is_callable()) 

414 value_validator = attrib(validator=is_callable()) 

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

416 

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

418 """ 

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

420 """ 

421 if self.mapping_validator is not None: 

422 self.mapping_validator(inst, attr, value) 

423 

424 for key in value: 

425 self.key_validator(inst, attr, key) 

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

427 

428 def __repr__(self): 

429 return ( 

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

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

432 

433 

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

435 """ 

436 A validator that performs deep validation of a dictionary. 

437 

438 :param key_validator: Validator to apply to dictionary keys 

439 :param value_validator: Validator to apply to dictionary values 

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

441 attribute (optional) 

442 

443 .. versionadded:: 19.1.0 

444 

445 :raises TypeError: if any sub-validators fail 

446 """ 

447 return _DeepMapping(key_validator, value_validator, mapping_validator) 

448 

449 

450@attrs(repr=False, frozen=True, slots=True) 

451class _NumberValidator: 

452 bound = attrib() 

453 compare_op = attrib() 

454 compare_func = attrib() 

455 

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

457 """ 

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

459 """ 

460 if not self.compare_func(value, self.bound): 

461 msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}" 

462 raise ValueError(msg) 

463 

464 def __repr__(self): 

465 return f"<Validator for x {self.compare_op} {self.bound}>" 

466 

467 

468def lt(val): 

469 """ 

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

471 with a number larger or equal to *val*. 

472 

473 :param val: Exclusive upper bound for values 

474 

475 .. versionadded:: 21.3.0 

476 """ 

477 return _NumberValidator(val, "<", operator.lt) 

478 

479 

480def le(val): 

481 """ 

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

483 with a number greater than *val*. 

484 

485 :param val: Inclusive upper bound for values 

486 

487 .. versionadded:: 21.3.0 

488 """ 

489 return _NumberValidator(val, "<=", operator.le) 

490 

491 

492def ge(val): 

493 """ 

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

495 with a number smaller than *val*. 

496 

497 :param val: Inclusive lower bound for values 

498 

499 .. versionadded:: 21.3.0 

500 """ 

501 return _NumberValidator(val, ">=", operator.ge) 

502 

503 

504def gt(val): 

505 """ 

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

507 with a number smaller or equal to *val*. 

508 

509 :param val: Exclusive lower bound for values 

510 

511 .. versionadded:: 21.3.0 

512 """ 

513 return _NumberValidator(val, ">", operator.gt) 

514 

515 

516@attrs(repr=False, frozen=True, slots=True) 

517class _MaxLengthValidator: 

518 max_length = attrib() 

519 

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

521 """ 

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

523 """ 

524 if len(value) > self.max_length: 

525 msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}" 

526 raise ValueError(msg) 

527 

528 def __repr__(self): 

529 return f"<max_len validator for {self.max_length}>" 

530 

531 

532def max_len(length): 

533 """ 

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

535 with a string or iterable that is longer than *length*. 

536 

537 :param int length: Maximum length of the string or iterable 

538 

539 .. versionadded:: 21.3.0 

540 """ 

541 return _MaxLengthValidator(length) 

542 

543 

544@attrs(repr=False, frozen=True, slots=True) 

545class _MinLengthValidator: 

546 min_length = attrib() 

547 

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

549 """ 

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

551 """ 

552 if len(value) < self.min_length: 

553 msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" 

554 raise ValueError(msg) 

555 

556 def __repr__(self): 

557 return f"<min_len validator for {self.min_length}>" 

558 

559 

560def min_len(length): 

561 """ 

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

563 with a string or iterable that is shorter than *length*. 

564 

565 :param int length: Minimum length of the string or iterable 

566 

567 .. versionadded:: 22.1.0 

568 """ 

569 return _MinLengthValidator(length) 

570 

571 

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

573class _SubclassOfValidator: 

574 type = attrib() 

575 

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

577 """ 

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

579 """ 

580 if not issubclass(value, self.type): 

581 msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})." 

582 raise TypeError( 

583 msg, 

584 attr, 

585 self.type, 

586 value, 

587 ) 

588 

589 def __repr__(self): 

590 return f"<subclass_of validator for type {self.type!r}>" 

591 

592 

593def _subclass_of(type): 

594 """ 

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

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

597 `issubclass` therefore it's also valid to pass a tuple of types). 

598 

599 :param type: The type to check for. 

600 :type type: type or tuple of types 

601 

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

603 (of type `attrs.Attribute`), the expected type, and the value it 

604 got. 

605 """ 

606 return _SubclassOfValidator(type) 

607 

608 

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

610class _NotValidator: 

611 validator = attrib() 

612 msg = attrib( 

613 converter=default_if_none( 

614 "not_ validator child '{validator!r}' " 

615 "did not raise a captured error" 

616 ) 

617 ) 

618 exc_types = attrib( 

619 validator=deep_iterable( 

620 member_validator=_subclass_of(Exception), 

621 iterable_validator=instance_of(tuple), 

622 ), 

623 ) 

624 

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

626 try: 

627 self.validator(inst, attr, value) 

628 except self.exc_types: 

629 pass # suppress error to invert validity 

630 else: 

631 raise ValueError( 

632 self.msg.format( 

633 validator=self.validator, 

634 exc_types=self.exc_types, 

635 ), 

636 attr, 

637 self.validator, 

638 value, 

639 self.exc_types, 

640 ) 

641 

642 def __repr__(self): 

643 return ( 

644 "<not_ validator wrapping {what!r}, capturing {exc_types!r}>" 

645 ).format( 

646 what=self.validator, 

647 exc_types=self.exc_types, 

648 ) 

649 

650 

651def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): 

652 """ 

653 A validator that wraps and logically 'inverts' the validator passed to it. 

654 It will raise a `ValueError` if the provided validator *doesn't* raise a 

655 `ValueError` or `TypeError` (by default), and will suppress the exception 

656 if the provided validator *does*. 

657 

658 Intended to be used with existing validators to compose logic without 

659 needing to create inverted variants, for example, ``not_(in_(...))``. 

660 

661 :param validator: A validator to be logically inverted. 

662 :param msg: Message to raise if validator fails. 

663 Formatted with keys ``exc_types`` and ``validator``. 

664 :type msg: str 

665 :param exc_types: Exception type(s) to capture. 

666 Other types raised by child validators will not be intercepted and 

667 pass through. 

668 

669 :raises ValueError: With a human readable error message, 

670 the attribute (of type `attrs.Attribute`), 

671 the validator that failed to raise an exception, 

672 the value it got, 

673 and the expected exception types. 

674 

675 .. versionadded:: 22.2.0 

676 """ 

677 try: 

678 exc_types = tuple(exc_types) 

679 except TypeError: 

680 exc_types = (exc_types,) 

681 return _NotValidator(validator, msg, exc_types)