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

201 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

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 raise TypeError( 

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

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

103 name=attr.name, 

104 type=self.type, 

105 actual=value.__class__, 

106 value=value, 

107 ), 

108 attr, 

109 self.type, 

110 value, 

111 ) 

112 

113 def __repr__(self): 

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

115 type=self.type 

116 ) 

117 

118 

119def instance_of(type): 

120 """ 

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

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

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

124 

125 :param type: The type to check for. 

126 :type type: type or tuple of type 

127 

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

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

130 got. 

131 """ 

132 return _InstanceOfValidator(type) 

133 

134 

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

136class _MatchesReValidator: 

137 pattern = attrib() 

138 match_func = attrib() 

139 

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

141 """ 

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

143 """ 

144 if not self.match_func(value): 

145 raise ValueError( 

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

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

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

149 ), 

150 attr, 

151 self.pattern, 

152 value, 

153 ) 

154 

155 def __repr__(self): 

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

157 pattern=self.pattern 

158 ) 

159 

160 

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

162 r""" 

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

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

165 

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

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

168 (default 0) 

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

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

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

172 precompiled using `re.compile`. 

173 

174 .. versionadded:: 19.2.0 

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

176 """ 

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

178 if func not in valid_funcs: 

179 raise ValueError( 

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

181 ", ".join( 

182 sorted( 

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

184 ) 

185 ) 

186 ) 

187 ) 

188 

189 if isinstance(regex, Pattern): 

190 if flags: 

191 raise TypeError( 

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

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

194 ) 

195 pattern = regex 

196 else: 

197 pattern = re.compile(regex, flags) 

198 

199 if func is re.match: 

200 match_func = pattern.match 

201 elif func is re.search: 

202 match_func = pattern.search 

203 else: 

204 match_func = pattern.fullmatch 

205 

206 return _MatchesReValidator(pattern, match_func) 

207 

208 

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

210class _ProvidesValidator: 

211 interface = attrib() 

212 

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

214 """ 

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

216 """ 

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

218 raise TypeError( 

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

220 "doesn't.".format( 

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

222 ), 

223 attr, 

224 self.interface, 

225 value, 

226 ) 

227 

228 def __repr__(self): 

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

230 interface=self.interface 

231 ) 

232 

233 

234def provides(interface): 

235 """ 

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

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

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

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

240 

241 :param interface: The interface to check for. 

242 :type interface: ``zope.interface.Interface`` 

243 

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

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

246 value it got. 

247 

248 .. deprecated:: 23.1.0 

249 """ 

250 import warnings 

251 

252 warnings.warn( 

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

254 "or after, April 2024.", 

255 DeprecationWarning, 

256 stacklevel=2, 

257 ) 

258 return _ProvidesValidator(interface) 

259 

260 

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

262class _OptionalValidator: 

263 validator = attrib() 

264 

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

266 if value is None: 

267 return 

268 

269 self.validator(inst, attr, value) 

270 

271 def __repr__(self): 

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

273 what=repr(self.validator) 

274 ) 

275 

276 

277def optional(validator): 

278 """ 

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

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

281 the sub-validator. 

282 

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

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

285 

286 .. versionadded:: 15.1.0 

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

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

289 """ 

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

291 return _OptionalValidator(_AndValidator(validator)) 

292 

293 return _OptionalValidator(validator) 

294 

295 

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

297class _InValidator: 

298 options = attrib() 

299 

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

301 try: 

302 in_options = value in self.options 

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

304 in_options = False 

305 

306 if not in_options: 

307 raise ValueError( 

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

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

310 ), 

311 attr, 

312 self.options, 

313 value, 

314 ) 

315 

316 def __repr__(self): 

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

318 options=self.options 

319 ) 

320 

321 

322def in_(options): 

323 """ 

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

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

326 performed using ``value in options``. 

327 

328 :param options: Allowed options. 

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

330 

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

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

333 got. 

334 

335 .. versionadded:: 17.1.0 

336 .. versionchanged:: 22.1.0 

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

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

339 been promised since 17.1.0. 

340 """ 

341 return _InValidator(options) 

342 

343 

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

345class _IsCallableValidator: 

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

347 """ 

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

349 """ 

350 if not callable(value): 

351 message = ( 

352 "'{name}' must be callable " 

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

354 ) 

355 raise NotCallableError( 

356 msg=message.format( 

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

358 ), 

359 value=value, 

360 ) 

361 

362 def __repr__(self): 

363 return "<is_callable validator>" 

364 

365 

366def is_callable(): 

367 """ 

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

369 initializer is called with a value for this particular attribute 

370 that is not callable. 

371 

372 .. versionadded:: 19.1.0 

373 

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

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

376 and the value it got. 

377 """ 

378 return _IsCallableValidator() 

379 

380 

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

382class _DeepIterable: 

383 member_validator = attrib(validator=is_callable()) 

384 iterable_validator = attrib( 

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

386 ) 

387 

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

389 """ 

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

391 """ 

392 if self.iterable_validator is not None: 

393 self.iterable_validator(inst, attr, value) 

394 

395 for member in value: 

396 self.member_validator(inst, attr, member) 

397 

398 def __repr__(self): 

399 iterable_identifier = ( 

400 "" 

401 if self.iterable_validator is None 

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

403 ) 

404 return ( 

405 "<deep_iterable validator for{iterable_identifier}" 

406 " iterables of {member!r}>" 

407 ).format( 

408 iterable_identifier=iterable_identifier, 

409 member=self.member_validator, 

410 ) 

411 

412 

413def deep_iterable(member_validator, iterable_validator=None): 

