previous value api
[tiramisu.git] / tiramisu / value.py
1 # -*- coding: utf-8 -*-
2 "takes care of the option's values and multi values"
3 # Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 from tiramisu.error import NoValueReturned, MandatoryError, MultiTypeError
24 from tiramisu.setting import owners, multitypes
25
26 class Values(object):
27     def __init__(self, context):
28         """
29         Initializes the values's dict.
30
31         :param context: the context is the home config's values and properties
32         """
33         self.owners = {}
34         "Config's root indeed is in charge of the `Option()`'s values"
35         self.values = {}
36         self.previous_values = {}
37         self.masters = {}
38         self.slaves = {}
39         self.context = context
40
41     def _get_multitype(self, opt):
42         if opt in self.slaves:
43             # slave
44             multitype = multitypes.slave
45         elif opt in self.masters:
46             # master
47             multitype = multitypes.master
48         # FIXME : default value for a multi, we shall work on groups
49         else:
50             multitype = multitypes.default
51         return multitype
52
53     def _get_value(self, opt):
54         "special case for the multis: they never return None"
55         if opt not in self.values:
56             if opt.is_multi():
57                 multitype = self._get_multitype(opt)
58                 return Multi(opt.getdefault(), self.context, opt, multitype)
59             else:
60                 return opt.getdefault()
61         return self.values[opt]
62
63     def reset(self, opt):
64         if opt in self.values:
65             self.set_previous_value(opt)
66             del(self.values[opt])
67         self.setowner(opt, owners.default)
68
69     def set_previous_value(self, opt):
70         if opt in self.values:
71             old_value = self.values[opt]
72         else:
73             old_value = None
74         if type(old_value) == Multi:
75            self.previous_values[opt] = list(old_value)
76         else:
77            self.previous_values[opt] = old_value
78
79     def get_previous_value(self, opt):
80         if opt in self.previous_values:
81             prec_value = self.previous_values[opt]
82         else:
83             prec_value = None
84         return prec_value
85
86     def _is_empty(self, opt, value=None):
87         "convenience method to know if an option is empty"
88         if value is not None:
89             return False
90         if (not opt.is_multi() and value == None) or \
91             (opt.is_multi() and (value == [] or \
92                 None in self._get_value(opt))):
93             return True
94         return False
95
96     def is_empty(self, opt):
97         if opt not in self.values:
98             return True
99         value = self.values[opt]
100         if not opt.is_multi():
101             if self._get_value(opt) == None:
102                 return True
103             return False
104         else:
105             value = list(value)
106             for val in value:
107                 if val != None:
108                     return False
109             return True
110
111     def _test_mandatory(self, opt, value=None):
112         # mandatory options
113         mandatory = self.context._cfgimpl_settings.mandatory
114         if opt.is_mandatory() and mandatory:
115             if self._is_empty(opt, value) and \
116                     opt.is_empty_by_default():
117                 raise MandatoryError("option: {0} is mandatory "
118                                       "and shall have a value".format(opt._name))
119
120     def fill_multi(self, opt, result):
121         """fills a multi option with default and calculated values
122         """
123         value = self._get_value(opt)
124         if not isinstance(result, list):
125             _result = [result]
126         else:
127             _result = result
128         multitype = self._get_multitype(opt)
129         return Multi(_result, self.context, opt, multitype)
130
131     def __getitem__(self, opt):
132         # options with callbacks
133         value = self._get_value(opt)
134         if opt.has_callback():
135             if (not opt.is_frozen() or \
136                     not opt.is_forced_on_freeze()) and \
137                     not opt.is_default_owner(self.context):
138                 return self._get_value(opt)
139             try:
140                 result = opt.getcallback_value(
141                         self.context)
142             except NoValueReturned, err:
143                 pass
144             else:
145                 if opt.is_multi():
146                     value = self.fill_multi(opt, result)
147                 else:
148                     # this result **shall not** be a list
149                     if isinstance(result, list):
150                         raise ConfigError('invalid calculated value returned '
151                             'for option {0} : shall not be a list'.format(name))
152                     value = result
153                 if value != None and not opt.validate(value,
154                             self.context._cfgimpl_settings.validator):
155                     raise ConfigError('invalid calculated value returned'
156                         ' for option {0}'.format(name))
157         # frozen and force default
158         if not opt.has_callback() and opt.is_forced_on_freeze():
159             value = opt.getdefault()
160             if opt.is_multi():
161                 value = self.fill_multi(opt, value)
162         self._test_mandatory(opt, value)
163         return value
164
165     def __setitem__(self, opt, value):
166         if opt in self.masters:
167             masterlen = len(value)
168             for slave in self.masters[opt]:
169                 value_slave = self._get_value(slave)
170                 if len(value_slave) > masterlen:
171                     raise MultiTypeError("invalid len for the slave: {0}"
172                     " which has {1} as master".format(slave._name,
173                                                       opt._name))
174                 elif len(value_slave) < masterlen:
175                     for num in range(0, masterlen - len(value_slave)):
176                         value_slave.append(None, force=True)
177
178         elif opt in self.slaves:
179             if len(self._get_value(self.slaves[opt])) != len(value):
180                    raise MultiTypeError("invalid len for the slave: {0}"
181                     " which has {1} as master".format(opt._name,
182                                                       self.slaves[opt]._name))
183         self.setitem(opt, value)
184
185     def setitem(self, opt, value):
186         self.set_previous_value(opt)
187         self.values[opt] = value
188         self.setowner(opt, self.context._cfgimpl_settings.getowner())
189
190     def __contains__(self, opt):
191         return opt in self.values
192     #____________________________________________________________
193     def setowner(self, opt, owner):
194         if isinstance(owner, owners.Owner):
195             self.owners[opt] = owner
196         else:
197             raise OptionValueError("Bad owner: " + str(owner))
198
199     def getowner(self, opt):
200         return self.owners.get(opt, owners.default)
201
202 # ____________________________________________________________
203 # multi types
204 class Multi(list):
205     """multi options values container
206     that support item notation for the values of multi options"""
207     def __init__(self, lst, context, opt, multitype):
208         """
209         :param lst: the Multi wraps a list value
210         :param context: the home config that has the settings and the values
211         :param opt: the option object that have this Multi value
212         """
213         self.settings = context._cfgimpl_settings
214         self.opt = opt
215         self.values = context._cfgimpl_values
216         self.multitype = multitype
217         super(Multi, self).__init__(lst)
218         if multitype == multitypes.master:
219             self.slaves = context._cfgimpl_values.masters[opt]
220         else:
221             self.slaves = None
222     def __setitem__(self, key, value):
223         self._validate(value)
224         self.values[self.opt] = self
225         super(Multi, self).__setitem__(key, value)
226
227     def append(self, value, force=False):
228         """the list value can be updated (appened)
229         only if the option is a master
230         """
231         if not force:
232             if self.multitype == multitypes.slave:
233                 raise MultiTypeError("cannot append a value on a multi option {0}"
234                         " which is a slave".format(self.opt._name))
235             elif self.multitype == multitypes.master:
236                 for slave in self.slaves:
237                     self.values[slave].append(None, force=True)
238         self._validate(value)
239         self.values.setitem(self.opt, self)
240         super(Multi, self).append(value)
241
242     def _validate(self, value):
243         if value != None and not self.opt._validate(value):
244             raise ConfigError("invalid value {0} "
245                     "for option {1}".format(str(value), self.opt._name))
246
247     def pop(self, key, force=False):
248         """the list value can be updated (poped)
249         only if the option is a master
250
251         :param key: index of the element to pop
252         :return: the requested element
253         """
254         if not force:
255             if self.multitype == multitypes.slave:
256                 raise MultiTypeError("cannot append a value on a multi option {0}"
257                         " which is a slave".format(self.opt._name))
258             elif self.multitype == multitypes.master:
259                 for slave in self.slaves:
260                     self.values[slave].pop(key, force=True)
261         self.values.setitem(self.opt, self)
262         return super(Multi, self).pop(key)