/formencode/validators.py
Python | 2997 lines | 2883 code | 48 blank | 66 comment | 68 complexity | a3c37d079b41e7bee5e9f12da31a1f15 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- ## FormEncode, a Form processor
- ## Copyright (C) 2003, Ian Bicking <ianb@colorstudy.com>
- """
- Validator/Converters for use with FormEncode.
- """
- import cgi
- import locale
- import re
- import warnings
- try: # import dnspython
- import dns.resolver
- import dns.exception
- from encodings import idna
- except (IOError, ImportError):
- have_dns = False
- else:
- have_dns = True
- # These are only imported when needed
- httplib = None
- random = None
- sha1 = None
- socket = None
- urlparse = None
- from api import (FancyValidator, Identity, Invalid, NoDefault, Validator,
- deprecation_warning, is_empty)
- assert Identity and Invalid and NoDefault # silence unused import warnings
- # Dummy i18n translation function, nothing is translated here.
- # Instead this is actually done in api.message.
- # The surrounding _('string') of the strings is only for extracting
- # the strings automatically.
- # If you run pygettext with this source comment this function out temporarily.
- _ = lambda s: s
- ############################################################
- ## Utility methods
- ############################################################
- # These all deal with accepting both datetime and mxDateTime modules and types
- datetime_module = None
- mxDateTime_module = None
- def import_datetime(module_type):
- global datetime_module, mxDateTime_module
- module_type = module_type.lower() if module_type else 'datetime'
- if module_type == 'datetime':
- if datetime_module is None:
- import datetime as datetime_module
- return datetime_module
- elif module_type == 'mxdatetime':
- if mxDateTime_module is None:
- from mx import DateTime as mxDateTime_module
- return mxDateTime_module
- else:
- raise ImportError('Invalid datetime module %r' % module_type)
- def datetime_now(module):
- if module.__name__ == 'datetime':
- return module.datetime.now()
- else:
- return module.now()
- def datetime_makedate(module, year, month, day):
- if module.__name__ == 'datetime':
- return module.date(year, month, day)
- else:
- try:
- return module.DateTime(year, month, day)
- except module.RangeError, e:
- raise ValueError(str(e))
- def datetime_time(module):
- if module.__name__ == 'datetime':
- return module.time
- else:
- return module.Time
- def datetime_isotime(module):
- if module.__name__ == 'datetime':
- return module.time.isoformat
- else:
- return module.ISO.Time
- ############################################################
- ## Wrapper Validators
- ############################################################
- class ConfirmType(FancyValidator):
- """
- Confirms that the input/output is of the proper type.
- Uses the parameters:
- subclass:
- The class or a tuple of classes; the item must be an instance
- of the class or a subclass.
- type:
- A type or tuple of types (or classes); the item must be of
- the exact class or type. Subclasses are not allowed.
- Examples::
- >>> cint = ConfirmType(subclass=int)
- >>> cint.to_python(True)
- True
- >>> cint.to_python('1')
- Traceback (most recent call last):
- ...
- Invalid: '1' is not a subclass of <type 'int'>
- >>> cintfloat = ConfirmType(subclass=(float, int))
- >>> cintfloat.to_python(1.0), cintfloat.from_python(1.0)
- (1.0, 1.0)
- >>> cintfloat.to_python(1), cintfloat.from_python(1)
- (1, 1)
- >>> cintfloat.to_python(None)
- Traceback (most recent call last):
- ...
- Invalid: None is not a subclass of one of the types <type 'float'>, <type 'int'>
- >>> cint2 = ConfirmType(type=int)
- >>> cint2(accept_python=False).from_python(True)
- Traceback (most recent call last):
- ...
- Invalid: True must be of the type <type 'int'>
- """
- subclass = None
- type = None
- messages = dict(
- subclass=_('%(object)r is not a subclass of %(subclass)s'),
- inSubclass=_('%(object)r is not a subclass of one of the types %(subclassList)s'),
- inType=_('%(object)r must be one of the types %(typeList)s'),
- type=_('%(object)r must be of the type %(type)s'))
- def __init__(self, *args, **kw):
- FancyValidator.__init__(self, *args, **kw)
- if self.subclass:
- if isinstance(self.subclass, list):
- self.subclass = tuple(self.subclass)
- elif not isinstance(self.subclass, tuple):
- self.subclass = (self.subclass,)
- self._validate_python = self.confirm_subclass
- if self.type:
- if isinstance(self.type, list):
- self.type = tuple(self.type)
- elif not isinstance(self.type, tuple):
- self.type = (self.type,)
- self._validate_python = self.confirm_type
- def confirm_subclass(self, value, state):
- if not isinstance(value, self.subclass):
- if len(self.subclass) == 1:
- msg = self.message('subclass', state, object=value,
- subclass=self.subclass[0])
- else:
- subclass_list = ', '.join(map(str, self.subclass))
- msg = self.message('inSubclass', state, object=value,
- subclassList=subclass_list)
- raise Invalid(msg, value, state)
- def confirm_type(self, value, state):
- for t in self.type:
- if type(value) is t:
- break
- else:
- if len(self.type) == 1:
- msg = self.message('type', state, object=value,
- type=self.type[0])
- else:
- msg = self.message('inType', state, object=value,
- typeList=', '.join(map(str, self.type)))
- raise Invalid(msg, value, state)
- return value
- def is_empty(self, value):
- return False
- class Wrapper(FancyValidator):
- """
- Used to convert functions to validator/converters.
- You can give a simple function for `_convert_to_python`,
- `_convert_from_python`, `_validate_python` or `_validate_other`.
- If that function raises an exception, the value is considered invalid.
- Whatever value the function returns is considered the converted value.
- Unlike validators, the `state` argument is not used. Functions
- like `int` can be used here, that take a single argument.
- Note that as Wrapper will generate a FancyValidator, empty
- values (those who pass ``FancyValidator.is_empty)`` will return ``None``.
- To override this behavior you can use ``Wrapper(empty_value=callable)``.
- For example passing ``Wrapper(empty_value=lambda val: val)`` will return
- the value itself when is considered empty.
- Examples::
- >>> def downcase(v):
- ... return v.lower()
- >>> wrap = Wrapper(convert_to_python=downcase)
- >>> wrap.to_python('This')
- 'this'
- >>> wrap.from_python('This')
- 'This'
- >>> wrap.to_python('') is None
- True
- >>> wrap2 = Wrapper(
- ... convert_from_python=downcase, empty_value=lambda value: value)
- >>> wrap2.from_python('This')
- 'this'
- >>> wrap2.to_python('')
- ''
- >>> wrap2.from_python(1)
- Traceback (most recent call last):
- ...
- Invalid: 'int' object has no attribute 'lower'
- >>> wrap3 = Wrapper(validate_python=int)
- >>> wrap3.to_python('1')
- '1'
- >>> wrap3.to_python('a') # doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- Invalid: invalid literal for int()...
- """
- func_convert_to_python = None
- func_convert_from_python = None
- func_validate_python = None
- func_validate_other = None
- _deprecated_methods = (
- ('func_to_python', 'func_convert_to_python'),
- ('func_from_python', 'func_convert_from_python'))
- def __init__(self, *args, **kw):
- # allow old method names as parameters
- if 'to_python' in kw and 'convert_to_python' not in kw:
- kw['convert_to_python'] = kw.pop('to_python')
- if 'from_python' in kw and 'convert_from_python' not in kw:
- kw['convert_from_python'] = kw.pop('from_python')
- for n in ('convert_to_python', 'convert_from_python',
- 'validate_python', 'validate_other'):
- if n in kw:
- kw['func_%s' % n] = kw.pop(n)
- FancyValidator.__init__(self, *args, **kw)
- self._convert_to_python = self.wrap(self.func_convert_to_python)
- self._convert_from_python = self.wrap(self.func_convert_from_python)
- self._validate_python = self.wrap(self.func_validate_python)
- self._validate_other = self.wrap(self.func_validate_other)
- def wrap(self, func):
- if not func:
- return None
- def result(value, state, func=func):
- try:
- return func(value)
- except Exception, e:
- raise Invalid(str(e), value, state)
- return result
- class Constant(FancyValidator):
- """
- This converter converts everything to the same thing.
- I.e., you pass in the constant value when initializing, then all
- values get converted to that constant value.
- This is only really useful for funny situations, like::
- # Any evaluates sub validators in reverse order for to_python
- fromEmailValidator = Any(
- Constant('unknown@localhost'),
- Email())
- In this case, the if the email is not valid
- ``'unknown@localhost'`` will be used instead. Of course, you
- could use ``if_invalid`` instead.
- Examples::
- >>> Constant('X').to_python('y')
- 'X'
- """
- __unpackargs__ = ('value',)
- def _convert_to_python(self, value, state):
- return self.value
- _convert_from_python = _convert_to_python
- ############################################################
- ## Normal validators
- ############################################################
- class MaxLength(FancyValidator):
- """
- Invalid if the value is longer than `maxLength`. Uses len(),
- so it can work for strings, lists, or anything with length.
- Examples::
- >>> max5 = MaxLength(5)
- >>> max5.to_python('12345')
- '12345'
- >>> max5.from_python('12345')
- '12345'
- >>> max5.to_python('123456')
- Traceback (most recent call last):
- ...
- Invalid: Enter a value less than 5 characters long
- >>> max5(accept_python=False).from_python('123456')
- Traceback (most recent call last):
- ...
- Invalid: Enter a value less than 5 characters long
- >>> max5.to_python([1, 2, 3])
- [1, 2, 3]
- >>> max5.to_python([1, 2, 3, 4, 5, 6])
- Traceback (most recent call last):
- ...
- Invalid: Enter a value less than 5 characters long
- >>> max5.to_python(5)
- Traceback (most recent call last):
- ...
- Invalid: Invalid value (value with length expected)
- """
- __unpackargs__ = ('maxLength',)
- messages = dict(
- tooLong=_('Enter a value less than %(maxLength)i characters long'),
- invalid=_('Invalid value (value with length expected)'))
- def _validate_python(self, value, state):
- try:
- if value and len(value) > self.maxLength:
- raise Invalid(
- self.message('tooLong', state,
- maxLength=self.maxLength), value, state)
- else:
- return None
- except TypeError:
- raise Invalid(
- self.message('invalid', state), value, state)
- class MinLength(FancyValidator):
- """
- Invalid if the value is shorter than `minlength`. Uses len(), so
- it can work for strings, lists, or anything with length. Note
- that you **must** use ``not_empty=True`` if you don't want to
- accept empty values -- empty values are not tested for length.
- Examples::
- >>> min5 = MinLength(5)
- >>> min5.to_python('12345')
- '12345'
- >>> min5.from_python('12345')
- '12345'
- >>> min5.to_python('1234')
- Traceback (most recent call last):
- ...
- Invalid: Enter a value at least 5 characters long
- >>> min5(accept_python=False).from_python('1234')
- Traceback (most recent call last):
- ...
- Invalid: Enter a value at least 5 characters long
- >>> min5.to_python([1, 2, 3, 4, 5])
- [1, 2, 3, 4, 5]
- >>> min5.to_python([1, 2, 3])
- Traceback (most recent call last):
- ...
- Invalid: Enter a value at least 5 characters long
- >>> min5.to_python(5)
- Traceback (most recent call last):
- ...
- Invalid: Invalid value (value with length expected)
- """
- __unpackargs__ = ('minLength',)
- messages = dict(
- tooShort=_('Enter a value at least %(minLength)i characters long'),
- invalid=_('Invalid value (value with length expected)'))
- def _validate_python(self, value, state):
- try:
- if len(value) < self.minLength:
- raise Invalid(
- self.message('tooShort', state,
- minLength=self.minLength), value, state)
- except TypeError:
- raise Invalid(
- self.message('invalid', state), value, state)
- class NotEmpty(FancyValidator):
- """
- Invalid if value is empty (empty string, empty list, etc).
- Generally for objects that Python considers false, except zero
- which is not considered invalid.
- Examples::
- >>> ne = NotEmpty(messages=dict(empty='enter something'))
- >>> ne.to_python('')
- Traceback (most recent call last):
- ...
- Invalid: enter something
- >>> ne.to_python(0)
- 0
- """
- not_empty = True
- messages = dict(
- empty=_('Please enter a value'))
- def _validate_python(self, value, state):
- if value == 0:
- # This isn't "empty" for this definition.
- return value
- if not value:
- raise Invalid(self.message('empty', state), value, state)
- class Empty(FancyValidator):
- """
- Invalid unless the value is empty. Use cleverly, if at all.
- Examples::
- >>> Empty.to_python(0)
- Traceback (most recent call last):
- ...
- Invalid: You cannot enter a value here
- """
- messages = dict(
- notEmpty=_('You cannot enter a value here'))
- def _validate_python(self, value, state):
- if value or value == 0:
- raise Invalid(self.message('notEmpty', state), value, state)
- class Regex(FancyValidator):
- """
- Invalid if the value doesn't match the regular expression `regex`.
- The regular expression can be a compiled re object, or a string
- which will be compiled for you.
- Use strip=True if you want to strip the value before validation,
- and as a form of conversion (often useful).
- Examples::
- >>> cap = Regex(r'^[A-Z]+$')
- >>> cap.to_python('ABC')
- 'ABC'
- Note that ``.from_python()`` calls (in general) do not validate
- the input::
- >>> cap.from_python('abc')
- 'abc'
- >>> cap(accept_python=False).from_python('abc')
- Traceback (most recent call last):
- ...
- Invalid: The input is not valid
- >>> cap.to_python(1)
- Traceback (most recent call last):
- ...
- Invalid: The input must be a string (not a <type 'int'>: 1)
- >>> Regex(r'^[A-Z]+$', strip=True).to_python(' ABC ')
- 'ABC'
- >>> Regex(r'this', regexOps=('I',)).to_python('THIS')
- 'THIS'
- """
- regexOps = ()
- strip = False
- regex = None
- __unpackargs__ = ('regex',)
- messages = dict(
- invalid=_('The input is not valid'))
- def __init__(self, *args, **kw):
- FancyValidator.__init__(self, *args, **kw)
- if isinstance(self.regex, basestring):
- ops = 0
- assert not isinstance(self.regexOps, basestring), (
- "regexOps should be a list of options from the re module "
- "(names, or actual values)")
- for op in self.regexOps:
- if isinstance(op, basestring):
- ops |= getattr(re, op)
- else:
- ops |= op
- self.regex = re.compile(self.regex, ops)
- def _validate_python(self, value, state):
- self.assert_string(value, state)
- if self.strip and isinstance(value, basestring):
- value = value.strip()
- if not self.regex.search(value):
- raise Invalid(self.message('invalid', state), value, state)
- def _convert_to_python(self, value, state):
- if self.strip and isinstance(value, basestring):
- return value.strip()
- return value
- class PlainText(Regex):
- """
- Test that the field contains only letters, numbers, underscore,
- and the hyphen. Subclasses Regex.
- Examples::
- >>> PlainText.to_python('_this9_')
- '_this9_'
- >>> PlainText.from_python(' this ')
- ' this '
- >>> PlainText(accept_python=False).from_python(' this ')
- Traceback (most recent call last):
- ...
- Invalid: Enter only letters, numbers, or _ (underscore)
- >>> PlainText(strip=True).to_python(' this ')
- 'this'
- >>> PlainText(strip=True).from_python(' this ')
- 'this'
- """
- regex = r"^[a-zA-Z_\-0-9]*$"
- messages = dict(
- invalid=_('Enter only letters, numbers, or _ (underscore)'))
- class OneOf(FancyValidator):
- """
- Tests that the value is one of the members of a given list.
- If ``testValueList=True``, then if the input value is a list or
- tuple, all the members of the sequence will be checked (i.e., the
- input must be a subset of the allowed values).
- Use ``hideList=True`` to keep the list of valid values out of the
- error message in exceptions.
- Examples::
- >>> oneof = OneOf([1, 2, 3])
- >>> oneof.to_python(1)
- 1
- >>> oneof.to_python(4)
- Traceback (most recent call last):
- ...
- Invalid: Value must be one of: 1; 2; 3 (not 4)
- >>> oneof(testValueList=True).to_python([2, 3, [1, 2, 3]])
- [2, 3, [1, 2, 3]]
- >>> oneof.to_python([2, 3, [1, 2, 3]])
- Traceback (most recent call last):
- ...
- Invalid: Value must be one of: 1; 2; 3 (not [2, 3, [1, 2, 3]])
- """
- list = None
- testValueList = False
- hideList = False
- __unpackargs__ = ('list',)
- messages = dict(
- invalid=_('Invalid value'),
- notIn=_('Value must be one of: %(items)s (not %(value)r)'))
- def _validate_python(self, value, state):
- if self.testValueList and isinstance(value, (list, tuple)):
- for v in value:
- self._validate_python(v, state)
- else:
- if not value in self.list:
- if self.hideList:
- raise Invalid(self.message('invalid', state), value, state)
- else:
- try:
- items = '; '.join(map(str, self.list))
- except UnicodeError:
- items = '; '.join(map(unicode, self.list))
- raise Invalid(
- self.message('notIn', state,
- items=items, value=value), value, state)
- @property
- def accept_iterator(self):
- return self.testValueList
- class DictConverter(FancyValidator):
- """
- Converts values based on a dictionary which has values as keys for
- the resultant values.
- If ``allowNull`` is passed, it will not balk if a false value
- (e.g., '' or None) is given (it will return None in these cases).
- to_python takes keys and gives values, from_python takes values and
- gives keys.
- If you give hideDict=True, then the contents of the dictionary
- will not show up in error messages.
- Examples::
- >>> dc = DictConverter({1: 'one', 2: 'two'})
- >>> dc.to_python(1)
- 'one'
- >>> dc.from_python('one')
- 1
- >>> dc.to_python(3)
- Traceback (most recent call last):
- ....
- Invalid: Enter a value from: 1; 2
- >>> dc2 = dc(hideDict=True)
- >>> dc2.hideDict
- True
- >>> dc2.dict
- {1: 'one', 2: 'two'}
- >>> dc2.to_python(3)
- Traceback (most recent call last):
- ....
- Invalid: Choose something
- >>> dc.from_python('three')
- Traceback (most recent call last):
- ....
- Invalid: Nothing in my dictionary goes by the value 'three'. Choose one of: 'one'; 'two'
- """
- messages = dict(
- keyNotFound=_('Choose something'),
- chooseKey=_('Enter a value from: %(items)s'),
- valueNotFound=_('That value is not known'),
- chooseValue=_('Nothing in my dictionary goes by the value %(value)s.'
- ' Choose one of: %(items)s'))
- dict = None
- hideDict = False
- __unpackargs__ = ('dict',)
- def _convert_to_python(self, value, state):
- try:
- return self.dict[value]
- except KeyError:
- if self.hideDict:
- raise Invalid(self.message('keyNotFound', state), value, state)
- else:
- items = sorted(self.dict)
- items = '; '.join(map(repr, items))
- raise Invalid(self.message('chooseKey',
- state, items=items), value, state)
- def _convert_from_python(self, value, state):
- for k, v in self.dict.iteritems():
- if value == v:
- return k
- if self.hideDict:
- raise Invalid(self.message('valueNotFound', state), value, state)
- else:
- items = '; '.join(map(repr, self.dict.itervalues()))
- raise Invalid(
- self.message('chooseValue', state,
- value=repr(value), items=items), value, state)
- class IndexListConverter(FancyValidator):
- """
- Converts a index (which may be a string like '2') to the value in
- the given list.
- Examples::
- >>> index = IndexListConverter(['zero', 'one', 'two'])
- >>> index.to_python(0)
- 'zero'
- >>> index.from_python('zero')
- 0
- >>> index.to_python('1')
- 'one'
- >>> index.to_python(5)
- Traceback (most recent call last):
- Invalid: Index out of range
- >>> index(not_empty=True).to_python(None)
- Traceback (most recent call last):
- Invalid: Please enter a value
- >>> index.from_python('five')
- Traceback (most recent call last):
- Invalid: Item 'five' was not found in the list
- """
- list = None
- __unpackargs__ = ('list',)
- messages = dict(
- integer=_('Must be an integer index'),
- outOfRange=_('Index out of range'),
- notFound=_('Item %(value)s was not found in the list'))
- def _convert_to_python(self, value, state):
- try:
- value = int(value)
- except (ValueError, TypeError):
- raise Invalid(self.message('integer', state), value, state)
- try:
- return self.list[value]
- except IndexError:
- raise Invalid(self.message('outOfRange', state), value, state)
- def _convert_from_python(self, value, state):
- for i, v in enumerate(self.list):
- if v == value:
- return i
- raise Invalid(
- self.message('notFound', state, value=repr(value)), value, state)
- class DateValidator(FancyValidator):
- """
- Validates that a date is within the given range. Be sure to call
- DateConverter first if you aren't expecting mxDateTime input.
- ``earliest_date`` and ``latest_date`` may be functions; if so,
- they will be called each time before validating.
- ``after_now`` means a time after the current timestamp; note that
- just a few milliseconds before now is invalid! ``today_or_after``
- is more permissive, and ignores hours and minutes.
- Examples::
- >>> from datetime import datetime, timedelta
- >>> d = DateValidator(earliest_date=datetime(2003, 1, 1))
- >>> d.to_python(datetime(2004, 1, 1))
- datetime.datetime(2004, 1, 1, 0, 0)
- >>> d.to_python(datetime(2002, 1, 1))
- Traceback (most recent call last):
- ...
- Invalid: Date must be after Wednesday, 01 January 2003
- >>> d.to_python(datetime(2003, 1, 1))
- datetime.datetime(2003, 1, 1, 0, 0)
- >>> d = DateValidator(after_now=True)
- >>> now = datetime.now()
- >>> d.to_python(now+timedelta(seconds=5)) == now+timedelta(seconds=5)
- True
- >>> d.to_python(now-timedelta(days=1))
- Traceback (most recent call last):
- ...
- Invalid: The date must be sometime in the future
- >>> d.to_python(now+timedelta(days=1)) > now
- True
- >>> d = DateValidator(today_or_after=True)
- >>> d.to_python(now) == now
- True
- """
- earliest_date = None
- latest_date = None
- after_now = False
- # Like after_now, but just after this morning:
- today_or_after = False
- # Use None or 'datetime' for the datetime module in the standard lib,
- # or 'mxDateTime' to force the mxDateTime module
- datetime_module = None
- messages = dict(
- after=_('Date must be after %(date)s'),
- before=_('Date must be before %(date)s'),
- # Double %'s, because this will be substituted twice:
- date_format=_('%%A, %%d %%B %%Y'),
- future=_('The date must be sometime in the future'))
- def _validate_python(self, value, state):
- date_format = self.message('date_format', state)
- if (str is not unicode # Python 2
- and isinstance(date_format, unicode)):
- # strftime uses the locale encoding, not Unicode
- encoding = locale.getlocale(locale.LC_TIME)[1] or 'utf-8'
- date_format = date_format.encode(encoding)
- else:
- encoding = None
- if self.earliest_date:
- if callable(self.earliest_date):
- earliest_date = self.earliest_date()
- else:
- earliest_date = self.earliest_date
- if value < earliest_date:
- date_formatted = earliest_date.strftime(date_format)
- if encoding:
- date_formatted = date_formatted.decode(encoding)
- raise Invalid(
- self.message('after', state, date=date_formatted),
- value, state)
- if self.latest_date:
- if callable(self.latest_date):
- latest_date = self.latest_date()
- else:
- latest_date = self.latest_date
- if value > latest_date:
- date_formatted = latest_date.strftime(date_format)
- if encoding:
- date_formatted = date_formatted.decode(encoding)
- raise Invalid(
- self.message('before', state, date=date_formatted),
- value, state)
- if self.after_now:
- dt_mod = import_datetime(self.datetime_module)
- now = datetime_now(dt_mod)
- if value < now:
- date_formatted = now.strftime(date_format)
- if encoding:
- date_formatted = date_formatted.decode(encoding)
- raise Invalid(
- self.message('future', state, date=date_formatted),
- value, state)
- if self.today_or_after:
- dt_mod = import_datetime(self.datetime_module)
- now = datetime_now(dt_mod)
- today = datetime_makedate(dt_mod,
- now.year, now.month, now.day)
- value_as_date = datetime_makedate(
- dt_mod, value.year, value.month, value.day)
- if value_as_date < today:
- date_formatted = now.strftime(date_format)
- if encoding:
- date_formatted = date_formatted.decode(encoding)
- raise Invalid(
- self.message('future', state, date=date_formatted),
- value, state)
- class Bool(FancyValidator):
- """
- Always Valid, returns True or False based on the value and the
- existance of the value.
- If you want to convert strings like ``'true'`` to booleans, then
- use ``StringBool``.
- Examples::
- >>> Bool.to_python(0)
- False
- >>> Bool.to_python(1)
- True
- >>> Bool.to_python('')
- False
- >>> Bool.to_python(None)
- False
- """
- if_missing = False
- def _convert_to_python(self, value, state):
- return bool(value)
- _convert_from_python = _convert_to_python
- def empty_value(self, value):
- return False
- class RangeValidator(FancyValidator):
- """This is an abstract base class for Int and Number.
- It verifies that a value is within range. It accepts min and max
- values in the constructor.
- (Since this is an abstract base class, the tests are in Int and Number.)
- """
- messages = dict(
- tooLow=_('Please enter a number that is %(min)s or greater'),
- tooHigh=_('Please enter a number that is %(max)s or smaller'))
- min = None
- max = None
- def _validate_python(self, value, state):
- if self.min is not None:
- if value < self.min:
- msg = self.message('tooLow', state, min=self.min)
- raise Invalid(msg, value, state)
- if self.max is not None:
- if value > self.max:
- msg = self.message('tooHigh', state, max=self.max)
- raise Invalid(msg, value, state)
- class Int(RangeValidator):
- """Convert a value to an integer.
- Example::
- >>> Int.to_python('10')
- 10
- >>> Int.to_python('ten')
- Traceback (most recent call last):
- ...
- Invalid: Please enter an integer value
- >>> Int(min=5).to_python('6')
- 6
- >>> Int(max=10).to_python('11')
- Traceback (most recent call last):
- ...
- Invalid: Please enter a number that is 10 or smaller
- """
- messages = dict(
- integer=_('Please enter an integer value'))
- def _convert_to_python(self, value, state):
- try:
- return int(value)
- except (ValueError, TypeError):
- raise Invalid(self.message('integer', state), value, state)
- _convert_from_python = _convert_to_python
- class Number(RangeValidator):
- """Convert a value to a float or integer.
- Tries to convert it to an integer if no information is lost.
- Example::
- >>> Number.to_python('10')
- 10
- >>> Number.to_python('10.5')
- 10.5
- >>> Number.to_python('ten')
- Traceback (most recent call last):
- ...
- Invalid: Please enter a number
- >>> Number(min=5).to_python('6.5')
- 6.5
- >>> Number(max=10.5).to_python('11.5')
- Traceback (most recent call last):
- ...
- Invalid: Please enter a number that is 10.5 or smaller
- """
- messages = dict(
- number=_('Please enter a number'))
- def _convert_to_python(self, value, state):
- try:
- value = float(value)
- try:
- int_value = int(value)
- except OverflowError:
- int_value = None
- if value == int_value:
- return int_value
- return value
- except ValueError:
- raise Invalid(self.message('number', state), value, state)
- class String(FancyValidator):
- """
- Converts things to string, but treats empty things as the empty string.
- Also takes a `max` and `min` argument, and the string length must fall
- in that range.
- Also you may give an `encoding` argument, which will encode any unicode
- that is found. Lists and tuples are joined with `list_joiner`
- (default ``', '``) in ``from_python``.
- ::
- >>> String(min=2).to_python('a')
- Traceback (most recent call last):
- ...
- Invalid: Enter a value 2 characters long or more
- >>> String(max=10).to_python('xxxxxxxxxxx')
- Traceback (most recent call last):
- ...
- Invalid: Enter a value not more than 10 characters long
- >>> String().from_python(None)
- ''
- >>> String().from_python([])
- ''
- >>> String().to_python(None)
- ''
- >>> String(min=3).to_python(None)
- Traceback (most recent call last):
- ...
- Invalid: Please enter a value
- >>> String(min=1).to_python('')
- Traceback (most recent call last):
- ...
- Invalid: Please enter a value
- """
- min = None
- max = None
- not_empty = None
- encoding = None
- list_joiner = ', '
- messages = dict(
- tooLong=_('Enter a value not more than %(max)i characters long'),
- tooShort=_('Enter a value %(min)i characters long or more'))
- def __initargs__(self, new_attrs):
- if self.not_empty is None and self.min:
- self.not_empty = True
- def _convert_to_python(self, value, state):
- if value is None:
- value = ''
- elif not isinstance(value, basestring):
- try:
- value = str(value)
- except UnicodeEncodeError:
- value = unicode(value)
- if self.encoding is not None and isinstance(value, unicode):
- value = value.encode(self.encoding)
- return value
- def _convert_from_python(self, value, state):
- if value is None:
- value = ''
- elif not isinstance(value, basestring):
- if isinstance(value, (list, tuple)):
- value = self.list_joiner.join(
- self._convert_from_python(v, state) for v in value)
- try:
- value = str(value)
- except UnicodeEncodeError:
- value = unicode(value)
- if self.encoding is not None and isinstance(value, unicode):
- value = value.encode(self.encoding)
- if self.strip:
- value = value.strip()
- return value
- def _validate_other(self, value, state):
- if self.max is None and self.min is None:
- return
- if value is None:
- value = ''
- elif not isinstance(value, basestring):
- try:
- value = str(value)
- except UnicodeEncodeError:
- value = unicode(value)
- if self.max is not None and len(value) > self.max:
- raise Invalid(
- self.message('tooLong', state, max=self.max), value, state)
- if self.min is not None and len(value) < self.min:
- raise Invalid(
- self.message('tooShort', state, min=self.min), value, state)
- def empty_value(self, value):
- return ''
- class UnicodeString(String):
- """
- Converts things to unicode string, this is a specialization of
- the String class.
- In addition to the String arguments, an encoding argument is also
- accepted. By default the encoding will be utf-8. You can overwrite
- this using the encoding parameter. You can also set inputEncoding
- and outputEncoding differently. An inputEncoding of None means
- "do not decode", an outputEncoding of None means "do not encode".
- All converted strings are returned as Unicode strings.
- ::
- >>> UnicodeString().to_python(None)
- u''
- >>> UnicodeString().to_python([])
- u''
- >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni')
- u'Ni Ni Ni'
- """
- encoding = 'utf-8'
- inputEncoding = NoDefault
- outputEncoding = NoDefault
- messages = dict(
- badEncoding=_('Invalid data or incorrect encoding'))
- def __init__(self, **kw):
- String.__init__(self, **kw)
- if self.inputEncoding is NoDefault:
- self.inputEncoding = self.encoding
- if self.outputEncoding is NoDefault:
- self.outputEncoding = self.encoding
- def _convert_to_python(self, value, state):
- if not value:
- return u''
- if isinstance(value, unicode):
- return value
- if not isinstance(value, unicode):
- if hasattr(value, '__unicode__'):
- value = unicode(value)
- return value
- if not (unicode is str # Python 3
- and isinstance(value, bytes) and self.inputEncoding):
- value = str(value)
- if self.inputEncoding and not isinstance(value, unicode):
- try:
- value = unicode(value, self.inputEncoding)
- except UnicodeDecodeError:
- raise Invalid(self.message('badEncoding', state), value, state)
- except TypeError:
- raise Invalid(
- self.message('badType', state,
- type=type(value), value=value), value, state)
- return value
- def _convert_from_python(self, value, state):
- if not isinstance(value, unicode):
- if hasattr(value, '__unicode__'):
- value = unicode(value)
- else:
- value = str(value)
- if self.outputEncoding and isinstance(value, unicode):
- value = value.encode(self.outputEncoding)
- return value
- def empty_value(self, value):
- return u''
- class Set(FancyValidator):
- """
- This is for when you think you may return multiple values for a
- certain field.
- This way the result will always be a list, even if there's only
- one result. It's equivalent to ForEach(convert_to_list=True).
- If you give ``use_set=True``, then it will return an actual
- ``set`` object.
- ::
- >>> Set.to_python(None)
- []
- >>> Set.to_python('this')
- ['this']
- >>> Set.to_python(('this', 'that'))
- ['this', 'that']
- >>> s = Set(use_set=True)
- >>> s.to_python(None)
- set([])
- >>> s.to_python('this')
- set(['this'])
- >>> s.to_python(('this',))
- set(['this'])
- """
- use_set = False
- if_missing = ()
- accept_iterator = True
- def _convert_to_python(self, value, state):
- if self.use_set:
- if isinstance(value, set):
- return value
- elif isinstance(value, (list, tuple)):
- return set(value)
- elif value is None:
- return set()
- else:
- return set([value])
- else:
- if isinstance(value, list):
- return value
- elif isinstance(value, set):
- return list(value)
- elif isinstance(value, tuple):
- return list(value)
- elif value is None:
- return []
- else:
- return [value]
- def empty_value(self, value):
- if self.use_set:
- return set()
- else:
- return []
- class Email(FancyValidator):
- r"""
- Validate an email address.
- If you pass ``resolve_domain=True``, then it will try to resolve
- the domain name to make sure it's valid. This takes longer, of
- course. You must have the `dnspython <http://www.dnspython.org/>`__ modules
- installed to look up DNS (MX and A) records.
- ::
- >>> e = Email()
- >>> e.to_python(' test@foo.com ')
- 'test@foo.com'
- >>> e.to_python('test')
- Traceback (most recent call last):
- ...
- Invalid: An email address must contain a single @
- >>> e.to_python('test@foobar')
- Traceback (most recent call last):
- ...
- Invalid: The domain portion of the email address is invalid (the portion after the @: foobar)
- >>> e.to_python('test@foobar.com.5')
- Traceback (most recent call last):
- ...
- Invalid: The domain portion of the email address is invalid (the portion after the @: foobar.com.5)
- >>> e.to_python('test@foo..bar.com')
- Traceback (most recent call last):
- ...
- Invalid: The domain portion of the email address is invalid (the portion after the @: foo..bar.com)
- >>> e.to_python('test@.foo.bar.com')
- Traceback (most recent call last):
- ...
- Invalid: The domain portion of the email address is invalid (the portion after the @: .foo.bar.com)
- >>> e.to_python('nobody@xn--m7r7ml7t24h.com')
- 'nobody@xn--m7r7ml7t24h.com'
- >>> e.to_python('o*reilly@test.com')
- 'o*reilly@test.com'
- >>> e = Email(resolve_domain=True)
- >>> e.resolve_domain
- True
- >>> e.to_python('doesnotexist@colorstudy.com')
- 'doesnotexist@colorstudy.com'
- >>> e.to_python('test@nyu.edu')
- 'test@nyu.edu'
- >>> # NOTE: If you do not have dnspython installed this example won't work:
- >>> e.to_python('test@thisdomaindoesnotexistithinkforsure.com')
- Traceback (most recent call last):
- ...
- Invalid: The domain of the email address does not exist (the portion after the @: thisdomaindoesnotexistithinkforsure.com)
- >>> e.to_python(u'test@google.com')
- u'test@google.com'
- >>> e = Email(not_empty=False)
- >>> e.to_python('')
- """
- resolve_domain = False
- resolve_timeout = 10 # timeout in seconds when resolving domains
- usernameRE = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
- domainRE = re.compile(r'''
- ^(?:[a-z0-9][a-z0-9\-]{,62}\.)+ # subdomain
- (?:[a-z]{2,63}|xn--[a-z0-9\-]{2,59})$ # top level domain
- ''', re.I | re.VERBOSE)
- messages = dict(
- empty=_('Please enter an email address'),
- noAt=_('An email address must contain a single @'),
- badUsername=_('The username portion of the email address is invalid'
- ' (the portion before the @: %(username)s)'),
- socketError=_('An error occured when trying to connect to the server:'
- ' %(error)s'),
- badDomain=_('The domain portion of the email address is invalid'
- ' (the portion after the @: %(domain)s)'),
- domainDoesNotExist=_('The domain of the email address does not exist'
- ' (the portion after the @: %(domain)s)'))
- def __init__(self, *args, **kw):
- FancyValidator.__init__(self, *args, **kw)
- if self.resolve_domain:
- if not have_dns:
- warnings.warn(
- "dnspython <http://www.dnspython.org/> is not installed on"
- " your system (or the dns.resolver package cannot be found)."
- " I cannot resolve domain names in addresses")
- raise ImportError("no module named dns.resolver")
- def _validate_python(self, value, state):
- if not value:
- raise Invalid(self.message('empty', state), value, state)
- value = value.strip()
- splitted = value.split('@', 1)
- try:
- username, domain = splitted
- except ValueError:
- raise Invalid(self.message('noAt', state), value, state)
- if not self.usernameRE.search(username):
- raise Invalid(
- self.message('badUsername', state, username=username),
- value, state)
- try:
- idna_domain = [idna.ToASCII(p) for p in domain.split('.')]
- if unicode is str: # Python 3
- idna_domain = [p.decode('ascii') for p in idna_domain]
- idna_domain = '.'.join(idna_domain)
- except UnicodeError:
- # UnicodeError: label empty or too long
- # This exception might happen if we have an invalid domain name part
- # (for example test@.foo.bar.com)
- raise Invalid(
- self.message('badDomain', state, domain=domain),
- value, state)
- if not self.domainRE.search(idna_domain):
- raise Invalid(
- self.message('badDomain', state, domain=domain),
- value, state)
- if self.resolve_domain:
- assert have_dns, "dnspython should be available"
- global socket
- if socket is None:
- import socket
- try:
- try:
- dns.resolver.query(domain, 'MX')
- except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer), e:
- try:
- dns.resolver.query(domain, 'A')
- except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer), e:
- raise Invalid(
- self.message('domainDoesNotExist',
- state, domain=domain), value, state)
- except (socket.error, dns.exception.DNSException), e:
- raise Invalid(
- self.message('socketError', state, error=e), value, state)
- def _convert_to_python(self, value, state):
- return value.strip()
- class URL(FancyValidator):
- """
- Validate a URL, either http://... or https://. If check_exists
- is true, then we'll actually make a request for the page.
- If add_http is true, then if no scheme is present we'll add
- http://
- ::
- >>> u = URL(add_http=True)
- >>> u.to_python('foo.com')
- 'http://foo.com'
- >>> u.to_python('http://hahaha.ha/bar.html')
- 'http://hahaha.ha/bar.html'
- >>> u.to_python('http://xn--m7r7ml7t24h.com')
- 'http://xn--m7r7ml7t24h.com'
- >>> u.to_python('http://xn--c1aay4a.xn--p1ai')
- 'http://xn--c1aay4a.xn--p1ai'
- >>> u.to_python('http://foo.com/test?bar=baz&fleem=morx')
- 'http://foo.com/test?bar=baz&fleem=morx'
- >>> u.to_python('http://foo.com/login?came_from=http%3A%2F%2Ffoo.com%2Ftest')
- 'http://foo.com/login?came_from=http%3A%2F%2Ffoo.com%2Ftest'
- >>> u.to_python('http://foo.com:8000/test.html')
- 'http://foo.com:8000/test.html'
- >>> u.to_python('http://foo.com/something\\nelse')
- Traceback (most recent call last):
- ...
- Invalid: That is not a valid URL
- >>> u.to_python('https://test.com')
- 'https://test.com'
- >>> u.to_python('http://test')
- Traceback (most recent call last):
- ...
- Invalid: You must provide a full domain name (like test.com)
- >>> u.to_python('http://test..com')
- Traceback (most recent call last):
- ...
- Invalid: That is not a valid URL
- >>> u = URL(add_http=False, check_exists=True)
- >>> u.to_python('http://google.com')
- 'http://google.com'
- >>> u.to_python('google.com')
- Traceback (most recent call last):
- ...
- Invalid: You must start your URL with http://, https://, etc
- >>> u.to_python('http://www.formencode.org/does/not/exist/page.html')
- Traceback (most recent call last):
- ...
- Invalid: The server responded that the page could not be found
- >>> u.to_python('http://this.domain.does.not.exist.example.org/test.html')
- ... # doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- Invalid: An error occured when trying to connect to the server: ...
- If you want to allow addresses without a TLD (e.g., ``localhost``) you can do::
- >>> URL(require_tld=False).to_python('http://localhost')
- 'http://localhost'
- By default, internationalized domain names (IDNA) in Unicode will be
- accepted and encoded to ASCII using Punycode (as described in RFC 3490).
- You may set allow_idna to False to change this behavior::
- >>> URL(allow_idna=True).to_python(
- ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444')
- 'http://xn--c1aay4a.xn--p1ai'
- >>> URL(allow_idna=True, add_http=True).to_python(
- ... u'\u0433\u0443\u0433\u043b.\u0440\u0444')
- 'http://xn--c1aay4a.xn--p1ai'
- >>> URL(allow_idna=False).to_python(
- ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444')
- Traceback (most recent call last):
- ...
- Invalid: That is not a valid URL
- """
- add_http = True
- allow_idna = True
- check_exists = False
- require_tld = True
- url_re = re.compile(r'''
- ^(http|https)://
- (?:[%:\w]*@)? # authenticator
- (?: # ip or domain
- (?P<ip>(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|
- (?P<domain>[a-z0-9][a-z0-9\-]{,62}\.)* # subdomain
- (?P<tld>[a-z]{2,63}|xn--[a-z0-9\-]{2,59}) # top level domain
- )
- (?::[0-9]{1,5})? # port
- # files/delims/etc
- (?P<path>/[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]*)?
- $
- ''', re.I | re.VERBOSE)
- scheme_re = re.compile(r'^[a-zA-Z]+:')
- messages = dict(
- noScheme=_('You must start your URL with http://, https://, etc'),
- badURL=_('That is not a valid URL'),
- httpError=_('An error occurred when trying to access the URL:'
- ' %(error)s'),
- socketError=_('An error occured when trying to connect to the server:'
- ' %(error)s'),
- notFound=_('The server responded that the page could not be found'),
- status=_('The server responded with a bad status code (%(status)s)'),
- noTLD=_('You must provide a full domain name (like %(domain)s.com)'))
- def _convert_to_python(self, value, state):
- value = value.strip()
- if self.add_http:…
Large files files are truncated, but you can click here to view the full file