python/configobj/validate.py

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

michael@0 1 # validate.py
michael@0 2 # A Validator object
michael@0 3 # Copyright (C) 2005-2010 Michael Foord, Mark Andrews, Nicola Larosa
michael@0 4 # E-mail: fuzzyman AT voidspace DOT org DOT uk
michael@0 5 # mark AT la-la DOT com
michael@0 6 # nico AT tekNico DOT net
michael@0 7
michael@0 8 # This software is licensed under the terms of the BSD license.
michael@0 9 # http://www.voidspace.org.uk/python/license.shtml
michael@0 10 # Basically you're free to copy, modify, distribute and relicense it,
michael@0 11 # So long as you keep a copy of the license with it.
michael@0 12
michael@0 13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
michael@0 14 # For information about bugfixes, updates and support, please join the
michael@0 15 # ConfigObj mailing list:
michael@0 16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
michael@0 17 # Comments, suggestions and bug reports welcome.
michael@0 18
michael@0 19 """
michael@0 20 The Validator object is used to check that supplied values
michael@0 21 conform to a specification.
michael@0 22
michael@0 23 The value can be supplied as a string - e.g. from a config file.
michael@0 24 In this case the check will also *convert* the value to
michael@0 25 the required type. This allows you to add validation
michael@0 26 as a transparent layer to access data stored as strings.
michael@0 27 The validation checks that the data is correct *and*
michael@0 28 converts it to the expected type.
michael@0 29
michael@0 30 Some standard checks are provided for basic data types.
michael@0 31 Additional checks are easy to write. They can be
michael@0 32 provided when the ``Validator`` is instantiated or
michael@0 33 added afterwards.
michael@0 34
michael@0 35 The standard functions work with the following basic data types :
michael@0 36
michael@0 37 * integers
michael@0 38 * floats
michael@0 39 * booleans
michael@0 40 * strings
michael@0 41 * ip_addr
michael@0 42
michael@0 43 plus lists of these datatypes
michael@0 44
michael@0 45 Adding additional checks is done through coding simple functions.
michael@0 46
michael@0 47 The full set of standard checks are :
michael@0 48
michael@0 49 * 'integer': matches integer values (including negative)
michael@0 50 Takes optional 'min' and 'max' arguments : ::
michael@0 51
michael@0 52 integer()
michael@0 53 integer(3, 9) # any value from 3 to 9
michael@0 54 integer(min=0) # any positive value
michael@0 55 integer(max=9)
michael@0 56
michael@0 57 * 'float': matches float values
michael@0 58 Has the same parameters as the integer check.
michael@0 59
michael@0 60 * 'boolean': matches boolean values - ``True`` or ``False``
michael@0 61 Acceptable string values for True are :
michael@0 62 true, on, yes, 1
michael@0 63 Acceptable string values for False are :
michael@0 64 false, off, no, 0
michael@0 65
michael@0 66 Any other value raises an error.
michael@0 67
michael@0 68 * 'ip_addr': matches an Internet Protocol address, v.4, represented
michael@0 69 by a dotted-quad string, i.e. '1.2.3.4'.
michael@0 70
michael@0 71 * 'string': matches any string.
michael@0 72 Takes optional keyword args 'min' and 'max'
michael@0 73 to specify min and max lengths of the string.
michael@0 74
michael@0 75 * 'list': matches any list.
michael@0 76 Takes optional keyword args 'min', and 'max' to specify min and
michael@0 77 max sizes of the list. (Always returns a list.)
michael@0 78
michael@0 79 * 'tuple': matches any tuple.
michael@0 80 Takes optional keyword args 'min', and 'max' to specify min and
michael@0 81 max sizes of the tuple. (Always returns a tuple.)
michael@0 82
michael@0 83 * 'int_list': Matches a list of integers.
michael@0 84 Takes the same arguments as list.
michael@0 85
michael@0 86 * 'float_list': Matches a list of floats.
michael@0 87 Takes the same arguments as list.
michael@0 88
michael@0 89 * 'bool_list': Matches a list of boolean values.
michael@0 90 Takes the same arguments as list.
michael@0 91
michael@0 92 * 'ip_addr_list': Matches a list of IP addresses.
michael@0 93 Takes the same arguments as list.
michael@0 94
michael@0 95 * 'string_list': Matches a list of strings.
michael@0 96 Takes the same arguments as list.
michael@0 97
michael@0 98 * 'mixed_list': Matches a list with different types in
michael@0 99 specific positions. List size must match
michael@0 100 the number of arguments.
michael@0 101
michael@0 102 Each position can be one of :
michael@0 103 'integer', 'float', 'ip_addr', 'string', 'boolean'
michael@0 104
michael@0 105 So to specify a list with two strings followed
michael@0 106 by two integers, you write the check as : ::
michael@0 107
michael@0 108 mixed_list('string', 'string', 'integer', 'integer')
michael@0 109
michael@0 110 * 'pass': This check matches everything ! It never fails
michael@0 111 and the value is unchanged.
michael@0 112
michael@0 113 It is also the default if no check is specified.
michael@0 114
michael@0 115 * 'option': This check matches any from a list of options.
michael@0 116 You specify this check with : ::
michael@0 117
michael@0 118 option('option 1', 'option 2', 'option 3')
michael@0 119
michael@0 120 You can supply a default value (returned if no value is supplied)
michael@0 121 using the default keyword argument.
michael@0 122
michael@0 123 You specify a list argument for default using a list constructor syntax in
michael@0 124 the check : ::
michael@0 125
michael@0 126 checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
michael@0 127
michael@0 128 A badly formatted set of arguments will raise a ``VdtParamError``.
michael@0 129 """
michael@0 130
michael@0 131 __version__ = '1.0.1'
michael@0 132
michael@0 133
michael@0 134 __all__ = (
michael@0 135 '__version__',
michael@0 136 'dottedQuadToNum',
michael@0 137 'numToDottedQuad',
michael@0 138 'ValidateError',
michael@0 139 'VdtUnknownCheckError',
michael@0 140 'VdtParamError',
michael@0 141 'VdtTypeError',
michael@0 142 'VdtValueError',
michael@0 143 'VdtValueTooSmallError',
michael@0 144 'VdtValueTooBigError',
michael@0 145 'VdtValueTooShortError',
michael@0 146 'VdtValueTooLongError',
michael@0 147 'VdtMissingValue',
michael@0 148 'Validator',
michael@0 149 'is_integer',
michael@0 150 'is_float',
michael@0 151 'is_boolean',
michael@0 152 'is_list',
michael@0 153 'is_tuple',
michael@0 154 'is_ip_addr',
michael@0 155 'is_string',
michael@0 156 'is_int_list',
michael@0 157 'is_bool_list',
michael@0 158 'is_float_list',
michael@0 159 'is_string_list',
michael@0 160 'is_ip_addr_list',
michael@0 161 'is_mixed_list',
michael@0 162 'is_option',
michael@0 163 '__docformat__',
michael@0 164 )
michael@0 165
michael@0 166
michael@0 167 import re
michael@0 168
michael@0 169
michael@0 170 _list_arg = re.compile(r'''
michael@0 171 (?:
michael@0 172 ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
michael@0 173 (
michael@0 174 (?:
michael@0 175 \s*
michael@0 176 (?:
michael@0 177 (?:".*?")| # double quotes
michael@0 178 (?:'.*?')| # single quotes
michael@0 179 (?:[^'",\s\)][^,\)]*?) # unquoted
michael@0 180 )
michael@0 181 \s*,\s*
michael@0 182 )*
michael@0 183 (?:
michael@0 184 (?:".*?")| # double quotes
michael@0 185 (?:'.*?')| # single quotes
michael@0 186 (?:[^'",\s\)][^,\)]*?) # unquoted
michael@0 187 )? # last one
michael@0 188 )
michael@0 189 \)
michael@0 190 )
michael@0 191 ''', re.VERBOSE | re.DOTALL) # two groups
michael@0 192
michael@0 193 _list_members = re.compile(r'''
michael@0 194 (
michael@0 195 (?:".*?")| # double quotes
michael@0 196 (?:'.*?')| # single quotes
michael@0 197 (?:[^'",\s=][^,=]*?) # unquoted
michael@0 198 )
michael@0 199 (?:
michael@0 200 (?:\s*,\s*)|(?:\s*$) # comma
michael@0 201 )
michael@0 202 ''', re.VERBOSE | re.DOTALL) # one group
michael@0 203
michael@0 204 _paramstring = r'''
michael@0 205 (?:
michael@0 206 (
michael@0 207 (?:
michael@0 208 [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
michael@0 209 (?:
michael@0 210 \s*
michael@0 211 (?:
michael@0 212 (?:".*?")| # double quotes
michael@0 213 (?:'.*?')| # single quotes
michael@0 214 (?:[^'",\s\)][^,\)]*?) # unquoted
michael@0 215 )
michael@0 216 \s*,\s*
michael@0 217 )*
michael@0 218 (?:
michael@0 219 (?:".*?")| # double quotes
michael@0 220 (?:'.*?')| # single quotes
michael@0 221 (?:[^'",\s\)][^,\)]*?) # unquoted
michael@0 222 )? # last one
michael@0 223 \)
michael@0 224 )|
michael@0 225 (?:
michael@0 226 (?:".*?")| # double quotes
michael@0 227 (?:'.*?')| # single quotes
michael@0 228 (?:[^'",\s=][^,=]*?)| # unquoted
michael@0 229 (?: # keyword argument
michael@0 230 [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
michael@0 231 (?:
michael@0 232 (?:".*?")| # double quotes
michael@0 233 (?:'.*?')| # single quotes
michael@0 234 (?:[^'",\s=][^,=]*?) # unquoted
michael@0 235 )
michael@0 236 )
michael@0 237 )
michael@0 238 )
michael@0 239 (?:
michael@0 240 (?:\s*,\s*)|(?:\s*$) # comma
michael@0 241 )
michael@0 242 )
michael@0 243 '''
michael@0 244
michael@0 245 _matchstring = '^%s*' % _paramstring
michael@0 246
michael@0 247 # Python pre 2.2.1 doesn't have bool
michael@0 248 try:
michael@0 249 bool
michael@0 250 except NameError:
michael@0 251 def bool(val):
michael@0 252 """Simple boolean equivalent function. """
michael@0 253 if val:
michael@0 254 return 1
michael@0 255 else:
michael@0 256 return 0
michael@0 257
michael@0 258
michael@0 259 def dottedQuadToNum(ip):
michael@0 260 """
michael@0 261 Convert decimal dotted quad string to long integer
michael@0 262
michael@0 263 >>> int(dottedQuadToNum('1 '))
michael@0 264 1
michael@0 265 >>> int(dottedQuadToNum(' 1.2'))
michael@0 266 16777218
michael@0 267 >>> int(dottedQuadToNum(' 1.2.3 '))
michael@0 268 16908291
michael@0 269 >>> int(dottedQuadToNum('1.2.3.4'))
michael@0 270 16909060
michael@0 271 >>> dottedQuadToNum('255.255.255.255')
michael@0 272 4294967295L
michael@0 273 >>> dottedQuadToNum('255.255.255.256')
michael@0 274 Traceback (most recent call last):
michael@0 275 ValueError: Not a good dotted-quad IP: 255.255.255.256
michael@0 276 """
michael@0 277
michael@0 278 # import here to avoid it when ip_addr values are not used
michael@0 279 import socket, struct
michael@0 280
michael@0 281 try:
michael@0 282 return struct.unpack('!L',
michael@0 283 socket.inet_aton(ip.strip()))[0]
michael@0 284 except socket.error:
michael@0 285 # bug in inet_aton, corrected in Python 2.4
michael@0 286 if ip.strip() == '255.255.255.255':
michael@0 287 return 0xFFFFFFFFL
michael@0 288 else:
michael@0 289 raise ValueError('Not a good dotted-quad IP: %s' % ip)
michael@0 290 return
michael@0 291
michael@0 292
michael@0 293 def numToDottedQuad(num):
michael@0 294 """
michael@0 295 Convert long int to dotted quad string
michael@0 296
michael@0 297 >>> numToDottedQuad(-1L)
michael@0 298 Traceback (most recent call last):
michael@0 299 ValueError: Not a good numeric IP: -1
michael@0 300 >>> numToDottedQuad(1L)
michael@0 301 '0.0.0.1'
michael@0 302 >>> numToDottedQuad(16777218L)
michael@0 303 '1.0.0.2'
michael@0 304 >>> numToDottedQuad(16908291L)
michael@0 305 '1.2.0.3'
michael@0 306 >>> numToDottedQuad(16909060L)
michael@0 307 '1.2.3.4'
michael@0 308 >>> numToDottedQuad(4294967295L)
michael@0 309 '255.255.255.255'
michael@0 310 >>> numToDottedQuad(4294967296L)
michael@0 311 Traceback (most recent call last):
michael@0 312 ValueError: Not a good numeric IP: 4294967296
michael@0 313 """
michael@0 314
michael@0 315 # import here to avoid it when ip_addr values are not used
michael@0 316 import socket, struct
michael@0 317
michael@0 318 # no need to intercept here, 4294967295L is fine
michael@0 319 if num > 4294967295L or num < 0:
michael@0 320 raise ValueError('Not a good numeric IP: %s' % num)
michael@0 321 try:
michael@0 322 return socket.inet_ntoa(
michael@0 323 struct.pack('!L', long(num)))
michael@0 324 except (socket.error, struct.error, OverflowError):
michael@0 325 raise ValueError('Not a good numeric IP: %s' % num)
michael@0 326
michael@0 327
michael@0 328 class ValidateError(Exception):
michael@0 329 """
michael@0 330 This error indicates that the check failed.
michael@0 331 It can be the base class for more specific errors.
michael@0 332
michael@0 333 Any check function that fails ought to raise this error.
michael@0 334 (or a subclass)
michael@0 335
michael@0 336 >>> raise ValidateError
michael@0 337 Traceback (most recent call last):
michael@0 338 ValidateError
michael@0 339 """
michael@0 340
michael@0 341
michael@0 342 class VdtMissingValue(ValidateError):
michael@0 343 """No value was supplied to a check that needed one."""
michael@0 344
michael@0 345
michael@0 346 class VdtUnknownCheckError(ValidateError):
michael@0 347 """An unknown check function was requested"""
michael@0 348
michael@0 349 def __init__(self, value):
michael@0 350 """
michael@0 351 >>> raise VdtUnknownCheckError('yoda')
michael@0 352 Traceback (most recent call last):
michael@0 353 VdtUnknownCheckError: the check "yoda" is unknown.
michael@0 354 """
michael@0 355 ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
michael@0 356
michael@0 357
michael@0 358 class VdtParamError(SyntaxError):
michael@0 359 """An incorrect parameter was passed"""
michael@0 360
michael@0 361 def __init__(self, name, value):
michael@0 362 """
michael@0 363 >>> raise VdtParamError('yoda', 'jedi')
michael@0 364 Traceback (most recent call last):
michael@0 365 VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
michael@0 366 """
michael@0 367 SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
michael@0 368
michael@0 369
michael@0 370 class VdtTypeError(ValidateError):
michael@0 371 """The value supplied was of the wrong type"""
michael@0 372
michael@0 373 def __init__(self, value):
michael@0 374 """
michael@0 375 >>> raise VdtTypeError('jedi')
michael@0 376 Traceback (most recent call last):
michael@0 377 VdtTypeError: the value "jedi" is of the wrong type.
michael@0 378 """
michael@0 379 ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
michael@0 380
michael@0 381
michael@0 382 class VdtValueError(ValidateError):
michael@0 383 """The value supplied was of the correct type, but was not an allowed value."""
michael@0 384
michael@0 385 def __init__(self, value):
michael@0 386 """
michael@0 387 >>> raise VdtValueError('jedi')
michael@0 388 Traceback (most recent call last):
michael@0 389 VdtValueError: the value "jedi" is unacceptable.
michael@0 390 """
michael@0 391 ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
michael@0 392
michael@0 393
michael@0 394 class VdtValueTooSmallError(VdtValueError):
michael@0 395 """The value supplied was of the correct type, but was too small."""
michael@0 396
michael@0 397 def __init__(self, value):
michael@0 398 """
michael@0 399 >>> raise VdtValueTooSmallError('0')
michael@0 400 Traceback (most recent call last):
michael@0 401 VdtValueTooSmallError: the value "0" is too small.
michael@0 402 """
michael@0 403 ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
michael@0 404
michael@0 405
michael@0 406 class VdtValueTooBigError(VdtValueError):
michael@0 407 """The value supplied was of the correct type, but was too big."""
michael@0 408
michael@0 409 def __init__(self, value):
michael@0 410 """
michael@0 411 >>> raise VdtValueTooBigError('1')
michael@0 412 Traceback (most recent call last):
michael@0 413 VdtValueTooBigError: the value "1" is too big.
michael@0 414 """
michael@0 415 ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
michael@0 416
michael@0 417
michael@0 418 class VdtValueTooShortError(VdtValueError):
michael@0 419 """The value supplied was of the correct type, but was too short."""
michael@0 420
michael@0 421 def __init__(self, value):
michael@0 422 """
michael@0 423 >>> raise VdtValueTooShortError('jed')
michael@0 424 Traceback (most recent call last):
michael@0 425 VdtValueTooShortError: the value "jed" is too short.
michael@0 426 """
michael@0 427 ValidateError.__init__(
michael@0 428 self,
michael@0 429 'the value "%s" is too short.' % (value,))
michael@0 430
michael@0 431
michael@0 432 class VdtValueTooLongError(VdtValueError):
michael@0 433 """The value supplied was of the correct type, but was too long."""
michael@0 434
michael@0 435 def __init__(self, value):
michael@0 436 """
michael@0 437 >>> raise VdtValueTooLongError('jedie')
michael@0 438 Traceback (most recent call last):
michael@0 439 VdtValueTooLongError: the value "jedie" is too long.
michael@0 440 """
michael@0 441 ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
michael@0 442
michael@0 443
michael@0 444 class Validator(object):
michael@0 445 """
michael@0 446 Validator is an object that allows you to register a set of 'checks'.
michael@0 447 These checks take input and test that it conforms to the check.
michael@0 448
michael@0 449 This can also involve converting the value from a string into
michael@0 450 the correct datatype.
michael@0 451
michael@0 452 The ``check`` method takes an input string which configures which
michael@0 453 check is to be used and applies that check to a supplied value.
michael@0 454
michael@0 455 An example input string would be:
michael@0 456 'int_range(param1, param2)'
michael@0 457
michael@0 458 You would then provide something like:
michael@0 459
michael@0 460 >>> def int_range_check(value, min, max):
michael@0 461 ... # turn min and max from strings to integers
michael@0 462 ... min = int(min)
michael@0 463 ... max = int(max)
michael@0 464 ... # check that value is of the correct type.
michael@0 465 ... # possible valid inputs are integers or strings
michael@0 466 ... # that represent integers
michael@0 467 ... if not isinstance(value, (int, long, basestring)):
michael@0 468 ... raise VdtTypeError(value)
michael@0 469 ... elif isinstance(value, basestring):
michael@0 470 ... # if we are given a string
michael@0 471 ... # attempt to convert to an integer
michael@0 472 ... try:
michael@0 473 ... value = int(value)
michael@0 474 ... except ValueError:
michael@0 475 ... raise VdtValueError(value)
michael@0 476 ... # check the value is between our constraints
michael@0 477 ... if not min <= value:
michael@0 478 ... raise VdtValueTooSmallError(value)
michael@0 479 ... if not value <= max:
michael@0 480 ... raise VdtValueTooBigError(value)
michael@0 481 ... return value
michael@0 482
michael@0 483 >>> fdict = {'int_range': int_range_check}
michael@0 484 >>> vtr1 = Validator(fdict)
michael@0 485 >>> vtr1.check('int_range(20, 40)', '30')
michael@0 486 30
michael@0 487 >>> vtr1.check('int_range(20, 40)', '60')
michael@0 488 Traceback (most recent call last):
michael@0 489 VdtValueTooBigError: the value "60" is too big.
michael@0 490
michael@0 491 New functions can be added with : ::
michael@0 492
michael@0 493 >>> vtr2 = Validator()
michael@0 494 >>> vtr2.functions['int_range'] = int_range_check
michael@0 495
michael@0 496 Or by passing in a dictionary of functions when Validator
michael@0 497 is instantiated.
michael@0 498
michael@0 499 Your functions *can* use keyword arguments,
michael@0 500 but the first argument should always be 'value'.
michael@0 501
michael@0 502 If the function doesn't take additional arguments,
michael@0 503 the parentheses are optional in the check.
michael@0 504 It can be written with either of : ::
michael@0 505
michael@0 506 keyword = function_name
michael@0 507 keyword = function_name()
michael@0 508
michael@0 509 The first program to utilise Validator() was Michael Foord's
michael@0 510 ConfigObj, an alternative to ConfigParser which supports lists and
michael@0 511 can validate a config file using a config schema.
michael@0 512 For more details on using Validator with ConfigObj see:
michael@0 513 http://www.voidspace.org.uk/python/configobj.html
michael@0 514 """
michael@0 515
michael@0 516 # this regex does the initial parsing of the checks
michael@0 517 _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL)
michael@0 518
michael@0 519 # this regex takes apart keyword arguments
michael@0 520 _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL)
michael@0 521
michael@0 522
michael@0 523 # this regex finds keyword=list(....) type values
michael@0 524 _list_arg = _list_arg
michael@0 525
michael@0 526 # this regex takes individual values out of lists - in one pass
michael@0 527 _list_members = _list_members
michael@0 528
michael@0 529 # These regexes check a set of arguments for validity
michael@0 530 # and then pull the members out
michael@0 531 _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL)
michael@0 532 _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL)
michael@0 533
michael@0 534
michael@0 535 def __init__(self, functions=None):
michael@0 536 """
michael@0 537 >>> vtri = Validator()
michael@0 538 """
michael@0 539 self.functions = {
michael@0 540 '': self._pass,
michael@0 541 'integer': is_integer,
michael@0 542 'float': is_float,
michael@0 543 'boolean': is_boolean,
michael@0 544 'ip_addr': is_ip_addr,
michael@0 545 'string': is_string,
michael@0 546 'list': is_list,
michael@0 547 'tuple': is_tuple,
michael@0 548 'int_list': is_int_list,
michael@0 549 'float_list': is_float_list,
michael@0 550 'bool_list': is_bool_list,
michael@0 551 'ip_addr_list': is_ip_addr_list,
michael@0 552 'string_list': is_string_list,
michael@0 553 'mixed_list': is_mixed_list,
michael@0 554 'pass': self._pass,
michael@0 555 'option': is_option,
michael@0 556 'force_list': force_list,
michael@0 557 }
michael@0 558 if functions is not None:
michael@0 559 self.functions.update(functions)
michael@0 560 # tekNico: for use by ConfigObj
michael@0 561 self.baseErrorClass = ValidateError
michael@0 562 self._cache = {}
michael@0 563
michael@0 564
michael@0 565 def check(self, check, value, missing=False):
michael@0 566 """
michael@0 567 Usage: check(check, value)
michael@0 568
michael@0 569 Arguments:
michael@0 570 check: string representing check to apply (including arguments)
michael@0 571 value: object to be checked
michael@0 572 Returns value, converted to correct type if necessary
michael@0 573
michael@0 574 If the check fails, raises a ``ValidateError`` subclass.
michael@0 575
michael@0 576 >>> vtor.check('yoda', '')
michael@0 577 Traceback (most recent call last):
michael@0 578 VdtUnknownCheckError: the check "yoda" is unknown.
michael@0 579 >>> vtor.check('yoda()', '')
michael@0 580 Traceback (most recent call last):
michael@0 581 VdtUnknownCheckError: the check "yoda" is unknown.
michael@0 582
michael@0 583 >>> vtor.check('string(default="")', '', missing=True)
michael@0 584 ''
michael@0 585 """
michael@0 586 fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
michael@0 587
michael@0 588 if missing:
michael@0 589 if default is None:
michael@0 590 # no information needed here - to be handled by caller
michael@0 591 raise VdtMissingValue()
michael@0 592 value = self._handle_none(default)
michael@0 593
michael@0 594 if value is None:
michael@0 595 return None
michael@0 596
michael@0 597 return self._check_value(value, fun_name, fun_args, fun_kwargs)
michael@0 598
michael@0 599
michael@0 600 def _handle_none(self, value):
michael@0 601 if value == 'None':
michael@0 602 return None
michael@0 603 elif value in ("'None'", '"None"'):
michael@0 604 # Special case a quoted None
michael@0 605 value = self._unquote(value)
michael@0 606 return value
michael@0 607
michael@0 608
michael@0 609 def _parse_with_caching(self, check):
michael@0 610 if check in self._cache:
michael@0 611 fun_name, fun_args, fun_kwargs, default = self._cache[check]
michael@0 612 # We call list and dict below to work with *copies* of the data
michael@0 613 # rather than the original (which are mutable of course)
michael@0 614 fun_args = list(fun_args)
michael@0 615 fun_kwargs = dict(fun_kwargs)
michael@0 616 else:
michael@0 617 fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
michael@0 618 fun_kwargs = dict([(str(key), value) for (key, value) in fun_kwargs.items()])
michael@0 619 self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
michael@0 620 return fun_name, fun_args, fun_kwargs, default
michael@0 621
michael@0 622
michael@0 623 def _check_value(self, value, fun_name, fun_args, fun_kwargs):
michael@0 624 try:
michael@0 625 fun = self.functions[fun_name]
michael@0 626 except KeyError:
michael@0 627 raise VdtUnknownCheckError(fun_name)
michael@0 628 else:
michael@0 629 return fun(value, *fun_args, **fun_kwargs)
michael@0 630
michael@0 631
michael@0 632 def _parse_check(self, check):
michael@0 633 fun_match = self._func_re.match(check)
michael@0 634 if fun_match:
michael@0 635 fun_name = fun_match.group(1)
michael@0 636 arg_string = fun_match.group(2)
michael@0 637 arg_match = self._matchfinder.match(arg_string)
michael@0 638 if arg_match is None:
michael@0 639 # Bad syntax
michael@0 640 raise VdtParamError('Bad syntax in check "%s".' % check)
michael@0 641 fun_args = []
michael@0 642 fun_kwargs = {}
michael@0 643 # pull out args of group 2
michael@0 644 for arg in self._paramfinder.findall(arg_string):
michael@0 645 # args may need whitespace removing (before removing quotes)
michael@0 646 arg = arg.strip()
michael@0 647 listmatch = self._list_arg.match(arg)
michael@0 648 if listmatch:
michael@0 649 key, val = self._list_handle(listmatch)
michael@0 650 fun_kwargs[key] = val
michael@0 651 continue
michael@0 652 keymatch = self._key_arg.match(arg)
michael@0 653 if keymatch:
michael@0 654 val = keymatch.group(2)
michael@0 655 if not val in ("'None'", '"None"'):
michael@0 656 # Special case a quoted None
michael@0 657 val = self._unquote(val)
michael@0 658 fun_kwargs[keymatch.group(1)] = val
michael@0 659 continue
michael@0 660
michael@0 661 fun_args.append(self._unquote(arg))
michael@0 662 else:
michael@0 663 # allows for function names without (args)
michael@0 664 return check, (), {}, None
michael@0 665
michael@0 666 # Default must be deleted if the value is specified too,
michael@0 667 # otherwise the check function will get a spurious "default" keyword arg
michael@0 668 default = fun_kwargs.pop('default', None)
michael@0 669 return fun_name, fun_args, fun_kwargs, default
michael@0 670
michael@0 671
michael@0 672 def _unquote(self, val):
michael@0 673 """Unquote a value if necessary."""
michael@0 674 if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
michael@0 675 val = val[1:-1]
michael@0 676 return val
michael@0 677
michael@0 678
michael@0 679 def _list_handle(self, listmatch):
michael@0 680 """Take apart a ``keyword=list('val, 'val')`` type string."""
michael@0 681 out = []
michael@0 682 name = listmatch.group(1)
michael@0 683 args = listmatch.group(2)
michael@0 684 for arg in self._list_members.findall(args):
michael@0 685 out.append(self._unquote(arg))
michael@0 686 return name, out
michael@0 687
michael@0 688
michael@0 689 def _pass(self, value):
michael@0 690 """
michael@0 691 Dummy check that always passes
michael@0 692
michael@0 693 >>> vtor.check('', 0)
michael@0 694 0
michael@0 695 >>> vtor.check('', '0')
michael@0 696 '0'
michael@0 697 """
michael@0 698 return value
michael@0 699
michael@0 700
michael@0 701 def get_default_value(self, check):
michael@0 702 """
michael@0 703 Given a check, return the default value for the check
michael@0 704 (converted to the right type).
michael@0 705
michael@0 706 If the check doesn't specify a default value then a
michael@0 707 ``KeyError`` will be raised.
michael@0 708 """
michael@0 709 fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
michael@0 710 if default is None:
michael@0 711 raise KeyError('Check "%s" has no default value.' % check)
michael@0 712 value = self._handle_none(default)
michael@0 713 if value is None:
michael@0 714 return value
michael@0 715 return self._check_value(value, fun_name, fun_args, fun_kwargs)
michael@0 716
michael@0 717
michael@0 718 def _is_num_param(names, values, to_float=False):
michael@0 719 """
michael@0 720 Return numbers from inputs or raise VdtParamError.
michael@0 721
michael@0 722 Lets ``None`` pass through.
michael@0 723 Pass in keyword argument ``to_float=True`` to
michael@0 724 use float for the conversion rather than int.
michael@0 725
michael@0 726 >>> _is_num_param(('', ''), (0, 1.0))
michael@0 727 [0, 1]
michael@0 728 >>> _is_num_param(('', ''), (0, 1.0), to_float=True)
michael@0 729 [0.0, 1.0]
michael@0 730 >>> _is_num_param(('a'), ('a'))
michael@0 731 Traceback (most recent call last):
michael@0 732 VdtParamError: passed an incorrect value "a" for parameter "a".
michael@0 733 """
michael@0 734 fun = to_float and float or int
michael@0 735 out_params = []
michael@0 736 for (name, val) in zip(names, values):
michael@0 737 if val is None:
michael@0 738 out_params.append(val)
michael@0 739 elif isinstance(val, (int, long, float, basestring)):
michael@0 740 try:
michael@0 741 out_params.append(fun(val))
michael@0 742 except ValueError, e:
michael@0 743 raise VdtParamError(name, val)
michael@0 744 else:
michael@0 745 raise VdtParamError(name, val)
michael@0 746 return out_params
michael@0 747
michael@0 748
michael@0 749 # built in checks
michael@0 750 # you can override these by setting the appropriate name
michael@0 751 # in Validator.functions
michael@0 752 # note: if the params are specified wrongly in your input string,
michael@0 753 # you will also raise errors.
michael@0 754
michael@0 755 def is_integer(value, min=None, max=None):
michael@0 756 """
michael@0 757 A check that tests that a given value is an integer (int, or long)
michael@0 758 and optionally, between bounds. A negative value is accepted, while
michael@0 759 a float will fail.
michael@0 760
michael@0 761 If the value is a string, then the conversion is done - if possible.
michael@0 762 Otherwise a VdtError is raised.
michael@0 763
michael@0 764 >>> vtor.check('integer', '-1')
michael@0 765 -1
michael@0 766 >>> vtor.check('integer', '0')
michael@0 767 0
michael@0 768 >>> vtor.check('integer', 9)
michael@0 769 9
michael@0 770 >>> vtor.check('integer', 'a')
michael@0 771 Traceback (most recent call last):
michael@0 772 VdtTypeError: the value "a" is of the wrong type.
michael@0 773 >>> vtor.check('integer', '2.2')
michael@0 774 Traceback (most recent call last):
michael@0 775 VdtTypeError: the value "2.2" is of the wrong type.
michael@0 776 >>> vtor.check('integer(10)', '20')
michael@0 777 20
michael@0 778 >>> vtor.check('integer(max=20)', '15')
michael@0 779 15
michael@0 780 >>> vtor.check('integer(10)', '9')
michael@0 781 Traceback (most recent call last):
michael@0 782 VdtValueTooSmallError: the value "9" is too small.
michael@0 783 >>> vtor.check('integer(10)', 9)
michael@0 784 Traceback (most recent call last):
michael@0 785 VdtValueTooSmallError: the value "9" is too small.
michael@0 786 >>> vtor.check('integer(max=20)', '35')
michael@0 787 Traceback (most recent call last):
michael@0 788 VdtValueTooBigError: the value "35" is too big.
michael@0 789 >>> vtor.check('integer(max=20)', 35)
michael@0 790 Traceback (most recent call last):
michael@0 791 VdtValueTooBigError: the value "35" is too big.
michael@0 792 >>> vtor.check('integer(0, 9)', False)
michael@0 793 0
michael@0 794 """
michael@0 795 (min_val, max_val) = _is_num_param(('min', 'max'), (min, max))
michael@0 796 if not isinstance(value, (int, long, basestring)):
michael@0 797 raise VdtTypeError(value)
michael@0 798 if isinstance(value, basestring):
michael@0 799 # if it's a string - does it represent an integer ?
michael@0 800 try:
michael@0 801 value = int(value)
michael@0 802 except ValueError:
michael@0 803 raise VdtTypeError(value)
michael@0 804 if (min_val is not None) and (value < min_val):
michael@0 805 raise VdtValueTooSmallError(value)
michael@0 806 if (max_val is not None) and (value > max_val):
michael@0 807 raise VdtValueTooBigError(value)
michael@0 808 return value
michael@0 809
michael@0 810
michael@0 811 def is_float(value, min=None, max=None):
michael@0 812 """
michael@0 813 A check that tests that a given value is a float
michael@0 814 (an integer will be accepted), and optionally - that it is between bounds.
michael@0 815
michael@0 816 If the value is a string, then the conversion is done - if possible.
michael@0 817 Otherwise a VdtError is raised.
michael@0 818
michael@0 819 This can accept negative values.
michael@0 820
michael@0 821 >>> vtor.check('float', '2')
michael@0 822 2.0
michael@0 823
michael@0 824 From now on we multiply the value to avoid comparing decimals
michael@0 825
michael@0 826 >>> vtor.check('float', '-6.8') * 10
michael@0 827 -68.0
michael@0 828 >>> vtor.check('float', '12.2') * 10
michael@0 829 122.0
michael@0 830 >>> vtor.check('float', 8.4) * 10
michael@0 831 84.0
michael@0 832 >>> vtor.check('float', 'a')
michael@0 833 Traceback (most recent call last):
michael@0 834 VdtTypeError: the value "a" is of the wrong type.
michael@0 835 >>> vtor.check('float(10.1)', '10.2') * 10
michael@0 836 102.0
michael@0 837 >>> vtor.check('float(max=20.2)', '15.1') * 10
michael@0 838 151.0
michael@0 839 >>> vtor.check('float(10.0)', '9.0')
michael@0 840 Traceback (most recent call last):
michael@0 841 VdtValueTooSmallError: the value "9.0" is too small.
michael@0 842 >>> vtor.check('float(max=20.0)', '35.0')
michael@0 843 Traceback (most recent call last):
michael@0 844 VdtValueTooBigError: the value "35.0" is too big.
michael@0 845 """
michael@0 846 (min_val, max_val) = _is_num_param(
michael@0 847 ('min', 'max'), (min, max), to_float=True)
michael@0 848 if not isinstance(value, (int, long, float, basestring)):
michael@0 849 raise VdtTypeError(value)
michael@0 850 if not isinstance(value, float):
michael@0 851 # if it's a string - does it represent a float ?
michael@0 852 try:
michael@0 853 value = float(value)
michael@0 854 except ValueError:
michael@0 855 raise VdtTypeError(value)
michael@0 856 if (min_val is not None) and (value < min_val):
michael@0 857 raise VdtValueTooSmallError(value)
michael@0 858 if (max_val is not None) and (value > max_val):
michael@0 859 raise VdtValueTooBigError(value)
michael@0 860 return value
michael@0 861
michael@0 862
michael@0 863 bool_dict = {
michael@0 864 True: True, 'on': True, '1': True, 'true': True, 'yes': True,
michael@0 865 False: False, 'off': False, '0': False, 'false': False, 'no': False,
michael@0 866 }
michael@0 867
michael@0 868
michael@0 869 def is_boolean(value):
michael@0 870 """
michael@0 871 Check if the value represents a boolean.
michael@0 872
michael@0 873 >>> vtor.check('boolean', 0)
michael@0 874 0
michael@0 875 >>> vtor.check('boolean', False)
michael@0 876 0
michael@0 877 >>> vtor.check('boolean', '0')
michael@0 878 0
michael@0 879 >>> vtor.check('boolean', 'off')
michael@0 880 0
michael@0 881 >>> vtor.check('boolean', 'false')
michael@0 882 0
michael@0 883 >>> vtor.check('boolean', 'no')
michael@0 884 0
michael@0 885 >>> vtor.check('boolean', 'nO')
michael@0 886 0
michael@0 887 >>> vtor.check('boolean', 'NO')
michael@0 888 0
michael@0 889 >>> vtor.check('boolean', 1)
michael@0 890 1
michael@0 891 >>> vtor.check('boolean', True)
michael@0 892 1
michael@0 893 >>> vtor.check('boolean', '1')
michael@0 894 1
michael@0 895 >>> vtor.check('boolean', 'on')
michael@0 896 1
michael@0 897 >>> vtor.check('boolean', 'true')
michael@0 898 1
michael@0 899 >>> vtor.check('boolean', 'yes')
michael@0 900 1
michael@0 901 >>> vtor.check('boolean', 'Yes')
michael@0 902 1
michael@0 903 >>> vtor.check('boolean', 'YES')
michael@0 904 1
michael@0 905 >>> vtor.check('boolean', '')
michael@0 906 Traceback (most recent call last):
michael@0 907 VdtTypeError: the value "" is of the wrong type.
michael@0 908 >>> vtor.check('boolean', 'up')
michael@0 909 Traceback (most recent call last):
michael@0 910 VdtTypeError: the value "up" is of the wrong type.
michael@0 911
michael@0 912 """
michael@0 913 if isinstance(value, basestring):
michael@0 914 try:
michael@0 915 return bool_dict[value.lower()]
michael@0 916 except KeyError:
michael@0 917 raise VdtTypeError(value)
michael@0 918 # we do an equality test rather than an identity test
michael@0 919 # this ensures Python 2.2 compatibilty
michael@0 920 # and allows 0 and 1 to represent True and False
michael@0 921 if value == False:
michael@0 922 return False
michael@0 923 elif value == True:
michael@0 924 return True
michael@0 925 else:
michael@0 926 raise VdtTypeError(value)
michael@0 927
michael@0 928
michael@0 929 def is_ip_addr(value):
michael@0 930 """
michael@0 931 Check that the supplied value is an Internet Protocol address, v.4,
michael@0 932 represented by a dotted-quad string, i.e. '1.2.3.4'.
michael@0 933
michael@0 934 >>> vtor.check('ip_addr', '1 ')
michael@0 935 '1'
michael@0 936 >>> vtor.check('ip_addr', ' 1.2')
michael@0 937 '1.2'
michael@0 938 >>> vtor.check('ip_addr', ' 1.2.3 ')
michael@0 939 '1.2.3'
michael@0 940 >>> vtor.check('ip_addr', '1.2.3.4')
michael@0 941 '1.2.3.4'
michael@0 942 >>> vtor.check('ip_addr', '0.0.0.0')
michael@0 943 '0.0.0.0'
michael@0 944 >>> vtor.check('ip_addr', '255.255.255.255')
michael@0 945 '255.255.255.255'
michael@0 946 >>> vtor.check('ip_addr', '255.255.255.256')
michael@0 947 Traceback (most recent call last):
michael@0 948 VdtValueError: the value "255.255.255.256" is unacceptable.
michael@0 949 >>> vtor.check('ip_addr', '1.2.3.4.5')
michael@0 950 Traceback (most recent call last):
michael@0 951 VdtValueError: the value "1.2.3.4.5" is unacceptable.
michael@0 952 >>> vtor.check('ip_addr', 0)
michael@0 953 Traceback (most recent call last):
michael@0 954 VdtTypeError: the value "0" is of the wrong type.
michael@0 955 """
michael@0 956 if not isinstance(value, basestring):
michael@0 957 raise VdtTypeError(value)
michael@0 958 value = value.strip()
michael@0 959 try:
michael@0 960 dottedQuadToNum(value)
michael@0 961 except ValueError:
michael@0 962 raise VdtValueError(value)
michael@0 963 return value
michael@0 964
michael@0 965
michael@0 966 def is_list(value, min=None, max=None):
michael@0 967 """
michael@0 968 Check that the value is a list of values.
michael@0 969
michael@0 970 You can optionally specify the minimum and maximum number of members.
michael@0 971
michael@0 972 It does no check on list members.
michael@0 973
michael@0 974 >>> vtor.check('list', ())
michael@0 975 []
michael@0 976 >>> vtor.check('list', [])
michael@0 977 []
michael@0 978 >>> vtor.check('list', (1, 2))
michael@0 979 [1, 2]
michael@0 980 >>> vtor.check('list', [1, 2])
michael@0 981 [1, 2]
michael@0 982 >>> vtor.check('list(3)', (1, 2))
michael@0 983 Traceback (most recent call last):
michael@0 984 VdtValueTooShortError: the value "(1, 2)" is too short.
michael@0 985 >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6))
michael@0 986 Traceback (most recent call last):
michael@0 987 VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
michael@0 988 >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4))
michael@0 989 [1, 2, 3, 4]
michael@0 990 >>> vtor.check('list', 0)
michael@0 991 Traceback (most recent call last):
michael@0 992 VdtTypeError: the value "0" is of the wrong type.
michael@0 993 >>> vtor.check('list', '12')
michael@0 994 Traceback (most recent call last):
michael@0 995 VdtTypeError: the value "12" is of the wrong type.
michael@0 996 """
michael@0 997 (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
michael@0 998 if isinstance(value, basestring):
michael@0 999 raise VdtTypeError(value)
michael@0 1000 try:
michael@0 1001 num_members = len(value)
michael@0 1002 except TypeError:
michael@0 1003 raise VdtTypeError(value)
michael@0 1004 if min_len is not None and num_members < min_len:
michael@0 1005 raise VdtValueTooShortError(value)
michael@0 1006 if max_len is not None and num_members > max_len:
michael@0 1007 raise VdtValueTooLongError(value)
michael@0 1008 return list(value)
michael@0 1009
michael@0 1010
michael@0 1011 def is_tuple(value, min=None, max=None):
michael@0 1012 """
michael@0 1013 Check that the value is a tuple of values.
michael@0 1014
michael@0 1015 You can optionally specify the minimum and maximum number of members.
michael@0 1016
michael@0 1017 It does no check on members.
michael@0 1018
michael@0 1019 >>> vtor.check('tuple', ())
michael@0 1020 ()
michael@0 1021 >>> vtor.check('tuple', [])
michael@0 1022 ()
michael@0 1023 >>> vtor.check('tuple', (1, 2))
michael@0 1024 (1, 2)
michael@0 1025 >>> vtor.check('tuple', [1, 2])
michael@0 1026 (1, 2)
michael@0 1027 >>> vtor.check('tuple(3)', (1, 2))
michael@0 1028 Traceback (most recent call last):
michael@0 1029 VdtValueTooShortError: the value "(1, 2)" is too short.
michael@0 1030 >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6))
michael@0 1031 Traceback (most recent call last):
michael@0 1032 VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
michael@0 1033 >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4))
michael@0 1034 (1, 2, 3, 4)
michael@0 1035 >>> vtor.check('tuple', 0)
michael@0 1036 Traceback (most recent call last):
michael@0 1037 VdtTypeError: the value "0" is of the wrong type.
michael@0 1038 >>> vtor.check('tuple', '12')
michael@0 1039 Traceback (most recent call last):
michael@0 1040 VdtTypeError: the value "12" is of the wrong type.
michael@0 1041 """
michael@0 1042 return tuple(is_list(value, min, max))
michael@0 1043
michael@0 1044
michael@0 1045 def is_string(value, min=None, max=None):
michael@0 1046 """
michael@0 1047 Check that the supplied value is a string.
michael@0 1048
michael@0 1049 You can optionally specify the minimum and maximum number of members.
michael@0 1050
michael@0 1051 >>> vtor.check('string', '0')
michael@0 1052 '0'
michael@0 1053 >>> vtor.check('string', 0)
michael@0 1054 Traceback (most recent call last):
michael@0 1055 VdtTypeError: the value "0" is of the wrong type.
michael@0 1056 >>> vtor.check('string(2)', '12')
michael@0 1057 '12'
michael@0 1058 >>> vtor.check('string(2)', '1')
michael@0 1059 Traceback (most recent call last):
michael@0 1060 VdtValueTooShortError: the value "1" is too short.
michael@0 1061 >>> vtor.check('string(min=2, max=3)', '123')
michael@0 1062 '123'
michael@0 1063 >>> vtor.check('string(min=2, max=3)', '1234')
michael@0 1064 Traceback (most recent call last):
michael@0 1065 VdtValueTooLongError: the value "1234" is too long.
michael@0 1066 """
michael@0 1067 if not isinstance(value, basestring):
michael@0 1068 raise VdtTypeError(value)
michael@0 1069 (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
michael@0 1070 try:
michael@0 1071 num_members = len(value)
michael@0 1072 except TypeError:
michael@0 1073 raise VdtTypeError(value)
michael@0 1074 if min_len is not None and num_members < min_len:
michael@0 1075 raise VdtValueTooShortError(value)
michael@0 1076 if max_len is not None and num_members > max_len:
michael@0 1077 raise VdtValueTooLongError(value)
michael@0 1078 return value
michael@0 1079
michael@0 1080
michael@0 1081 def is_int_list(value, min=None, max=None):
michael@0 1082 """
michael@0 1083 Check that the value is a list of integers.
michael@0 1084
michael@0 1085 You can optionally specify the minimum and maximum number of members.
michael@0 1086
michael@0 1087 Each list member is checked that it is an integer.
michael@0 1088
michael@0 1089 >>> vtor.check('int_list', ())
michael@0 1090 []
michael@0 1091 >>> vtor.check('int_list', [])
michael@0 1092 []
michael@0 1093 >>> vtor.check('int_list', (1, 2))
michael@0 1094 [1, 2]
michael@0 1095 >>> vtor.check('int_list', [1, 2])
michael@0 1096 [1, 2]
michael@0 1097 >>> vtor.check('int_list', [1, 'a'])
michael@0 1098 Traceback (most recent call last):
michael@0 1099 VdtTypeError: the value "a" is of the wrong type.
michael@0 1100 """
michael@0 1101 return [is_integer(mem) for mem in is_list(value, min, max)]
michael@0 1102
michael@0 1103
michael@0 1104 def is_bool_list(value, min=None, max=None):
michael@0 1105 """
michael@0 1106 Check that the value is a list of booleans.
michael@0 1107
michael@0 1108 You can optionally specify the minimum and maximum number of members.
michael@0 1109
michael@0 1110 Each list member is checked that it is a boolean.
michael@0 1111
michael@0 1112 >>> vtor.check('bool_list', ())
michael@0 1113 []
michael@0 1114 >>> vtor.check('bool_list', [])
michael@0 1115 []
michael@0 1116 >>> check_res = vtor.check('bool_list', (True, False))
michael@0 1117 >>> check_res == [True, False]
michael@0 1118 1
michael@0 1119 >>> check_res = vtor.check('bool_list', [True, False])
michael@0 1120 >>> check_res == [True, False]
michael@0 1121 1
michael@0 1122 >>> vtor.check('bool_list', [True, 'a'])
michael@0 1123 Traceback (most recent call last):
michael@0 1124 VdtTypeError: the value "a" is of the wrong type.
michael@0 1125 """
michael@0 1126 return [is_boolean(mem) for mem in is_list(value, min, max)]
michael@0 1127
michael@0 1128
michael@0 1129 def is_float_list(value, min=None, max=None):
michael@0 1130 """
michael@0 1131 Check that the value is a list of floats.
michael@0 1132
michael@0 1133 You can optionally specify the minimum and maximum number of members.
michael@0 1134
michael@0 1135 Each list member is checked that it is a float.
michael@0 1136
michael@0 1137 >>> vtor.check('float_list', ())
michael@0 1138 []
michael@0 1139 >>> vtor.check('float_list', [])
michael@0 1140 []
michael@0 1141 >>> vtor.check('float_list', (1, 2.0))
michael@0 1142 [1.0, 2.0]
michael@0 1143 >>> vtor.check('float_list', [1, 2.0])
michael@0 1144 [1.0, 2.0]
michael@0 1145 >>> vtor.check('float_list', [1, 'a'])
michael@0 1146 Traceback (most recent call last):
michael@0 1147 VdtTypeError: the value "a" is of the wrong type.
michael@0 1148 """
michael@0 1149 return [is_float(mem) for mem in is_list(value, min, max)]
michael@0 1150
michael@0 1151
michael@0 1152 def is_string_list(value, min=None, max=None):
michael@0 1153 """
michael@0 1154 Check that the value is a list of strings.
michael@0 1155
michael@0 1156 You can optionally specify the minimum and maximum number of members.
michael@0 1157
michael@0 1158 Each list member is checked that it is a string.
michael@0 1159
michael@0 1160 >>> vtor.check('string_list', ())
michael@0 1161 []
michael@0 1162 >>> vtor.check('string_list', [])
michael@0 1163 []
michael@0 1164 >>> vtor.check('string_list', ('a', 'b'))
michael@0 1165 ['a', 'b']
michael@0 1166 >>> vtor.check('string_list', ['a', 1])
michael@0 1167 Traceback (most recent call last):
michael@0 1168 VdtTypeError: the value "1" is of the wrong type.
michael@0 1169 >>> vtor.check('string_list', 'hello')
michael@0 1170 Traceback (most recent call last):
michael@0 1171 VdtTypeError: the value "hello" is of the wrong type.
michael@0 1172 """
michael@0 1173 if isinstance(value, basestring):
michael@0 1174 raise VdtTypeError(value)
michael@0 1175 return [is_string(mem) for mem in is_list(value, min, max)]
michael@0 1176
michael@0 1177
michael@0 1178 def is_ip_addr_list(value, min=None, max=None):
michael@0 1179 """
michael@0 1180 Check that the value is a list of IP addresses.
michael@0 1181
michael@0 1182 You can optionally specify the minimum and maximum number of members.
michael@0 1183
michael@0 1184 Each list member is checked that it is an IP address.
michael@0 1185
michael@0 1186 >>> vtor.check('ip_addr_list', ())
michael@0 1187 []
michael@0 1188 >>> vtor.check('ip_addr_list', [])
michael@0 1189 []
michael@0 1190 >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8'))
michael@0 1191 ['1.2.3.4', '5.6.7.8']
michael@0 1192 >>> vtor.check('ip_addr_list', ['a'])
michael@0 1193 Traceback (most recent call last):
michael@0 1194 VdtValueError: the value "a" is unacceptable.
michael@0 1195 """
michael@0 1196 return [is_ip_addr(mem) for mem in is_list(value, min, max)]
michael@0 1197
michael@0 1198
michael@0 1199 def force_list(value, min=None, max=None):
michael@0 1200 """
michael@0 1201 Check that a value is a list, coercing strings into
michael@0 1202 a list with one member. Useful where users forget the
michael@0 1203 trailing comma that turns a single value into a list.
michael@0 1204
michael@0 1205 You can optionally specify the minimum and maximum number of members.
michael@0 1206 A minumum of greater than one will fail if the user only supplies a
michael@0 1207 string.
michael@0 1208
michael@0 1209 >>> vtor.check('force_list', ())
michael@0 1210 []
michael@0 1211 >>> vtor.check('force_list', [])
michael@0 1212 []
michael@0 1213 >>> vtor.check('force_list', 'hello')
michael@0 1214 ['hello']
michael@0 1215 """
michael@0 1216 if not isinstance(value, (list, tuple)):
michael@0 1217 value = [value]
michael@0 1218 return is_list(value, min, max)
michael@0 1219
michael@0 1220
michael@0 1221
michael@0 1222 fun_dict = {
michael@0 1223 'integer': is_integer,
michael@0 1224 'float': is_float,
michael@0 1225 'ip_addr': is_ip_addr,
michael@0 1226 'string': is_string,
michael@0 1227 'boolean': is_boolean,
michael@0 1228 }
michael@0 1229
michael@0 1230
michael@0 1231 def is_mixed_list(value, *args):
michael@0 1232 """
michael@0 1233 Check that the value is a list.
michael@0 1234 Allow specifying the type of each member.
michael@0 1235 Work on lists of specific lengths.
michael@0 1236
michael@0 1237 You specify each member as a positional argument specifying type
michael@0 1238
michael@0 1239 Each type should be one of the following strings :
michael@0 1240 'integer', 'float', 'ip_addr', 'string', 'boolean'
michael@0 1241
michael@0 1242 So you can specify a list of two strings, followed by
michael@0 1243 two integers as :
michael@0 1244
michael@0 1245 mixed_list('string', 'string', 'integer', 'integer')
michael@0 1246
michael@0 1247 The length of the list must match the number of positional
michael@0 1248 arguments you supply.
michael@0 1249
michael@0 1250 >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')"
michael@0 1251 >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True))
michael@0 1252 >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
michael@0 1253 1
michael@0 1254 >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True'))
michael@0 1255 >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
michael@0 1256 1
michael@0 1257 >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True))
michael@0 1258 Traceback (most recent call last):
michael@0 1259 VdtTypeError: the value "b" is of the wrong type.
michael@0 1260 >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a'))
michael@0 1261 Traceback (most recent call last):
michael@0 1262 VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short.
michael@0 1263 >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b'))
michael@0 1264 Traceback (most recent call last):
michael@0 1265 VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long.
michael@0 1266 >>> vtor.check(mix_str, 0)
michael@0 1267 Traceback (most recent call last):
michael@0 1268 VdtTypeError: the value "0" is of the wrong type.
michael@0 1269
michael@0 1270 This test requires an elaborate setup, because of a change in error string
michael@0 1271 output from the interpreter between Python 2.2 and 2.3 .
michael@0 1272
michael@0 1273 >>> res_seq = (
michael@0 1274 ... 'passed an incorrect value "',
michael@0 1275 ... 'yoda',
michael@0 1276 ... '" for parameter "mixed_list".',
michael@0 1277 ... )
michael@0 1278 >>> res_str = "'".join(res_seq)
michael@0 1279 >>> try:
michael@0 1280 ... vtor.check('mixed_list("yoda")', ('a'))
michael@0 1281 ... except VdtParamError, err:
michael@0 1282 ... str(err) == res_str
michael@0 1283 1
michael@0 1284 """
michael@0 1285 try:
michael@0 1286 length = len(value)
michael@0 1287 except TypeError:
michael@0 1288 raise VdtTypeError(value)
michael@0 1289 if length < len(args):
michael@0 1290 raise VdtValueTooShortError(value)
michael@0 1291 elif length > len(args):
michael@0 1292 raise VdtValueTooLongError(value)
michael@0 1293 try:
michael@0 1294 return [fun_dict[arg](val) for arg, val in zip(args, value)]
michael@0 1295 except KeyError, e:
michael@0 1296 raise VdtParamError('mixed_list', e)
michael@0 1297
michael@0 1298
michael@0 1299 def is_option(value, *options):
michael@0 1300 """
michael@0 1301 This check matches the value to any of a set of options.
michael@0 1302
michael@0 1303 >>> vtor.check('option("yoda", "jedi")', 'yoda')
michael@0 1304 'yoda'
michael@0 1305 >>> vtor.check('option("yoda", "jedi")', 'jed')
michael@0 1306 Traceback (most recent call last):
michael@0 1307 VdtValueError: the value "jed" is unacceptable.
michael@0 1308 >>> vtor.check('option("yoda", "jedi")', 0)
michael@0 1309 Traceback (most recent call last):
michael@0 1310 VdtTypeError: the value "0" is of the wrong type.
michael@0 1311 """
michael@0 1312 if not isinstance(value, basestring):
michael@0 1313 raise VdtTypeError(value)
michael@0 1314 if not value in options:
michael@0 1315 raise VdtValueError(value)
michael@0 1316 return value
michael@0 1317
michael@0 1318
michael@0 1319 def _test(value, *args, **keywargs):
michael@0 1320 """
michael@0 1321 A function that exists for test purposes.
michael@0 1322
michael@0 1323 >>> checks = [
michael@0 1324 ... '3, 6, min=1, max=3, test=list(a, b, c)',
michael@0 1325 ... '3',
michael@0 1326 ... '3, 6',
michael@0 1327 ... '3,',
michael@0 1328 ... 'min=1, test="a b c"',
michael@0 1329 ... 'min=5, test="a, b, c"',
michael@0 1330 ... 'min=1, max=3, test="a, b, c"',
michael@0 1331 ... 'min=-100, test=-99',
michael@0 1332 ... 'min=1, max=3',
michael@0 1333 ... '3, 6, test="36"',
michael@0 1334 ... '3, 6, test="a, b, c"',
michael@0 1335 ... '3, max=3, test=list("a", "b", "c")',
michael@0 1336 ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''',
michael@0 1337 ... "test='x=fish(3)'",
michael@0 1338 ... ]
michael@0 1339 >>> v = Validator({'test': _test})
michael@0 1340 >>> for entry in checks:
michael@0 1341 ... print v.check(('test(%s)' % entry), 3)
michael@0 1342 (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'})
michael@0 1343 (3, ('3',), {})
michael@0 1344 (3, ('3', '6'), {})
michael@0 1345 (3, ('3',), {})
michael@0 1346 (3, (), {'test': 'a b c', 'min': '1'})
michael@0 1347 (3, (), {'test': 'a, b, c', 'min': '5'})
michael@0 1348 (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'})
michael@0 1349 (3, (), {'test': '-99', 'min': '-100'})
michael@0 1350 (3, (), {'max': '3', 'min': '1'})
michael@0 1351 (3, ('3', '6'), {'test': '36'})
michael@0 1352 (3, ('3', '6'), {'test': 'a, b, c'})
michael@0 1353 (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'})
michael@0 1354 (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'})
michael@0 1355 (3, (), {'test': 'x=fish(3)'})
michael@0 1356
michael@0 1357 >>> v = Validator()
michael@0 1358 >>> v.check('integer(default=6)', '3')
michael@0 1359 3
michael@0 1360 >>> v.check('integer(default=6)', None, True)
michael@0 1361 6
michael@0 1362 >>> v.get_default_value('integer(default=6)')
michael@0 1363 6
michael@0 1364 >>> v.get_default_value('float(default=6)')
michael@0 1365 6.0
michael@0 1366 >>> v.get_default_value('pass(default=None)')
michael@0 1367 >>> v.get_default_value("string(default='None')")
michael@0 1368 'None'
michael@0 1369 >>> v.get_default_value('pass')
michael@0 1370 Traceback (most recent call last):
michael@0 1371 KeyError: 'Check "pass" has no default value.'
michael@0 1372 >>> v.get_default_value('pass(default=list(1, 2, 3, 4))')
michael@0 1373 ['1', '2', '3', '4']
michael@0 1374
michael@0 1375 >>> v = Validator()
michael@0 1376 >>> v.check("pass(default=None)", None, True)
michael@0 1377 >>> v.check("pass(default='None')", None, True)
michael@0 1378 'None'
michael@0 1379 >>> v.check('pass(default="None")', None, True)
michael@0 1380 'None'
michael@0 1381 >>> v.check('pass(default=list(1, 2, 3, 4))', None, True)
michael@0 1382 ['1', '2', '3', '4']
michael@0 1383
michael@0 1384 Bug test for unicode arguments
michael@0 1385 >>> v = Validator()
michael@0 1386 >>> v.check(u'string(min=4)', u'test')
michael@0 1387 u'test'
michael@0 1388
michael@0 1389 >>> v = Validator()
michael@0 1390 >>> v.get_default_value(u'string(min=4, default="1234")')
michael@0 1391 u'1234'
michael@0 1392 >>> v.check(u'string(min=4, default="1234")', u'test')
michael@0 1393 u'test'
michael@0 1394
michael@0 1395 >>> v = Validator()
michael@0 1396 >>> default = v.get_default_value('string(default=None)')
michael@0 1397 >>> default == None
michael@0 1398 1
michael@0 1399 """
michael@0 1400 return (value, args, keywargs)
michael@0 1401
michael@0 1402
michael@0 1403 def _test2():
michael@0 1404 """
michael@0 1405 >>>
michael@0 1406 >>> v = Validator()
michael@0 1407 >>> v.get_default_value('string(default="#ff00dd")')
michael@0 1408 '#ff00dd'
michael@0 1409 >>> v.get_default_value('integer(default=3) # comment')
michael@0 1410 3
michael@0 1411 """
michael@0 1412
michael@0 1413 def _test3():
michael@0 1414 r"""
michael@0 1415 >>> vtor.check('string(default="")', '', missing=True)
michael@0 1416 ''
michael@0 1417 >>> vtor.check('string(default="\n")', '', missing=True)
michael@0 1418 '\n'
michael@0 1419 >>> print vtor.check('string(default="\n")', '', missing=True),
michael@0 1420 <BLANKLINE>
michael@0 1421 >>> vtor.check('string()', '\n')
michael@0 1422 '\n'
michael@0 1423 >>> vtor.check('string(default="\n\n\n")', '', missing=True)
michael@0 1424 '\n\n\n'
michael@0 1425 >>> vtor.check('string()', 'random \n text goes here\n\n')
michael@0 1426 'random \n text goes here\n\n'
michael@0 1427 >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")',
michael@0 1428 ... '', missing=True)
michael@0 1429 ' \nrandom text\ngoes \n here\n\n '
michael@0 1430 >>> vtor.check("string(default='\n\n\n')", '', missing=True)
michael@0 1431 '\n\n\n'
michael@0 1432 >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True)
michael@0 1433 '\n'
michael@0 1434 >>> vtor.check("string_list()", ['foo', '\n', 'bar'])
michael@0 1435 ['foo', '\n', 'bar']
michael@0 1436 >>> vtor.check("string_list(default=list('\n'))", '', missing=True)
michael@0 1437 ['\n']
michael@0 1438 """
michael@0 1439
michael@0 1440
michael@0 1441 if __name__ == '__main__':
michael@0 1442 # run the code tests in doctest format
michael@0 1443 import sys
michael@0 1444 import doctest
michael@0 1445 m = sys.modules.get('__main__')
michael@0 1446 globs = m.__dict__.copy()
michael@0 1447 globs.update({
michael@0 1448 'vtor': Validator(),
michael@0 1449 })
michael@0 1450 doctest.testmod(m, globs=globs)

mercurial