2008-08-27 15:09:19 -05:00
|
|
|
# Authors:
|
|
|
|
# Jason Gerard DeRose <jderose@redhat.com>
|
|
|
|
#
|
|
|
|
# Copyright (C) 2008 Red Hat
|
|
|
|
# see file 'COPYING' for use and warranty information
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU General Public License as
|
|
|
|
# published by the Free Software Foundation; version 2 only
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
"""
|
|
|
|
Type system for coercing and normalizing input values.
|
|
|
|
"""
|
|
|
|
|
2008-08-27 16:52:13 -05:00
|
|
|
import re
|
2008-08-27 15:09:19 -05:00
|
|
|
from plugable import ReadOnly, lock
|
|
|
|
import errors
|
|
|
|
|
|
|
|
|
2008-08-27 16:20:19 -05:00
|
|
|
def check_min_max(min_value, max_value, min_name, max_name):
|
|
|
|
assert type(min_name) is str, 'min_name must be an str'
|
|
|
|
assert type(max_name) is str, 'max_name must be an str'
|
|
|
|
for (name, value) in [(min_name, min_value), (max_name, max_value)]:
|
|
|
|
if not (value is None or type(value) is int):
|
|
|
|
raise TypeError(
|
2008-08-27 16:52:13 -05:00
|
|
|
'%s must be an int or None, got: %r' % (name, value)
|
2008-08-27 16:20:19 -05:00
|
|
|
)
|
2008-08-27 16:52:13 -05:00
|
|
|
if None not in (min_value, max_value) and min_value > max_value:
|
2008-08-27 16:20:19 -05:00
|
|
|
d = dict(
|
|
|
|
k0=min_name,
|
|
|
|
v0=min_value,
|
|
|
|
k1=max_name,
|
|
|
|
v1=max_value,
|
|
|
|
)
|
|
|
|
raise ValueError(
|
2008-08-27 16:52:13 -05:00
|
|
|
'%(k0)s > %(k1)s: %(k0)s=%(v0)r, %(k1)s=%(v1)r' % d
|
2008-08-27 16:20:19 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2008-08-27 15:09:19 -05:00
|
|
|
class Type(ReadOnly):
|
|
|
|
"""
|
|
|
|
Base class for all IPA types.
|
|
|
|
"""
|
|
|
|
|
2008-08-27 21:02:03 -05:00
|
|
|
def __init__(self, type_):
|
2008-08-27 21:45:04 -05:00
|
|
|
if type(type_) is not type:
|
|
|
|
raise TypeError('%r is not %r' % (type(type_), type))
|
2008-08-27 21:02:03 -05:00
|
|
|
allowed = (bool, int, float, unicode)
|
|
|
|
if type_ not in allowed:
|
2008-08-27 21:45:04 -05:00
|
|
|
raise ValueError('not an allowed type: %r' % type_)
|
2008-08-27 21:02:03 -05:00
|
|
|
self.type = type_
|
2008-09-03 13:32:49 -05:00
|
|
|
# FIXME: This should be replaced with a more user friendly message
|
|
|
|
# as this is what is returned to the user.
|
|
|
|
self.conversion_error = 'Must be a %r' % self.type
|
2008-08-27 21:02:03 -05:00
|
|
|
lock(self)
|
2008-08-27 15:09:19 -05:00
|
|
|
|
|
|
|
def __get_name(self):
|
|
|
|
"""
|
|
|
|
Convenience property to return the class name.
|
|
|
|
"""
|
|
|
|
return self.__class__.__name__
|
|
|
|
name = property(__get_name)
|
|
|
|
|
2008-08-27 20:38:29 -05:00
|
|
|
def convert(self, value):
|
|
|
|
try:
|
|
|
|
return self.type(value)
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
return None
|
|
|
|
|
2008-09-03 13:32:49 -05:00
|
|
|
def validate(self, value):
|
|
|
|
pass
|
|
|
|
|
2008-08-27 20:38:29 -05:00
|
|
|
def __call__(self, value):
|
|
|
|
if value is None:
|
|
|
|
raise TypeError('value cannot be None')
|
|
|
|
if type(value) is self.type:
|
|
|
|
return value
|
|
|
|
return self.convert(value)
|
|
|
|
|
2008-08-27 15:09:19 -05:00
|
|
|
|
2008-08-27 23:54:48 -05:00
|
|
|
class Bool(Type):
|
|
|
|
def __init__(self, true='Yes', false='No'):
|
|
|
|
if true is None:
|
|
|
|
raise TypeError('`true` cannot be None')
|
|
|
|
if false is None:
|
|
|
|
raise TypeError('`false` cannot be None')
|
|
|
|
if true == false:
|
|
|
|
raise ValueError(
|
|
|
|
'cannot be equal: true=%r, false=%r' % (true, false)
|
|
|
|
)
|
|
|
|
self.true = true
|
|
|
|
self.false = false
|
|
|
|
super(Bool, self).__init__(bool)
|
|
|
|
|
|
|
|
def convert(self, value):
|
|
|
|
if value == self.true:
|
|
|
|
return True
|
|
|
|
if value == self.false:
|
|
|
|
return False
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2008-08-27 15:09:19 -05:00
|
|
|
class Int(Type):
|
|
|
|
def __init__(self, min_value=None, max_value=None):
|
2008-08-27 16:20:19 -05:00
|
|
|
check_min_max(min_value, max_value, 'min_value', 'max_value')
|
2008-08-27 15:09:19 -05:00
|
|
|
self.min_value = min_value
|
|
|
|
self.max_value = max_value
|
2008-08-27 21:02:03 -05:00
|
|
|
super(Int, self).__init__(int)
|
2008-08-27 15:09:19 -05:00
|
|
|
|
|
|
|
def validate(self, value):
|
|
|
|
if type(value) is not self.type:
|
|
|
|
return 'Must be an integer'
|
|
|
|
if self.min_value is not None and value < self.min_value:
|
|
|
|
return 'Cannot be smaller than %d' % self.min_value
|
|
|
|
if self.max_value is not None and value > self.max_value:
|
|
|
|
return 'Cannot be larger than %d' % self.max_value
|
|
|
|
|
|
|
|
|
|
|
|
class Unicode(Type):
|
2008-08-27 17:26:35 -05:00
|
|
|
def __init__(self, min_length=None, max_length=None, pattern=None):
|
2008-08-27 16:52:13 -05:00
|
|
|
check_min_max(min_length, max_length, 'min_length', 'max_length')
|
|
|
|
if min_length is not None and min_length < 0:
|
2008-08-27 17:26:35 -05:00
|
|
|
raise ValueError('min_length must be >= 0, got: %r' % min_length)
|
|
|
|
if max_length is not None and max_length < 1:
|
|
|
|
raise ValueError('max_length must be >= 1, got: %r' % max_length)
|
|
|
|
if not (pattern is None or isinstance(pattern, basestring)):
|
|
|
|
raise TypeError(
|
|
|
|
'pattern must be a basestring or None, got: %r' % pattern
|
2008-08-27 15:09:19 -05:00
|
|
|
)
|
|
|
|
self.min_length = min_length
|
|
|
|
self.max_length = max_length
|
|
|
|
self.pattern = pattern
|
2008-08-27 16:52:13 -05:00
|
|
|
if pattern is None:
|
|
|
|
self.regex = None
|
|
|
|
else:
|
|
|
|
self.regex = re.compile(pattern)
|
2008-08-27 21:02:03 -05:00
|
|
|
super(Unicode, self).__init__(unicode)
|
2008-08-27 18:40:34 -05:00
|
|
|
|
2008-11-25 14:52:40 -06:00
|
|
|
def convert(self, value):
|
|
|
|
assert type(value) not in (list, tuple)
|
|
|
|
try:
|
|
|
|
return self.type(value)
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
return None
|
|
|
|
|
2008-08-27 18:40:34 -05:00
|
|
|
def validate(self, value):
|
|
|
|
if type(value) is not self.type:
|
|
|
|
return 'Must be a string'
|
|
|
|
|
|
|
|
if self.regex and self.regex.match(value) is None:
|
|
|
|
return 'Must match %r' % self.pattern
|
|
|
|
|
|
|
|
if self.min_length is not None and len(value) < self.min_length:
|
|
|
|
return 'Must be at least %d characters long' % self.min_length
|
|
|
|
|
|
|
|
if self.max_length is not None and len(value) > self.max_length:
|
|
|
|
return 'Can be at most %d characters long' % self.max_length
|
2008-08-28 01:56:45 -05:00
|
|
|
|
|
|
|
|
|
|
|
class Enum(Type):
|
|
|
|
def __init__(self, *values):
|
|
|
|
if len(values) < 1:
|
|
|
|
raise ValueError('%s requires at least one value' % self.name)
|
|
|
|
type_ = type(values[0])
|
|
|
|
if type_ not in (unicode, int, float):
|
|
|
|
raise TypeError(
|
|
|
|
'%r: %r not unicode, int, nor float' % (values[0], type_)
|
|
|
|
)
|
|
|
|
for val in values[1:]:
|
|
|
|
if type(val) is not type_:
|
|
|
|
raise TypeError('%r: %r is not %r' % (val, type(val), type_))
|
|
|
|
self.values = values
|
2008-08-28 02:57:07 -05:00
|
|
|
self.frozenset = frozenset(values)
|
2008-08-28 01:56:45 -05:00
|
|
|
super(Enum, self).__init__(type_)
|
2008-08-28 02:57:07 -05:00
|
|
|
|
|
|
|
def validate(self, value):
|
|
|
|
if type(value) is not self.type:
|
|
|
|
return 'Incorrect type'
|
|
|
|
if value not in self.frozenset:
|
|
|
|
return 'Invalid value'
|