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

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

302 statements  

1import ipaddress 

2import math 

3import re 

4import uuid 

5 

6__all__ = ( 

7 "DataRequired", 

8 "data_required", 

9 "Email", 

10 "email", 

11 "EqualTo", 

12 "equal_to", 

13 "IPAddress", 

14 "ip_address", 

15 "InputRequired", 

16 "input_required", 

17 "Length", 

18 "length", 

19 "NumberRange", 

20 "number_range", 

21 "Optional", 

22 "optional", 

23 "Regexp", 

24 "regexp", 

25 "URL", 

26 "url", 

27 "AnyOf", 

28 "any_of", 

29 "NoneOf", 

30 "none_of", 

31 "MacAddress", 

32 "mac_address", 

33 "UUID", 

34 "ValidationError", 

35 "StopValidation", 

36 "readonly", 

37 "ReadOnly", 

38 "disabled", 

39 "Disabled", 

40) 

41 

42 

43class ValidationError(ValueError): 

44 """ 

45 Raised when a validator fails to validate its input. 

46 """ 

47 

48 def __init__(self, message="", *args, **kwargs): 

49 ValueError.__init__(self, message, *args, **kwargs) 

50 

51 

52class StopValidation(Exception): 

53 """ 

54 Causes the validation chain to stop. 

55 

56 If StopValidation is raised, no more validators in the validation chain are 

57 called. If raised with a message, the message will be added to the errors 

58 list. 

59 """ 

60 

61 def __init__(self, message="", *args, **kwargs): 

62 Exception.__init__(self, message, *args, **kwargs) 

63 

64 

65class EqualTo: 

66 """ 

67 Compares the values of two fields. 

68 

69 :param fieldname: 

70 The name of the other field to compare to. 

71 :param message: 

72 Error message to raise in case of a validation error. Can be 

73 interpolated with `%(other_label)s` and `%(other_name)s` to provide a 

74 more helpful error. 

75 """ 

76 

77 def __init__(self, fieldname, message=None): 

78 self.fieldname = fieldname 

79 self.message = message 

80 

81 def __call__(self, form, field): 

82 try: 

83 other = form[self.fieldname] 

84 except KeyError as exc: 

85 raise ValidationError( 

86 field.gettext("Invalid field name '%s'.") % self.fieldname 

87 ) from exc 

88 if field.data == other.data: 

89 return 

90 

91 d = { 

92 "other_label": hasattr(other, "label") 

93 and other.label.text 

94 or self.fieldname, 

95 "other_name": self.fieldname, 

96 } 

97 message = self.message 

98 if message is None: 

99 message = field.gettext("Field must be equal to %(other_name)s.") 

100 

101 raise ValidationError(message % d) 

102 

103 

104class Length: 

105 """ 

106 Validates the length of a string. 

107 

108 :param min: 

109 The minimum required length of the string. If not provided, minimum 

110 length will not be checked. 

111 :param max: 

112 The maximum length of the string. If not provided, maximum length 

113 will not be checked. 

114 :param message: 

115 Error message to raise in case of a validation error. Can be 

116 interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults 

117 are provided depending on the existence of min and max. 

118 

119 When supported, sets the `minlength` and `maxlength` attributes on widgets. 

120 """ 

121 

122 def __init__(self, min=-1, max=-1, message=None): 

123 assert ( 

124 min != -1 or max != -1 

125 ), "At least one of `min` or `max` must be specified." 

126 assert max == -1 or min <= max, "`min` cannot be more than `max`." 

127 self.min = min 

128 self.max = max 

129 self.message = message 

130 self.field_flags = {} 

131 if self.min != -1: 

132 self.field_flags["minlength"] = self.min 

133 if self.max != -1: 

134 self.field_flags["maxlength"] = self.max 

135 

136 def __call__(self, form, field): 

137 length = field.data and len(field.data) or 0 

138 if length >= self.min and (self.max == -1 or length <= self.max): 

139 return 

140 

141 if self.message is not None: 

142 message = self.message 

143 

144 elif self.max == -1: 

145 message = field.ngettext( 

146 "Field must be at least %(min)d character long.", 

147 "Field must be at least %(min)d characters long.", 

148 self.min, 

149 ) 

150 elif self.min == -1: 

151 message = field.ngettext( 

152 "Field cannot be longer than %(max)d character.", 

153 "Field cannot be longer than %(max)d characters.", 

154 self.max, 

155 ) 

