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

202 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# SPDX-License-Identifier: MIT 

2 

3""" 

4Commonly useful validators. 

5""" 

6 

7 

8import operator 

9import re 

10 

11from contextlib import contextmanager 

12 

13from ._config import get_run_validators, set_run_validators 

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

15from .converters import default_if_none 

16from .exceptions import NotCallableError 

17 

18 

19try: 

20 Pattern = re.Pattern 

21except AttributeError: # Python <3.7 lacks a Pattern type. 

22 Pattern = type(re.compile("")) 

23 

24 

25__all__ = [ 

26 "and_", 

27 "deep_iterable", 

28 "deep_mapping", 

29 "disabled", 

30 "ge", 

31 "get_disabled", 

32 "gt", 

33 "in_", 

34 "instance_of", 

35 "is_callable", 

36 "le", 

37 "lt", 

38 "matches_re", 

39 "max_len", 

40 "min_len", 

41 "not_", 

42 "optional", 

43 "provides", 

44 "set_disabled", 

45] 

46 

47 

48def set_disabled(disabled): 

49 """ 

50 Globally disable or enable running validators. 

51 

52 By default, they are run. 

53 

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

55 :type disabled: bool 

56 

57 .. warning:: 

58 

59 This function is not thread-safe! 

60 

61 .. versionadded:: 21.3.0 

62 """ 

63 set_run_validators(not disabled) 

64 

65 

66def get_disabled(): 

67 """ 

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

69 

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

71 :rtype: bool 

72 

73 .. versionadded:: 21.3.0 

74 """ 

75 return not get_run_validators() 

76 

77 

78@contextmanager 

79def disabled(): 

80 """ 

81 Context manager that disables running validators within its context. 

82 

83 .. warning:: 

84 

85 This context manager is not thread-safe! 

86 

87 .. versionadded:: 21.3.0 

88 """ 

89 set_run_validators(False) 

90 try: 

91 yield 

92 finally: 

93 set_run_validators(True) 

94 

95 

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

97class _InstanceOfValidator: 

98 type = attrib() 

99 

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

101 """ 

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

103 """ 

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

105 raise TypeError( 

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

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

108 name=attr.name, 

109 type=self.type, 

110 actual=value.__class__, 

111 value=value, 

112 ), 

113 attr, 

114 self.type, 

115 value, 

116 ) 

117 

118 def __repr__(self): 

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

120 type=self.type 

121 ) 

122 

123 

124def instance_of(type): 

125 """ 

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

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

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

129 

130 :param type: The type to check for. 

131 :type type: type or tuple of type 

132 

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

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

135 got. 

136 """ 

137 return _InstanceOfValidator(type) 

138 

139 

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

141class _MatchesReValidator: 

142 pattern = attrib() 

143 match_func = attrib() 

144 

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

146 """ 

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

148 """ 

149 if not self.match_func(value): 

150 raise ValueError( 

151 "'{name}' must match regex {pattern!r}" 

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

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

154 ), 

155 attr, 

156 self.pattern, 

157 value, 

158 ) 

159 

160 def __repr__(self): 

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

162 pattern=self.pattern 

163 ) 

164 

165 

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

167 r""" 

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

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

170 

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

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

173 (default 0) 

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

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

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

177 precompiled using `re.compile`. 

178 

179 .. versionadded:: 19.2.0 

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

181 """ 

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

183 if func not in valid_funcs: 

184 raise ValueError( 

185 "'func' must be one of {}.".format( 

186 ", ".join( 

187 sorted( 

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

189 ) 

190 ) 

191 ) 

192 ) 

193 

194 if isinstance(regex, Pattern): 

195 if flags: 

196 raise TypeError( 

197 "'flags' can only be used with a string pattern; " 

198 "pass flags to re.compile() instead" 

199 ) 

200 pattern = regex 

201 else: 

202 pattern = re.compile(regex, flags) 

203 

204 if func is re.match: 

205 match_func = pattern.match 

206 elif func is re.search: 

207 match_func = pattern.search 

208 else: 

209 match_func = pattern.fullmatch 

210 

211 return _MatchesReValidator(pattern, match_func) 

212 

213 

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

215class _ProvidesValidator: 

216 interface = attrib() 

217 

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

219 """ 

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

221 """ 

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

223 raise TypeError( 

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

225 "doesn't.".format( 

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

227 ), 

228 attr, 

229 self.interface, 

230 value, 

231 ) 

232 

233 def __repr__(self): 

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

235 interface=self.interface 

236 ) 

237 

238 

239def provides(interface): 

240 """ 

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

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

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

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

245 

246 :param interface: The interface to check for. 

247 :type interface: ``zope.interface.Interface`` 

248 

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

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

251 value it got. 

252 """ 

