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