156 elif self.min == self.max: 

157 message = field.ngettext( 

158 "Field must be exactly %(max)d character long.", 

159 "Field must be exactly %(max)d characters long.", 

160 self.max, 

161 ) 

162 else: 

163 message = field.gettext( 

164 "Field must be between %(min)d and %(max)d characters long." 

165 ) 

166 

167 raise ValidationError(message % dict(min=self.min, max=self.max, length=length)) 

168 

169 

170class NumberRange: 

171 """ 

172 Validates that a number is of a minimum and/or maximum value, inclusive. 

173 This will work with any comparable number type, such as floats and 

174 decimals, not just integers. 

175 

176 :param min: 

177 The minimum required value of the number. If not provided, minimum 

178 value will not be checked. 

179 :param max: 

180 The maximum value of the number. If not provided, maximum value 

181 will not be checked. 

182 :param message: 

183 Error message to raise in case of a validation error. Can be 

184 interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults 

185 are provided depending on the existence of min and max. 

186 

187 When supported, sets the `min` and `max` attributes on widgets. 

188 """ 

189 

190 def __init__(self, min=None, max=None, message=None): 

191 self.min = min 

192 self.max = max 

193 self.message = message 

194 self.field_flags = {} 

195 if self.min is not None: 

196 self.field_flags["min"] = self.min 

197 if self.max is not None: 

198 self.field_flags["max"] = self.max 

199 

200 def __call__(self, form, field): 

201 data = field.data 

202 if ( 

203 data is not None 

204 and not math.isnan(data) 

205 and (self.min is None or data >= self.min) 

206 and (self.max is None or data <= self.max) 

207 ): 

208 return 

209 

210 if self.message is not None: 

211 message = self.message 

212 

213 # we use %(min)s interpolation to support floats, None, and 

214 # Decimals without throwing a formatting exception. 

215 elif self.max is None: 

216 message = field.gettext("Number must be at least %(min)s.") 

217 

218 elif self.min is None: 

219 message = field.gettext("Number must be at most %(max)s.") 

220 

221 else: 

222 message = field.gettext("Number must be between %(min)s and %(max)s.") 

223 

224 raise ValidationError(message % dict(min=self.min, max=self.max)) 

225 

226 

227class Optional: 

228 """ 

229 Allows empty input and stops the validation chain from continuing. 

230 

231 If input is empty, also removes prior errors (such as processing errors) 

232 from the field. 

233 

234 :param strip_whitespace: 

235 If True (the default) also stop the validation chain on input which 

236 consists of only whitespace. 

237 

238 Sets the `optional` attribute on widgets. 

239 """ 

240 

241 def __init__(self, strip_whitespace=True): 

242 if strip_whitespace: 

243 self.string_check = lambda s: s.strip() 

244 else: 

245 self.string_check = lambda s: s 

246 

247 self.field_flags = {"optional": True} 

248 

249 def __call__(self, form, field): 

250 if ( 

251 not field.raw_data 

252 or isinstance(field.raw_data[0], str) 

253 and not self.string_check(field.raw_data[0]) 

254 ): 

255 field.errors[:] = [] 

256 raise StopValidation() 

257 

258 

259class DataRequired: 

260 """ 

261 Checks the field's data is 'truthy' otherwise stops the validation chain. 

262 

263 This validator checks that the ``data`` attribute on the field is a 'true' 

264 value (effectively, it does ``if field.data``.) Furthermore, if the data 

265 is a string type, a string containing only whitespace characters is 

266 considered false. 

267 

268 If the data is empty, also removes prior errors (such as processing errors) 

269 from the field. 

270 

271 **NOTE** this validator used to be called `Required` but the way it behaved 

272 (requiring coerced data, not input data) meant it functioned in a way 

273 which was not symmetric to the `Optional` validator and furthermore caused 

274 confusion with certain fields which coerced data to 'falsey' values like 

275 ``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason 

276 exists, we recommend using the :class:`InputRequired` instead. 

277 

278 :param message: 

279 Error message to raise in case of a validation error. 

280 

281 Sets the `required` attribute on widgets. 

282 """ 

283 

284 def __init__(self, message=None): 

285 self.message = message 

286 self.field_flags = {"required": True} 

287 

288 def __call__(self, form, field): 

