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