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

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

236 statements  

1# SPDX-License-Identifier: MIT 

2 

3""" 

4Commonly useful validators. 

5""" 

6 

7import operator 

8import re 

9 

10from contextlib import contextmanager 

11from re import Pattern 

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 

19__all__ = [ 

20 "and_", 

21 "deep_iterable", 

22 "deep_mapping", 

23 "disabled", 

24 "ge", 

25 "get_disabled", 

26 "gt", 

27 "in_", 

28 "instance_of", 

29 "is_callable", 

30 "le", 

31 "lt", 

32 "matches_re", 

33 "max_len", 

34 "min_len", 

35 "not_", 

36 "optional", 

37 "or_", 

38 "set_disabled", 

39] 

40 

41 

42def set_disabled(disabled): 

43 """ 

44 Globally disable or enable running validators. 

45 

46 By default, they are run. 

47 

48 Args: 

49 disabled (bool): If `True`, disable running all validators. 

50 

51 .. warning:: 

52 

53 This function is not thread-safe! 

54 

55 .. versionadded:: 21.3.0 

56 """ 

57 set_run_validators(not disabled) 

58 

59 

60def get_disabled(): 

61 """ 

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

63 

64 Returns: 

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

66 

67 .. versionadded:: 21.3.0 

68 """ 

69 return not get_run_validators() 

70 

71 

72@contextmanager 

73def disabled(): 

74 """ 

75 Context manager that disables running validators within its context. 

76 

77 .. warning:: 

78 

79 This context manager is not thread-safe! 

80 

81 .. versionadded:: 21.3.0 

82 .. versionchanged:: 26.1.0 The contextmanager is nestable. 

83 """ 

84 prev = get_run_validators() 

85 set_run_validators(False) 

86 try: 

87 yield 

88 finally: 

89 set_run_validators(prev) 

90 

91 

92@attrs(repr=False, slots=True, unsafe_hash=True) 

93class _InstanceOfValidator: 

94 type = attrib() 

95 

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

97 """ 

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

99 """ 

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

101 msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})." 

102 raise TypeError( 

103 msg, 

104 attr, 

105 self.type, 

106 value, 

107 ) 

108 

109 def __repr__(self): 

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

111 

112 

113def instance_of(type): 

114 """ 

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

116 wrong type for this particular attribute (checks are performed using 

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

118 

119 Args: 

120 type (type | tuple[type]): The type to check for. 

121 

122 Raises: 

123 TypeError: 

124 With a human readable error message, the attribute (of type 

125 `attrs.Attribute`), the expected type, and the value it got. 

126 """ 

127 return _InstanceOfValidator(type) 

128 

129 

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

131class _MatchesReValidator: 

132 pattern = attrib() 

133 match_func = attrib() 

134 

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

136 """ 

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

138 """ 

139 if not self.match_func(value): 

140 msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)" 

141 raise ValueError( 

142 msg, 

143 attr, 

144 self.pattern, 

145 value, 

146 ) 

147 

148 def __repr__(self): 

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

150 

151 

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

153 r""" 

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

155 string that doesn't match *regex*. 

156 

157 Args: 

158 regex (str, re.Pattern): 

159 A regex string or precompiled pattern to match against 

160 

161 flags (int): 

162 Flags that will be passed to the underlying re function (default 0) 

163 

164 func (typing.Callable): 

165 Which underlying `re` function to call. Valid options are 

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

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

168 always 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, unsafe_hash=True) 

201class _OptionalValidator: 

202 validator = attrib() 

203 

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

205 if value is None: 

206 return 

207 

208 self.validator(inst, attr, value) 

209 

210 def __repr__(self): 

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

212 

213 

214def optional(validator): 

215 """ 

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

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

218 the sub-validator. 

219 

220 Args: 

221 validator 

222 (typing.Callable | tuple[typing.Callable] | list[typing.Callable]): 

223 A validator (or validators) that is used for non-`None` values. 

224 

225 .. versionadded:: 15.1.0 

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

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