289 if field.data and (not isinstance(field.data, str) or field.data.strip()): 

290 return 

291 

292 if self.message is None: 

293 message = field.gettext("This field is required.") 

294 else: 

295 message = self.message 

296 

297 field.errors[:] = [] 

298 raise StopValidation(message) 

299 

300 

301class InputRequired: 

302 """ 

303 Validates that input was provided for this field. 

304 

305 Note there is a distinction between this and DataRequired in that 

306 InputRequired looks that form-input data was provided, and DataRequired 

307 looks at the post-coercion data. This means that this validator only checks 

308 whether non-empty data was sent, not whether non-empty data was coerced 

309 from that data. Initially populated data is not considered sent. 

310 

311 Sets the `required` attribute on widgets. 

312 """ 

313 

314 def __init__(self, message=None): 

315 self.message = message 

316 self.field_flags = {"required": True} 

317 

318 def __call__(self, form, field): 

319 if field.raw_data and field.raw_data[0]: 

320 return 

321 

322 if self.message is None: 

323 message = field.gettext("This field is required.") 

324 else: 

325 message = self.message 

326 

327 field.errors[:] = [] 

328 raise StopValidation(message) 

329 

330 

331class Regexp: 

332 """ 

333 Validates the field against a user provided regexp. 

334 

335 :param regex: 

336 The regular expression string to use. Can also be a compiled regular 

337 expression pattern. 

338 :param flags: 

339 The regexp flags to use, for example re.IGNORECASE. Ignored if 

340 `regex` is not a string. 

341 :param message: 

342 Error message to raise in case of a validation error. 

343 """ 

344 

345 def __init__(self, regex, flags=0, message=None): 

346 if isinstance(regex, str): 

347 regex = re.compile(regex, flags) 

348 self.regex = regex 

349 self.message = message 

350 

351 def __call__(self, form, field, message=None): 

352 match = self.regex.match(field.data or "") 

353 if match: 

354 return match 

355 

356 if message is None: 

357 if self.message is None: 

358 message = field.gettext("Invalid input.") 

359 else: 

360 message = self.message 

361 

362 raise ValidationError(message) 

363 

364 

365class Email: 

366 """ 

367 Validates an email address. Requires email_validator package to be 

368 installed. For ex: pip install wtforms[email]. 

369 

370 :param message: 

371 Error message to raise in case of a validation error. 

372 :param granular_message: 

373 Use validation failed message from email_validator library 

374 (Default False). 

375 :param check_deliverability: 

376 Perform domain name resolution check (Default False). 

377 :param allow_smtputf8: 

378 Fail validation for addresses that would require SMTPUTF8 

379 (Default True). 

380 :param allow_empty_local: 

381 Allow an empty local part (i.e. @example.com), e.g. for validating 

382 Postfix aliases (Default False). 

383 """ 

384 

385 def __init__( 

386 self, 

387 message=None, 

388 granular_message=False, 

389 check_deliverability=False, 

390 allow_smtputf8=True, 

391 allow_empty_local=False, 

392 ): 

393 self.message = message 

394 self.granular_message = granular_message 

395 self.check_deliverability = check_deliverability 

396 self.allow_smtputf8 = allow_smtputf8 

397 self.allow_empty_local = allow_empty_local 

398 

399 def __call__(self, form, field): 

400 try: 

401 import email_validator 

402 except ImportError as exc: # pragma: no cover 

403 raise Exception( 

404 "Install 'email_validator' for email validation support." 

405 ) from exc 

406 

407 try: 

408 if field.data is None: 

409 raise email_validator.EmailNotValidError() 

410 email_validator.validate_email( 

411 field.data, 

412 check_deliverability=self.check_deliverability, 

413 allow_smtputf8=self.allow_smtputf8, 

414 allow_empty_local=self.allow_empty_local, 

415 ) 

416 except email_validator.EmailNotValidError as e: 

417 message = self.message 

418 if message is None: 

419 if self.granular_message: 

420 message = field.gettext(e) 

421 else: 

422 message = field.gettext("Invalid email address.") 

423 raise ValidationError(message) from e 

424 

425 

426class IPAddress: 

427 """ 

428 Validates an IP address. 

429 

430 :param ipv4: 

431 If True, accept IPv4 addresses as valid (default True) 

432 :param ipv6: 

433 If True, accept IPv6 addresses as valid (default False) 

434 :param message: 

435 Error message to raise in case of a validation error. 

436 """ 

