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