228 """ 

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

230 return _OptionalValidator(_AndValidator(validator)) 

231 

232 return _OptionalValidator(validator) 

233 

234 

235@attrs(repr=False, slots=True, unsafe_hash=True) 

236class _InValidator: 

237 options = attrib() 

238 _original_options = attrib(hash=False) 

239 

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

241 try: 

242 in_options = value in self.options 

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

244 in_options = False 

245 

246 if not in_options: 

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

248 raise ValueError( 

249 msg, 

250 attr, 

251 self._original_options, 

252 value, 

253 ) 

254 

255 def __repr__(self): 

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

257 

258 

259def in_(options): 

260 """ 

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

262 value that does not belong in the *options* provided. 

263 

264 The check is performed using ``value in options``, so *options* has to 

265 support that operation. 

266 

267 To keep the validator hashable, dicts, lists, and sets are transparently 

268 transformed into a `tuple`. 

269 

270 Args: 

271 options: Allowed options. 

272 

273 Raises: 

274 ValueError: 

275 With a human readable error message, the attribute (of type 

276 `attrs.Attribute`), the expected options, and the value it got. 

277 

278 .. versionadded:: 17.1.0 

279 .. versionchanged:: 22.1.0 

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

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

282 been promised since 17.1.0. 

283 .. versionchanged:: 24.1.0 

284 *options* that are a list, dict, or a set are now transformed into a 

285 tuple to keep the validator hashable. 

286 """ 

287 repr_options = options 

288 if isinstance(options, (list, dict, set)): 

289 options = tuple(options) 

290 

291 return _InValidator(options, repr_options) 

292 

293 

294@attrs(repr=False, slots=False, unsafe_hash=True) 

295class _IsCallableValidator: 

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

297 """ 

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

299 """ 

300 if not callable(value): 

301 message = ( 

302 "'{name}' must be callable " 

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

304 ) 

305 raise NotCallableError( 

306 msg=message.format( 

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

308 ), 

309 value=value, 

310 ) 

311 

312 def __repr__(self): 

313 return "<is_callable validator>" 

314 

315 

316def is_callable(): 

317 """ 

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

319 initializer is called with a value for this particular attribute that is 

320 not callable. 

321 

322 .. versionadded:: 19.1.0 

323 

324 Raises: 

325 attrs.exceptions.NotCallableError: 

326 With a human readable error message containing the attribute 

327 (`attrs.Attribute`) name, and the value it got. 

328 """ 

329 return _IsCallableValidator() 

330 

331 

332@attrs(repr=False, slots=True, unsafe_hash=True) 

333class _DeepIterable: 

334 member_validator = attrib(validator=is_callable()) 

335 iterable_validator = attrib( 

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

337 ) 

338 

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

340 """ 

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

342 """ 

343 if self.iterable_validator is not None: 

344 self.iterable_validator(inst, attr, value) 

345 

346 for member in value: 

347 self.member_validator(inst, attr, member) 

348 

349 def __repr__(self): 

350 iterable_identifier = ( 

351 "" 

352 if self.iterable_validator is None 

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

354 ) 

355 return ( 

356 f"<deep_iterable validator for{iterable_identifier}" 

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

358 ) 

359 

360 

361def deep_iterable(member_validator, iterable_validator=None): 

362 """ 

363 A validator that performs deep validation of an iterable. 

364 

365 Args: 

366 member_validator: Validator(s) to apply to iterable members. 

367 

368 iterable_validator: 

369 Validator(s) to apply to iterable itself (optional). 

370 

371 Raises 

372 TypeError: if any sub-validators fail 

373 

374 .. versionadded:: 19.1.0 

375 

376 .. versionchanged:: 25.4.0 

377 *member_validator* and *iterable_validator* can now be a list or tuple 

378 of validators. 

379 """ 

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

381 member_validator = and_(*member_validator) 

382 if isinstance(iterable_validator, (list, tuple)): 

383 iterable_validator = and_(*iterable_validator) 

384 return _DeepIterable(member_validator, iterable_validator) 

385 

386 

387@attrs(repr=False, slots=True, unsafe_hash=True) 

388class _DeepMapping: 

389 key_validator = attrib(validator=optional(is_callable())) 

390 value_validator = attrib(validator=optional(is_callable())) 

391 mapping_validator = attrib(validator=optional(is_callable())) 

392 

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

394 """ 

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