437 

438 def __init__(self, ipv4=True, ipv6=False, message=None): 

439 if not ipv4 and not ipv6: 

440 raise ValueError( 

441 "IP Address Validator must have at least one of ipv4 or ipv6 enabled." 

442 ) 

443 self.ipv4 = ipv4 

444 self.ipv6 = ipv6 

445 self.message = message 

446 

447 def __call__(self, form, field): 

448 value = field.data 

449 valid = False 

450 if value: 

451 valid = (self.ipv4 and self.check_ipv4(value)) or ( 

452 self.ipv6 and self.check_ipv6(value) 

453 ) 

454 

455 if valid: 

456 return 

457 

458 message = self.message 

459 if message is None: 

460 message = field.gettext("Invalid IP address.") 

461 raise ValidationError(message) 

462 

463 @classmethod 

464 def check_ipv4(cls, value): 

465 try: 

466 address = ipaddress.ip_address(value) 

467 except ValueError: 

468 return False 

469 

470 if not isinstance(address, ipaddress.IPv4Address): 

471 return False 

472 

473 return True 

474 

475 @classmethod 

476 def check_ipv6(cls, value): 

477 try: 

478 address = ipaddress.ip_address(value) 

479 except ValueError: 

480 return False 

481 

482 if not isinstance(address, ipaddress.IPv6Address): 

483 return False 

484 

485 return True 

486 

487 

488class MacAddress(Regexp): 

489 """ 

490 Validates a MAC address. 

491 

492 :param message: 

493 Error message to raise in case of a validation error. 

494 """ 

495 

496 def __init__(self, message=None): 

497 pattern = r"^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" 

498 super().__init__(pattern, message=message) 

499 

500 def __call__(self, form, field): 

501 message = self.message 

502 if message is None: 

503 message = field.gettext("Invalid Mac address.") 

504 

505 super().__call__(form, field, message) 

506 

507 

508class URL(Regexp): 

509 """ 

510 Simple regexp based url validation. Much like the email validator, you 

511 probably want to validate the url later by other means if the url must 

512 resolve. 

513 

514 :param require_tld: 

515 If true, then the domain-name portion of the URL must contain a .tld 

516 suffix. Set this to false if you want to allow domains like 

517 `localhost`. 

518 :param allow_ip: 

519 If false, then give ip as host will fail validation 

520 :param message: 

521 Error message to raise in case of a validation error. 

522 """ 

523 

524 def __init__(self, require_tld=True, allow_ip=True, message=None): 

525 regex = ( 

526 r"^[a-z]+://" 

527 r"(?P<host>[^\/\?:]+)" 

528 r"(?P<port>:[0-9]+)?" 

529 r"(?P<path>\/.*?)?" 

530 r"(?P<query>\?.*)?$" 

531 ) 

532 super().__init__(regex, re.IGNORECASE, message) 

533 self.validate_hostname = HostnameValidation( 

534 require_tld=require_tld, allow_ip=allow_ip 

535 ) 

536 

537 def __call__(self, form, field): 

538 message = self.message 

539 if message is None: 

540 message = field.gettext("Invalid URL.") 

541 

542 match = super().__call__(form, field, message) 

543 if not self.validate_hostname(match.group("host")): 

544 raise ValidationError(message) 

545 

546 

547class UUID: 

548 """ 

549 Validates a UUID. 

550 

551 :param message: 

552 Error message to raise in case of a validation error. 

553 """ 

554 

555 def __init__(self, message=None): 

556 self.message = message 

557 

558 def __call__(self, form, field): 

559 message = self.message 

560 if message is None: 

561 message = field.gettext("Invalid UUID.") 

562 try: 

563 uuid.UUID(field.data) 

564 except ValueError as exc: 

565 raise ValidationError(message) from exc 

566 

567 

568class AnyOf: 

569 """ 

570 Compares the incoming data to a sequence of valid inputs. 

571 

572 :param values: 

573 A sequence of valid inputs. 

574 :param message: 

575 Error message to raise in case of a validation error. `%(values)s` 

576 contains the list of values. 

577 :param values_formatter: 

578 Function used to format the list of values in the error message. 

579 """ 

580 

581 def __init__(self, values, message=None, values_formatter=None): 

582 self.values = values 

583 self.message = message 