253 return _ProvidesValidator(interface) 

254 

255 

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

257class _OptionalValidator: 

258 validator = attrib() 

259 

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

261 if value is None: 

262 return 

263 

264 self.validator(inst, attr, value) 

265 

266 def __repr__(self): 

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

268 what=repr(self.validator) 

269 ) 

270 

271 

272def optional(validator): 

273 """ 

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

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

276 the sub-validator. 

277 

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

279 non-``None`` values. 

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

281 

282 .. versionadded:: 15.1.0 

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

284 """ 

285 if isinstance(validator, list): 

286 return _OptionalValidator(_AndValidator(validator)) 

287 return _OptionalValidator(validator) 

288 

289 

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

291class _InValidator: 

292 options = attrib() 

293 

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

295 try: 

296 in_options = value in self.options 

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

298 in_options = False 

299 

300 if not in_options: 

301 raise ValueError( 

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

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

304 ), 

305 attr, 

306 self.options, 

307 value, 

308 ) 

309 

310 def __repr__(self): 

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

312 options=self.options 

313 ) 

314 

315 

316def in_(options): 

317 """ 

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

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

320 performed using ``value in options``. 

321 

322 :param options: Allowed options. 

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

324 

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

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

327 got. 

328 

329 .. versionadded:: 17.1.0 

330 .. versionchanged:: 22.1.0 

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

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

333 been promised since 17.1.0. 

334 """ 

335 return _InValidator(options) 

336 

337 

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

339class _IsCallableValidator: 

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

341 """ 

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

343 """ 

344 if not callable(value): 

345 message = ( 

346 "'{name}' must be callable " 

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

348 ) 

349 raise NotCallableError( 

350 msg=message.format( 

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

352 ), 

353 value=value, 

354 ) 

355 

356 def __repr__(self): 

357 return "<is_callable validator>" 

358 

359 

360def is_callable(): 

361 """ 

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

363 initializer is called with a value for this particular attribute 

364 that is not callable. 

365 

366 .. versionadded:: 19.1.0 

367 

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

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

370 and the value it got. 

371 """ 

372 return _IsCallableValidator() 

373 

374 

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

376class _DeepIterable: 

377 member_validator = attrib(validator=is_callable()) 

378 iterable_validator = attrib( 

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

380 ) 

381 

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

383 """ 

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

385 """ 

386 if self.iterable_validator is not None: 

387 self.iterable_validator(inst, attr, value) 

388 

389 for member in value: 

390 self.member_validator(inst, attr, member) 

391 

392 def __repr__(self): 

393 iterable_identifier = ( 

394 "" 

395 if self.iterable_validator is None 

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

397 ) 

398 return ( 

399 "<deep_iterable validator for{iterable_identifier}" 

400 " iterables of {member!r}>" 

401 ).format( 

402 iterable_identifier=iterable_identifier, 

403 member=self.member_validator, 

404 ) 

405 

406 

407def deep_iterable(member_validator, iterable_validator=None): 

408 """ 

409 A validator that performs deep validation of an iterable. 

410 

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

412 :param iterable_validator: Validator to apply to iterable itself 

413 (optional) 

414 

415 .. versionadded:: 19.1.0 

416 

417 :raises TypeError: if any sub-validators fail 

418 """ 

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

420 member_validator = and_(*member_validator) 

421 return _DeepIterable(member_validator, iterable_validator) 

422 

423 

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

425class _DeepMapping: 

426 key_validator = attrib(validator=is_callable()) 

427 value_validator = attrib(validator=is_callable()) 

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

429 

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

431 """ 

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

433 """ 

434 if self.mapping_validator is not None: 

435 self.mapping_validator(inst, attr, value) 

436 

437 for key in value: 

438 self.key_validator(inst, attr, key) 

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

440 

441 def __repr__(self): 