396 """ 

397 if self.mapping_validator is not None: 

398 self.mapping_validator(inst, attr, value) 

399 

400 for key in value: 

401 if self.key_validator is not None: 

402 self.key_validator(inst, attr, key) 

403 if self.value_validator is not None: 

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

405 

406 def __repr__(self): 

407 return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>" 

408 

409 

410def deep_mapping( 

411 key_validator=None, value_validator=None, mapping_validator=None 

412): 

413 """ 

414 A validator that performs deep validation of a dictionary. 

415 

416 All validators are optional, but at least one of *key_validator* or 

417 *value_validator* must be provided. 

418 

419 Args: 

420 key_validator: Validator(s) to apply to dictionary keys. 

421 

422 value_validator: Validator(s) to apply to dictionary values. 

423 

424 mapping_validator: 

425 Validator(s) to apply to top-level mapping attribute. 

426 

427 .. versionadded:: 19.1.0 

428 

429 .. versionchanged:: 25.4.0 

430 *key_validator* and *value_validator* are now optional, but at least one 

431 of them must be provided. 

432 

433 .. versionchanged:: 25.4.0 

434 *key_validator*, *value_validator*, and *mapping_validator* can now be a 

435 list or tuple of validators. 

436 

437 Raises: 

438 TypeError: If any sub-validator fails on validation. 

439 

440 ValueError: 

441 If neither *key_validator* nor *value_validator* is provided on 

442 instantiation. 

443 """ 

444 if key_validator is None and value_validator is None: 

445 msg = ( 

446 "At least one of key_validator or value_validator must be provided" 

447 ) 

448 raise ValueError(msg) 

449 

450 if isinstance(key_validator, (list, tuple)): 

451 key_validator = and_(*key_validator) 

452 if isinstance(value_validator, (list, tuple)): 

453 value_validator = and_(*value_validator) 

454 if isinstance(mapping_validator, (list, tuple)): 

455 mapping_validator = and_(*mapping_validator) 

456 

457 return _DeepMapping(key_validator, value_validator, mapping_validator) 

458 

459 

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

461class _NumberValidator: 

462 bound = attrib() 

463 compare_op = attrib() 

464 compare_func = attrib() 

465 

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

467 """ 

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

469 """ 

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

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

472 raise ValueError(msg) 

473 

474 def __repr__(self): 

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

476 

477 

478def lt(val): 

479 """ 

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

481 number larger or equal to *val*. 

482 

483 The validator uses `operator.lt` to compare the values. 

484 

485 Args: 

486 val: Exclusive upper bound for values. 

487 

488 .. versionadded:: 21.3.0 

489 """ 

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

491 

492 

493def le(val): 

494 """ 

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

496 number greater than *val*. 

497 

498 The validator uses `operator.le` to compare the values. 

499 

500 Args: 

501 val: Inclusive upper bound for values. 

502 

503 .. versionadded:: 21.3.0 

504 """ 

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

506 

507 

508def ge(val): 

509 """ 

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

511 number smaller than *val*. 

512 

513 The validator uses `operator.ge` to compare the values. 

514 

515 Args: 

516 val: Inclusive lower bound for values 

517 

518 .. versionadded:: 21.3.0 

519 """ 

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

521 

522 

523def gt(val): 

524 """ 

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

526 number smaller or equal to *val*. 

527 

528 The validator uses `operator.gt` to compare the values. 

529 

530 Args: 

531 val: Exclusive lower bound for values 

532 

533 .. versionadded:: 21.3.0 

534 """ 

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

536 

537 

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

539class _MaxLengthValidator: 

540 max_length = attrib() 

541 

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

543 """ 

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

545 """ 

546 if len(value) > self.max_length: 

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

548 raise ValueError(msg) 

549 

550 def __repr__(self): 

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

552 

553 

554def max_len(length): 

555 """ 

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

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

558 

559 Args: 

560 length (int): Maximum length of the string or iterable 

561 

562 .. versionadded:: 21.3.0 

563 """ 

564 return _MaxLengthValidator(length) 

565 

566 

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

568class _MinLengthValidator: 

569 min_length = attrib() 

570 

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

572 """ 

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

574 """ 

575 if len(value) < self.min_length: 

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

577 raise ValueError(msg) 

578 

579 def __repr__(self): 

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

581 

582 

583def min_len(length): 

584 """ 

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

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

