separate baseoption and option
[tiramisu.git] / tiramisu / option / masterslave.py
1 # -*- coding: utf-8 -*-
2 "master slave support"
3 # Copyright (C) 2014-2017 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 # The original `Config` design model is unproudly borrowed from
19 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
20 # the whole pypy projet is under MIT licence
21 # ____________________________________________________________
22 from ..i18n import _
23 from ..setting import log, undefined, debug
24 from ..error import SlaveError, PropertiesOptionError
25
26
27 class MasterSlaves(object):
28     __slots__ = ('master', 'slaves')
29
30     def __init__(self, name, childs=None, validate=True, add=True):
31         #if master (same name has group) is set
32         #for collect all slaves
33         slaves = []
34         if childs[0].impl_getname() == name:
35             master = childs[0]
36         else:
37             raise ValueError(_('master group with wrong'
38                                ' master name for {0}'
39                               ).format(name))
40         for child in childs[1:]:
41             if child.impl_getdefault() != []:
42                 raise ValueError(_("not allowed default value for option {0} "
43                                    "in master/slave object {1}").format(child.impl_getname(),
44                                                                         name))
45             slaves.append(child)
46         if validate:
47             callback, callback_params = master.impl_get_callback()
48             if callback is not None and callback_params != {}:
49                 for callbacks in callback_params.values():
50                     for callbk in callbacks:
51                         if isinstance(callbk, tuple):
52                             if callbk[0] in slaves:
53                                 raise ValueError(_("callback of master's option shall "
54                                                    "not refered a slave's ones"))
55         #everything is ok, store references
56         self.master = master
57         self.slaves = tuple(slaves)
58         if add:
59             for child in childs:
60                 child._master_slaves = self
61
62     def is_master(self, opt):
63         master = self.master.impl_getname()
64         return opt.impl_getname() == master or (opt.impl_is_dynsymlinkoption() and
65                                       opt._opt.impl_getname() == master)
66
67     def getmaster(self, opt):
68         master = self.master
69         if opt.impl_is_dynsymlinkoption():
70             suffix = opt.impl_getsuffix()
71             name = master.impl_getname() + suffix
72             base_path = opt._dyn.split('.')[0] + '.'
73             path = base_path + name
74             master = master._impl_to_dyn(name, path)
75         return master
76
77     def getslaves(self, opt):
78         if opt.impl_is_dynsymlinkoption():
79             for slave in self.slaves:
80                 suffix = opt.impl_getsuffix()
81                 name = slave.impl_getname() + suffix
82                 base_path = opt._dyn.split('.')[0] + '.'
83                 path = base_path + name
84                 yield slave._impl_to_dyn(name, path)
85         else:
86             for slave in self.slaves:
87                 yield slave
88
89     def in_same_group(self, opt):
90         if opt.impl_is_dynsymlinkoption():
91             return opt._opt == self.master or opt._opt in self.slaves
92         else:
93             return opt == self.master or opt in self.slaves
94
95     def reset(self, opt, values, setting_properties, _commit=True):
96         for slave in self.getslaves(opt):
97             values.reset(slave, validate=False, _setting_properties=setting_properties,
98                          _commit=_commit)
99
100     def pop(self, opt, values, index):
101         for slave in self.getslaves(opt):
102             if not values.is_default_owner(slave, validate_properties=False,
103                                            validate_meta=False, index=index):
104                 multi = values._get_cached_value(slave, validate=False,
105                                                validate_properties=False,
106                                                )
107                 if isinstance(multi, Exception):
108                     raise multi
109                 multi.pop(index, force=True)
110
111     def getitem(self, values, opt, path, validate, force_permissive,
112                 trusted_cached_properties, validate_properties, session,
113                 slave_path=undefined, slave_value=undefined,
114                 setting_properties=undefined, self_properties=undefined, index=None,
115                 check_frozen=False):
116         if self.is_master(opt):
117             return self._getmaster(values, opt, path, validate,
118                                    force_permissive,
119                                    validate_properties, slave_path,
120                                    slave_value, self_properties, index,
121                                    setting_properties, session, check_frozen)
122         else:
123             return self._getslave(values, opt, path, validate,
124                                   force_permissive, trusted_cached_properties,
125                                   validate_properties, setting_properties,
126                                   self_properties, index,
127                                   session, check_frozen)
128
129     def _getmaster(self, values, opt, path, validate, force_permissive,
130                    validate_properties, c_slave_path,
131                    c_slave_value, self_properties, index,
132                    setting_properties, session, check_frozen):
133         value = values._get_cached_value(opt, path=path, validate=validate,
134                                          force_permissive=force_permissive,
135                                          validate_properties=validate_properties,
136                                          self_properties=self_properties,
137                                          from_masterslave=True, index=index,
138                                          setting_properties=setting_properties,
139                                          check_frozen=check_frozen)
140         if isinstance(value, Exception):
141             return value
142         if index is None and validate is True:
143             masterlen = len(value)
144             for slave in self.getslaves(opt):
145                 slave_path = slave.impl_getpath(values._getcontext())
146                 slavelen = values._p_.get_max_length(slave_path, session)
147                 self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
148         return value
149
150     def _getslave(self, values, opt, path, validate, force_permissive,
151                   trusted_cached_properties, validate_properties, setting_properties,
152                   self_properties, index, session, check_frozen):
153         """
154         if master has length 0:
155             return []
156         if master has length bigger than 0:
157             if default owner:
158                 if has callback:
159                     if return a list:
160                         list same length as master: return list
161                         list is smaller than master: return list + None
162                         list is greater than master: raise SlaveError
163                 if has default value:
164                     list same length as master: return list
165                     list is smaller than master: return list + None
166                     list is greater than master: raise SlaveError
167                 if has default_multi value:
168                     return default_multi * master's length
169             if has value:
170                 list same length as master: return list
171                 list is smaller than master: return list + None
172                 list is greater than master: raise SlaveError
173         """
174         master = self.getmaster(opt)
175         context = values._getcontext()
176         masterp = master.impl_getpath(context)
177         masterlen = self.get_length(values, opt, session, validate, undefined,
178                                     undefined, force_permissive,
179                                     master=master, setting_properties=setting_properties)
180         if isinstance(masterlen, Exception):
181             if isinstance(masterlen, PropertiesOptionError):
182                 masterlen.set_orig_opt(opt)
183             return masterlen
184         master_is_meta = values._is_meta(master, masterp, session)
185         multi = values._get_multi(opt, path)
186         #if masterlen is [], test properties (has no value, don't get any value)
187         #if masterlen == 0:
188         if validate_properties:
189             props = context.cfgimpl_get_settings().validate_properties(opt, False,
190                                                                        check_frozen,
191                                                                        value=multi,
192                                                                        path=path,
193                                                                        force_permissive=force_permissive,
194                                                                        setting_properties=setting_properties)
195             if props:
196                 return props
197         #else:
198         if index is None:
199             indexes = range(0, masterlen)
200         else:
201             indexes = [index]
202         for idx in indexes:
203             value = values._get_cached_value(opt, path, validate,
204                                              force_permissive,
205                                              trusted_cached_properties,
206                                              validate_properties,
207                                              with_meta=master_is_meta,
208                                              index=idx,
209                                              # not self_properties,
210                                              # depends to index
211                                              #self_properties=self_properties,
212                                              setting_properties=setting_properties,
213                                              masterlen=masterlen,
214                                              from_masterslave=True,
215                                              check_frozen=check_frozen)
216             if isinstance(value, Exception):
217                 if isinstance(value, PropertiesOptionError):
218                     err = value
219                     if index is None:
220                         multi.append_properties_error(value)
221                     else:
222                         multi = value
223                 else:
224                     return value
225             elif index is None:
226                 multi.append(value, setitem=False, force=True, validate=False,
227                              force_permissive=force_permissive)
228             else:
229                 multi = value
230         return multi
231
232     def validate(self, values, opt, index, path, session, setitem):
233         if self.is_master(opt):
234             #for regen slave path
235             base_path = '.'.join(path.split('.')[:-1]) + '.'
236             for slave in self.getslaves(opt):
237                 slave_path = base_path + slave.impl_getname()
238                 slavelen = values._p_.get_max_length(slave_path, session)
239                 self.validate_slave_length(index, slavelen, slave.impl_getname(), opt)
240         else:
241             val_len = self.get_length(values, opt, session, slave_path=path)
242             if isinstance(val_len, Exception):
243                 return val_len
244             self.validate_slave_length(val_len, index,
245                                        opt.impl_getname(), opt, setitem=setitem)
246
247     def get_length(self, values, opt, session, validate=True, slave_path=undefined,
248                    slave_value=undefined, force_permissive=False, master=None,
249                    masterp=None, setting_properties=undefined):
250         """get master len with slave option"""
251         if master is None:
252             master = self.getmaster(opt)
253         if masterp is None:
254             masterp = master.impl_getpath(values._getcontext())
255         if slave_value is undefined:
256             slave_path = undefined
257         value = self.getitem(values, master, masterp, validate,
258                              force_permissive, None, True, session, slave_path=slave_path,
259                              slave_value=slave_value, setting_properties=setting_properties)
260         if isinstance(value, Exception):
261             return value
262         return len(value)
263
264     def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False):
265         if valuelen > masterlen or (valuelen < masterlen and setitem):
266             if debug:  # pragma: no cover
267                 log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
268                           'setitem: {2}'.format(masterlen, valuelen, setitem))
269             raise SlaveError(_("invalid len for the slave: {0}"
270                                " which has {1} as master").format(
271                                    name, self.getmaster(opt).impl_getname()))
272
273     def reset_cache(self, opt, values, type_):
274         for slave in self.getslaves(opt):
275             slave_path = slave.impl_getpath(values._getcontext())
276             values._p_.delcache(slave_path)