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