summaryrefslogtreecommitdiffstats
path: root/ipalib/ipa_types.py
blob: 2da8e0be842b84cbb7ddc357a80fcbbb5fdc3b46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# 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.
"""

import re
from plugable import ReadOnly, lock
import errors


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(
                '%s must be an int or None, got: %r' % (name, value)
            )
    if None not in (min_value, max_value) and min_value > max_value:
        d = dict(
            k0=min_name,
            v0=min_value,
            k1=max_name,
            v1=max_value,
        )
        raise ValueError(
            '%(k0)s > %(k1)s: %(k0)s=%(v0)r, %(k1)s=%(v1)r' % d
        )


class Type(ReadOnly):
    """
    Base class for all IPA types.
    """

    def __init__(self, type_):
        if type(type_) is not type:
            raise TypeError('%r is not %r' % (type(type_), type))
        allowed = (bool, int, float, unicode)
        if type_ not in allowed:
            raise ValueError('not an allowed type: %r' % type_)
        self.type = type_
        # 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
        lock(self)

    def __get_name(self):
        """
        Convenience property to return the class name.
        """
        return self.__class__.__name__
    name = property(__get_name)

    def convert(self, value):
        try:
            return self.type(value)
        except (TypeError, ValueError):
            return None

    def validate(self, value):
        pass

    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)


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


class Int(Type):
    def __init__(self, min_value=None, max_value=None):
        check_min_max(min_value, max_value, 'min_value', 'max_value')
        self.min_value = min_value
        self.max_value = max_value
        super(Int, self).__init__(int)

    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):
    def __init__(self, min_length=None, max_length=None, pattern=None):
        check_min_max(min_length, max_length, 'min_length', 'max_length')
        if min_length is not None and min_length < 0:
            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
            )
        self.min_length = min_length
        self.max_length = max_length
        self.pattern = pattern
        if pattern is None:
            self.regex = None
        else:
            self.regex = re.compile(pattern)
        super(Unicode, self).__init__(unicode)

    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


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
        self.frozenset = frozenset(values)
        super(Enum, self).__init__(type_)

    def validate(self, value):
        if type(value) is not self.type:
            return 'Incorrect type'
        if value not in self.frozenset:
            return 'Invalid value'