separate baseoption and option
[tiramisu.git] / tiramisu / config.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2012-2017 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 "options handler global entry point"
22 import weakref
23 import sys
24 from time import time
25
26
27 from .error import PropertiesOptionError, ConfigError, ConflictError
28 from .option import OptionDescription, Option, SymLinkOption, \
29     DynSymLinkOption, SynDynOptionDescription
30 from .option.baseoption import valid_name
31 from .setting import groups, Settings, default_encoding, undefined
32 from .storage import get_storages, set_storage, get_default_values_storages
33 from .value import Values, Multi
34 from .i18n import _
35
36
37 if sys.version_info[0] >= 3:  # pragma: no cover
38     xrange = range
39
40
41 class SubConfig(object):
42     """Sub configuration management entry.
43     Tree if OptionDescription's responsability. SubConfig are generated
44     on-demand. A Config is also a SubConfig.
45     Root Config is call context below
46     """
47     __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
48
49     def __init__(self, descr, context, subpath=None):
50         """ Configuration option management master class
51
52         :param descr: describes the configuration schema
53         :type descr: an instance of ``option.OptionDescription``
54         :param context: the current root config
55         :type context: `Config`
56         :type subpath: `str` with the path name
57         """
58         # main option description
59         error = False
60         if descr is not None and not isinstance(descr, OptionDescription) and \
61                 not isinstance(descr, SynDynOptionDescription):  # pragma: optional cover
62             error = True
63         if error:
64             raise TypeError(_('descr must be an optiondescription, not {0}'
65                               ).format(type(descr)))
66         self._impl_descr = descr
67         # sub option descriptions
68         if not isinstance(context, weakref.ReferenceType):  # pragma: optional cover
69             raise ValueError('context must be a Weakref')
70         self._impl_context = context
71         self._impl_path = subpath
72
73     def cfgimpl_reset_cache(self,
74                             only_expired=False,
75                             only=('values', 'properties', 'permissives', 'settings'),
76                             opt=None,
77                             path=None):
78         """reset all settings in cache
79
80         :param only_expired: if True reset only expired cached values
81         :type only_expired: boolean
82         """
83         context = self._cfgimpl_get_context()
84         if 'values' in only:
85             values = context.cfgimpl_get_values()
86         if 'settings' in only or 'properties' in only or 'permissives' in only:
87             settings = context.cfgimpl_get_settings()
88         if only_expired:
89             if 'values' in only:
90                 values._p_.reset_expired_cache(int(time()))
91             if 'settings' in only or 'properties' in only:
92                 settings._p_.reset_expired_cache(int(time()))
93             if 'settings' in only or 'permissives' in only:
94                 settings._pp_.reset_expired_cache(int(time()))
95         elif not None in (opt, path):
96             if opt.__class__.__name__ == 'DynOptionDescription':
97                 descr = context.cfgimpl_get_description()
98                 spath = path.split('.')
99                 subpath = '.'.join(spath[:-1])
100                 dynopt = getattr(descr, subpath)._getattr(spath[-1], context=context,
101                                                           dyn=False)
102                 for suffix in dynopt._impl_get_suffixes(context):
103                     path = subpath + '.' + spath[-1] + suffix
104                     if 'values' in only:
105                         values._p_.delcache(path)
106                     if 'settings' in only or 'properties' in only:
107                         settings._p_.delcache(path)
108                     if 'settings' in only or 'permissives' in only:
109                         settings._pp_.delcache(path)
110             elif not isinstance(opt, DynSymLinkOption) and opt._is_subdyn():
111                 descr = context.cfgimpl_get_description()
112                 spath = path.split('.')
113                 try:
114                     subpath = '.'.join(spath[:-2])
115                     dynsubopt = getattr(descr, subpath)
116                     spath1 = spath[-2]
117                     spath2 = spath[-1]
118                     spath3 = None
119                 except AttributeError:
120                     subpath = '.'.join(spath[:-3])
121                     dynsubopt = getattr(descr, subpath)
122                     spath1 = spath[-3]
123                     spath2 = spath[-2]
124                     spath3 = spath[-1]
125                 dynopt = dynsubopt._getattr(spath1, context=context, dyn=False)
126                 for suffix in dynopt._impl_get_suffixes(context):
127                     path = subpath + '.' + spath1 + suffix + '.' + spath2 + suffix
128                     if spath3:
129                         path += '.' + spath3 + suffix
130                     if 'values' in only:
131                         values._p_.delcache(path)
132                     if 'settings' in only or 'properties' in only:
133                         settings._p_.delcache(path)
134                     if 'settings' in only or 'permissives' in only:
135                         settings._pp_.delcache(path)
136             else:
137                 if 'values' in only:
138                     values._p_.delcache(path)
139                 if 'settings' in only or 'permissives' in only:
140                     settings._p_.delcache(path)
141                 if 'settings' in only or 'permissives' in only:
142                     settings._pp_.delcache(path)
143             for option in getattr(opt, '_dependencies', []):
144                 if 'values' in only:
145                     option.reset_cache(opt, values, 'values')
146                 if 'settings' in only:
147                     option.reset_cache(opt, settings, 'settings')
148                 else:
149                     if 'properties' in only:
150                         option.reset_cache(opt, settings, 'properties')
151                     if 'permissives' in only:
152                         option.reset_cache(opt, settings, 'permissives')
153         else:
154             if 'values' in only:
155                 values._p_.reset_all_cache()
156             if 'settings' in only:
157                 settings._p_.reset_all_cache()
158                 settings._pp_.reset_all_cache()
159
160     def cfgimpl_get_home_by_path(self, path, force_permissive=False,
161                                  returns_raise=False, _setting_properties=undefined):
162         """:returns: tuple (config, name)"""
163         path = path.split('.')
164         for step in path[:-1]:
165             self = self.getattr(step,
166                                 force_permissive=force_permissive,
167                                 returns_raise=returns_raise,
168                                 _setting_properties=_setting_properties)
169             if isinstance(self, Exception):
170                 return self, None
171         return self, path[-1]
172
173     # ______________________________________________________________________
174     def __iter__(self, force_permissive=False):
175         """Pythonesque way of parsing group's ordered options.
176         iteration only on Options (not OptionDescriptions)"""
177         for child in self.cfgimpl_get_description()._impl_getchildren(
178                 context=self._cfgimpl_get_context()):
179             if not child.impl_is_optiondescription():
180                 try:
181                     name = child.impl_getname()
182                     yield name, self.getattr(name,
183                                              force_permissive=force_permissive)
184                 except GeneratorExit:  # pragma: optional cover
185                     if sys.version_info[0] < 3:
186                         raise StopIteration
187                     else:
188                         raise GeneratorExit()
189                 except PropertiesOptionError:  # pragma: optional cover
190                     pass  # option with properties
191
192     def iter_all(self, force_permissive=False):
193         """A way of parsing options **and** groups.
194         iteration on Options and OptionDescriptions."""
195         for child in self.cfgimpl_get_description().impl_getchildren():
196             try:
197                 yield child.impl_getname(), self.getattr(child.impl_getname(),
198                                                          force_permissive=force_permissive)
199             except GeneratorExit:  # pragma: optional cover
200                 if sys.version_info[0] < 3:
201                     raise StopIteration
202                 else:
203                     raise GeneratorExit()
204             except PropertiesOptionError:  # pragma: optional cover
205                 pass  # option with properties
206
207     def iter_groups(self, group_type=None, force_permissive=False):
208         """iteration on groups objects only.
209         All groups are returned if `group_type` is `None`, otherwise the groups
210         can be filtered by categories (families, or whatever).
211
212         :param group_type: if defined, is an instance of `groups.GroupType`
213                            or `groups.MasterGroupType` that lives in
214                            `setting.groups`
215         """
216         if group_type is not None and not isinstance(group_type,
217                                                      groups.GroupType):  # pragma: optional cover
218             raise TypeError(_("unknown group_type: {0}").format(group_type))
219         for child in self.cfgimpl_get_description()._impl_getchildren(
220                 context=self._cfgimpl_get_context()):
221             if child.impl_is_optiondescription():
222                 try:
223                     if group_type is None or (group_type is not None and
224                                               child.impl_get_group_type()
225                                               == group_type):
226                         name = child.impl_getname()
227                         yield name, self.getattr(name, force_permissive=force_permissive)
228                 except GeneratorExit:  # pragma: optional cover
229                     if sys.version_info[0] < 3:
230                         raise StopIteration
231                     else:
232                         raise GeneratorExit()
233                 except PropertiesOptionError:  # pragma: optional cover
234                     pass
235     # ______________________________________________________________________
236
237     def __str__(self):
238         "Config's string representation"
239         lines = []
240         for name, grp in self.iter_groups():
241             lines.append("[{0}]".format(name))
242         for name, value in self:
243             try:
244                 lines.append("{0} = {1}".format(name, value))
245             except UnicodeEncodeError:  # pragma: optional cover
246                 lines.append("{0} = {1}".format(name,
247                                                 value.encode(default_encoding)))
248         return '\n'.join(lines)
249
250     __repr__ = __str__
251
252     def _cfgimpl_get_context(self):
253         """context could be None, we need to test it
254         context is None only if all reference to `Config` object is deleted
255         (for example we delete a `Config` and we manipulate a reference to
256         old `SubConfig`, `Values`, `Multi` or `Settings`)
257         """
258         context = self._impl_context()
259         if context is None:  # pragma: optional cover
260             raise ConfigError(_('the context does not exist anymore'))
261         return context
262
263     def cfgimpl_get_context(self):
264         return self._cfgimpl_get_context()
265
266     def cfgimpl_get_description(self):
267         if self._impl_descr is None:  # pragma: optional cover
268             raise ConfigError(_('no option description found for this config'
269                                 ' (may be GroupConfig)'))
270         else:
271             return self._impl_descr
272
273     def cfgimpl_get_settings(self):
274         return self._cfgimpl_get_context()._impl_settings
275
276     def cfgimpl_get_values(self):
277         return self._cfgimpl_get_context()._impl_values
278
279     # ____________________________________________________________
280     # attribute methods
281     def __setattr__(self, name, value):
282         "attribute notation mechanism for the setting of the value of an option"
283         self.setattr(name, value)
284
285     def _setattr(self, name, value, force_permissive=False, not_raises=False):
286         """use setattr instead of _setattr
287         """
288         self.setattr(name, value, force_permissive=force_permissive,
289                      not_raises=not_raises)
290
291     def setattr(self, name, value, force_permissive=False, not_raises=False, index=None,
292                 _setting_properties=undefined, _commit=True):
293         if name.startswith('_impl_'):
294             return object.__setattr__(self, name, value)
295         context = self._cfgimpl_get_context()
296         if _setting_properties is undefined:
297             _setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=True)
298         if '.' in name:  # pragma: optional cover
299             homeconfig, name = self.cfgimpl_get_home_by_path(name,
300                                                              force_permissive=force_permissive,
301                                                              _setting_properties=_setting_properties)
302             return homeconfig.setattr(name, value, force_permissive,
303                                       not_raises, index=index,
304                                       _setting_properties=_setting_properties,
305                                       _commit=_commit)
306         child = self.cfgimpl_get_description().__getattr__(name,
307                                                            context=context)
308         if isinstance(child, OptionDescription) or isinstance(child, SynDynOptionDescription):
309             raise TypeError(_("can't assign to an OptionDescription"))  # pragma: optional cover
310         elif child._is_symlinkoption() and \
311                 not isinstance(child, DynSymLinkOption):  # pragma: no dynoptiondescription cover
312             path = context.cfgimpl_get_description().impl_get_path_by_opt(
313                 child._impl_getopt())
314             context.setattr(path, value, force_permissive, not_raises, index=index,
315                             _setting_properties=_setting_properties,
316                             _commit=_commit)
317         else:
318             subpath = self._get_subpath(name)
319             ret = self.cfgimpl_get_values().setitem(child, value, subpath,
320                                                     force_permissive=force_permissive,
321                                                     not_raises=not_raises, index=index,
322                                                     _setting_properties=_setting_properties,
323                                                     _commit=_commit)
324             if ret is not None:
325                 return ret
326
327     def __delattr__(self, name):
328         context = self._cfgimpl_get_context()
329         child = self.cfgimpl_get_description().__getattr__(name, context)
330         self.cfgimpl_get_values().__delitem__(child)
331
332     def __getattr__(self, name):
333         return self.getattr(name)
334
335     def _getattr(self, name, force_permissive=False, validate=True):  # pragma: optional cover
336         """use getattr instead of _getattr
337         """
338         return self.getattr(name, force_permissive, validate)
339
340     def _get_subpath(self, name):
341         if self._impl_path is None:
342             subpath = name
343         else:
344             subpath = self._impl_path + '.' + name
345         return subpath
346
347     def getattr(self, name, force_permissive=False, validate=True,
348                 _setting_properties=undefined, _self_properties=undefined, index=None,
349                 returns_raise=False):
350         """
351         attribute notation mechanism for accessing the value of an option
352         :param name: attribute name
353         :return: option's value if name is an option name, OptionDescription
354                  otherwise
355         """
356         # attribute access by passing a path,
357         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
358         context = self._cfgimpl_get_context()
359         if _setting_properties is undefined:
360             _setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=True)
361         if '.' in name:
362             homeconfig, name = self.cfgimpl_get_home_by_path(
363                 name, force_permissive=force_permissive,
364                 returns_raise=returns_raise, _setting_properties=_setting_properties)
365             if isinstance(homeconfig, Exception):
366                 cfg = homeconfig
367             else:
368                 cfg = homeconfig.getattr(name, force_permissive=force_permissive,
369                                          validate=validate,
370                                          _setting_properties=_setting_properties,
371                                          _self_properties=_self_properties,
372                                          index=index, returns_raise=returns_raise)
373         else:
374             option = self.cfgimpl_get_description().__getattr__(name,
375                                                                 context=context)
376             subpath = self._get_subpath(name)
377             if isinstance(option, DynSymLinkOption):
378                 cfg = self.cfgimpl_get_values()._get_cached_value(
379                     option, path=subpath,
380                     validate=validate,
381                     force_permissive=force_permissive,
382                     setting_properties=_setting_properties,
383                     self_properties=_self_properties,
384                     index=index)
385             elif option._is_symlinkoption():  # pragma: no dynoptiondescription cover
386                 path = context.cfgimpl_get_description().impl_get_path_by_opt(
387                     option._impl_getopt())
388                 cfg = context.getattr(path, validate=validate,
389                                       force_permissive=force_permissive,
390                                       _setting_properties=_setting_properties,
391                                       _self_properties=_self_properties,
392                                       index=index, returns_raise=True)
393             elif option.impl_is_optiondescription():
394                 props = self.cfgimpl_get_settings().validate_properties(
395                     option, True, False, path=subpath,
396                     force_permissive=force_permissive,
397                     self_properties=_self_properties,
398                     setting_properties=_setting_properties)
399                 if props:
400                     if returns_raise:
401                         return props
402                     else:
403                         raise props
404                 return SubConfig(option, self._impl_context, subpath)
405             else:
406                 cfg = self.cfgimpl_get_values()._get_cached_value(
407                     option, path=subpath,
408                     validate=validate,
409                     force_permissive=force_permissive,
410                     setting_properties=_setting_properties,
411                     self_properties=_self_properties,
412                     index=index)
413         if not returns_raise and isinstance(cfg, Exception):
414             raise cfg
415         return cfg
416
417     def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
418              check_properties=True, force_permissive=False):
419         """
420             finds a list of options recursively in the config
421
422             :param bytype: Option class (BoolOption, StrOption, ...)
423             :param byname: filter by Option.impl_getname()
424             :param byvalue: filter by the option's value
425             :returns: list of matching Option objects
426         """
427         return self._cfgimpl_get_context()._find(bytype, byname, byvalue,
428                                                  first=False,
429                                                  type_=type_,
430                                                  _subpath=self.cfgimpl_get_path(False),
431                                                  check_properties=check_properties,
432                                                  force_permissive=force_permissive)
433
434     def find_first(self, bytype=None, byname=None, byvalue=undefined,
435                    type_='option', raise_if_not_found=True, check_properties=True,
436                    force_permissive=False):
437         """
438             finds an option recursively in the config
439
440             :param bytype: Option class (BoolOption, StrOption, ...)
441             :param byname: filter by Option.impl_getname()
442             :param byvalue: filter by the option's value
443             :returns: list of matching Option objects
444         """
445         return self._cfgimpl_get_context()._find(
446             bytype, byname, byvalue, first=True, type_=type_,
447             _subpath=self.cfgimpl_get_path(False), raise_if_not_found=raise_if_not_found,
448             check_properties=check_properties,
449             force_permissive=force_permissive)
450
451     def _find(self, bytype, byname, byvalue, first, type_='option',
452               _subpath=None, check_properties=True, raise_if_not_found=True,
453               force_permissive=False, only_path=undefined,
454               only_option=undefined, setting_properties=undefined):
455         """
456         convenience method for finding an option that lives only in the subtree
457
458         :param first: return only one option if True, a list otherwise
459         :return: find list or an exception if nothing has been found
460         """
461
462         def _filter_by_value():
463             if byvalue is undefined:
464                 return True
465             value = self.getattr(path, force_permissive=force_permissive,
466                                  _setting_properties=setting_properties,
467                                  returns_raise=True)
468             if isinstance(value, Exception):
469                 if isinstance(value, PropertiesOptionError):
470                     return False
471                 raise value  # pragma: no cover
472             elif isinstance(value, Multi):
473                 return byvalue in value
474             else:
475                 return value == byvalue
476
477         if type_ not in ('option', 'path', 'value'):  # pragma: optional cover
478             raise ValueError(_('unknown type_ type {0}'
479                                'for _find').format(type_))
480         find_results = []
481         # if value and/or check_properties are set, need all avalaible option
482         # If first one has no good value or not good property check second one
483         # and so on
484         only_first = first is True and byvalue is undefined and \
485             check_properties is None
486         if only_path is not undefined:
487             options = [(only_path, only_option)]
488         else:
489             options = self.cfgimpl_get_description().impl_get_options_paths(
490                 bytype, byname, _subpath, only_first,
491                 self._cfgimpl_get_context())
492         for path, option in options:
493             if not _filter_by_value():
494                 continue
495             #remove option with propertyerror, ...
496             if byvalue is undefined and check_properties:
497                 value = self.getattr(path,
498                                      force_permissive=force_permissive,
499                                      _setting_properties=setting_properties,
500                                      returns_raise=True)
501                 if isinstance(value, Exception):
502                     if isinstance(value, PropertiesOptionError):
503                         continue
504                     else:
505                         raise value  # pragma: no cover
506             if type_ == 'value':
507                 retval = value
508             elif type_ == 'path':
509                 retval = path
510             elif type_ == 'option':
511                 retval = option
512             if first:
513                 return retval
514             else:
515                 find_results.append(retval)
516         return self._find_return_results(find_results, raise_if_not_found)
517
518     def _find_return_results(self, find_results, raise_if_not_found):
519         if find_results == []:  # pragma: optional cover
520             if raise_if_not_found:
521                 raise AttributeError(_("no option found in config"
522                                        " with these criteria"))
523         else:
524             return find_results
525
526     def make_dict(self, flatten=False, _currpath=None, withoption=None,
527                   withvalue=undefined, force_permissive=False,
528                   setting_properties=undefined, fullpath=False):
529         """exports the whole config into a `dict`, for example:
530
531         >>> print cfg.make_dict()
532         {'od2.var4': None, 'od2.var5': None, 'od2.var6': None}
533
534
535
536         :param flatten: returns a dict(name=value) instead of a dict(path=value)
537                         ::
538
539                             >>> print cfg.make_dict(flatten=True)
540                             {'var5': None, 'var4': None, 'var6': None}
541
542         :param withoption: returns the options that are present in the very same
543                            `OptionDescription` than the `withoption` itself::
544
545                                 >>> print cfg.make_dict(withoption='var1')
546                                 {'od2.var4': None, 'od2.var5': None,
547                                 'od2.var6': None,
548                                 'od2.var1': u'value',
549                                 'od1.var1': None,
550                                 'od1.var3': None,
551                                 'od1.var2': None}
552
553         :param withvalue: returns the options that have the value `withvalue`
554                           ::
555
556                             >>> print c.make_dict(withoption='var1',
557                                                   withvalue=u'value')
558                             {'od2.var4': None,
559                             'od2.var5': None,
560                             'od2.var6': None,
561                             'od2.var1': u'value'}
562
563         :returns: dict of Option's name (or path) and values
564         """
565         pathsvalues = []
566         if _currpath is None:
567             _currpath = []
568         if withoption is None and withvalue is not undefined:  # pragma: optional cover
569             raise ValueError(_("make_dict can't filtering with value without "
570                                "option"))
571         if setting_properties is undefined:
572             setting_properties = self.cfgimpl_get_settings()._getproperties(
573                 read_write=False)
574         if withoption is not None:
575             context = self._cfgimpl_get_context()
576             for path in context._find(bytype=None, byname=withoption,
577                                       byvalue=withvalue, first=False,
578                                       type_='path', _subpath=self.cfgimpl_get_path(False),
579                                       force_permissive=force_permissive,
580                                       setting_properties=setting_properties):
581                 path = '.'.join(path.split('.')[:-1])
582                 opt = context.unwrap_from_path(path, force_permissive=True)
583                 mypath = self.cfgimpl_get_path()
584                 if mypath is not None:
585                     if mypath == path:
586                         withoption = None
587                         withvalue = undefined
588                         break
589                     else:
590                         tmypath = mypath + '.'
591                         if not path.startswith(tmypath):  # pragma: optional cover
592                             raise AttributeError(_('unexpected path {0}, '
593                                                    'should start with {1}'
594                                                    '').format(path, mypath))
595                         path = path[len(tmypath):]
596                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
597                                     force_permissive=force_permissive,
598                                     setting_properties=setting_properties,
599                                     fullpath=fullpath)
600         #withoption can be set to None below !
601         if withoption is None:
602             for opt in self.cfgimpl_get_description().impl_getchildren():
603                 path = opt.impl_getname()
604                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
605                                     force_permissive=force_permissive,
606                                     setting_properties=setting_properties,
607                                     fullpath=fullpath)
608         if _currpath == []:
609             options = dict(pathsvalues)
610             return options
611         return pathsvalues
612
613     def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
614                        setting_properties, force_permissive=False, fullpath=False):
615         value = self.getattr(path,
616                              force_permissive=force_permissive,
617                              _setting_properties=setting_properties,
618                              returns_raise=True)
619         if isinstance(value, Exception):
620             if not isinstance(value, PropertiesOptionError):  # pragma: no cover
621                 raise value
622         else:
623             if opt.impl_is_optiondescription():
624                     pathsvalues += value.make_dict(flatten,
625                                                    _currpath + path.split('.'),
626                                                    force_permissive=force_permissive,
627                                                    setting_properties=setting_properties,
628                                                    fullpath=fullpath)
629             else:
630                 if flatten:
631                     name = opt.impl_getname()
632                 else:
633                     if fullpath:
634                         root_path = self.cfgimpl_get_path()
635                         if root_path is None:
636                             name = opt.impl_getname()
637                         else:
638                             name = '.'.join([root_path, opt.impl_getname()])
639                     else:
640                         name = '.'.join(_currpath + [opt.impl_getname()])
641                 pathsvalues.append((name, value))
642
643     def cfgimpl_get_path(self, dyn=True):
644         descr = self.cfgimpl_get_description()
645         if not dyn and descr.impl_is_dynoptiondescription():
646             context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
647             return context_descr.impl_get_path_by_opt(descr._impl_getopt())
648         return self._impl_path
649
650
651 class _CommonConfig(SubConfig):
652     "abstract base class for the Config, GroupConfig and the MetaConfig"
653     __slots__ = ('_impl_values', '_impl_settings', '_impl_meta', '_impl_test')
654
655     def _impl_build_all_caches(self, force_store_values):
656         descr = self.cfgimpl_get_description()
657         if not descr.impl_already_build_caches():
658             descr.impl_build_cache_option()
659             descr.impl_build_cache(self)
660         descr.impl_build_force_store_values(self, force_store_values)
661
662     def read_only(self):
663         "read only is a global config's setting, see `settings.py`"
664         self.cfgimpl_get_settings().read_only()
665
666     def read_write(self):
667         "read write is a global config's setting, see `settings.py`"
668         self.cfgimpl_get_settings().read_write()
669
670     def getowner(self, opt, index=None, force_permissive=False):
671         """convenience method to retrieve an option's owner
672         from the config itself
673         """
674         if not isinstance(opt, Option) and \
675                 not isinstance(opt, SymLinkOption) and \
676                 not isinstance(opt, DynSymLinkOption):  # pragma: optional cover
677             raise TypeError(_('opt in getowner must be an option not {0}'
678                               '').format(type(opt)))
679         return self.cfgimpl_get_values().getowner(opt, index=index,
680                                                   force_permissive=force_permissive)
681
682     def unwrap_from_path(self, path, force_permissive=False):
683         """convenience method to extract and Option() object from the Config()
684         and it is **fast**: finds the option directly in the appropriate
685         namespace
686
687         :returns: Option()
688         """
689         context = self._cfgimpl_get_context()
690         if '.' in path:
691             homeconfig, path = self.cfgimpl_get_home_by_path(
692                 path, force_permissive=force_permissive)
693             return homeconfig.cfgimpl_get_description().__getattr__(path, context=context)
694         return self.cfgimpl_get_description().__getattr__(path, context=context)
695
696     def cfgimpl_get_path(self, dyn=True):
697         return None
698
699     def cfgimpl_get_meta(self):
700         if self._impl_meta is not None:
701             return self._impl_meta()
702
703     # information
704     def impl_set_information(self, key, value):
705         """updates the information's attribute
706
707         :param key: information's key (ex: "help", "doc"
708         :param value: information's value (ex: "the help string")
709         """
710         self._impl_values.set_information(key, value)
711
712     def impl_get_information(self, key, default=undefined):
713         """retrieves one information's item
714
715         :param key: the item string (ex: "help")
716         """
717         return self._impl_values.get_information(key, default)
718
719     def impl_del_information(self, key, raises=True):
720         self._impl_values.del_information(key, raises)
721
722     def __getstate__(self):
723         raise NotImplementedError()
724
725     def _gen_fake_values(self, session):
726         fake_config = Config(self._impl_descr, persistent=False,
727                              force_values=get_default_values_storages(),
728                              force_settings=self.cfgimpl_get_settings())
729         fake_config.cfgimpl_get_values()._p_.importation(self.cfgimpl_get_values()._p_.exportation(session, fake=True))
730         return fake_config
731
732     def duplicate(self, force_values=None, force_settings=None):
733         config = Config(self._impl_descr, _duplicate=True, force_values=force_values,
734                         force_settings=force_settings)
735         session = self.cfgimpl_get_values()._p_.getsession()
736         config.cfgimpl_get_values()._p_.importation(self.cfgimpl_get_values()._p_.exportation(
737             session))
738         config.cfgimpl_get_settings()._p_.set_modified_properties(self.cfgimpl_get_settings(
739             )._p_.get_modified_properties())
740         config.cfgimpl_get_settings()._pp_.set_modified_permissives(self.cfgimpl_get_settings(
741             )._pp_.get_modified_permissives())
742         return config
743
744
745 # ____________________________________________________________
746 class Config(_CommonConfig):
747     "main configuration management entry"
748     __slots__ = ('__weakref__', '_impl_test', '_impl_name')
749
750     def __init__(self, descr, session_id=None, persistent=False,
751                  force_values=None, force_settings=None,
752                  _duplicate=False, _force_store_values=True):
753         """ Configuration option management master class
754
755         :param descr: describes the configuration schema
756         :type descr: an instance of ``option.OptionDescription``
757         :param context: the current root config
758         :type context: `Config`
759         :param session_id: session ID is import with persistent Config to
760         retrieve good session
761         :type session_id: `str`
762         :param persistent: if persistent, don't delete storage when leaving
763         :type persistent: `boolean`
764         """
765         if force_settings is not None and force_values is not None:
766             if isinstance(force_settings, tuple):
767                 self._impl_settings = Settings(self, force_settings[0], force_settings[1])
768             else:
769                 self._impl_settings = force_settings
770             self._impl_values = Values(self, force_values)
771         else:
772             properties, permissives, values, session_id = get_storages(self, session_id, persistent)
773             if not valid_name(session_id):  # pragma: optional cover
774                 raise ValueError(_("invalid session ID: {0} for config").format(session_id))
775             self._impl_settings = Settings(self, properties, permissives)
776             self._impl_values = Values(self, values)
777         super(Config, self).__init__(descr, weakref.ref(self))
778         self._impl_meta = None
779         #undocumented option used only in test script
780         self._impl_test = False
781         if _duplicate is False and (force_settings is None or force_values is None):
782             self._impl_build_all_caches(_force_store_values)
783         self._impl_name = session_id
784
785     def impl_getname(self):
786         return self._impl_name
787
788     def impl_getsessionid(self):
789         return self._impl_values._p_._storage.session_id
790
791
792 class GroupConfig(_CommonConfig):
793     __slots__ = ('__weakref__', '_impl_children', '_impl_name')
794
795     def __init__(self, children, session_id=None, persistent=False,
796                  _descr=None):
797         if not isinstance(children, list):
798             raise ValueError(_("groupconfig's children must be a list"))
799         names = []
800         for child in children:
801             if not isinstance(child, _CommonConfig):
802                 raise ValueError(_("groupconfig's children must be Config, MetaConfig or GroupConfig"))
803             name_ = child._impl_name
804             names.append(name_)
805         if len(names) != len(set(names)):
806             for idx in xrange(1, len(names) + 1):
807                 name = names.pop(0)
808                 if name in names:
809                     raise ConflictError(_('config name must be uniq in '
810                                           'groupconfig for {0}').format(name))
811         self._impl_children = children
812         properties, permissives, values, session_id = get_storages(self, session_id, persistent)
813         self._impl_settings = Settings(self, properties, permissives)
814         self._impl_values = Values(self, values)
815         super(GroupConfig, self).__init__(_descr, weakref.ref(self))
816         self._impl_meta = None
817         #undocumented option used only in test script
818         self._impl_test = False
819         self._impl_name = session_id
820
821     def cfgimpl_get_children(self):
822         return self._impl_children
823
824     def cfgimpl_reset_cache(self,
825                             only_expired=False,
826                             only=('values', 'settings'),
827                             opt=None,
828                             path=None):
829         if isinstance(self, MetaConfig):
830             super(GroupConfig, self).cfgimpl_reset_cache(only_expired=only_expired, only=only, opt=opt, path=path)
831         for child in self._impl_children:
832             child.cfgimpl_reset_cache(only_expired=only_expired, only=only, opt=opt, path=path)
833
834     def set_value(self, path, value, _commit=True):
835         """Setattr not in current GroupConfig, but in each children
836         """
837         ret = []
838         for child in self._impl_children:
839             if isinstance(child, MetaConfig):
840                 ret.extend(child.set_value(path, value, only_config=True, _commit=False))
841             elif isinstance(child, GroupConfig):
842                 ret.extend(child.set_value(path, value, _commit=False))
843             else:
844                 childret = child.setattr(path, value, not_raises=True, _commit=False)
845                 if childret is not None:
846                     ret.append(childret)
847         if _commit:
848             self.cfgimpl_get_values()._p_.commit()
849         return ret
850
851
852     def find_firsts(self, byname=None, bypath=undefined, byoption=undefined,
853                     byvalue=undefined, raise_if_not_found=True, _sub=False,
854                     check_properties=True):
855         """Find first not in current GroupConfig, but in each children
856         """
857         ret = []
858
859         #if MetaConfig, all children have same OptionDescription in context
860         #so search only one time the option for all children
861         if bypath is undefined and byname is not None and \
862                 isinstance(self, MetaConfig):
863             bypath = self._find(bytype=None, byvalue=undefined, byname=byname,
864                                 first=True, type_='path',
865                                 check_properties=None,
866                                 raise_if_not_found=raise_if_not_found)
867             byname = None
868             byoption = self.cfgimpl_get_description(
869             ).impl_get_opt_by_path(bypath)
870
871         for child in self._impl_children:
872             if isinstance(child, GroupConfig):
873                 ret.extend(child.find_firsts(byname=byname, bypath=bypath,
874                                              byoption=byoption,
875                                              byvalue=byvalue,
876                                              check_properties=check_properties,
877                                              raise_if_not_found=False,
878                                              _sub=True))
879             elif child._find(None, byname, byvalue, first=True,
880                              type_='path', raise_if_not_found=False,
881                              check_properties=check_properties,
882                              only_path=bypath, only_option=byoption):
883                 ret.append(child)
884         if _sub:
885             return ret
886         else:
887             return GroupConfig(self._find_return_results(ret, raise_if_not_found))
888
889     def __str__(self):
890         ret = ''
891         for child in self._impl_children:
892             ret += '({0})\n'.format(child._impl_name)
893         if self._impl_descr is not None:
894             ret += super(GroupConfig, self).__str__()
895         return ret
896
897     __repr__ = __str__
898
899     def getattr(self, name, force_permissive=False, validate=True,
900                 _setting_properties=undefined, _self_properties=undefined, index=None,
901                 returns_raise=False):
902         for child in self._impl_children:
903             if name == child._impl_name:
904                 return child
905         return super(GroupConfig, self).getattr(name, force_permissive,
906                                                 validate,
907                                                 _self_properties=_self_properties,
908                                                 index=index,
909                                                 _setting_properties=_setting_properties,
910                                                 returns_raise=returns_raise)
911
912
913 class MetaConfig(GroupConfig):
914     __slots__ = tuple()
915
916     def __init__(self, children, session_id=None, persistent=False,
917                  optiondescription=None, _force_store_values=True):
918         descr = None
919         if optiondescription is not None:
920             new_children = []
921             for child_session_id in children:
922                 #FIXME _force_store_values doit etre a true si inexistant !
923                 new_children.append(Config(optiondescription, persistent=persistent,
924                                            session_id=child_session_id, _force_store_values=_force_store_values))
925             children = new_children
926         for child in children:
927             if not isinstance(child, _CommonConfig):
928                 raise TypeError(_("metaconfig's children "
929                                   "should be config, not {0}"
930                                   ).format(type(child)))
931             if child.cfgimpl_get_meta() is not None:
932                 raise ValueError(_("child has already a metaconfig's"))
933             if descr is None:
934                 descr = child.cfgimpl_get_description()
935             elif not descr is child.cfgimpl_get_description():
936                 raise ValueError(_('all config in metaconfig must '
937                                    'have the same optiondescription'))
938             child._impl_meta = weakref.ref(self)
939
940         super(MetaConfig, self).__init__(children, session_id, persistent,
941                                          descr)
942
943     def set_value(self, path, value, force_default=False,
944                   force_dont_change_value=False, force_default_if_same=False,
945                   only_config=False, _commit=True):
946         """only_config: could be set if you want modify value in all Config included in
947                         this MetaConfig
948         """
949         if only_config:
950             if force_default or force_default_if_same or force_dont_change_value:
951                 raise ValueError(_('force_default, force_default_if_same or '
952                                    'force_dont_change_value cannot be set with'
953                                    ' only_config'))
954             return super(MetaConfig, self).set_value(path, value, _commit=_commit)
955         ret = []
956         if force_default or force_default_if_same or force_dont_change_value:
957             if force_default and force_dont_change_value:
958                 raise ValueError(_('force_default and force_dont_change_value'
959                                    ' cannot be set together'))
960             opt = self.cfgimpl_get_description().impl_get_opt_by_path(path)
961             setting_properties = self.cfgimpl_get_settings()._getproperties(read_write=False)
962             for child in self._impl_children:
963                 if force_default_if_same or force_default:
964                     if force_default_if_same:
965                         if not child.cfgimpl_get_values()._contains(path):
966                             child_value = undefined
967                         else:
968                             child_value = child.getattr(path)
969                     if force_default or value == child_value:
970                         child.cfgimpl_get_values().reset(opt, path=path,
971                                                          validate=False,
972                                                          _setting_properties=setting_properties,
973                                                          _commit=False)
974                         continue
975                 if force_dont_change_value:
976                     child_value = child.getattr(path, _setting_properties=setting_properties,
977                                                 returns_raise=True)
978                     if isinstance(child_value, Exception):
979                         ret.append(child_value)
980                     elif value != child_value:
981                         childret = child.setattr(path, child_value, _commit=False, not_raises=True)
982                         if childret is not None:  # pragma: no cover
983                             ret.append(childret)
984
985         setret = self.setattr(path, value, _commit=_commit, not_raises=True)
986         if setret is not None:
987             ret.append(setret)
988         return ret
989
990     def reset(self, path):
991         opt = self.cfgimpl_get_description().impl_get_opt_by_path(path)
992         setting_properties = self.cfgimpl_get_settings()._getproperties(read_write=False)
993         for child in self._impl_children:
994             child.cfgimpl_get_values().reset(opt, path=path,
995                                              validate=False,
996                                              _setting_properties=setting_properties,
997                                              _commit=False)
998         self.cfgimpl_get_values().reset(opt, path=path,
999                                         validate=False,
1000                                         _setting_properties=setting_properties)
1001
1002     def new_config(self, session_id, persistent=False):
1003         config = Config(self._impl_descr, session_id=session_id,
1004                         persistent=persistent)
1005
1006         if config._impl_name in [child._impl_name for child in self._impl_children]:  # pragma: no cover
1007             raise ConflictError(_('config name must be uniq in '
1008                                   'groupconfig for {0}').format(config._impl_name))
1009         config._impl_meta = weakref.ref(self)
1010         self._impl_children.append(config)
1011         return config