add docstrings
[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 time import time
21 from tiramisu.error import ConfigError, SlaveError
22 from tiramisu.setting import owners, multitypes, expires_time
23 from tiramisu.autolib import carry_out_calculation
24 from tiramisu.i18n import _
25
26
27 class Values(object):
28     """`Config`'s root indeed is in charge of the `Option()`'s values,
29     and the values are physicaly located here. `Values` is also
30     responsible for a caching utility.
31     """
32     __slots__ = ('context', '_values', '_cache')
33
34     def __init__(self, context):
35         """
36         Initializes the values's dict.
37
38         :param context: the context is the home config's values
39
40         """
41
42         self.context = context
43         self._values = {}
44         self._cache = {}
45         super(Values, self).__init__()
46
47     def _get_default(self, opt):
48         meta = self.context.cfgimpl_get_meta()
49         if meta is not None:
50             return meta.cfgimpl_get_values()[opt]
51         else:
52             return opt.impl_getdefault()
53
54     def _get_value(self, opt, validate=True):
55         "return value or default value if not set"
56         #if no value
57         if opt not in self._values:
58             value = self._get_default(opt)
59             if opt.impl_is_multi():
60                 value = Multi(value, self.context, opt, validate)
61         else:
62             #if value
63             value = self._values[opt][1]
64         return value
65
66     def __delitem__(self, opt):
67         self._reset(opt)
68
69     def _reset(self, opt):
70         if opt in self._values:
71             setting = self.context.cfgimpl_get_settings()
72             opt.impl_validate(opt.impl_getdefault(), self.context,
73                               'validator' in setting)
74             self.context.cfgimpl_reset_cache()
75             del(self._values[opt])
76
77     def _is_empty(self, opt, value):
78         "convenience method to know if an option is empty"
79         empty = opt._empty
80         if (not opt.impl_is_multi() and (value is None or value == empty)) or \
81            (opt.impl_is_multi() and (value == [] or
82                                      None in value or empty in value)):
83             return True
84         return False
85
86     def _getcallback_value(self, opt):
87         callback, callback_params = opt._callback
88         if callback_params is None:
89             callback_params = {}
90         return carry_out_calculation(opt._name, config=self.context,
91                                      callback=callback,
92                                      callback_params=callback_params)
93
94     def __getitem__(self, opt):
95         return self.getitem(opt)
96
97     def get_modified_values(self):
98         return self._values
99
100     def getitem(self, opt, validate=True, force_permissive=False,
101                 force_properties=None, validate_properties=True):
102         if opt in self._cache:
103             exp = time()
104             value, created = self._cache[opt]
105             if exp < created:
106                 return value
107         val = self._getitem(opt, validate, force_permissive, force_properties,
108                             validate_properties)
109         if validate and validate_properties and force_permissive is False and \
110                 force_properties is None:
111             self._set_cache(opt, val)
112         return val
113
114     def _getitem(self, opt, validate, force_permissive, force_properties,
115                  validate_properties):
116         # options with callbacks
117         setting = self.context.cfgimpl_get_settings()
118         value = self._get_value(opt, validate)
119         is_frozen = 'frozen' in setting[opt]
120         if opt.impl_has_callback():
121             #if value is set and :
122             # - not frozen
123             # - frozen and not force_default_on_freeze
124             if not self.is_default_owner(opt) and (
125                     not is_frozen or (is_frozen and
126                                       not 'force_default_on_freeze' in setting[opt])):
127                 pass
128             else:
129                 value = self._getcallback_value(opt)
130                 if opt.impl_is_multi():
131                     value = Multi(value, self.context, opt, validate)
132                 #suppress value if already set
133                 self._reset(opt)
134         # frozen and force default
135         elif is_frozen and 'force_default_on_freeze' in setting[opt]:
136             value = self._get_default(opt)
137             if opt.impl_is_multi():
138                 value = Multi(value, self.context, opt, validate)
139         if validate:
140             opt.impl_validate(value, self.context, 'validator' in setting)
141         if self.is_default_owner(opt) and \
142                 'force_store_value' in setting[opt]:
143             self.setitem(opt, value, is_write=False)
144         if validate_properties:
145             setting.validate_properties(opt, False, False, value=value,
146                                         force_permissive=force_permissive,
147                                         force_properties=force_properties)
148         return value
149
150     def __setitem__(self, opt, value):
151         self.setitem(opt, value)
152
153     def setitem(self, opt, value, force_permissive=False, is_write=True):
154         #is_write is, for example, used with "force_store_value"
155         #user didn't change value, so not write
156         #valid opt
157         opt.impl_validate(value, self.context,
158                           'validator' in self.context.cfgimpl_get_settings())
159         if opt.impl_is_multi() and not isinstance(value, Multi):
160             value = Multi(value, self.context, opt)
161         self._setvalue(opt, value, force_permissive=force_permissive,
162                        is_write=is_write)
163
164     def _setvalue(self, opt, value, force_permissive=False, force_properties=None,
165                   is_write=True, validate_properties=True):
166         self.context.cfgimpl_reset_cache()
167         setting = self.context.cfgimpl_get_settings()
168         if validate_properties:
169             setting.validate_properties(opt, False, is_write,
170                                         value=value,
171                                         force_permissive=force_permissive,
172                                         force_properties=force_properties)
173         self._values[opt] = (setting.getowner(), value)
174
175     def getowner(self, opt):
176         owner = self._values.get(opt, (owners.default, None))[0]
177         meta = self.context.cfgimpl_get_meta()
178         if owner is owners.default and meta is not None:
179             owner = meta.cfgimpl_get_values().getowner(opt)
180         return owner
181
182     def setowner(self, opt, owner):
183         if opt not in self._values:
184             raise ConfigError(_('no value for {1} cannot change owner to {2}').format(opt))
185         if not isinstance(owner, owners.Owner):
186             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
187         self._values[opt] = (owner, self._values[opt][1])
188
189     def is_default_owner(self, opt):
190         """
191         :param config: *must* be only the **parent** config
192                        (not the toplevel config)
193         :return: boolean
194         """
195         return self.getowner(opt) == owners.default
196
197     def _set_cache(self, opt, val):
198         if 'expire' in self.context.cfgimpl_get_settings():
199             self._cache[opt] = (val, time() + expires_time)
200
201     def reset_cache(self, only_expired):
202         if only_expired:
203             exp = time()
204             keys = self._cache.keys()
205             for key in keys:
206                 val, created = self._cache[key]
207                 if exp > created:
208                     del(self._cache[key])
209         else:
210             self._cache.clear()
211
212     def __contains__(self, opt):
213         return opt in self._values
214
215 # ____________________________________________________________
216 # multi types
217
218
219 class Multi(list):
220     """multi options values container
221     that support item notation for the values of multi options"""
222     __slots__ = ('opt', 'context')
223
224     def __init__(self, value, context, opt, validate=True):
225         """
226         :param value: the Multi wraps a list value
227         :param context: the home config that has the values
228         :param opt: the option object that have this Multi value
229         """
230         self.opt = opt
231         self.context = context
232         if not isinstance(value, list):
233             value = [value]
234         if validate and self.opt.impl_get_multitype() == multitypes.slave:
235             value = self._valid_slave(value)
236         elif self.opt.impl_get_multitype() == multitypes.master:
237             self._valid_master(value)
238         super(Multi, self).__init__(value)
239
240     def _valid_slave(self, value):
241         #if slave, had values until master's one
242         masterp = self.context.cfgimpl_get_description().impl_get_path_by_opt(
243             self.opt.impl_get_master_slaves())
244         mastervalue = getattr(self.context, masterp)
245         masterlen = len(mastervalue)
246         if len(value) > masterlen or (len(value) < masterlen and
247                                       not self.context.cfgimpl_get_values().is_default_owner(self.opt)):
248             raise SlaveError(_("invalid len for the slave: {0}"
249                                " which has {1} as master").format(
250                                    self.opt._name, masterp))
251         elif len(value) < masterlen:
252             for num in range(0, masterlen - len(value)):
253                 value.append(self.opt.impl_getdefault_multi())
254         #else: same len so do nothing
255         return value
256
257     def _valid_master(self, value):
258         masterlen = len(value)
259         values = self.context.cfgimpl_get_values()
260         for slave in self.opt._master_slaves:
261             if not values.is_default_owner(slave):
262                 value_slave = values._get_value(slave)
263                 if len(value_slave) > masterlen:
264                     raise SlaveError(_("invalid len for the master: {0}"
265                                        " which has {1} as slave with"
266                                        " greater len").format(
267                                            self.opt._name, slave._name))
268                 elif len(value_slave) < masterlen:
269                     for num in range(0, masterlen - len(value_slave)):
270                         value_slave.append(slave.impl_getdefault_multi(), force=True)
271
272     def __setitem__(self, key, value):
273         self._validate(value)
274         #assume not checking mandatory property
275         self.context.cfgimpl_get_values()._setvalue(self.opt, self)
276         super(Multi, self).__setitem__(key, value)
277
278     def append(self, value, force=False):
279         """the list value can be updated (appened)
280         only if the option is a master
281         """
282         if not force:
283             if self.opt.impl_get_multitype() == multitypes.slave:
284                 raise SlaveError(_("cannot append a value on a multi option {0}"
285                                    " which is a slave").format(self.opt._name))
286             elif self.opt.impl_get_multitype() == multitypes.master:
287                 for slave in self.opt.impl_get_master_slaves():
288                     values = self.context.cfgimpl_get_values()
289                     if not values.is_default_owner(slave):
290                         #get multi without valid properties
291                         values.getitem(slave, validate_properties=False).append(
292                             slave.impl_getdefault_multi(),
293                             force=True)
294         self._validate(value)
295         #set value without valid properties
296         self.context.cfgimpl_get_values()._setvalue(self.opt, self, validate_properties=not force)
297         super(Multi, self).append(value)
298
299     def _validate(self, value):
300         if value is not None and not self.opt._validate(value):
301             raise ValueError(_("invalid value {0} "
302                              "for option {1}").format(str(value),
303                                                       self.opt._name))
304
305     def pop(self, key, force=False):
306         """the list value can be updated (poped)
307         only if the option is a master
308
309         :param key: index of the element to pop
310         :return: the requested element
311         """
312         if not force:
313             if self.opt.impl_get_multitype() == multitypes.slave:
314                 raise SlaveError(_("cannot pop a value on a multi option {0}"
315                                    " which is a slave").format(self.opt._name))
316             elif self.opt.impl_get_multitype() == multitypes.master:
317                 for slave in self.opt.impl_get_master_slaves():
318                     values = self.context.cfgimpl_get_values()
319                     if not values.is_default_owner(slave):
320                         #get multi without valid properties
321                         values.getitem(slave, validate_properties=False).pop(key, force=True)
322         #set value without valid properties
323         self.context.cfgimpl_get_values()._setvalue(self.opt, self, validate_properties=not force)
324         return super(Multi, self).pop(key)