coverage for tiramisu/option/masterslave.py
[tiramisu.git] / tiramisu / option / masterslave.py
1 # -*- coding: utf-8 -*-
2 "master slave support"
3 # Copyright (C) 2014 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 from ..storage import get_storages_option
26
27
28 StorageMasterSlaves = get_storages_option('masterslaves')
29
30
31 class MasterSlaves(object):
32     __slots__ = ('_p_')
33
34     def __init__(self, name, childs=None, validate=True, add=True):
35         if isinstance(name, StorageMasterSlaves):  # pragma: no cover
36             # only for sqlalchemy
37             self._p_ = name
38         else:
39             #if master (same name has group) is set
40             #for collect all slaves
41             slaves = []
42             if childs[0].impl_getname() == name:
43                 master = childs[0]
44             else:
45                 raise ValueError(_('master group with wrong'
46                                    ' master name for {0}'
47                                   ).format(name))
48             for child in childs[1:]:
49                 if child.impl_getdefault() != []:
50                     raise ValueError(_("not allowed default value for option {0} "
51                                        "in master/slave object {1}").format(child.impl_getname(),
52                                                                             name))
53                 slaves.append(child)
54             if validate:
55                 callback, callback_params = master.impl_get_callback()
56                 if callback is not None and callback_params != {}:
57                     for callbacks in callback_params.values():
58                         for callbk in callbacks:
59                             if isinstance(callbk, tuple):
60                                 if callbk[0] in slaves:
61                                     raise ValueError(_("callback of master's option shall "
62                                                        "not refered a slave's ones"))
63             #everything is ok, store references
64             self._p_ = StorageMasterSlaves(master, slaves)
65             if add:
66                 for child in childs:
67                     child._set_master_slaves(self)
68
69     def is_master(self, opt):
70         master = self._p_._sm_getmaster().impl_getname()
71         return opt.impl_getname() == master or (opt.impl_is_dynsymlinkoption() and
72                                       opt._opt.impl_getname() == master)
73
74     def getmaster(self, opt):
75         master = self._p_._sm_getmaster()
76         if opt.impl_is_dynsymlinkoption():
77             suffix = opt.impl_getsuffix()
78             name = master.impl_getname() + suffix
79             base_path = opt._dyn.split('.')[0] + '.'
80             path = base_path + name
81             master = master._impl_to_dyn(name, path)
82         return master
83
84     def getslaves(self, opt):
85         if opt.impl_is_dynsymlinkoption():
86             for slave in self._p_._sm_getslaves():
87                 suffix = opt.impl_getsuffix()
88                 name = slave.impl_getname() + suffix
89                 base_path = opt._dyn.split('.')[0] + '.'
90                 path = base_path + name
91                 yield slave._impl_to_dyn(name, path)
92         else:
93             for slave in self._p_._sm_getslaves():
94                 yield slave
95
96     def in_same_group(self, opt):
97         if opt.impl_is_dynsymlinkoption():
98             return opt._opt == self._p_._sm_getmaster() or opt._opt in self._p_._sm_getslaves()
99         else:
100             return opt == self._p_._sm_getmaster() or opt in self._p_._sm_getslaves()
101
102     def reset(self, opt, values, setting_properties):
103         for slave in self.getslaves(opt):
104             values.reset(slave, validate=False, _setting_properties=setting_properties)
105
106     def pop(self, opt, values, index):
107         for slave in self.getslaves(opt):
108             if not values.is_default_owner(slave, validate_properties=False,
109                                            validate_meta=False, index=index):
110                 multi = values._get_cached_value(slave, validate=False,
111                                                validate_properties=False,
112                                                )
113                 if isinstance(multi, Exception):
114                     raise multi
115                 multi.pop(index, force=True)
116
117     def getitem(self, values, opt, path, validate, force_permissive,
118                 trusted_cached_properties, validate_properties, session,
119                 slave_path=undefined, slave_value=undefined,
120                 setting_properties=undefined, self_properties=undefined, index=None):
121         if self.is_master(opt):
122             return self._getmaster(values, opt, path, validate,
123                                    force_permissive,
124                                    validate_properties, slave_path,
125                                    slave_value, self_properties, index,
126                                    setting_properties, session)
127         else:
128             return self._getslave(values, opt, path, validate,
129                                   force_permissive, trusted_cached_properties,
130                                   validate_properties, setting_properties,
131                                   self_properties, index,
132                                   session)
133
134     def _getmaster(self, values, opt, path, validate, force_permissive,
135                    validate_properties, c_slave_path,
136                    c_slave_value, self_properties, index,
137                    setting_properties, session):
138         value = values._get_cached_value(opt, path=path, validate=validate,
139                                          force_permissive=force_permissive,
140                                          validate_properties=validate_properties,
141                                          self_properties=self_properties,
142                                          from_masterslave=True, index=index,
143                                          setting_properties=setting_properties)
144         if isinstance(value, Exception):
145             return value
146         if index is None and validate is True:
147             masterlen = len(value)
148             for slave in self.getslaves(opt):
149                 slave_path = slave.impl_getpath(values._getcontext())
150                 slavelen = values._p_.get_max_length(slave_path, session)
151                 self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
152         return value
153
154     def _getslave(self, values, opt, path, validate, force_permissive,
155                   trusted_cached_properties, validate_properties, setting_properties,
156                   self_properties, index, session):
157         """
158         if master has length 0:
159             return []
160         if master has length bigger than 0:
161             if default owner:
162                 if has callback:
163                     if return a list:
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 value:
168                     list same length as master: return list
169                     list is smaller than master: return list + None
170                     list is greater than master: raise SlaveError
171                 if has default_multi value:
172                     return default_multi * master's length
173             if has value:
174                 list same length as master: return list
175                 list is smaller than master: return list + None
176                 list is greater than master: raise SlaveError
177         """
178         master = self.getmaster(opt)
179         context = values._getcontext()
180         masterp = master.impl_getpath(context)
181         masterlen = self.get_length(values, opt, session, validate, undefined,
182                                     undefined, force_permissive,
183                                     master=master)
184         if isinstance(masterlen, Exception):
185             if isinstance(masterlen, PropertiesOptionError):
186                 masterlen.set_orig_opt(opt)
187             return masterlen
188         master_is_meta = values._is_meta(master, masterp, session)
189         multi = values._get_multi(opt, path)
190         #if masterlen is [], test properties (has no value, don't get any value)
191         #if masterlen == 0:
192         if validate_properties:
193             props = context.cfgimpl_get_settings().validate_properties(opt, False,
194                                                                        False,
195                                                                        value=multi,
196                                                                        path=path,
197                                                                        force_permissive=force_permissive,
198                                                                        setting_properties=setting_properties)
199             if props:
200                 return props
201         #else:
202         if index is None:
203             indexes = range(0, masterlen)
204         else:
205             indexes = [index]
206         for idx in indexes:
207             value = values._get_cached_value(opt, path, validate,
208                                              force_permissive,
209                                              trusted_cached_properties,
210                                              validate_properties,
211                                              with_meta=master_is_meta,
212                                              index=idx,
213                                              # not self_properties,
214                                              # depends to index
215                                              #self_properties=self_properties,
216                                              setting_properties=setting_properties,
217                                              masterlen=masterlen,
218                                              from_masterslave=True)
219             if isinstance(value, Exception):
220                 if isinstance(value, PropertiesOptionError):
221                     err = value
222                     if index is None:
223                         multi.append_properties_error(value)
224                     else:
225                         multi = value
226                 else:
227                     return value
228             elif index is None:
229                 multi.append(value, setitem=False, force=True, validate=validate,
230                              force_permissive=force_permissive)
231             else:
232                 multi = value
233         return multi
234
235     def validate(self, values, opt, value, path, session):
236         if self.is_master(opt):
237             masterlen = len(value)
238             #for regen slave path
239             base_path = '.'.join(path.split('.')[:-1]) + '.'
240             for slave in self.getslaves(opt):
241                 slave_path = base_path + slave.impl_getname()
242                 slavelen = values._p_.get_max_length(slave_path, session)
243                 self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
244         else:
245             val_len = self.get_length(values, opt, session, slave_path=path)
246             if isinstance(val_len, Exception):
247                 return val_len
248             self.validate_slave_length(val_len,
249                                        len(value),
250                                        opt.impl_getname(), opt, setitem=True)
251
252     def get_length(self, values, opt, session, validate=True, slave_path=undefined,
253                    slave_value=undefined, force_permissive=False, master=None,
254                    masterp=None):
255         """get master len with slave option"""
256         if master is None:
257             master = self.getmaster(opt)
258         if masterp is None:
259             masterp = master.impl_getpath(values._getcontext())
260         if slave_value is undefined:
261             slave_path = undefined
262         value = self.getitem(values, master, masterp, validate,
263                              force_permissive, None, True, session, slave_path=slave_path,
264                              slave_value=slave_value)
265         if isinstance(value, Exception):
266             return value
267         return len(value)
268
269     def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False):
270         if valuelen > masterlen or (valuelen < masterlen and setitem):
271             if debug:  # pragma: no cover
272                 log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
273                           'setitem: {2}'.format(masterlen, valuelen, setitem))
274             raise SlaveError(_("invalid len for the slave: {0}"
275                                " which has {1} as master").format(
276                                    name, self.getmaster(opt).impl_getname()))