michael@0: # validate.py michael@0: # A Validator object michael@0: # Copyright (C) 2005-2010 Michael Foord, Mark Andrews, Nicola Larosa michael@0: # E-mail: fuzzyman AT voidspace DOT org DOT uk michael@0: # mark AT la-la DOT com michael@0: # nico AT tekNico DOT net michael@0: michael@0: # This software is licensed under the terms of the BSD license. michael@0: # http://www.voidspace.org.uk/python/license.shtml michael@0: # Basically you're free to copy, modify, distribute and relicense it, michael@0: # So long as you keep a copy of the license with it. michael@0: michael@0: # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml michael@0: # For information about bugfixes, updates and support, please join the michael@0: # ConfigObj mailing list: michael@0: # http://lists.sourceforge.net/lists/listinfo/configobj-develop michael@0: # Comments, suggestions and bug reports welcome. michael@0: michael@0: """ michael@0: The Validator object is used to check that supplied values michael@0: conform to a specification. michael@0: michael@0: The value can be supplied as a string - e.g. from a config file. michael@0: In this case the check will also *convert* the value to michael@0: the required type. This allows you to add validation michael@0: as a transparent layer to access data stored as strings. michael@0: The validation checks that the data is correct *and* michael@0: converts it to the expected type. michael@0: michael@0: Some standard checks are provided for basic data types. michael@0: Additional checks are easy to write. They can be michael@0: provided when the ``Validator`` is instantiated or michael@0: added afterwards. michael@0: michael@0: The standard functions work with the following basic data types : michael@0: michael@0: * integers michael@0: * floats michael@0: * booleans michael@0: * strings michael@0: * ip_addr michael@0: michael@0: plus lists of these datatypes michael@0: michael@0: Adding additional checks is done through coding simple functions. michael@0: michael@0: The full set of standard checks are : michael@0: michael@0: * 'integer': matches integer values (including negative) michael@0: Takes optional 'min' and 'max' arguments : :: michael@0: michael@0: integer() michael@0: integer(3, 9) # any value from 3 to 9 michael@0: integer(min=0) # any positive value michael@0: integer(max=9) michael@0: michael@0: * 'float': matches float values michael@0: Has the same parameters as the integer check. michael@0: michael@0: * 'boolean': matches boolean values - ``True`` or ``False`` michael@0: Acceptable string values for True are : michael@0: true, on, yes, 1 michael@0: Acceptable string values for False are : michael@0: false, off, no, 0 michael@0: michael@0: Any other value raises an error. michael@0: michael@0: * 'ip_addr': matches an Internet Protocol address, v.4, represented michael@0: by a dotted-quad string, i.e. '1.2.3.4'. michael@0: michael@0: * 'string': matches any string. michael@0: Takes optional keyword args 'min' and 'max' michael@0: to specify min and max lengths of the string. michael@0: michael@0: * 'list': matches any list. michael@0: Takes optional keyword args 'min', and 'max' to specify min and michael@0: max sizes of the list. (Always returns a list.) michael@0: michael@0: * 'tuple': matches any tuple. michael@0: Takes optional keyword args 'min', and 'max' to specify min and michael@0: max sizes of the tuple. (Always returns a tuple.) michael@0: michael@0: * 'int_list': Matches a list of integers. michael@0: Takes the same arguments as list. michael@0: michael@0: * 'float_list': Matches a list of floats. michael@0: Takes the same arguments as list. michael@0: michael@0: * 'bool_list': Matches a list of boolean values. michael@0: Takes the same arguments as list. michael@0: michael@0: * 'ip_addr_list': Matches a list of IP addresses. michael@0: Takes the same arguments as list. michael@0: michael@0: * 'string_list': Matches a list of strings. michael@0: Takes the same arguments as list. michael@0: michael@0: * 'mixed_list': Matches a list with different types in michael@0: specific positions. List size must match michael@0: the number of arguments. michael@0: michael@0: Each position can be one of : michael@0: 'integer', 'float', 'ip_addr', 'string', 'boolean' michael@0: michael@0: So to specify a list with two strings followed michael@0: by two integers, you write the check as : :: michael@0: michael@0: mixed_list('string', 'string', 'integer', 'integer') michael@0: michael@0: * 'pass': This check matches everything ! It never fails michael@0: and the value is unchanged. michael@0: michael@0: It is also the default if no check is specified. michael@0: michael@0: * 'option': This check matches any from a list of options. michael@0: You specify this check with : :: michael@0: michael@0: option('option 1', 'option 2', 'option 3') michael@0: michael@0: You can supply a default value (returned if no value is supplied) michael@0: using the default keyword argument. michael@0: michael@0: You specify a list argument for default using a list constructor syntax in michael@0: the check : :: michael@0: michael@0: checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3')) michael@0: michael@0: A badly formatted set of arguments will raise a ``VdtParamError``. michael@0: """ michael@0: michael@0: __version__ = '1.0.1' michael@0: michael@0: michael@0: __all__ = ( michael@0: '__version__', michael@0: 'dottedQuadToNum', michael@0: 'numToDottedQuad', michael@0: 'ValidateError', michael@0: 'VdtUnknownCheckError', michael@0: 'VdtParamError', michael@0: 'VdtTypeError', michael@0: 'VdtValueError', michael@0: 'VdtValueTooSmallError', michael@0: 'VdtValueTooBigError', michael@0: 'VdtValueTooShortError', michael@0: 'VdtValueTooLongError', michael@0: 'VdtMissingValue', michael@0: 'Validator', michael@0: 'is_integer', michael@0: 'is_float', michael@0: 'is_boolean', michael@0: 'is_list', michael@0: 'is_tuple', michael@0: 'is_ip_addr', michael@0: 'is_string', michael@0: 'is_int_list', michael@0: 'is_bool_list', michael@0: 'is_float_list', michael@0: 'is_string_list', michael@0: 'is_ip_addr_list', michael@0: 'is_mixed_list', michael@0: 'is_option', michael@0: '__docformat__', michael@0: ) michael@0: michael@0: michael@0: import re michael@0: michael@0: michael@0: _list_arg = re.compile(r''' michael@0: (?: michael@0: ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\( michael@0: ( michael@0: (?: michael@0: \s* michael@0: (?: michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s\)][^,\)]*?) # unquoted michael@0: ) michael@0: \s*,\s* michael@0: )* michael@0: (?: michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s\)][^,\)]*?) # unquoted michael@0: )? # last one michael@0: ) michael@0: \) michael@0: ) michael@0: ''', re.VERBOSE | re.DOTALL) # two groups michael@0: michael@0: _list_members = re.compile(r''' michael@0: ( michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s=][^,=]*?) # unquoted michael@0: ) michael@0: (?: michael@0: (?:\s*,\s*)|(?:\s*$) # comma michael@0: ) michael@0: ''', re.VERBOSE | re.DOTALL) # one group michael@0: michael@0: _paramstring = r''' michael@0: (?: michael@0: ( michael@0: (?: michael@0: [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\( michael@0: (?: michael@0: \s* michael@0: (?: michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s\)][^,\)]*?) # unquoted michael@0: ) michael@0: \s*,\s* michael@0: )* michael@0: (?: michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s\)][^,\)]*?) # unquoted michael@0: )? # last one michael@0: \) michael@0: )| michael@0: (?: michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s=][^,=]*?)| # unquoted michael@0: (?: # keyword argument michael@0: [a-zA-Z_][a-zA-Z0-9_]*\s*=\s* michael@0: (?: michael@0: (?:".*?")| # double quotes michael@0: (?:'.*?')| # single quotes michael@0: (?:[^'",\s=][^,=]*?) # unquoted michael@0: ) michael@0: ) michael@0: ) michael@0: ) michael@0: (?: michael@0: (?:\s*,\s*)|(?:\s*$) # comma michael@0: ) michael@0: ) michael@0: ''' michael@0: michael@0: _matchstring = '^%s*' % _paramstring michael@0: michael@0: # Python pre 2.2.1 doesn't have bool michael@0: try: michael@0: bool michael@0: except NameError: michael@0: def bool(val): michael@0: """Simple boolean equivalent function. """ michael@0: if val: michael@0: return 1 michael@0: else: michael@0: return 0 michael@0: michael@0: michael@0: def dottedQuadToNum(ip): michael@0: """ michael@0: Convert decimal dotted quad string to long integer michael@0: michael@0: >>> int(dottedQuadToNum('1 ')) michael@0: 1 michael@0: >>> int(dottedQuadToNum(' 1.2')) michael@0: 16777218 michael@0: >>> int(dottedQuadToNum(' 1.2.3 ')) michael@0: 16908291 michael@0: >>> int(dottedQuadToNum('1.2.3.4')) michael@0: 16909060 michael@0: >>> dottedQuadToNum('255.255.255.255') michael@0: 4294967295L michael@0: >>> dottedQuadToNum('255.255.255.256') michael@0: Traceback (most recent call last): michael@0: ValueError: Not a good dotted-quad IP: 255.255.255.256 michael@0: """ michael@0: michael@0: # import here to avoid it when ip_addr values are not used michael@0: import socket, struct michael@0: michael@0: try: michael@0: return struct.unpack('!L', michael@0: socket.inet_aton(ip.strip()))[0] michael@0: except socket.error: michael@0: # bug in inet_aton, corrected in Python 2.4 michael@0: if ip.strip() == '255.255.255.255': michael@0: return 0xFFFFFFFFL michael@0: else: michael@0: raise ValueError('Not a good dotted-quad IP: %s' % ip) michael@0: return michael@0: michael@0: michael@0: def numToDottedQuad(num): michael@0: """ michael@0: Convert long int to dotted quad string michael@0: michael@0: >>> numToDottedQuad(-1L) michael@0: Traceback (most recent call last): michael@0: ValueError: Not a good numeric IP: -1 michael@0: >>> numToDottedQuad(1L) michael@0: '0.0.0.1' michael@0: >>> numToDottedQuad(16777218L) michael@0: '1.0.0.2' michael@0: >>> numToDottedQuad(16908291L) michael@0: '1.2.0.3' michael@0: >>> numToDottedQuad(16909060L) michael@0: '1.2.3.4' michael@0: >>> numToDottedQuad(4294967295L) michael@0: '255.255.255.255' michael@0: >>> numToDottedQuad(4294967296L) michael@0: Traceback (most recent call last): michael@0: ValueError: Not a good numeric IP: 4294967296 michael@0: """ michael@0: michael@0: # import here to avoid it when ip_addr values are not used michael@0: import socket, struct michael@0: michael@0: # no need to intercept here, 4294967295L is fine michael@0: if num > 4294967295L or num < 0: michael@0: raise ValueError('Not a good numeric IP: %s' % num) michael@0: try: michael@0: return socket.inet_ntoa( michael@0: struct.pack('!L', long(num))) michael@0: except (socket.error, struct.error, OverflowError): michael@0: raise ValueError('Not a good numeric IP: %s' % num) michael@0: michael@0: michael@0: class ValidateError(Exception): michael@0: """ michael@0: This error indicates that the check failed. michael@0: It can be the base class for more specific errors. michael@0: michael@0: Any check function that fails ought to raise this error. michael@0: (or a subclass) michael@0: michael@0: >>> raise ValidateError michael@0: Traceback (most recent call last): michael@0: ValidateError michael@0: """ michael@0: michael@0: michael@0: class VdtMissingValue(ValidateError): michael@0: """No value was supplied to a check that needed one.""" michael@0: michael@0: michael@0: class VdtUnknownCheckError(ValidateError): michael@0: """An unknown check function was requested""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtUnknownCheckError('yoda') michael@0: Traceback (most recent call last): michael@0: VdtUnknownCheckError: the check "yoda" is unknown. michael@0: """ michael@0: ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,)) michael@0: michael@0: michael@0: class VdtParamError(SyntaxError): michael@0: """An incorrect parameter was passed""" michael@0: michael@0: def __init__(self, name, value): michael@0: """ michael@0: >>> raise VdtParamError('yoda', 'jedi') michael@0: Traceback (most recent call last): michael@0: VdtParamError: passed an incorrect value "jedi" for parameter "yoda". michael@0: """ michael@0: SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name)) michael@0: michael@0: michael@0: class VdtTypeError(ValidateError): michael@0: """The value supplied was of the wrong type""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtTypeError('jedi') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "jedi" is of the wrong type. michael@0: """ michael@0: ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,)) michael@0: michael@0: michael@0: class VdtValueError(ValidateError): michael@0: """The value supplied was of the correct type, but was not an allowed value.""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtValueError('jedi') michael@0: Traceback (most recent call last): michael@0: VdtValueError: the value "jedi" is unacceptable. michael@0: """ michael@0: ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,)) michael@0: michael@0: michael@0: class VdtValueTooSmallError(VdtValueError): michael@0: """The value supplied was of the correct type, but was too small.""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtValueTooSmallError('0') michael@0: Traceback (most recent call last): michael@0: VdtValueTooSmallError: the value "0" is too small. michael@0: """ michael@0: ValidateError.__init__(self, 'the value "%s" is too small.' % (value,)) michael@0: michael@0: michael@0: class VdtValueTooBigError(VdtValueError): michael@0: """The value supplied was of the correct type, but was too big.""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtValueTooBigError('1') michael@0: Traceback (most recent call last): michael@0: VdtValueTooBigError: the value "1" is too big. michael@0: """ michael@0: ValidateError.__init__(self, 'the value "%s" is too big.' % (value,)) michael@0: michael@0: michael@0: class VdtValueTooShortError(VdtValueError): michael@0: """The value supplied was of the correct type, but was too short.""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtValueTooShortError('jed') michael@0: Traceback (most recent call last): michael@0: VdtValueTooShortError: the value "jed" is too short. michael@0: """ michael@0: ValidateError.__init__( michael@0: self, michael@0: 'the value "%s" is too short.' % (value,)) michael@0: michael@0: michael@0: class VdtValueTooLongError(VdtValueError): michael@0: """The value supplied was of the correct type, but was too long.""" michael@0: michael@0: def __init__(self, value): michael@0: """ michael@0: >>> raise VdtValueTooLongError('jedie') michael@0: Traceback (most recent call last): michael@0: VdtValueTooLongError: the value "jedie" is too long. michael@0: """ michael@0: ValidateError.__init__(self, 'the value "%s" is too long.' % (value,)) michael@0: michael@0: michael@0: class Validator(object): michael@0: """ michael@0: Validator is an object that allows you to register a set of 'checks'. michael@0: These checks take input and test that it conforms to the check. michael@0: michael@0: This can also involve converting the value from a string into michael@0: the correct datatype. michael@0: michael@0: The ``check`` method takes an input string which configures which michael@0: check is to be used and applies that check to a supplied value. michael@0: michael@0: An example input string would be: michael@0: 'int_range(param1, param2)' michael@0: michael@0: You would then provide something like: michael@0: michael@0: >>> def int_range_check(value, min, max): michael@0: ... # turn min and max from strings to integers michael@0: ... min = int(min) michael@0: ... max = int(max) michael@0: ... # check that value is of the correct type. michael@0: ... # possible valid inputs are integers or strings michael@0: ... # that represent integers michael@0: ... if not isinstance(value, (int, long, basestring)): michael@0: ... raise VdtTypeError(value) michael@0: ... elif isinstance(value, basestring): michael@0: ... # if we are given a string michael@0: ... # attempt to convert to an integer michael@0: ... try: michael@0: ... value = int(value) michael@0: ... except ValueError: michael@0: ... raise VdtValueError(value) michael@0: ... # check the value is between our constraints michael@0: ... if not min <= value: michael@0: ... raise VdtValueTooSmallError(value) michael@0: ... if not value <= max: michael@0: ... raise VdtValueTooBigError(value) michael@0: ... return value michael@0: michael@0: >>> fdict = {'int_range': int_range_check} michael@0: >>> vtr1 = Validator(fdict) michael@0: >>> vtr1.check('int_range(20, 40)', '30') michael@0: 30 michael@0: >>> vtr1.check('int_range(20, 40)', '60') michael@0: Traceback (most recent call last): michael@0: VdtValueTooBigError: the value "60" is too big. michael@0: michael@0: New functions can be added with : :: michael@0: michael@0: >>> vtr2 = Validator() michael@0: >>> vtr2.functions['int_range'] = int_range_check michael@0: michael@0: Or by passing in a dictionary of functions when Validator michael@0: is instantiated. michael@0: michael@0: Your functions *can* use keyword arguments, michael@0: but the first argument should always be 'value'. michael@0: michael@0: If the function doesn't take additional arguments, michael@0: the parentheses are optional in the check. michael@0: It can be written with either of : :: michael@0: michael@0: keyword = function_name michael@0: keyword = function_name() michael@0: michael@0: The first program to utilise Validator() was Michael Foord's michael@0: ConfigObj, an alternative to ConfigParser which supports lists and michael@0: can validate a config file using a config schema. michael@0: For more details on using Validator with ConfigObj see: michael@0: http://www.voidspace.org.uk/python/configobj.html michael@0: """ michael@0: michael@0: # this regex does the initial parsing of the checks michael@0: _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL) michael@0: michael@0: # this regex takes apart keyword arguments michael@0: _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) michael@0: michael@0: michael@0: # this regex finds keyword=list(....) type values michael@0: _list_arg = _list_arg michael@0: michael@0: # this regex takes individual values out of lists - in one pass michael@0: _list_members = _list_members michael@0: michael@0: # These regexes check a set of arguments for validity michael@0: # and then pull the members out michael@0: _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL) michael@0: _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL) michael@0: michael@0: michael@0: def __init__(self, functions=None): michael@0: """ michael@0: >>> vtri = Validator() michael@0: """ michael@0: self.functions = { michael@0: '': self._pass, michael@0: 'integer': is_integer, michael@0: 'float': is_float, michael@0: 'boolean': is_boolean, michael@0: 'ip_addr': is_ip_addr, michael@0: 'string': is_string, michael@0: 'list': is_list, michael@0: 'tuple': is_tuple, michael@0: 'int_list': is_int_list, michael@0: 'float_list': is_float_list, michael@0: 'bool_list': is_bool_list, michael@0: 'ip_addr_list': is_ip_addr_list, michael@0: 'string_list': is_string_list, michael@0: 'mixed_list': is_mixed_list, michael@0: 'pass': self._pass, michael@0: 'option': is_option, michael@0: 'force_list': force_list, michael@0: } michael@0: if functions is not None: michael@0: self.functions.update(functions) michael@0: # tekNico: for use by ConfigObj michael@0: self.baseErrorClass = ValidateError michael@0: self._cache = {} michael@0: michael@0: michael@0: def check(self, check, value, missing=False): michael@0: """ michael@0: Usage: check(check, value) michael@0: michael@0: Arguments: michael@0: check: string representing check to apply (including arguments) michael@0: value: object to be checked michael@0: Returns value, converted to correct type if necessary michael@0: michael@0: If the check fails, raises a ``ValidateError`` subclass. michael@0: michael@0: >>> vtor.check('yoda', '') michael@0: Traceback (most recent call last): michael@0: VdtUnknownCheckError: the check "yoda" is unknown. michael@0: >>> vtor.check('yoda()', '') michael@0: Traceback (most recent call last): michael@0: VdtUnknownCheckError: the check "yoda" is unknown. michael@0: michael@0: >>> vtor.check('string(default="")', '', missing=True) michael@0: '' michael@0: """ michael@0: fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) michael@0: michael@0: if missing: michael@0: if default is None: michael@0: # no information needed here - to be handled by caller michael@0: raise VdtMissingValue() michael@0: value = self._handle_none(default) michael@0: michael@0: if value is None: michael@0: return None michael@0: michael@0: return self._check_value(value, fun_name, fun_args, fun_kwargs) michael@0: michael@0: michael@0: def _handle_none(self, value): michael@0: if value == 'None': michael@0: return None michael@0: elif value in ("'None'", '"None"'): michael@0: # Special case a quoted None michael@0: value = self._unquote(value) michael@0: return value michael@0: michael@0: michael@0: def _parse_with_caching(self, check): michael@0: if check in self._cache: michael@0: fun_name, fun_args, fun_kwargs, default = self._cache[check] michael@0: # We call list and dict below to work with *copies* of the data michael@0: # rather than the original (which are mutable of course) michael@0: fun_args = list(fun_args) michael@0: fun_kwargs = dict(fun_kwargs) michael@0: else: michael@0: fun_name, fun_args, fun_kwargs, default = self._parse_check(check) michael@0: fun_kwargs = dict([(str(key), value) for (key, value) in fun_kwargs.items()]) michael@0: self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default michael@0: return fun_name, fun_args, fun_kwargs, default michael@0: michael@0: michael@0: def _check_value(self, value, fun_name, fun_args, fun_kwargs): michael@0: try: michael@0: fun = self.functions[fun_name] michael@0: except KeyError: michael@0: raise VdtUnknownCheckError(fun_name) michael@0: else: michael@0: return fun(value, *fun_args, **fun_kwargs) michael@0: michael@0: michael@0: def _parse_check(self, check): michael@0: fun_match = self._func_re.match(check) michael@0: if fun_match: michael@0: fun_name = fun_match.group(1) michael@0: arg_string = fun_match.group(2) michael@0: arg_match = self._matchfinder.match(arg_string) michael@0: if arg_match is None: michael@0: # Bad syntax michael@0: raise VdtParamError('Bad syntax in check "%s".' % check) michael@0: fun_args = [] michael@0: fun_kwargs = {} michael@0: # pull out args of group 2 michael@0: for arg in self._paramfinder.findall(arg_string): michael@0: # args may need whitespace removing (before removing quotes) michael@0: arg = arg.strip() michael@0: listmatch = self._list_arg.match(arg) michael@0: if listmatch: michael@0: key, val = self._list_handle(listmatch) michael@0: fun_kwargs[key] = val michael@0: continue michael@0: keymatch = self._key_arg.match(arg) michael@0: if keymatch: michael@0: val = keymatch.group(2) michael@0: if not val in ("'None'", '"None"'): michael@0: # Special case a quoted None michael@0: val = self._unquote(val) michael@0: fun_kwargs[keymatch.group(1)] = val michael@0: continue michael@0: michael@0: fun_args.append(self._unquote(arg)) michael@0: else: michael@0: # allows for function names without (args) michael@0: return check, (), {}, None michael@0: michael@0: # Default must be deleted if the value is specified too, michael@0: # otherwise the check function will get a spurious "default" keyword arg michael@0: default = fun_kwargs.pop('default', None) michael@0: return fun_name, fun_args, fun_kwargs, default michael@0: michael@0: michael@0: def _unquote(self, val): michael@0: """Unquote a value if necessary.""" michael@0: if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]): michael@0: val = val[1:-1] michael@0: return val michael@0: michael@0: michael@0: def _list_handle(self, listmatch): michael@0: """Take apart a ``keyword=list('val, 'val')`` type string.""" michael@0: out = [] michael@0: name = listmatch.group(1) michael@0: args = listmatch.group(2) michael@0: for arg in self._list_members.findall(args): michael@0: out.append(self._unquote(arg)) michael@0: return name, out michael@0: michael@0: michael@0: def _pass(self, value): michael@0: """ michael@0: Dummy check that always passes michael@0: michael@0: >>> vtor.check('', 0) michael@0: 0 michael@0: >>> vtor.check('', '0') michael@0: '0' michael@0: """ michael@0: return value michael@0: michael@0: michael@0: def get_default_value(self, check): michael@0: """ michael@0: Given a check, return the default value for the check michael@0: (converted to the right type). michael@0: michael@0: If the check doesn't specify a default value then a michael@0: ``KeyError`` will be raised. michael@0: """ michael@0: fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) michael@0: if default is None: michael@0: raise KeyError('Check "%s" has no default value.' % check) michael@0: value = self._handle_none(default) michael@0: if value is None: michael@0: return value michael@0: return self._check_value(value, fun_name, fun_args, fun_kwargs) michael@0: michael@0: michael@0: def _is_num_param(names, values, to_float=False): michael@0: """ michael@0: Return numbers from inputs or raise VdtParamError. michael@0: michael@0: Lets ``None`` pass through. michael@0: Pass in keyword argument ``to_float=True`` to michael@0: use float for the conversion rather than int. michael@0: michael@0: >>> _is_num_param(('', ''), (0, 1.0)) michael@0: [0, 1] michael@0: >>> _is_num_param(('', ''), (0, 1.0), to_float=True) michael@0: [0.0, 1.0] michael@0: >>> _is_num_param(('a'), ('a')) michael@0: Traceback (most recent call last): michael@0: VdtParamError: passed an incorrect value "a" for parameter "a". michael@0: """ michael@0: fun = to_float and float or int michael@0: out_params = [] michael@0: for (name, val) in zip(names, values): michael@0: if val is None: michael@0: out_params.append(val) michael@0: elif isinstance(val, (int, long, float, basestring)): michael@0: try: michael@0: out_params.append(fun(val)) michael@0: except ValueError, e: michael@0: raise VdtParamError(name, val) michael@0: else: michael@0: raise VdtParamError(name, val) michael@0: return out_params michael@0: michael@0: michael@0: # built in checks michael@0: # you can override these by setting the appropriate name michael@0: # in Validator.functions michael@0: # note: if the params are specified wrongly in your input string, michael@0: # you will also raise errors. michael@0: michael@0: def is_integer(value, min=None, max=None): michael@0: """ michael@0: A check that tests that a given value is an integer (int, or long) michael@0: and optionally, between bounds. A negative value is accepted, while michael@0: a float will fail. michael@0: michael@0: If the value is a string, then the conversion is done - if possible. michael@0: Otherwise a VdtError is raised. michael@0: michael@0: >>> vtor.check('integer', '-1') michael@0: -1 michael@0: >>> vtor.check('integer', '0') michael@0: 0 michael@0: >>> vtor.check('integer', 9) michael@0: 9 michael@0: >>> vtor.check('integer', 'a') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "a" is of the wrong type. michael@0: >>> vtor.check('integer', '2.2') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "2.2" is of the wrong type. michael@0: >>> vtor.check('integer(10)', '20') michael@0: 20 michael@0: >>> vtor.check('integer(max=20)', '15') michael@0: 15 michael@0: >>> vtor.check('integer(10)', '9') michael@0: Traceback (most recent call last): michael@0: VdtValueTooSmallError: the value "9" is too small. michael@0: >>> vtor.check('integer(10)', 9) michael@0: Traceback (most recent call last): michael@0: VdtValueTooSmallError: the value "9" is too small. michael@0: >>> vtor.check('integer(max=20)', '35') michael@0: Traceback (most recent call last): michael@0: VdtValueTooBigError: the value "35" is too big. michael@0: >>> vtor.check('integer(max=20)', 35) michael@0: Traceback (most recent call last): michael@0: VdtValueTooBigError: the value "35" is too big. michael@0: >>> vtor.check('integer(0, 9)', False) michael@0: 0 michael@0: """ michael@0: (min_val, max_val) = _is_num_param(('min', 'max'), (min, max)) michael@0: if not isinstance(value, (int, long, basestring)): michael@0: raise VdtTypeError(value) michael@0: if isinstance(value, basestring): michael@0: # if it's a string - does it represent an integer ? michael@0: try: michael@0: value = int(value) michael@0: except ValueError: michael@0: raise VdtTypeError(value) michael@0: if (min_val is not None) and (value < min_val): michael@0: raise VdtValueTooSmallError(value) michael@0: if (max_val is not None) and (value > max_val): michael@0: raise VdtValueTooBigError(value) michael@0: return value michael@0: michael@0: michael@0: def is_float(value, min=None, max=None): michael@0: """ michael@0: A check that tests that a given value is a float michael@0: (an integer will be accepted), and optionally - that it is between bounds. michael@0: michael@0: If the value is a string, then the conversion is done - if possible. michael@0: Otherwise a VdtError is raised. michael@0: michael@0: This can accept negative values. michael@0: michael@0: >>> vtor.check('float', '2') michael@0: 2.0 michael@0: michael@0: From now on we multiply the value to avoid comparing decimals michael@0: michael@0: >>> vtor.check('float', '-6.8') * 10 michael@0: -68.0 michael@0: >>> vtor.check('float', '12.2') * 10 michael@0: 122.0 michael@0: >>> vtor.check('float', 8.4) * 10 michael@0: 84.0 michael@0: >>> vtor.check('float', 'a') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "a" is of the wrong type. michael@0: >>> vtor.check('float(10.1)', '10.2') * 10 michael@0: 102.0 michael@0: >>> vtor.check('float(max=20.2)', '15.1') * 10 michael@0: 151.0 michael@0: >>> vtor.check('float(10.0)', '9.0') michael@0: Traceback (most recent call last): michael@0: VdtValueTooSmallError: the value "9.0" is too small. michael@0: >>> vtor.check('float(max=20.0)', '35.0') michael@0: Traceback (most recent call last): michael@0: VdtValueTooBigError: the value "35.0" is too big. michael@0: """ michael@0: (min_val, max_val) = _is_num_param( michael@0: ('min', 'max'), (min, max), to_float=True) michael@0: if not isinstance(value, (int, long, float, basestring)): michael@0: raise VdtTypeError(value) michael@0: if not isinstance(value, float): michael@0: # if it's a string - does it represent a float ? michael@0: try: michael@0: value = float(value) michael@0: except ValueError: michael@0: raise VdtTypeError(value) michael@0: if (min_val is not None) and (value < min_val): michael@0: raise VdtValueTooSmallError(value) michael@0: if (max_val is not None) and (value > max_val): michael@0: raise VdtValueTooBigError(value) michael@0: return value michael@0: michael@0: michael@0: bool_dict = { michael@0: True: True, 'on': True, '1': True, 'true': True, 'yes': True, michael@0: False: False, 'off': False, '0': False, 'false': False, 'no': False, michael@0: } michael@0: michael@0: michael@0: def is_boolean(value): michael@0: """ michael@0: Check if the value represents a boolean. michael@0: michael@0: >>> vtor.check('boolean', 0) michael@0: 0 michael@0: >>> vtor.check('boolean', False) michael@0: 0 michael@0: >>> vtor.check('boolean', '0') michael@0: 0 michael@0: >>> vtor.check('boolean', 'off') michael@0: 0 michael@0: >>> vtor.check('boolean', 'false') michael@0: 0 michael@0: >>> vtor.check('boolean', 'no') michael@0: 0 michael@0: >>> vtor.check('boolean', 'nO') michael@0: 0 michael@0: >>> vtor.check('boolean', 'NO') michael@0: 0 michael@0: >>> vtor.check('boolean', 1) michael@0: 1 michael@0: >>> vtor.check('boolean', True) michael@0: 1 michael@0: >>> vtor.check('boolean', '1') michael@0: 1 michael@0: >>> vtor.check('boolean', 'on') michael@0: 1 michael@0: >>> vtor.check('boolean', 'true') michael@0: 1 michael@0: >>> vtor.check('boolean', 'yes') michael@0: 1 michael@0: >>> vtor.check('boolean', 'Yes') michael@0: 1 michael@0: >>> vtor.check('boolean', 'YES') michael@0: 1 michael@0: >>> vtor.check('boolean', '') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "" is of the wrong type. michael@0: >>> vtor.check('boolean', 'up') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "up" is of the wrong type. michael@0: michael@0: """ michael@0: if isinstance(value, basestring): michael@0: try: michael@0: return bool_dict[value.lower()] michael@0: except KeyError: michael@0: raise VdtTypeError(value) michael@0: # we do an equality test rather than an identity test michael@0: # this ensures Python 2.2 compatibilty michael@0: # and allows 0 and 1 to represent True and False michael@0: if value == False: michael@0: return False michael@0: elif value == True: michael@0: return True michael@0: else: michael@0: raise VdtTypeError(value) michael@0: michael@0: michael@0: def is_ip_addr(value): michael@0: """ michael@0: Check that the supplied value is an Internet Protocol address, v.4, michael@0: represented by a dotted-quad string, i.e. '1.2.3.4'. michael@0: michael@0: >>> vtor.check('ip_addr', '1 ') michael@0: '1' michael@0: >>> vtor.check('ip_addr', ' 1.2') michael@0: '1.2' michael@0: >>> vtor.check('ip_addr', ' 1.2.3 ') michael@0: '1.2.3' michael@0: >>> vtor.check('ip_addr', '1.2.3.4') michael@0: '1.2.3.4' michael@0: >>> vtor.check('ip_addr', '0.0.0.0') michael@0: '0.0.0.0' michael@0: >>> vtor.check('ip_addr', '255.255.255.255') michael@0: '255.255.255.255' michael@0: >>> vtor.check('ip_addr', '255.255.255.256') michael@0: Traceback (most recent call last): michael@0: VdtValueError: the value "255.255.255.256" is unacceptable. michael@0: >>> vtor.check('ip_addr', '1.2.3.4.5') michael@0: Traceback (most recent call last): michael@0: VdtValueError: the value "1.2.3.4.5" is unacceptable. michael@0: >>> vtor.check('ip_addr', 0) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "0" is of the wrong type. michael@0: """ michael@0: if not isinstance(value, basestring): michael@0: raise VdtTypeError(value) michael@0: value = value.strip() michael@0: try: michael@0: dottedQuadToNum(value) michael@0: except ValueError: michael@0: raise VdtValueError(value) michael@0: return value michael@0: michael@0: michael@0: def is_list(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a list of values. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: It does no check on list members. michael@0: michael@0: >>> vtor.check('list', ()) michael@0: [] michael@0: >>> vtor.check('list', []) michael@0: [] michael@0: >>> vtor.check('list', (1, 2)) michael@0: [1, 2] michael@0: >>> vtor.check('list', [1, 2]) michael@0: [1, 2] michael@0: >>> vtor.check('list(3)', (1, 2)) michael@0: Traceback (most recent call last): michael@0: VdtValueTooShortError: the value "(1, 2)" is too short. michael@0: >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6)) michael@0: Traceback (most recent call last): michael@0: VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. michael@0: >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4)) michael@0: [1, 2, 3, 4] michael@0: >>> vtor.check('list', 0) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "0" is of the wrong type. michael@0: >>> vtor.check('list', '12') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "12" is of the wrong type. michael@0: """ michael@0: (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) michael@0: if isinstance(value, basestring): michael@0: raise VdtTypeError(value) michael@0: try: michael@0: num_members = len(value) michael@0: except TypeError: michael@0: raise VdtTypeError(value) michael@0: if min_len is not None and num_members < min_len: michael@0: raise VdtValueTooShortError(value) michael@0: if max_len is not None and num_members > max_len: michael@0: raise VdtValueTooLongError(value) michael@0: return list(value) michael@0: michael@0: michael@0: def is_tuple(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a tuple of values. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: It does no check on members. michael@0: michael@0: >>> vtor.check('tuple', ()) michael@0: () michael@0: >>> vtor.check('tuple', []) michael@0: () michael@0: >>> vtor.check('tuple', (1, 2)) michael@0: (1, 2) michael@0: >>> vtor.check('tuple', [1, 2]) michael@0: (1, 2) michael@0: >>> vtor.check('tuple(3)', (1, 2)) michael@0: Traceback (most recent call last): michael@0: VdtValueTooShortError: the value "(1, 2)" is too short. michael@0: >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6)) michael@0: Traceback (most recent call last): michael@0: VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. michael@0: >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4)) michael@0: (1, 2, 3, 4) michael@0: >>> vtor.check('tuple', 0) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "0" is of the wrong type. michael@0: >>> vtor.check('tuple', '12') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "12" is of the wrong type. michael@0: """ michael@0: return tuple(is_list(value, min, max)) michael@0: michael@0: michael@0: def is_string(value, min=None, max=None): michael@0: """ michael@0: Check that the supplied value is a string. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: >>> vtor.check('string', '0') michael@0: '0' michael@0: >>> vtor.check('string', 0) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "0" is of the wrong type. michael@0: >>> vtor.check('string(2)', '12') michael@0: '12' michael@0: >>> vtor.check('string(2)', '1') michael@0: Traceback (most recent call last): michael@0: VdtValueTooShortError: the value "1" is too short. michael@0: >>> vtor.check('string(min=2, max=3)', '123') michael@0: '123' michael@0: >>> vtor.check('string(min=2, max=3)', '1234') michael@0: Traceback (most recent call last): michael@0: VdtValueTooLongError: the value "1234" is too long. michael@0: """ michael@0: if not isinstance(value, basestring): michael@0: raise VdtTypeError(value) michael@0: (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) michael@0: try: michael@0: num_members = len(value) michael@0: except TypeError: michael@0: raise VdtTypeError(value) michael@0: if min_len is not None and num_members < min_len: michael@0: raise VdtValueTooShortError(value) michael@0: if max_len is not None and num_members > max_len: michael@0: raise VdtValueTooLongError(value) michael@0: return value michael@0: michael@0: michael@0: def is_int_list(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a list of integers. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: Each list member is checked that it is an integer. michael@0: michael@0: >>> vtor.check('int_list', ()) michael@0: [] michael@0: >>> vtor.check('int_list', []) michael@0: [] michael@0: >>> vtor.check('int_list', (1, 2)) michael@0: [1, 2] michael@0: >>> vtor.check('int_list', [1, 2]) michael@0: [1, 2] michael@0: >>> vtor.check('int_list', [1, 'a']) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "a" is of the wrong type. michael@0: """ michael@0: return [is_integer(mem) for mem in is_list(value, min, max)] michael@0: michael@0: michael@0: def is_bool_list(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a list of booleans. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: Each list member is checked that it is a boolean. michael@0: michael@0: >>> vtor.check('bool_list', ()) michael@0: [] michael@0: >>> vtor.check('bool_list', []) michael@0: [] michael@0: >>> check_res = vtor.check('bool_list', (True, False)) michael@0: >>> check_res == [True, False] michael@0: 1 michael@0: >>> check_res = vtor.check('bool_list', [True, False]) michael@0: >>> check_res == [True, False] michael@0: 1 michael@0: >>> vtor.check('bool_list', [True, 'a']) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "a" is of the wrong type. michael@0: """ michael@0: return [is_boolean(mem) for mem in is_list(value, min, max)] michael@0: michael@0: michael@0: def is_float_list(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a list of floats. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: Each list member is checked that it is a float. michael@0: michael@0: >>> vtor.check('float_list', ()) michael@0: [] michael@0: >>> vtor.check('float_list', []) michael@0: [] michael@0: >>> vtor.check('float_list', (1, 2.0)) michael@0: [1.0, 2.0] michael@0: >>> vtor.check('float_list', [1, 2.0]) michael@0: [1.0, 2.0] michael@0: >>> vtor.check('float_list', [1, 'a']) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "a" is of the wrong type. michael@0: """ michael@0: return [is_float(mem) for mem in is_list(value, min, max)] michael@0: michael@0: michael@0: def is_string_list(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a list of strings. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: Each list member is checked that it is a string. michael@0: michael@0: >>> vtor.check('string_list', ()) michael@0: [] michael@0: >>> vtor.check('string_list', []) michael@0: [] michael@0: >>> vtor.check('string_list', ('a', 'b')) michael@0: ['a', 'b'] michael@0: >>> vtor.check('string_list', ['a', 1]) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "1" is of the wrong type. michael@0: >>> vtor.check('string_list', 'hello') michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "hello" is of the wrong type. michael@0: """ michael@0: if isinstance(value, basestring): michael@0: raise VdtTypeError(value) michael@0: return [is_string(mem) for mem in is_list(value, min, max)] michael@0: michael@0: michael@0: def is_ip_addr_list(value, min=None, max=None): michael@0: """ michael@0: Check that the value is a list of IP addresses. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: michael@0: Each list member is checked that it is an IP address. michael@0: michael@0: >>> vtor.check('ip_addr_list', ()) michael@0: [] michael@0: >>> vtor.check('ip_addr_list', []) michael@0: [] michael@0: >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8')) michael@0: ['1.2.3.4', '5.6.7.8'] michael@0: >>> vtor.check('ip_addr_list', ['a']) michael@0: Traceback (most recent call last): michael@0: VdtValueError: the value "a" is unacceptable. michael@0: """ michael@0: return [is_ip_addr(mem) for mem in is_list(value, min, max)] michael@0: michael@0: michael@0: def force_list(value, min=None, max=None): michael@0: """ michael@0: Check that a value is a list, coercing strings into michael@0: a list with one member. Useful where users forget the michael@0: trailing comma that turns a single value into a list. michael@0: michael@0: You can optionally specify the minimum and maximum number of members. michael@0: A minumum of greater than one will fail if the user only supplies a michael@0: string. michael@0: michael@0: >>> vtor.check('force_list', ()) michael@0: [] michael@0: >>> vtor.check('force_list', []) michael@0: [] michael@0: >>> vtor.check('force_list', 'hello') michael@0: ['hello'] michael@0: """ michael@0: if not isinstance(value, (list, tuple)): michael@0: value = [value] michael@0: return is_list(value, min, max) michael@0: michael@0: michael@0: michael@0: fun_dict = { michael@0: 'integer': is_integer, michael@0: 'float': is_float, michael@0: 'ip_addr': is_ip_addr, michael@0: 'string': is_string, michael@0: 'boolean': is_boolean, michael@0: } michael@0: michael@0: michael@0: def is_mixed_list(value, *args): michael@0: """ michael@0: Check that the value is a list. michael@0: Allow specifying the type of each member. michael@0: Work on lists of specific lengths. michael@0: michael@0: You specify each member as a positional argument specifying type michael@0: michael@0: Each type should be one of the following strings : michael@0: 'integer', 'float', 'ip_addr', 'string', 'boolean' michael@0: michael@0: So you can specify a list of two strings, followed by michael@0: two integers as : michael@0: michael@0: mixed_list('string', 'string', 'integer', 'integer') michael@0: michael@0: The length of the list must match the number of positional michael@0: arguments you supply. michael@0: michael@0: >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')" michael@0: >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True)) michael@0: >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] michael@0: 1 michael@0: >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True')) michael@0: >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] michael@0: 1 michael@0: >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True)) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "b" is of the wrong type. michael@0: >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a')) michael@0: Traceback (most recent call last): michael@0: VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short. michael@0: >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b')) michael@0: Traceback (most recent call last): michael@0: VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long. michael@0: >>> vtor.check(mix_str, 0) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "0" is of the wrong type. michael@0: michael@0: This test requires an elaborate setup, because of a change in error string michael@0: output from the interpreter between Python 2.2 and 2.3 . michael@0: michael@0: >>> res_seq = ( michael@0: ... 'passed an incorrect value "', michael@0: ... 'yoda', michael@0: ... '" for parameter "mixed_list".', michael@0: ... ) michael@0: >>> res_str = "'".join(res_seq) michael@0: >>> try: michael@0: ... vtor.check('mixed_list("yoda")', ('a')) michael@0: ... except VdtParamError, err: michael@0: ... str(err) == res_str michael@0: 1 michael@0: """ michael@0: try: michael@0: length = len(value) michael@0: except TypeError: michael@0: raise VdtTypeError(value) michael@0: if length < len(args): michael@0: raise VdtValueTooShortError(value) michael@0: elif length > len(args): michael@0: raise VdtValueTooLongError(value) michael@0: try: michael@0: return [fun_dict[arg](val) for arg, val in zip(args, value)] michael@0: except KeyError, e: michael@0: raise VdtParamError('mixed_list', e) michael@0: michael@0: michael@0: def is_option(value, *options): michael@0: """ michael@0: This check matches the value to any of a set of options. michael@0: michael@0: >>> vtor.check('option("yoda", "jedi")', 'yoda') michael@0: 'yoda' michael@0: >>> vtor.check('option("yoda", "jedi")', 'jed') michael@0: Traceback (most recent call last): michael@0: VdtValueError: the value "jed" is unacceptable. michael@0: >>> vtor.check('option("yoda", "jedi")', 0) michael@0: Traceback (most recent call last): michael@0: VdtTypeError: the value "0" is of the wrong type. michael@0: """ michael@0: if not isinstance(value, basestring): michael@0: raise VdtTypeError(value) michael@0: if not value in options: michael@0: raise VdtValueError(value) michael@0: return value michael@0: michael@0: michael@0: def _test(value, *args, **keywargs): michael@0: """ michael@0: A function that exists for test purposes. michael@0: michael@0: >>> checks = [ michael@0: ... '3, 6, min=1, max=3, test=list(a, b, c)', michael@0: ... '3', michael@0: ... '3, 6', michael@0: ... '3,', michael@0: ... 'min=1, test="a b c"', michael@0: ... 'min=5, test="a, b, c"', michael@0: ... 'min=1, max=3, test="a, b, c"', michael@0: ... 'min=-100, test=-99', michael@0: ... 'min=1, max=3', michael@0: ... '3, 6, test="36"', michael@0: ... '3, 6, test="a, b, c"', michael@0: ... '3, max=3, test=list("a", "b", "c")', michael@0: ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''', michael@0: ... "test='x=fish(3)'", michael@0: ... ] michael@0: >>> v = Validator({'test': _test}) michael@0: >>> for entry in checks: michael@0: ... print v.check(('test(%s)' % entry), 3) michael@0: (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'}) michael@0: (3, ('3',), {}) michael@0: (3, ('3', '6'), {}) michael@0: (3, ('3',), {}) michael@0: (3, (), {'test': 'a b c', 'min': '1'}) michael@0: (3, (), {'test': 'a, b, c', 'min': '5'}) michael@0: (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'}) michael@0: (3, (), {'test': '-99', 'min': '-100'}) michael@0: (3, (), {'max': '3', 'min': '1'}) michael@0: (3, ('3', '6'), {'test': '36'}) michael@0: (3, ('3', '6'), {'test': 'a, b, c'}) michael@0: (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'}) michael@0: (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'}) michael@0: (3, (), {'test': 'x=fish(3)'}) michael@0: michael@0: >>> v = Validator() michael@0: >>> v.check('integer(default=6)', '3') michael@0: 3 michael@0: >>> v.check('integer(default=6)', None, True) michael@0: 6 michael@0: >>> v.get_default_value('integer(default=6)') michael@0: 6 michael@0: >>> v.get_default_value('float(default=6)') michael@0: 6.0 michael@0: >>> v.get_default_value('pass(default=None)') michael@0: >>> v.get_default_value("string(default='None')") michael@0: 'None' michael@0: >>> v.get_default_value('pass') michael@0: Traceback (most recent call last): michael@0: KeyError: 'Check "pass" has no default value.' michael@0: >>> v.get_default_value('pass(default=list(1, 2, 3, 4))') michael@0: ['1', '2', '3', '4'] michael@0: michael@0: >>> v = Validator() michael@0: >>> v.check("pass(default=None)", None, True) michael@0: >>> v.check("pass(default='None')", None, True) michael@0: 'None' michael@0: >>> v.check('pass(default="None")', None, True) michael@0: 'None' michael@0: >>> v.check('pass(default=list(1, 2, 3, 4))', None, True) michael@0: ['1', '2', '3', '4'] michael@0: michael@0: Bug test for unicode arguments michael@0: >>> v = Validator() michael@0: >>> v.check(u'string(min=4)', u'test') michael@0: u'test' michael@0: michael@0: >>> v = Validator() michael@0: >>> v.get_default_value(u'string(min=4, default="1234")') michael@0: u'1234' michael@0: >>> v.check(u'string(min=4, default="1234")', u'test') michael@0: u'test' michael@0: michael@0: >>> v = Validator() michael@0: >>> default = v.get_default_value('string(default=None)') michael@0: >>> default == None michael@0: 1 michael@0: """ michael@0: return (value, args, keywargs) michael@0: michael@0: michael@0: def _test2(): michael@0: """ michael@0: >>> michael@0: >>> v = Validator() michael@0: >>> v.get_default_value('string(default="#ff00dd")') michael@0: '#ff00dd' michael@0: >>> v.get_default_value('integer(default=3) # comment') michael@0: 3 michael@0: """ michael@0: michael@0: def _test3(): michael@0: r""" michael@0: >>> vtor.check('string(default="")', '', missing=True) michael@0: '' michael@0: >>> vtor.check('string(default="\n")', '', missing=True) michael@0: '\n' michael@0: >>> print vtor.check('string(default="\n")', '', missing=True), michael@0: michael@0: >>> vtor.check('string()', '\n') michael@0: '\n' michael@0: >>> vtor.check('string(default="\n\n\n")', '', missing=True) michael@0: '\n\n\n' michael@0: >>> vtor.check('string()', 'random \n text goes here\n\n') michael@0: 'random \n text goes here\n\n' michael@0: >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")', michael@0: ... '', missing=True) michael@0: ' \nrandom text\ngoes \n here\n\n ' michael@0: >>> vtor.check("string(default='\n\n\n')", '', missing=True) michael@0: '\n\n\n' michael@0: >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True) michael@0: '\n' michael@0: >>> vtor.check("string_list()", ['foo', '\n', 'bar']) michael@0: ['foo', '\n', 'bar'] michael@0: >>> vtor.check("string_list(default=list('\n'))", '', missing=True) michael@0: ['\n'] michael@0: """ michael@0: michael@0: michael@0: if __name__ == '__main__': michael@0: # run the code tests in doctest format michael@0: import sys michael@0: import doctest michael@0: m = sys.modules.get('__main__') michael@0: globs = m.__dict__.copy() michael@0: globs.update({ michael@0: 'vtor': Validator(), michael@0: }) michael@0: doctest.testmod(m, globs=globs)