refactor validation
[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 tiramisu.i18n import _
23 from tiramisu.setting import log, undefined
24 from tiramisu.error import SlaveError, ConfigError
25 from .baseoption import DynSymLinkOption, SymLinkOption, Option
26
27
28 class MasterSlaves(object):
29     __slots__ = ('master', 'slaves')
30
31     def __init__(self, name, childs, validate=True):
32         #if master (same name has group) is set
33         #for collect all slaves
34         self.master = None
35         slaves = []
36         for child in childs:
37             if isinstance(child, SymLinkOption):  # pragma: optional cover
38                 raise ValueError(_("master group {0} shall not have "
39                                    "a symlinkoption").format(name))
40             if not isinstance(child, Option):  # pragma: optional cover
41                 raise ValueError(_("master group {0} shall not have "
42                                    "a subgroup").format(name))
43             if not child.impl_is_multi():  # pragma: optional cover
44                 raise ValueError(_("not allowed option {0} "
45                                    "in group {1}"
46                                    ": this option is not a multi"
47                                    "").format(child.impl_getname(), name))
48             if child.impl_getname() == name:
49                 self.master = child
50             else:
51                 slaves.append(child)
52         if self.master is None:  # pragma: optional cover
53             raise ValueError(_('master group with wrong'
54                                ' master name for {0}'
55                                ).format(name))
56         if validate:
57             callback, callback_params = self.master.impl_get_callback()
58             if callback is not None and callback_params != {}:  # pragma: optional cover
59                 for key, callbacks in callback_params.items():
60                     for callbk in callbacks:
61                         if isinstance(callbk, tuple):
62                             if callbk[0] in slaves:
63                                 raise ValueError(_("callback of master's option shall "
64                                                    "not refered a slave's ones"))
65         #everything is ok, store references
66         self.slaves = tuple(slaves)
67         for child in childs:
68             child._master_slaves = self
69
70     def is_master(self, opt):
71         return opt == self.master or (isinstance(opt, DynSymLinkOption) and
72                                       opt._opt == self.master)
73
74     def getmaster(self, opt):
75         if isinstance(opt, DynSymLinkOption):
76             suffix = opt.impl_getsuffix()
77             name = self.master.impl_getname() + suffix
78             base_path = opt._dyn.split('.')[0] + '.'
79             path = base_path + name
80             master = self.master._impl_to_dyn(name, path)
81         else:  # pragma: no dynoptiondescription cover
82             master = self.master
83         return master
84
85     def getslaves(self, opt):
86         if isinstance(opt, DynSymLinkOption):
87             for slave in self.slaves:
88                 suffix = opt.impl_getsuffix()
89                 name = slave.impl_getname() + suffix
90                 base_path = opt._dyn.split('.')[0] + '.'
91                 path = base_path + name
92                 yield slave._impl_to_dyn(name, path)
93         else:  # pragma: no dynoptiondescription cover
94             for slave in self.slaves:
95                 yield slave
96
97     def in_same_group(self, opt):
98         if isinstance(opt, DynSymLinkOption):
99             return opt._opt == self.master or opt._opt in self.slaves
100         else:  # pragma: no dynoptiondescription cover
101             return opt == self.master or opt in self.slaves
102
103     def reset(self, opt, values):
104         for slave in self.getslaves(opt):
105             values.reset(slave, validate=False)
106
107     def pop(self, opt, values, index):
108         for slave in self.getslaves(opt):
109             if not values.is_default_owner(slave, validate_properties=False,
110                                            validate_meta=False):
111                 values._get_cached_item(slave, validate=False,
112                                         validate_properties=False
113                                         ).pop(index, force=True)
114         pass
115
116     def getitem(self, values, opt, path, validate, force_permissive,
117                 force_properties, validate_properties, slave_path=undefined,
118                 slave_value=undefined, setting_properties=undefined):
119         if self.is_master(opt):
120             return self._getmaster(values, opt, path, validate,
121                                    force_permissive, force_properties,
122                                    validate_properties, slave_path,
123                                    slave_value, setting_properties)
124         else:
125             return self._getslave(values, opt, path, validate,
126                                   force_permissive, force_properties,
127                                   validate_properties, setting_properties)
128
129     def _getmaster(self, values, opt, path, validate, force_permissive,
130                    force_properties, validate_properties, c_slave_path,
131                    c_slave_value, setting_properties):
132         value = values._get_validated_value(opt, path, validate,
133                                             force_permissive,
134                                             force_properties,
135                                             validate_properties,
136                                             setting_properties=setting_properties)
137         if validate is True:
138             masterlen = len(value)
139             for slave in self.getslaves(opt):
140                 try:
141                     slave_path = slave.impl_getpath(values._getcontext())
142                     if c_slave_path == slave_path:
143                         slave_value = c_slave_value
144                     else:
145                         slave_value = values._get_validated_value(slave,
146                                                                   slave_path,
147                                                                   False,
148                                                                   False,
149                                                                   None, False,
150                                                                   None,
151                                                                   setting_properties=setting_properties)
152                     slavelen = len(slave_value)
153                     self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
154                 except ConfigError:  # pragma: optional cover
155                     pass
156         return value
157
158     def _getslave(self, values, opt, path, validate, force_permissive,
159                   force_properties, validate_properties, setting_properties):
160         """
161         if master has length 0:
162             return []
163         if master has length bigger than 0:
164             if default owner:
165                 if has callback:
166                     if return a list:
167                         list same length as master: return list
168                         list is smaller than master: return list + None
169                         list is greater than master: raise SlaveError
170                 if has default value:
171                     list same length as master: return list
172                     list is smaller than master: return list + None
173                     list is greater than master: raise SlaveError
174                 if has default_multi value:
175                     return default_multi * master's length
176             if has value:
177                 list same length as master: return list
178                 list is smaller than master: return list + None
179                 list is greater than master: raise SlaveError
180         """
181         master = self.getmaster(opt)
182         context = values._getcontext()
183         masterp = master.impl_getpath(context)
184         masterlen = self.get_length(values, opt, validate, undefined,
185                                     undefined, force_permissive,
186                                     master=master)
187         master_is_meta = values._is_meta(opt, masterp)
188         value = values._get_validated_value(opt, path, validate,
189                                             force_permissive,
190                                             force_properties,
191                                             False,
192                                             None,  # not undefined
193                                             with_meta=master_is_meta,
194                                             setting_properties=setting_properties)
195         #if slave, had values until master's one
196         path = opt.impl_getpath(context)
197         valuelen = len(value)
198         if validate:
199             self.validate_slave_length(masterlen, valuelen,
200                                        opt.impl_getname(), opt)
201         if valuelen < masterlen:
202             for num in range(0, masterlen - valuelen):
203                 index = valuelen + num
204                 value.append(values._get_validated_value(opt, path, True,
205                                                          False, None,
206                                                          validate_properties=False,
207                                                          with_meta=master_is_meta,
208                                                          index=index,
209                                                          setting_properties=setting_properties),
210                              setitem=False,
211                              force=True,
212                              validate=validate)
213         if validate_properties:
214             context.cfgimpl_get_settings().validate_properties(opt, False,
215                                                                False,
216                                                                value=value,
217                                                                path=path,
218                                                                force_permissive=force_permissive,
219                                                                force_properties=force_properties,
220                                                                self_properties=setting_properties)
221         return value
222
223     def setitem(self, values, opt, value, path):
224         if self.is_master(opt):
225             masterlen = len(value)
226             for slave in self.getslaves(opt):
227                 slave_path = slave.impl_getpath(values._getcontext())
228                 slave_value = values._get_validated_value(slave,
229                                                           slave_path,
230                                                           False,
231                                                           False,
232                                                           None, False,
233                                                           None)  # not undefined
234                 slavelen = len(slave_value)
235                 self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
236         else:
237             self.validate_slave_length(self.get_length(values, opt,
238                                                        slave_path=path), len(value),
239                                        opt.impl_getname(), opt, setitem=True)
240
241     def get_length(self, values, opt, validate=True, slave_path=undefined,
242                    slave_value=undefined, force_permissive=False, master=None,
243                    masterp=None):
244         """get master len with slave option"""
245         if master is None:
246             master = self.getmaster(opt)
247         if masterp is None:
248             masterp = master.impl_getpath(values._getcontext())
249         if slave_value is undefined:
250             slave_path = undefined
251         return len(self.getitem(values, master, masterp, validate,
252                                 force_permissive, None, True, slave_path,
253                                 slave_value))
254
255     def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False):
256         if valuelen > masterlen or (valuelen < masterlen and setitem):  # pragma: optional cover
257             log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
258                       'setitem: {2}'.format(masterlen, valuelen, setitem))
259             raise SlaveError(_("invalid len for the slave: {0}"
260                                " which has {1} as master").format(
261                                    name, self.getmaster(opt).impl_getname()))