587 

588 Args: 

589 length (int): Minimum length of the string or iterable 

590 

591 .. versionadded:: 22.1.0 

592 """ 

593 return _MinLengthValidator(length) 

594 

595 

596@attrs(repr=False, slots=True, unsafe_hash=True) 

597class _SubclassOfValidator: 

598 type = attrib() 

599 

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

601 """ 

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

603 """ 

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

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

606 raise TypeError( 

607 msg, 

608 attr, 

609 self.type, 

610 value, 

611 ) 

612 

613 def __repr__(self): 

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

615 

616 

617def _subclass_of(type): 

618 """ 

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

620 wrong type for this particular attribute (checks are performed using 

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

622 

623 Args: 

624 type (type | tuple[type, ...]): The type(s) to check for. 

625 

626 Raises: 

627 TypeError: 

628 With a human readable error message, the attribute (of type 

629 `attrs.Attribute`), the expected type, and the value it got. 

630 """ 

631 return _SubclassOfValidator(type) 

632 

633 

634@attrs(repr=False, slots=True, unsafe_hash=True) 

635class _NotValidator: 

636 validator = attrib() 

637 msg = attrib( 

638 converter=default_if_none( 

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

640 "did not raise a captured error" 

641 ) 

642 ) 

643 exc_types = attrib( 

644 validator=deep_iterable( 

645 member_validator=_subclass_of(Exception), 

646 iterable_validator=instance_of(tuple), 

647 ), 

648 ) 

649 

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

651 try: 

652 self.validator(inst, attr, value) 

653 except self.exc_types: 

654 pass # suppress error to invert validity 

655 else: 

656 raise ValueError( 

657 self.msg.format( 

658 validator=self.validator, 

659 exc_types=self.exc_types, 

660 ), 

661 attr, 

662 self.validator, 

663 value, 

664 self.exc_types, 

665 ) 

666 

667 def __repr__(self): 

668 return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>" 

669 

670 

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

672 """ 

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

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

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

676 if the provided validator *does*. 

677 

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

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

680 

681 Args: 

682 validator: A validator to be logically inverted. 

683 

684 msg (str): 

685 Message to raise if validator fails. Formatted with keys 

686 ``exc_types`` and ``validator``. 

687 

688 exc_types (tuple[type, ...]): 

689 Exception type(s) to capture. Other types raised by child 

690 validators will not be intercepted and pass through. 

691 

692 Raises: 

693 ValueError: 

694 With a human readable error message, the attribute (of type 

695 `attrs.Attribute`), the validator that failed to raise an 

696 exception, the value it got, and the expected exception types. 

697 

698 .. versionadded:: 22.2.0 

699 """ 

700 try: 

701 exc_types = tuple(exc_types) 

702 except TypeError: 

703 exc_types = (exc_types,) 

704 return _NotValidator(validator, msg, exc_types) 

705 

706 

707@attrs(repr=False, slots=True, unsafe_hash=True) 

708class _OrValidator: 

709 validators = attrib() 

710 

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

712 for v in self.validators: 

713 try: 

714 v(inst, attr, value) 

715 except Exception: # noqa: BLE001, PERF203, S112 

716 continue 

717 else: 

718 return 

719 

720 msg = f"None of {self.validators!r} satisfied for value {value!r}" 

721 raise ValueError(msg) 

722 

723 def __repr__(self): 

724 return f"<or validator wrapping {self.validators!r}>" 

725 

726 

727def or_(*validators): 

728 """ 

729 A validator that composes multiple validators into one. 

730 

731 When called on a value, it runs all wrapped validators until one of them is 

732 satisfied. 

733 

734 Args: 

735 validators (~collections.abc.Iterable[typing.Callable]): 

736 Arbitrary number of validators. 

737 

738 Raises: 

739 ValueError: 

740 If no validator is satisfied. Raised with a human-readable error 

741 message listing all the wrapped validators and the value that 

742 failed all of them. 

743 

744 .. versionadded:: 24.1.0 

745 """ 

746 vals = [] 

747 for v in validators: 

748 vals.extend(v.validators if isinstance(v, _OrValidator) else [v]) 

749 

750 return _OrValidator(tuple(vals))