414 """ 

415 A validator that performs deep validation of an iterable. 

416 

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

418 :param iterable_validator: Validator to apply to iterable itself 

419 (optional) 

420 

421 .. versionadded:: 19.1.0 

422 

423 :raises TypeError: if any sub-validators fail 

424 """ 

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

426 member_validator = and_(*member_validator) 

427 return _DeepIterable(member_validator, iterable_validator) 

428 

429 

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

431class _DeepMapping: 

432 key_validator = attrib(validator=is_callable()) 

433 value_validator = attrib(validator=is_callable()) 

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

435 

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

437 """ 

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

439 """ 

440 if self.mapping_validator is not None: 

441 self.mapping_validator(inst, attr, value) 

442 

443 for key in value: 

444 self.key_validator(inst, attr, key) 

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

446 

447 def __repr__(self): 

448 return ( 

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

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

451 

452 

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

454 """ 

455 A validator that performs deep validation of a dictionary. 

456 

457 :param key_validator: Validator to apply to dictionary keys 

458 :param value_validator: Validator to apply to dictionary values 

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

460 attribute (optional) 

461 

462 .. versionadded:: 19.1.0 

463 

464 :raises TypeError: if any sub-validators fail 

465 """ 

466 return _DeepMapping(key_validator, value_validator, mapping_validator) 

467 

468 

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

470class _NumberValidator: 

471 bound = attrib() 

472 compare_op = attrib() 

473 compare_func = attrib() 

474 

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

476 """ 

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

478 """ 

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

480 raise ValueError( 

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

482 name=attr.name, 

483 op=self.compare_op, 

484 bound=self.bound, 

485 value=value, 

486 ) 

487 ) 

488 

489 def __repr__(self): 

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

491 op=self.compare_op, bound=self.bound 

492 ) 

493 

494 

495def lt(val): 

496 """ 

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

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

499 

500 :param val: Exclusive upper bound for values 

501 

502 .. versionadded:: 21.3.0 

503 """ 

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

505 

506 

507def le(val): 

508 """ 

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

510 with a number greater than *val*. 

511 

512 :param val: Inclusive upper bound for values 

513 

514 .. versionadded:: 21.3.0 

515 """ 

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

517 

518 

519def ge(val): 

520 """ 

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

522 with a number smaller than *val*. 

523 

524 :param val: Inclusive lower bound for values 

525 

526 .. versionadded:: 21.3.0 

527 """ 

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

529 

530 

531def gt(val): 

532 """ 

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

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

535 

536 :param val: Exclusive lower bound for values 

537 

538 .. versionadded:: 21.3.0 

539 """ 

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

541 

542 

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

544class _MaxLengthValidator: 

545 max_length = attrib() 

546 

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

548 """ 

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

550 """ 

551 if len(value) > self.max_length: 

552 raise ValueError( 

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

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

555 ) 

556 ) 

557 

558 def __repr__(self): 

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

560 

561 

562def max_len(length): 

563 """ 

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

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

566 

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

568 

569 .. versionadded:: 21.3.0 

570 """ 

571 return _MaxLengthValidator(length) 

572 

573 

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

575class _MinLengthValidator: 

576 min_length = attrib() 

577 

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

579 """ 

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

581 """ 

582 if len(value) < self.min_length: 

583 raise ValueError( 

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

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

586 ) 

587 ) 

588 

589 def __repr__(self): 

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

591 

592 

593def min_len(length): 

594 """ 

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

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

597 

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

599 

600 .. versionadded:: 22.1.0 

601 """ 

602 return _MinLengthValidator(length) 

603 

604 

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

606class _SubclassOfValidator: 

607 type = attrib() 

608 

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

610 """ 

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

612 """ 

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

614 raise TypeError( 

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

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

617 name=attr.name, 

618 type=self.type, 

619 value=value, 

620 ), 

621 attr, 

622 self.type, 

623 value, 

624 ) 

625 

626 def __repr__(self): 

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

628 type=self.type 

629 ) 

630 

631 

632def _subclass_of(type): 

633 """ 

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

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

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

637 

638 :param type: The type to check for. 

639 :type type: type or tuple of types 

640 

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

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

643 got. 

644 """ 

645 return _SubclassOfValidator(type) 

646 

647 

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

649class _NotValidator: 

650 validator = attrib() 

651 msg = attrib( 

652 converter=default_if_none( 

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

654 "did not raise a captured error" 

655 ) 

656 ) 

657 exc_types = attrib( 

658 validator=deep_iterable( 

659 member_validator=_subclass_of(Exception), 

660 iterable_validator=instance_of(tuple), 

661 ), 

662 ) 

663 

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

665 try: 

666 self.validator(inst, attr, value) 

667 except self.exc_types: 

668 pass # suppress error to invert validity 

669 else: 

670 raise ValueError( 

671 self.msg.format( 

672 validator=self.validator, 

673 exc_types=self.exc_types, 

674 ), 

675 attr, 

676 self.validator, 

677 value, 

678 self.exc_types, 

679 ) 

680 

681 def __repr__(self): 

682 return ( 

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

684 ).format( 

685 what=self.validator, 

686 exc_types=self.exc_types, 

687 ) 

688 

689 

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

691 """ 

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

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

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

695 if the provided validator *does*. 

696 

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

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

699 

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

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

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

703 :type msg: str 

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

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

706 pass through. 

707 

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

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

710 the validator that failed to raise an exception, 

711 the value it got, 

712 and the expected exception types. 

713 

714 .. versionadded:: 22.2.0 

715 """ 

716 try: 

717 exc_types = tuple(exc_types) 

718 except TypeError: 

719 exc_types = (exc_types,) 

720 return _NotValidator(validator, msg, exc_types)