e692458bf83a1b4737e81741bf7c9af5041bdc93
[tiramisu.git] / tiramisu / option / optiondescription.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
3 #
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Lesser General Public License as published by the
6 # Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
12 # details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # The original `Config` design model is unproudly borrowed from
18 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
21 from copy import copy
22
23 from tiramisu.i18n import _
24 from tiramisu.setting import groups, log
25 from .baseoption import BaseOption
26 from . import MasterSlaves
27 from tiramisu.error import ConfigError, ConflictError, ValueWarning
28 from tiramisu.storage import get_storages_option
29
30
31 StorageOptionDescription = get_storages_option('optiondescription')
32
33
34 class OptionDescription(BaseOption, StorageOptionDescription):
35     """Config's schema (organisation, group) and container of Options
36     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
37     """
38     __slots__ = tuple()
39
40     def __init__(self, name, doc, children, requires=None, properties=None):
41         """
42         :param children: a list of options (including optiondescriptions)
43
44         """
45         super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
46         child_names = [child.impl_getname() for child in children]
47         #better performance like this
48         valid_child = copy(child_names)
49         valid_child.sort()
50         old = None
51         for child in valid_child:
52             if child == old:
53                 raise ConflictError(_('duplicate option name: '
54                                       '{0}').format(child))
55             old = child
56         self._add_children(child_names, children)
57         self._cache_paths = None
58         self._cache_consistencies = None
59         # the group_type is useful for filtering OptionDescriptions in a config
60         self._group_type = groups.default
61         self._is_build_cache = False
62
63     def impl_getrequires(self):
64         return self._requires
65
66     def impl_getdoc(self):
67         return self.impl_get_information('doc')
68
69     def impl_validate(self, *args):
70         """usefull for OptionDescription"""
71         pass
72
73     def impl_getpaths(self, include_groups=False, _currpath=None):
74         """returns a list of all paths in self, recursively
75            _currpath should not be provided (helps with recursion)
76         """
77         if _currpath is None:
78             _currpath = []
79         paths = []
80         for option in self.impl_getchildren():
81             attr = option.impl_getname()
82             if isinstance(option, OptionDescription):
83                 if include_groups:
84                     paths.append('.'.join(_currpath + [attr]))
85                 paths += option.impl_getpaths(include_groups=include_groups,
86                                               _currpath=_currpath + [attr])
87             else:
88                 paths.append('.'.join(_currpath + [attr]))
89         return paths
90
91     def impl_build_cache_consistency(self, _consistencies=None, cache_option=None):
92         #FIXME cache_option !
93         if _consistencies is None:
94             init = True
95             _consistencies = {}
96             cache_option = []
97         else:
98             init = False
99         for option in self.impl_getchildren():
100             cache_option.append(option._get_id())
101             if not isinstance(option, OptionDescription):
102                 for func, all_cons_opts, params in option._get_consistencies():
103                     for opt in all_cons_opts:
104                         _consistencies.setdefault(opt,
105                                                   []).append((func,
106                                                              all_cons_opts,
107                                                              params))
108             else:
109                 option.impl_build_cache_consistency(_consistencies, cache_option)
110         if init and _consistencies != {}:
111             self._cache_consistencies = {}
112             for opt, cons in _consistencies.items():
113                 #FIXME dans le cache ...
114                 if opt._get_id() not in cache_option:
115                     raise ConfigError(_('consistency with option {0} '
116                                         'which is not in Config').format(
117                                             opt.impl_getname()))
118                 self._cache_consistencies[opt] = tuple(cons)
119
120     def impl_validate_options(self, cache_option=None):
121         """validate duplicate option and set option has readonly option
122         """
123         if cache_option is None:
124             init = True
125             cache_option = []
126         else:
127             init = False
128         for option in self.impl_getchildren():
129             #FIXME specifique id for sqlalchemy?
130             #FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs diffĂ©rentes)
131             #if option.id is None:
132             #    raise SystemError(_("an option's id should not be None "
133             #                        "for {0}").format(option.impl_getname()))
134             if option._get_id() in cache_option:
135                 raise ConflictError(_('duplicate option: {0}').format(option))
136             cache_option.append(option._get_id())
137             option._readonly = True
138             if isinstance(option, OptionDescription):
139                 option.impl_validate_options(cache_option)
140         if init:
141             self._readonly = True
142
143     # ____________________________________________________________
144     def impl_set_group_type(self, group_type):
145         """sets a given group object to an OptionDescription
146
147         :param group_type: an instance of `GroupType` or `MasterGroupType`
148                               that lives in `setting.groups`
149         """
150         if self._group_type != groups.default:
151             raise TypeError(_('cannot change group_type if already set '
152                             '(old {0}, new {1})').format(self._group_type,
153                                                          group_type))
154         if isinstance(group_type, groups.GroupType):
155             self._group_type = group_type
156             if isinstance(group_type, groups.MasterGroupType):
157                 MasterSlaves(self.impl_getname(), self.impl_getchildren())
158         else:
159             raise ValueError(_('group_type: {0}'
160                                ' not allowed').format(group_type))
161
162     def impl_get_group_type(self):
163         return self._group_type
164
165     def _valid_consistency(self, option, value, context, index):
166         if self._cache_consistencies is None:
167             return True
168         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
169         consistencies = self._cache_consistencies.get(option)
170         if consistencies is not None:
171             for func, all_cons_opts, params in consistencies:
172                 warnings_only = params.get('warnings_only', False)
173                 #all_cons_opts[0] is the option where func is set
174                 try:
175                     all_cons_opts[0]._launch_consistency(func, option,
176                                                          value,
177                                                          context, index,
178                                                          all_cons_opts,
179                                                          warnings_only)
180                 except ValueError as err:
181                     if warnings_only:
182                         raise ValueWarning(err.message, option)
183                     else:
184                         raise err
185
186     def _impl_getstate(self, descr=None):
187         """enables us to export into a dict
188         :param descr: parent :class:`tiramisu.option.OptionDescription`
189         """
190         if descr is None:
191             self.impl_build_cache()
192             descr = self
193         super(OptionDescription, self)._impl_getstate(descr)
194         self._state_group_type = str(self._group_type)
195         for option in self.impl_getchildren():
196             option._impl_getstate(descr)
197
198     def __getstate__(self):
199         """special method to enable the serialization with pickle
200         """
201         stated = True
202         try:
203             # the `_state` attribute is a flag that which tells us if
204             # the serialization can be performed
205             self._stated
206         except AttributeError:
207             # if cannot delete, _impl_getstate never launch
208             # launch it recursivement
209             # _stated prevent __getstate__ launch more than one time
210             # _stated is delete, if re-serialize, re-lauch _impl_getstate
211             self._impl_getstate()
212             stated = False
213         return super(OptionDescription, self).__getstate__(stated)
214
215     def _impl_setstate(self, descr=None):
216         """enables us to import from a dict
217         :param descr: parent :class:`tiramisu.option.OptionDescription`
218         """
219         if descr is None:
220             self._cache_paths = None
221             self._cache_consistencies = None
222             self.impl_build_cache(force_no_consistencies=True)
223             descr = self
224         self._group_type = getattr(groups, self._state_group_type)
225         del(self._state_group_type)
226         super(OptionDescription, self)._impl_setstate(descr)
227         for option in self.impl_getchildren():
228             option._impl_setstate(descr)
229
230     def __setstate__(self, state):
231         super(OptionDescription, self).__setstate__(state)
232         try:
233             self._stated
234         except AttributeError:
235             self._impl_setstate()