584 if values_formatter is None: 

585 values_formatter = self.default_values_formatter 

586 self.values_formatter = values_formatter 

587 

588 def __call__(self, form, field): 

589 data = field.data if isinstance(field.data, list) else [field.data] 

590 if any(d in self.values for d in data): 

591 return 

592 

593 message = self.message 

594 if message is None: 

595 message = field.gettext("Invalid value, must be one of: %(values)s.") 

596 

597 raise ValidationError(message % dict(values=self.values_formatter(self.values))) 

598 

599 @staticmethod 

600 def default_values_formatter(values): 

601 return ", ".join(str(x) for x in values) 

602 

603 

604class NoneOf: 

605 """ 

606 Compares the incoming data to a sequence of invalid inputs. 

607 

608 :param values: 

609 A sequence of invalid inputs. 

610 :param message: 

611 Error message to raise in case of a validation error. `%(values)s` 

612 contains the list of values. 

613 :param values_formatter: 

614 Function used to format the list of values in the error message. 

615 """ 

616 

617 def __init__(self, values, message=None, values_formatter=None): 

618 self.values = values 

619 self.message = message 

620 if values_formatter is None: 

621 values_formatter = self.default_values_formatter 

622 self.values_formatter = values_formatter 

623 

624 def __call__(self, form, field): 

625 data = field.data if isinstance(field.data, list) else [field.data] 

626 if not any(d in self.values for d in data): 

627 return 

628 

629 message = self.message 

630 if message is None: 

631 message = field.gettext("Invalid value, can't be any of: %(values)s.") 

632 

633 raise ValidationError(message % dict(values=self.values_formatter(self.values))) 

634 

635 @staticmethod 

636 def default_values_formatter(v): 

637 return ", ".join(str(x) for x in v) 

638 

639 

640class HostnameValidation: 

641 """ 

642 Helper class for checking hostnames for validation. 

643 

644 This is not a validator in and of itself, and as such is not exported. 

645 """ 

646 

647 hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE) 

648 tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE) 

649 

650 def __init__(self, require_tld=True, allow_ip=False): 

651 self.require_tld = require_tld 

652 self.allow_ip = allow_ip 

653 

654 def __call__(self, hostname): 

655 if self.allow_ip and ( 

656 IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname) 

657 ): 

658 return True 

659 

660 # Encode out IDNA hostnames. This makes further validation easier. 

661 try: 

662 hostname = hostname.encode("idna") 

663 except UnicodeError: 

664 pass 

665 

666 # Turn back into a string in Python 3x 

667 if not isinstance(hostname, str): 

668 hostname = hostname.decode("ascii") 

669 

670 if len(hostname) > 253: 

671 return False 

672 

673 # Check that all labels in the hostname are valid 

674 parts = hostname.split(".") 

675 for part in parts: 

676 if not part or len(part) > 63: 

677 return False 

678 if not self.hostname_part.match(part): 

679 return False 

680 

681 if self.require_tld and (len(parts) < 2 or not self.tld_part.match(parts[-1])): 

682 return False 

683 

684 return True 

685 

686 

687class ReadOnly: 

688 """ 

689 Set a field readonly. 

690 

691 Validation fails if the form data is different than the 

692 field object data, or if unset, from the field default data. 

693 """ 

694 

695 def __init__(self): 

696 self.field_flags = {"readonly": True} 

697 

698 def __call__(self, form, field): 

699 if field.data != field.object_data: 

700 raise ValidationError(field.gettext("This field cannot be edited.")) 

701 

702 

703class Disabled: 

704 """ 

705 Set a field disabled. 

706 

707 Validation fails if the form data has any value. 

708 """ 

709 

710 def __init__(self): 

711 self.field_flags = {"disabled": True} 

712 

713 def __call__(self, form, field): 

714 if field.raw_data is not None: 

715 raise ValidationError( 

716 field.gettext("This field is disabled and cannot have a value.") 

717 ) 

718 

719 

720email = Email 

721equal_to = EqualTo 

722ip_address = IPAddress 

723mac_address = MacAddress 

724length = Length 

725number_range = NumberRange 

726optional = Optional 

727input_required = InputRequired 

728data_required = DataRequired 

729regexp = Regexp 

730url = URL 

731any_of = AnyOf 

732none_of = NoneOf 

733readonly = ReadOnly 

734disabled = Disabled