442 return ( 

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

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

445 

446 

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

448 """ 

449 A validator that performs deep validation of a dictionary. 

450 

451 :param key_validator: Validator to apply to dictionary keys 

452 :param value_validator: Validator to apply to dictionary values 

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

454 attribute (optional) 

455 

456 .. versionadded:: 19.1.0 

457 

458 :raises TypeError: if any sub-validators fail 

459 """ 

460 return _DeepMapping(key_validator, value_validator, mapping_validator) 

461 

462 

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

464class _NumberValidator: 

465 bound = attrib() 

466 compare_op = attrib() 

467 compare_func = attrib() 

468 

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

470 """ 

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

472 """ 

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

474 raise ValueError( 

475 "'{name}' must be {op} {bound}: {value}".format( 

476 name=attr.name, 

477 op=self.compare_op, 

478 bound=self.bound, 

479 value=value, 

480 ) 

481 ) 

482 

483 def __repr__(self): 

484 return "<Validator for x {op} {bound}>".format( 

485 op=self.compare_op, bound=self.bound 

486 ) 

487 

488 

489def lt(val): 

490 """ 

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

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

493 

494 :param val: Exclusive upper bound for values 

495 

496 .. versionadded:: 21.3.0 

497 """ 

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

499 

500 

501def le(val): 

502 """ 

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

504 with a number greater than *val*. 

505 

506 :param val: Inclusive upper bound for values 

507 

508 .. versionadded:: 21.3.0 

509 """ 

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

511 

512 

513def ge(val): 

514 """ 

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

516 with a number smaller than *val*. 

517 

518 :param val: Inclusive lower bound for values 

519 

520 .. versionadded:: 21.3.0 

521 """ 

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

523 

524 

525def gt(val): 

526 """ 

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

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

529 

530 :param val: Exclusive lower bound for values 

531 

532 .. versionadded:: 21.3.0 

533 """ 

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

535 

536 

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

538class _MaxLengthValidator: 

539 max_length = attrib() 

540 

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

542 """ 

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

544 """ 

545 if len(value) > self.max_length: 

546 raise ValueError( 

547 "Length of '{name}' must be <= {max}: {len}".format( 

548 name=attr.name, max=self.max_length, len=len(value) 

549 ) 

550 ) 

551 

552 def __repr__(self): 

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

554 

555 

556def max_len(length): 

557 """ 

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

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

560 

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

562 

563 .. versionadded:: 21.3.0 

564 """ 

565 return _MaxLengthValidator(length) 

566 

567 

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

569class _MinLengthValidator: 

570 min_length = attrib() 

571 

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

573 """ 

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

575 """ 

576 if len(value) < self.min_length: 

577 raise ValueError( 

578 "Length of '{name}' must be => {min}: {len}".format( 

579 name=attr.name, min=self.min_length, len=len(value) 

580 ) 

581 ) 

582 

583 def __repr__(self): 

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

585 

586 

587def min_len(length): 

588 """ 

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

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

591 

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

593 

594 .. versionadded:: 22.1.0 

595 """ 

596 return _MinLengthValidator(length) 

597 

598 

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

600class _SubclassOfValidator: 

601 type = attrib() 

602 

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

604 """ 

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

606 """ 

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

608 raise TypeError( 

609 "'{name}' must be a subclass of {type!r} " 

610 "(got {value!r}).".format( 

611 name=attr.name, 

612 type=self.type, 

613 value=value, 

614 ), 

615 attr, 

616 self.type, 

617 value, 

618 ) 

619 

620 def __repr__(self): 

621 return "<subclass_of validator for type {type!r}>".format( 

622 type=self.type 

623 ) 

624 

625 

626def _subclass_of(type): 

627 """ 

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

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

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

631 

632 :param type: The type to check for. 

633 :type type: type or tuple of types 

634 

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

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

637 got. 

638 """ 

639 return _SubclassOfValidator(type) 

640 

641 

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

643class _NotValidator: 

644 validator = attrib() 

645 msg = attrib( 

646 converter=default_if_none( 

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

648 "did not raise a captured error" 

649 ) 

650 ) 

651 exc_types = attrib( 

652 validator=deep_iterable( 

653 member_validator=_subclass_of(Exception), 

654 iterable_validator=instance_of(tuple), 

655 ), 

656 ) 

657 

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

659 try: 

660 self.validator(inst, attr, value) 

661 except self.exc_types: 

662 pass # suppress error to invert validity 

663 else: 

664 raise ValueError( 

665 self.msg.format( 

666 validator=self.validator, 

667 exc_types=self.exc_types, 

668 ), 

669 attr, 

670 self.validator, 

671 value, 

672 self.exc_types, 

673 ) 

674 

675 def __repr__(self): 

676 return ( 

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

678 ).format( 

679 what=self.validator, 

680 exc_types=self.exc_types, 

681 ) 

682 

683 

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

685 """ 

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

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

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

689 if the provided validator *does*. 

690 

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

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

693 

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

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

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

697 :type msg: str 

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

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

700 pass through. 

701 

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

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

704 the validator that failed to raise an exception, 

705 the value it got, 

706 and the expected exception types. 

707 

708 .. versionadded:: 22.2.0 

709 """ 

710 try: 

711 exc_types = tuple(exc_types) 

712 except TypeError: 

713 exc_types = (exc_types,) 

714 return _NotValidator(validator, msg, exc_types)