tiramisu/value.py : self.opt => opt + disable permissive in read_write mode
[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 # ____________________________________________________________
20 from tiramisu.error import NoValueReturned, MandatoryError, MultiTypeError, \
21     ConfigError  # , OptionValueError
22 from tiramisu.setting import owners, multitypes
23
24
25 class Values(object):
26     __slots__ = ('values', 'context')
27
28     def __init__(self, context):
29         """
30         Initializes the values's dict.
31
32         :param context: the context is the home config's values
33         """
34         "Config's root indeed is in charge of the `Option()`'s values"
35         self.values = {}
36         self.context = context
37
38     def _get_value(self, opt):
39         "special case for the multis: they never return None"
40         if opt not in self.values:
41             if opt.is_multi():
42                 value = Multi(opt.getdefault(), self.context, opt)
43                 if opt.multitype == multitypes.slave:
44                     masterpath = self.context._cfgimpl_descr.get_path_by_opt(opt.master_slaves)
45                     mastervalue = getattr(self.context, masterpath)
46                     masterlen = len(mastervalue)
47                     if len(value) < masterlen:
48                         for num in range(0, masterlen - len(value)):
49                             value.append(None, force=True)
50             else:
51                 value = opt.getdefault()
52
53             return value
54         return self.values[opt][1]
55
56     def reset(self, opt):
57         if opt in self.values:
58             del(self.values[opt])
59
60     def _is_empty(self, opt, value=None):
61         "convenience method to know if an option is empty"
62         #FIXME: buggy ?
63         #if value is not None:
64         #    return False
65         if (not opt.is_multi() and value is None) or \
66            (opt.is_multi() and (value == [] or
67                                 None in self._get_value(opt))):
68             return True
69         return False
70
71     def is_empty(self, opt):
72         #FIXME that not empty ... just no value!
73         if opt not in self.values:
74             return True
75         value = self.values[opt][1]
76         if not opt.is_multi():
77             if self._get_value(opt) is None:
78                 return True
79             return False
80         else:
81             value = list(value)
82             for val in value:
83                 if val is not None:
84                     return False
85             return True
86
87     def _test_mandatory(self, opt, value, force_properties=None):
88         setting = self.context.cfgimpl_get_settings()
89         if force_properties is None:
90             set_mandatory = setting.has_property('mandatory')
91         else:
92             set_mandatory = ('mandatory' in force_properties or
93                              setting.has_property('mandatory'))
94         if setting.has_property('mandatory', opt) and set_mandatory:
95             if self._is_empty(opt, value) and \
96                     opt.is_empty_by_default():
97                 raise MandatoryError("option: {0} is mandatory "
98                                      "and shall have a value".format(opt._name))
99             #empty value
100             if opt.is_multi():
101                 for val in value:
102                     if val == '':
103                         raise MandatoryError("option: {0} is mandatory "
104                                              "and shall have not empty value".format(opt._name))
105             else:
106                 if value == '':
107                     raise MandatoryError("option: {0} is mandatory "
108                                          "and shall have not empty value".format(opt._name))
109
110     def fill_multi(self, opt, result):
111         """fills a multi option with default and calculated values
112         """
113         if not isinstance(result, list):
114             _result = [result]
115         else:
116             _result = result
117         #multitype = self._get_multitype(opt)
118         return Multi(_result, self.context, opt)  # , multitype)
119
120     def __getitem__(self, opt):
121         return self._getitem(opt)
122
123     def _getitem(self, opt, force_properties=None):
124         # options with callbacks
125         value = self._get_value(opt)
126         if opt.has_callback():
127             setting = self.context.cfgimpl_get_settings()
128             if (not setting.has_property('frozen', opt) or
129                 (setting.has_property('frozen', opt) and
130                  not setting.has_property('force_default_on_freeze', opt)
131                  )) and not self.context.cfgimpl_get_values().is_default_owner(opt):
132                 return self._get_value(opt)
133             try:
134                 result = opt.getcallback_value(self.context)
135             except NoValueReturned:
136                 pass
137             else:
138                 if opt.is_multi():
139                     value = self.fill_multi(opt, result)
140                 else:
141                     # this result **shall not** be a list
142                     if isinstance(result, list):
143                         raise ConfigError('invalid calculated value returned '
144                                           'for option {0} : shall not be a list'
145                                           ''.format(opt._name))
146                     value = result
147                 if value is not None and \
148                         not opt.validate(value, setting.has_property('validator')):
149                     raise ConfigError('invalid calculated value returned'
150                                       ' for option {0}'.format(opt._name))
151         # frozen and force default
152         if not opt.has_callback() and self.context.cfgimpl_get_settings().has_property('force_default_on_freeze', opt):
153             value = opt.getdefault()
154             if opt.is_multi():
155                 value = self.fill_multi(opt, value)
156         self._test_mandatory(opt, value, force_properties)
157         return value
158
159     def __setitem__(self, opt, value):
160         if opt.is_multi():
161             if opt.multitype == multitypes.master:
162                 masterlen = len(value)
163                 for slave in opt.master_slaves:
164                     value_slave = self._get_value(slave)
165                     if len(value_slave) > masterlen:
166                         raise MultiTypeError("invalid len for the slave: {0}"
167                                              " which has {1} as master".format(
168                                                  slave._name, opt._name))
169                     elif len(value_slave) < masterlen:
170                         for num in range(0, masterlen - len(value_slave)):
171                             value_slave.append(None, force=True)
172
173             elif opt.multitype == multitypes.slave:
174                 if len(self._get_value(opt.master_slaves)) != len(value):
175                     raise MultiTypeError("invalid len for the slave: {0}"
176                                          " which has {1} as master".format(
177                                              opt._name, opt.master_slaves._name))
178             if not isinstance(value, Multi):
179                 value = Multi(value, self.context, opt)
180         self.setitem(opt, value)
181
182     def setitem(self, opt, value):
183         if type(value) == list:
184             raise MultiTypeError("the type of the value {0} which is multi shall "
185                                  "be Multi and not list".format(str(value)))
186         self._test_mandatory(opt, value)
187         self.values[opt] = (self.context.cfgimpl_get_settings().getowner(), value)
188
189     def __contains__(self, opt):
190         return opt in self.values
191
192     def getowner(self, opt):
193         return self.values.get(opt, (owners.default, None))[0]
194
195     def setowner(self, opt, owner):
196         if opt not in self.values:
197             raise ConfigError('no value for {} cannot change owner to {}'.format(opt))
198         if not isinstance(owner, owners.Owner):
199             raise TypeError("invalid generic owner {0}".format(str(owner)))
200         self.values[opt] = (owner, self.values[opt][1])
201
202     def is_default_owner(self, opt):
203         """
204         :param config: *must* be only the **parent** config
205                        (not the toplevel config)
206         :return: boolean
207         """
208         return self.getowner(opt) == owners.default
209
210 # ____________________________________________________________
211 # multi types
212
213
214 class Multi(list):
215     """multi options values container
216     that support item notation for the values of multi options"""
217     __slots__ = ('opt', 'context')
218
219     def __init__(self, lst, context, opt):
220         """
221         :param lst: the Multi wraps a list value
222         :param context: the home config that has the values
223         :param opt: the option object that have this Multi value
224         """
225         self.opt = opt
226         self.context = context
227         super(Multi, self).__init__(lst)
228
229     def __setitem__(self, key, value):
230         self._validate(value)
231         self.context.cfgimpl_get_values()[self.opt] = self
232         super(Multi, self).__setitem__(key, value)
233
234     def append(self, value, force=False):
235         """the list value can be updated (appened)
236         only if the option is a master
237         """
238         if not force:
239             if self.opt.multitype == multitypes.slave:
240                 raise MultiTypeError("cannot append a value on a multi option {0}"
241                                      " which is a slave".format(self.opt._name))
242             elif self.opt.multitype == multitypes.master:
243                 for slave in self.opt.master_slaves:
244                     self.context.cfgimpl_get_values()[slave].append(None, force=True)
245         self._validate(value)
246         self.context.cfgimpl_get_values().setitem(self.opt, self)
247         super(Multi, self).append(value)
248
249     def _validate(self, value):
250         if value is not None and not self.opt._validate(value):
251             raise ConfigError("invalid value {0} "
252                               "for option {1}".format(str(value),
253                                                       self.opt._name))
254
255     def pop(self, key, force=False):
256         """the list value can be updated (poped)
257         only if the option is a master
258
259         :param key: index of the element to pop
260         :return: the requested element
261         """
262         if not force:
263             if self.opt.multitype == multitypes.slave:
264                 raise MultiTypeError("cannot append a value on a multi option {0}"
265                                      " which is a slave".format(self.opt._name))
266             elif self.opt.multitype == multitypes.master:
267                 for slave in self.opt.master_slaves:
268                     self.context.cfgimpl_get_values()[slave].pop(key, force=True)
269         self.context.cfgimpl_get_values().setitem(self.opt, self)
270         return super(Multi, self).pop(key)