PageRenderTime 132ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/venv/lib/python2.7/site-packages/wtforms/validators.py

https://gitlab.com/ismailam/openexport
Python | 561 lines | 548 code | 9 blank | 4 comment | 7 complexity | dcaa113ee5209e684c680bbbeb3d3f80 MD5 | raw file
  1. from __future__ import unicode_literals
  2. import re
  3. import warnings
  4. from wtforms.compat import string_types, text_type
  5. __all__ = (
  6. 'DataRequired', 'data_required', 'Email', 'email', 'EqualTo', 'equal_to',
  7. 'IPAddress', 'ip_address', 'InputRequired', 'input_required', 'Length',
  8. 'length', 'NumberRange', 'number_range', 'Optional', 'optional',
  9. 'Required', 'required', 'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
  10. 'any_of', 'NoneOf', 'none_of', 'MacAddress', 'mac_address', 'UUID'
  11. )
  12. class ValidationError(ValueError):
  13. """
  14. Raised when a validator fails to validate its input.
  15. """
  16. def __init__(self, message='', *args, **kwargs):
  17. ValueError.__init__(self, message, *args, **kwargs)
  18. class StopValidation(Exception):
  19. """
  20. Causes the validation chain to stop.
  21. If StopValidation is raised, no more validators in the validation chain are
  22. called. If raised with a message, the message will be added to the errors
  23. list.
  24. """
  25. def __init__(self, message='', *args, **kwargs):
  26. Exception.__init__(self, message, *args, **kwargs)
  27. class EqualTo(object):
  28. """
  29. Compares the values of two fields.
  30. :param fieldname:
  31. The name of the other field to compare to.
  32. :param message:
  33. Error message to raise in case of a validation error. Can be
  34. interpolated with `%(other_label)s` and `%(other_name)s` to provide a
  35. more helpful error.
  36. """
  37. def __init__(self, fieldname, message=None):
  38. self.fieldname = fieldname
  39. self.message = message
  40. def __call__(self, form, field):
  41. try:
  42. other = form[self.fieldname]
  43. except KeyError:
  44. raise ValidationError(field.gettext("Invalid field name '%s'.") % self.fieldname)
  45. if field.data != other.data:
  46. d = {
  47. 'other_label': hasattr(other, 'label') and other.label.text or self.fieldname,
  48. 'other_name': self.fieldname
  49. }
  50. message = self.message
  51. if message is None:
  52. message = field.gettext('Field must be equal to %(other_name)s.')
  53. raise ValidationError(message % d)
  54. class Length(object):
  55. """
  56. Validates the length of a string.
  57. :param min:
  58. The minimum required length of the string. If not provided, minimum
  59. length will not be checked.
  60. :param max:
  61. The maximum length of the string. If not provided, maximum length
  62. will not be checked.
  63. :param message:
  64. Error message to raise in case of a validation error. Can be
  65. interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults
  66. are provided depending on the existence of min and max.
  67. """
  68. def __init__(self, min=-1, max=-1, message=None):
  69. assert min != -1 or max != -1, 'At least one of `min` or `max` must be specified.'
  70. assert max == -1 or min <= max, '`min` cannot be more than `max`.'
  71. self.min = min
  72. self.max = max
  73. self.message = message
  74. def __call__(self, form, field):
  75. l = field.data and len(field.data) or 0
  76. if l < self.min or self.max != -1 and l > self.max:
  77. message = self.message
  78. if message is None:
  79. if self.max == -1:
  80. message = field.ngettext('Field must be at least %(min)d character long.',
  81. 'Field must be at least %(min)d characters long.', self.min)
  82. elif self.min == -1:
  83. message = field.ngettext('Field cannot be longer than %(max)d character.',
  84. 'Field cannot be longer than %(max)d characters.', self.max)
  85. else:
  86. message = field.gettext('Field must be between %(min)d and %(max)d characters long.')
  87. raise ValidationError(message % dict(min=self.min, max=self.max, length=l))
  88. class NumberRange(object):
  89. """
  90. Validates that a number is of a minimum and/or maximum value, inclusive.
  91. This will work with any comparable number type, such as floats and
  92. decimals, not just integers.
  93. :param min:
  94. The minimum required value of the number. If not provided, minimum
  95. value will not be checked.
  96. :param max:
  97. The maximum value of the number. If not provided, maximum value
  98. will not be checked.
  99. :param message:
  100. Error message to raise in case of a validation error. Can be
  101. interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults
  102. are provided depending on the existence of min and max.
  103. """
  104. def __init__(self, min=None, max=None, message=None):
  105. self.min = min
  106. self.max = max
  107. self.message = message
  108. def __call__(self, form, field):
  109. data = field.data
  110. if data is None or (self.min is not None and data < self.min) or \
  111. (self.max is not None and data > self.max):
  112. message = self.message
  113. if message is None:
  114. # we use %(min)s interpolation to support floats, None, and
  115. # Decimals without throwing a formatting exception.
  116. if self.max is None:
  117. message = field.gettext('Number must be at least %(min)s.')
  118. elif self.min is None:
  119. message = field.gettext('Number must be at most %(max)s.')
  120. else:
  121. message = field.gettext('Number must be between %(min)s and %(max)s.')
  122. raise ValidationError(message % dict(min=self.min, max=self.max))
  123. class Optional(object):
  124. """
  125. Allows empty input and stops the validation chain from continuing.
  126. If input is empty, also removes prior errors (such as processing errors)
  127. from the field.
  128. :param strip_whitespace:
  129. If True (the default) also stop the validation chain on input which
  130. consists of only whitespace.
  131. """
  132. field_flags = ('optional', )
  133. def __init__(self, strip_whitespace=True):
  134. if strip_whitespace:
  135. self.string_check = lambda s: s.strip()
  136. else:
  137. self.string_check = lambda s: s
  138. def __call__(self, form, field):
  139. if not field.raw_data or isinstance(field.raw_data[0], string_types) and not self.string_check(field.raw_data[0]):
  140. field.errors[:] = []
  141. raise StopValidation()
  142. class DataRequired(object):
  143. """
  144. Checks the field's data is 'truthy' otherwise stops the validation chain.
  145. This validator checks that the ``data`` attribute on the field is a 'true'
  146. value (effectively, it does ``if field.data``.) Furthermore, if the data
  147. is a string type, a string containing only whitespace characters is
  148. considered false.
  149. If the data is empty, also removes prior errors (such as processing errors)
  150. from the field.
  151. **NOTE** this validator used to be called `Required` but the way it behaved
  152. (requiring coerced data, not input data) meant it functioned in a way
  153. which was not symmetric to the `Optional` validator and furthermore caused
  154. confusion with certain fields which coerced data to 'falsey' values like
  155. ``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason
  156. exists, we recommend using the :class:`InputRequired` instead.
  157. :param message:
  158. Error message to raise in case of a validation error.
  159. """
  160. field_flags = ('required', )
  161. def __init__(self, message=None):
  162. self.message = message
  163. def __call__(self, form, field):
  164. if not field.data or isinstance(field.data, string_types) and not field.data.strip():
  165. if self.message is None:
  166. message = field.gettext('This field is required.')
  167. else:
  168. message = self.message
  169. field.errors[:] = []
  170. raise StopValidation(message)
  171. class Required(DataRequired):
  172. """
  173. Legacy alias for DataRequired.
  174. This is needed over simple aliasing for those who require that the
  175. class-name of required be 'Required.'
  176. """
  177. def __init__(self, *args, **kwargs):
  178. super(Required, self).__init__(*args, **kwargs)
  179. warnings.warn(
  180. 'Required is going away in WTForms 3.0, use DataRequired',
  181. DeprecationWarning, stacklevel=2
  182. )
  183. class InputRequired(object):
  184. """
  185. Validates that input was provided for this field.
  186. Note there is a distinction between this and DataRequired in that
  187. InputRequired looks that form-input data was provided, and DataRequired
  188. looks at the post-coercion data.
  189. """
  190. field_flags = ('required', )
  191. def __init__(self, message=None):
  192. self.message = message
  193. def __call__(self, form, field):
  194. if not field.raw_data or not field.raw_data[0]:
  195. if self.message is None:
  196. message = field.gettext('This field is required.')
  197. else:
  198. message = self.message
  199. field.errors[:] = []
  200. raise StopValidation(message)
  201. class Regexp(object):
  202. """
  203. Validates the field against a user provided regexp.
  204. :param regex:
  205. The regular expression string to use. Can also be a compiled regular
  206. expression pattern.
  207. :param flags:
  208. The regexp flags to use, for example re.IGNORECASE. Ignored if
  209. `regex` is not a string.
  210. :param message:
  211. Error message to raise in case of a validation error.
  212. """
  213. def __init__(self, regex, flags=0, message=None):
  214. if isinstance(regex, string_types):
  215. regex = re.compile(regex, flags)
  216. self.regex = regex
  217. self.message = message
  218. def __call__(self, form, field, message=None):
  219. match = self.regex.match(field.data or '')
  220. if not match:
  221. if message is None:
  222. if self.message is None:
  223. message = field.gettext('Invalid input.')
  224. else:
  225. message = self.message
  226. raise ValidationError(message)
  227. return match
  228. class Email(Regexp):
  229. """
  230. Validates an email address. Note that this uses a very primitive regular
  231. expression and should only be used in instances where you later verify by
  232. other means, such as email activation or lookups.
  233. :param message:
  234. Error message to raise in case of a validation error.
  235. """
  236. def __init__(self, message=None):
  237. self.validate_hostname = HostnameValidation(
  238. require_tld=True,
  239. )
  240. super(Email, self).__init__(r'^.+@([^.@][^@]+)$', re.IGNORECASE, message)
  241. def __call__(self, form, field):
  242. message = self.message
  243. if message is None:
  244. message = field.gettext('Invalid email address.')
  245. match = super(Email, self).__call__(form, field, message)
  246. if not self.validate_hostname(match.group(1)):
  247. raise ValidationError(message)
  248. class IPAddress(object):
  249. """
  250. Validates an IP address.
  251. :param ipv4:
  252. If True, accept IPv4 addresses as valid (default True)
  253. :param ipv6:
  254. If True, accept IPv6 addresses as valid (default False)
  255. :param message:
  256. Error message to raise in case of a validation error.
  257. """
  258. def __init__(self, ipv4=True, ipv6=False, message=None):
  259. if not ipv4 and not ipv6:
  260. raise ValueError('IP Address Validator must have at least one of ipv4 or ipv6 enabled.')
  261. self.ipv4 = ipv4
  262. self.ipv6 = ipv6
  263. self.message = message
  264. def __call__(self, form, field):
  265. value = field.data
  266. valid = False
  267. if value:
  268. valid = (self.ipv4 and self.check_ipv4(value)) or (self.ipv6 and self.check_ipv6(value))
  269. if not valid:
  270. message = self.message
  271. if message is None:
  272. message = field.gettext('Invalid IP address.')
  273. raise ValidationError(message)
  274. @classmethod
  275. def check_ipv4(cls, value):
  276. parts = value.split('.')
  277. if len(parts) == 4 and all(x.isdigit() for x in parts):
  278. numbers = list(int(x) for x in parts)
  279. return all(num >= 0 and num < 256 for num in numbers)
  280. return False
  281. @classmethod
  282. def check_ipv6(cls, value):
  283. parts = value.split(':')
  284. if len(parts) > 8:
  285. return False
  286. num_blank = 0
  287. for part in parts:
  288. if not part:
  289. num_blank += 1
  290. else:
  291. try:
  292. value = int(part, 16)
  293. except ValueError:
  294. return False
  295. else:
  296. if value < 0 or value >= 65536:
  297. return False
  298. if num_blank < 2:
  299. return True
  300. elif num_blank == 2 and not parts[0] and not parts[1]:
  301. return True
  302. return False
  303. class MacAddress(Regexp):
  304. """
  305. Validates a MAC address.
  306. :param message:
  307. Error message to raise in case of a validation error.
  308. """
  309. def __init__(self, message=None):
  310. pattern = r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$'
  311. super(MacAddress, self).__init__(pattern, message=message)
  312. def __call__(self, form, field):
  313. message = self.message
  314. if message is None:
  315. message = field.gettext('Invalid Mac address.')
  316. super(MacAddress, self).__call__(form, field, message)
  317. class URL(Regexp):
  318. """
  319. Simple regexp based url validation. Much like the email validator, you
  320. probably want to validate the url later by other means if the url must
  321. resolve.
  322. :param require_tld:
  323. If true, then the domain-name portion of the URL must contain a .tld
  324. suffix. Set this to false if you want to allow domains like
  325. `localhost`.
  326. :param message:
  327. Error message to raise in case of a validation error.
  328. """
  329. def __init__(self, require_tld=True, message=None):
  330. regex = r'^[a-z]+://(?P<host>[^/:]+)(?P<port>:[0-9]+)?(?P<path>\/.*)?$'
  331. super(URL, self).__init__(regex, re.IGNORECASE, message)
  332. self.validate_hostname = HostnameValidation(
  333. require_tld=require_tld,
  334. allow_ip=True,
  335. )
  336. def __call__(self, form, field):
  337. message = self.message
  338. if message is None:
  339. message = field.gettext('Invalid URL.')
  340. match = super(URL, self).__call__(form, field, message)
  341. if not self.validate_hostname(match.group('host')):
  342. raise ValidationError(message)
  343. class UUID(Regexp):
  344. """
  345. Validates a UUID.
  346. :param message:
  347. Error message to raise in case of a validation error.
  348. """
  349. def __init__(self, message=None):
  350. pattern = r'^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$'
  351. super(UUID, self).__init__(pattern, message=message)
  352. def __call__(self, form, field):
  353. message = self.message
  354. if message is None:
  355. message = field.gettext('Invalid UUID.')
  356. super(UUID, self).__call__(form, field, message)
  357. class AnyOf(object):
  358. """
  359. Compares the incoming data to a sequence of valid inputs.
  360. :param values:
  361. A sequence of valid inputs.
  362. :param message:
  363. Error message to raise in case of a validation error. `%(values)s`
  364. contains the list of values.
  365. :param values_formatter:
  366. Function used to format the list of values in the error message.
  367. """
  368. def __init__(self, values, message=None, values_formatter=None):
  369. self.values = values
  370. self.message = message
  371. if values_formatter is None:
  372. values_formatter = self.default_values_formatter
  373. self.values_formatter = values_formatter
  374. def __call__(self, form, field):
  375. if field.data not in self.values:
  376. message = self.message
  377. if message is None:
  378. message = field.gettext('Invalid value, must be one of: %(values)s.')
  379. raise ValidationError(message % dict(values=self.values_formatter(self.values)))
  380. @staticmethod
  381. def default_values_formatter(values):
  382. return ', '.join(text_type(x) for x in values)
  383. class NoneOf(object):
  384. """
  385. Compares the incoming data to a sequence of invalid inputs.
  386. :param values:
  387. A sequence of invalid inputs.
  388. :param message:
  389. Error message to raise in case of a validation error. `%(values)s`
  390. contains the list of values.
  391. :param values_formatter:
  392. Function used to format the list of values in the error message.
  393. """
  394. def __init__(self, values, message=None, values_formatter=None):
  395. self.values = values
  396. self.message = message
  397. if values_formatter is None:
  398. values_formatter = self.default_values_formatter
  399. self.values_formatter = values_formatter
  400. def __call__(self, form, field):
  401. if field.data in self.values:
  402. message = self.message
  403. if message is None:
  404. message = field.gettext('Invalid value, can\'t be any of: %(values)s.')
  405. raise ValidationError(message % dict(values=self.values_formatter(self.values)))
  406. @staticmethod
  407. def default_values_formatter(v):
  408. return ', '.join(text_type(x) for x in v)
  409. class HostnameValidation(object):
  410. """
  411. Helper class for checking hostnames for validation.
  412. This is not a validator in and of itself, and as such is not exported.
  413. """
  414. hostname_part = re.compile(r'^(xn-|[a-z0-9]+)(-[a-z0-9]+)*$', re.IGNORECASE)
  415. tld_part = re.compile(r'^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$', re.IGNORECASE)
  416. def __init__(self, require_tld=True, allow_ip=False):
  417. self.require_tld = require_tld
  418. self.allow_ip = allow_ip
  419. def __call__(self, hostname):
  420. if self.allow_ip:
  421. if IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname):
  422. return True
  423. # Encode out IDNA hostnames. This makes further validation easier.
  424. hostname = hostname.encode('idna')
  425. # Turn back into a string in Python 3x
  426. if not isinstance(hostname, string_types):
  427. hostname = hostname.decode('ascii')
  428. if len(hostname) > 253:
  429. return False
  430. # Check that all labels in the hostname are valid
  431. parts = hostname.split('.')
  432. for part in parts:
  433. if not part or len(part) > 63:
  434. return False
  435. if not self.hostname_part.match(part):
  436. return False
  437. if self.require_tld:
  438. if len(parts) < 2 or not self.tld_part.match(parts[-1]):
  439. return False
  440. return True
  441. email = Email
  442. equal_to = EqualTo
  443. ip_address = IPAddress
  444. mac_address = MacAddress
  445. length = Length
  446. number_range = NumberRange
  447. optional = Optional
  448. required = Required
  449. input_required = InputRequired
  450. data_required = DataRequired
  451. regexp = Regexp
  452. url = URL
  453. any_of = AnyOf
  454. none_of = NoneOf