the value owner is a string now
[tiramisu.git] / tiramisu / config.py
1 # -*- coding: utf-8 -*-
2 "pretty small and local configuration management tool"
3 # Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 from copy import copy
24 from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError,
25     AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
26     MandatoryError, MethodCallError, NoValueReturned)
27 from tiramisu.option import (OptionDescription, Option, SymLinkOption,
28     group_types, Multi, apply_requires)
29
30 # ______________________________________________________________________
31 # generic owner. 'default' is the general config owner after init time
32 default_owner = 'user'
33
34 # ____________________________________________________________
35 class Config(object):
36     "main configuration management entry"
37     #properties attribute: the name of a property enables this property
38     _cfgimpl_properties = ['hidden', 'disabled']
39     _cfgimpl_permissive = []
40     #mandatory means: a mandatory option has to have a value that is not None
41     _cfgimpl_mandatory = True
42     _cfgimpl_frozen = True
43     _cfgimpl_owner = default_owner
44     _cfgimpl_toplevel = None
45
46     def __init__(self, descr, parent=None):
47         """ Configuration option management master class
48         :param descr: describes the configuration schema
49         :type descr: an instance of ``option.OptionDescription``
50         :param parent: is None if the ``Config`` is root parent Config otherwise
51         :type parent: ``Config``
52         """
53         self._cfgimpl_descr = descr
54         self._cfgimpl_value_owners = {}
55         self._cfgimpl_parent = parent
56         "`Config()` indeed is in charge of the `Option()`'s values"
57         self._cfgimpl_values = {}
58         self._cfgimpl_previous_values = {}
59         "warnings are a great idea, let's make up a better use of it"
60         self._cfgimpl_warnings = []
61         self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
62         '`freeze()` allows us to carry out this calculation again if necessary'
63         self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen
64         self._cfgimpl_build()
65
66     def _validate_duplicates(self, children):
67         """duplicates Option names in the schema
68         :type children: list of `Option` or `OptionDescription`
69         """
70         duplicates = []
71         for dup in children:
72             if dup._name not in duplicates:
73                 duplicates.append(dup._name)
74             else:
75                 raise ConflictConfigError('duplicate option name: '
76                     '{0}'.format(dup._name))
77
78     def _cfgimpl_build(self):
79         """
80         - builds the config object from the schema
81         - settles various default values for options
82         """
83         self._validate_duplicates(self._cfgimpl_descr._children)
84         for child in self._cfgimpl_descr._children:
85             if isinstance(child, Option):
86                 if child.is_multi():
87                     childdef = Multi(copy(child.getdefault()), config=self,
88                                      child=child)
89                     self._cfgimpl_values[child._name] = childdef
90                     self._cfgimpl_previous_values[child._name] = list(childdef)
91                 else:
92                     childdef = child.getdefault()
93                     self._cfgimpl_values[child._name] = childdef
94                     self._cfgimpl_previous_values[child._name] = childdef
95                 self._cfgimpl_value_owners[child._name] = 'default'
96             elif isinstance(child, OptionDescription):
97                 self._validate_duplicates(child._children)
98                 self._cfgimpl_values[child._name] = Config(child, parent=self)
99 #        self.override(overrides)
100
101     def cfgimpl_set_permissive(self, permissive):
102         if not isinstance(permissive, list):
103             raise TypeError('permissive must be a list')
104         self._cfgimpl_permissive = permissive
105
106     def cfgimpl_update(self):
107         """dynamically adds `Option()` or `OptionDescription()`
108         """
109         # FIXME this is an update for new options in the schema only
110         #┬ásee the update_child() method of the descr object
111         for child in self._cfgimpl_descr._children:
112             if isinstance(child, Option):
113                 if child._name not in self._cfgimpl_values:
114                     if child.is_multi():
115                         self._cfgimpl_values[child._name] = Multi(
116                                 copy(child.getdefault()), config=self, child=child)
117                     else:
118                         self._cfgimpl_values[child._name] = copy(child.getdefault())
119                     self._cfgimpl_value_owners[child._name] = 'default'
120             elif isinstance(child, OptionDescription):
121                 if child._name not in self._cfgimpl_values:
122                     self._cfgimpl_values[child._name] = Config(child, parent=self)
123
124     def cfgimpl_set_owner(self, owner):
125         ":param owner: sets the default value for owner at the Config level"
126         self._cfgimpl_owner = owner
127         for child in self._cfgimpl_descr._children:
128             if isinstance(child, OptionDescription):
129                 self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
130     # ____________________________________________________________
131     # properties methods
132     def _cfgimpl_has_properties(self):
133         "has properties means the Config's properties attribute is not empty"
134         return bool(len(self._cfgimpl_properties))
135
136     def _cfgimpl_has_property(self, propname):
137         """has property propname in the Config's properties attribute
138         :param property: string wich is the name of the property"""
139         return propname in self._cfgimpl_properties
140
141     def cfgimpl_enable_property(self, propname):
142         "puts property propname in the Config's properties attribute"
143         if self._cfgimpl_parent != None:
144             raise MethodCallError("this method root_hide() shall not be"
145                                   "used with non-root Config() object")
146         if propname not in self._cfgimpl_properties:
147             self._cfgimpl_properties.append(propname)
148
149     def cfgimpl_disable_property(self, propname):
150         "deletes property propname in the Config's properties attribute"
151         if self._cfgimpl_parent != None:
152             raise MethodCallError("this method root_hide() shall not be"
153                                   "used with non-root Config() object")
154         if self._cfgimpl_has_property(propname):
155             self._cfgimpl_properties.remove(propname)
156     # ____________________________________________________________
157     # attribute methods
158     def __setattr__(self, name, value):
159         "attribute notation mechanism for the setting of the value of an option"
160         if name.startswith('_cfgimpl_'):
161             self.__dict__[name] = value
162             return
163         if '.' in name:
164             homeconfig, name = self._cfgimpl_get_home_by_path(name)
165             return setattr(homeconfig, name, value)
166         if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
167             self._validate(name, getattr(self._cfgimpl_descr, name))
168         self.setoption(name, value, self._cfgimpl_owner)
169
170     def _validate(self, name, opt_or_descr, permissive=False):
171         "validation for the setattr and the getattr"
172         apply_requires(opt_or_descr, self)
173         if not isinstance(opt_or_descr, Option) and \
174                 not isinstance(opt_or_descr, OptionDescription):
175             raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr)))
176         properties = copy(opt_or_descr.properties)
177         for proper in copy(properties):
178             if not self._cfgimpl_toplevel._cfgimpl_has_property(proper):
179                 properties.remove(proper)
180         if permissive:
181             for perm in self._cfgimpl_toplevel._cfgimpl_permissive:
182                 if perm in properties:
183                     properties.remove(perm)
184         if properties != []:
185             raise PropertiesOptionError("trying to access"
186                     " to an option named: {0} with properties"
187                     " {1}".format(name, str(properties)),
188                     properties)
189
190     def _is_empty(self, opt):
191         "convenience method to know if an option is empty"
192         if (not opt.is_multi() and self._cfgimpl_values[opt._name] == None) or \
193             (opt.is_multi() and (self._cfgimpl_values[opt._name] == [] or \
194                 None in self._cfgimpl_values[opt._name])):
195             return True
196         return False
197
198     def _test_mandatory(self, path, opt):
199         # mandatory options
200         homeconfig = self._cfgimpl_get_toplevel()
201         mandatory = homeconfig._cfgimpl_mandatory
202         if opt.is_mandatory() and mandatory:
203             if self._is_empty(opt) and \
204                     opt.is_empty_by_default():
205                 raise MandatoryError("option: {0} is mandatory "
206                                       "and shall have a value".format(path))
207
208     def __getattr__(self, name):
209         return self._getattr(name)
210
211     def _getattr(self, name, permissive=False):
212         """
213         attribute notation mechanism for accessing the value of an option
214         :param name: attribute name
215         :param permissive: permissive doesn't raise some property error
216         (see ``_cfgimpl_permissive``)
217         :return: option's value if name is an option name, OptionDescription
218         otherwise
219         """
220         # attribute access by passing a path,
221         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
222         if '.' in name:
223             homeconfig, name = self._cfgimpl_get_home_by_path(name)
224             return homeconfig._getattr(name, permissive)
225         opt_or_descr = getattr(self._cfgimpl_descr, name)
226         # symlink options
227         if type(opt_or_descr) == SymLinkOption:
228             return getattr(self, opt_or_descr.path)
229         if name not in self._cfgimpl_values:
230             raise AttributeError("%s object has no attribute %s" %
231                                  (self.__class__, name))
232         self._validate(name, opt_or_descr, permissive)
233         # special attributes
234         if name.startswith('_cfgimpl_'):
235             # if it were in __dict__ it would have been found already
236             return self.__dict__[name]
237             raise AttributeError("%s object has no attribute %s" %
238                                  (self.__class__, name))
239         if not isinstance(opt_or_descr, OptionDescription):
240             # options with callbacks (fill or auto)
241             if opt_or_descr.has_callback():
242                 value = self._cfgimpl_values[name]
243                 if (not opt_or_descr.is_frozen() or \
244                         not opt_or_descr.is_forced_on_freeze()) and \
245                         not opt_or_descr.is_default_owner(self):
246                     if opt_or_descr.is_multi():
247                         if None not in value:
248                             return value
249                     else:
250                         return value
251                 try:
252                     result = opt_or_descr.getcallback_value(
253                             self._cfgimpl_get_toplevel())
254                 except NoValueReturned, err:
255                     pass
256                 else:
257                     if opt_or_descr.is_multi():
258                         if not isinstance(result, list):
259                             result = [result]
260                         _result = Multi(result, value.config, value.child)
261                     else:
262                         # this result **shall not** be a list
263                         if isinstance(result, list):
264                             raise ConfigError('invalid calculated value returned'
265                                 ' for option {0} : shall not be a list'.format(name))
266                         _result = result
267                     if _result != None and not opt_or_descr.validate(_result):
268                         raise ConfigError('invalid calculated value returned'
269                             ' for option {0}'.format(name))
270                     self._cfgimpl_values[name] = _result
271                     self._cfgimpl_value_owners[name] = 'default'
272             self._test_mandatory(name, opt_or_descr)
273             # frozen and force default
274             if not opt_or_descr.has_callback() and opt_or_descr.is_forced_on_freeze():
275                 return opt_or_descr.getdefault()
276
277         return self._cfgimpl_values[name]
278
279     def unwrap_from_name(self, name):
280         """convenience method to extract and Option() object from the Config()
281         **and it is slow**: it recursively searches into the namespaces
282
283         :returns: Option()
284         """
285         paths = self.getpaths(allpaths=True)
286         opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
287         all_paths = [p.split(".") for p in self.getpaths()]
288         for pth in all_paths:
289             if name in pth:
290                 return opts[".".join(pth)]
291         raise NotFoundError("name: {0} not found".format(name))
292
293     def unwrap_from_path(self, path):
294         """convenience method to extract and Option() object from the Config()
295         and it is **fast**: finds the option directly in the appropriate
296         namespace
297
298         :returns: Option()
299         """
300         if '.' in path:
301             homeconfig, path = self._cfgimpl_get_home_by_path(path)
302             return getattr(homeconfig._cfgimpl_descr, path)
303         return getattr(self._cfgimpl_descr, path)
304
305     #def __delattr__(self, name):
306     #    "if you use delattr you are responsible for all bad things happening"
307     #    if name.startswith('_cfgimpl_'):
308     #        del self.__dict__[name]
309     #        return
310     #    self._cfgimpl_value_owners[name] = 'default'
311     #    opt = getattr(self._cfgimpl_descr, name)
312     #    if isinstance(opt, OptionDescription):
313     #        raise AttributeError("can't option subgroup")
314     #    self._cfgimpl_values[name] = getattr(opt, 'default', None)
315
316     def setoption(self, name, value, who=None):
317         """effectively modifies the value of an Option()
318         (typically called by the __setattr__)
319
320         :param who: is an owner's name
321         who is **not necessarily** a owner, because it cannot be a list
322         :type who: string
323         """
324         child = getattr(self._cfgimpl_descr, name)
325         if type(child) != SymLinkOption:
326             if who == None:
327                 who = self._cfgimpl_owner
328             if child.is_multi():
329                 if type(value) != Multi:
330                     if type(value) == list:
331                         value = Multi(value, self, child)
332                     else:
333                         raise ConfigError("invalid value for option:"
334                                    " {0} that is set to multi".format(name))
335             child.setoption(self, value, who)
336             child.setowner(self, who)
337         else:
338             homeconfig = self._cfgimpl_get_toplevel()
339             child.setoption(homeconfig, value, who)
340
341     def set(self, **kwargs):
342         """
343         "do what I mean"-interface to option setting. Searches all paths
344         starting from that config for matches of the optional arguments
345         and sets the found option if the match is not ambiguous.
346         :param kwargs: dict of name strings to values.
347         """
348         all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
349         for key, value in kwargs.iteritems():
350             key_p = key.split('.')
351             candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
352             if len(candidates) == 1:
353                 name = '.'.join(candidates[0])
354                 homeconfig, name = self._cfgimpl_get_home_by_path(name)
355                 try:
356                     getattr(homeconfig, name)
357                 except MandatoryError:
358                     pass
359                 except Exception, e:
360                     raise e # HiddenOptionError or DisabledOptionError
361                 homeconfig.setoption(name, value, self._cfgimpl_owner)
362             elif len(candidates) > 1:
363                 raise AmbigousOptionError(
364                     'more than one option that ends with %s' % (key, ))
365             else:
366                 raise NoMatchingOptionFound(
367                     'there is no option that matches %s'
368                     ' or the option is hidden or disabled'% (key, ))
369
370     def get(self, name):
371         """
372         same as a find_first() method in a config that has identical names
373         that is : Returns the first item of an option named 'name'
374
375         much like the attribute access way, except that
376         the search for the option is performed recursively in the whole
377         configuration tree.
378         **carefull**: very slow !
379
380         :returns: option value.
381         """
382         paths = self.getpaths(allpaths=True)
383         pathsvalues = []
384         for path in paths:
385             pathname = path.split('.')[-1]
386             if pathname == name:
387                 try:
388                     value = getattr(self, path)
389                     return value
390                 except Exception, e:
391                     raise e
392         raise NotFoundError("option {0} not found in config".format(name))
393
394     def _cfgimpl_get_home_by_path(self, path):
395         """:returns: tuple (config, name)"""
396         path = path.split('.')
397         for step in path[:-1]:
398             self = getattr(self, step)
399         return self, path[-1]
400
401     def _cfgimpl_get_toplevel(self):
402         ":returns: root config"
403         while self._cfgimpl_parent is not None:
404             self = self._cfgimpl_parent
405         return self
406
407     def _cfgimpl_get_path(self):
408         "the path in the attribute access meaning."
409         subpath = []
410         obj = self
411         while obj._cfgimpl_parent is not None:
412             subpath.insert(0, obj._cfgimpl_descr._name)
413             obj = obj._cfgimpl_parent
414         return ".".join(subpath)
415     # ______________________________________________________________________
416     def cfgimpl_previous_value(self, path):
417         "stores the previous value"
418         home, name = self._cfgimpl_get_home_by_path(path)
419         return home._cfgimpl_previous_values[name]
420
421     def get_previous_value(self, name):
422         "for the time being, only the previous Option's value is accessible"
423         return self._cfgimpl_previous_values[name]
424     # ______________________________________________________________________
425     def add_warning(self, warning):
426         "Config implements its own warning pile. Could be useful"
427         self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
428
429     def get_warnings(self):
430         "Config implements its own warning pile"
431         return self._cfgimpl_get_toplevel()._cfgimpl_warnings
432     # ____________________________________________________________
433     # Config()'s status
434     def cfgimpl_freeze(self):
435         "cannot modify the frozen `Option`'s"
436         rootconfig = self._cfgimpl_get_toplevel()
437         rootconfig._cfgimpl_frozen = True
438         self._cfgimpl_frozen = True
439
440     def cfgimpl_unfreeze(self):
441         "can modify the Options that are frozen"
442         rootconfig = self._cfgimpl_get_toplevel()
443         rootconfig._cfgimpl_frozen = False
444         self._cfgimpl_frozen = False
445
446     def is_frozen(self):
447         "freeze flag at Config level"
448         rootconfig = self._cfgimpl_get_toplevel()
449         return rootconfig._cfgimpl_frozen
450
451     def cfgimpl_read_only(self):
452         "convenience method to freeze, hidde and disable"
453         self.cfgimpl_freeze()
454         rootconfig = self._cfgimpl_get_toplevel()
455         rootconfig.cfgimpl_disable_property('hidden')
456         rootconfig.cfgimpl_enable_property('disabled')
457         rootconfig._cfgimpl_mandatory = True
458
459     def cfgimpl_read_write(self):
460         "convenience method to freeze, hidde and disable"
461         self.cfgimpl_freeze()
462         rootconfig = self._cfgimpl_get_toplevel()
463         rootconfig.cfgimpl_enable_property('hidden')
464         rootconfig.cfgimpl_enable_property('disabled')
465         rootconfig._cfgimpl_mandatory = False
466
467     def cfgimpl_non_mandatory(self):
468         """mandatory at the Config level means that the Config raises an error
469         if a mandatory option is found"""
470         if self._cfgimpl_parent != None:
471             raise MethodCallError("this method root_mandatory shall"
472                                   " not be used with non-root Confit() object")
473         rootconfig = self._cfgimpl_get_toplevel()
474         rootconfig._cfgimpl_mandatory = False
475
476     def cfgimpl_mandatory(self):
477         """mandatory at the Config level means that the Config raises an error
478         if a mandatory option is found"""
479         if self._cfgimpl_parent != None:
480             raise MethodCallError("this method root_mandatory shall"
481                                   " not be used with non-root Confit() object")
482         rootconfig = self._cfgimpl_get_toplevel()
483         rootconfig._cfgimpl_mandatory = True
484
485     def is_mandatory(self):
486         "all mandatory Options shall have a value"
487         rootconfig = self._cfgimpl_get_toplevel()
488         return rootconfig._cfgimpl_mandatory
489     # ____________________________________________________________
490     def getkey(self):
491         return self._cfgimpl_descr.getkey(self)
492
493     def __hash__(self):
494         return hash(self.getkey())
495
496     def __eq__(self, other):
497         "Config comparison"
498         return self.getkey() == other.getkey()
499
500     def __ne__(self, other):
501         "Config comparison"
502         return not self == other
503     # ______________________________________________________________________
504     def __iter__(self):
505         "iteration only on Options (not OptionDescriptions)"
506         for child in self._cfgimpl_descr._children:
507             if isinstance(child, Option):
508                 try:
509                     yield child._name, getattr(self, child._name)
510                 except:
511                     pass # option with properties
512
513     def iter_groups(self, group_type=None):
514         "iteration on OptionDescriptions"
515         if group_type == None:
516             groups = group_types
517         else:
518             if group_type not in group_types:
519                 raise TypeError("Unknown group_type: {0}".format(group_type))
520             groups = [group_type]
521         for child in self._cfgimpl_descr._children:
522             if isinstance(child, OptionDescription):
523                 try:
524                     if child.get_group_type() in groups:
525                         yield child._name, getattr(self, child._name)
526                 except:
527                     pass # hidden, disabled option
528     # ______________________________________________________________________
529     def __str__(self, indent=""):
530         "Config's string representation"
531         lines = []
532         children = [(child._name, child)
533                     for child in self._cfgimpl_descr._children]
534         children.sort()
535         for name, child in children:
536             if self._cfgimpl_value_owners.get(name, None) == 'default':
537                 continue
538             value = getattr(self, name)
539             if isinstance(value, Config):
540                 substr = value.__str__(indent + "    ")
541             else:
542                 substr = "%s    %s = %s" % (indent, name, value)
543             if substr:
544                 lines.append(substr)
545         if indent and not lines:
546             return ''   # hide subgroups with all default values
547         lines.insert(0, "%s[%s]" % (indent, self._cfgimpl_descr._name,))
548         return '\n'.join(lines)
549
550     def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
551         """returns a list of all paths in self, recursively, taking care of
552         the context of properties (hidden/disabled)
553
554         :param include_groups: if true, OptionDescription are included
555         :param allpaths: all the options (event the properties protected ones)
556         :param mandatory: includes the mandatory options
557         :returns: list of all paths
558         """
559         paths = []
560         for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
561             try:
562                 value = getattr(self, path)
563
564             except MandatoryError:
565                 if mandatory or allpaths:
566                     paths.append(path)
567             except PropertiesOptionError:
568                 if allpaths:
569                     paths.append(path) # option which have properties added
570             else:
571                  paths.append(path)
572         return paths
573
574     def _find(self, bytype, byname, byvalue, byattrs, first):
575         """
576             :param first: return only one option if True, a list otherwise
577         """
578         def _filter_by_attrs():
579             if byattrs is None:
580                 return True
581             for key, value in byattrs.items():
582                 if not hasattr(option, key):
583                     return False
584                 else:
585                     if getattr(option, key) != value:
586                         return False
587                     else:
588                         continue
589             return True
590         def _filter_by_name():
591             if byname is None:
592                 return True
593             pathname = path.split('.')[-1]
594             if pathname == byname:
595                 return True
596             else:
597                 return False
598         def _filter_by_value():
599             if byvalue is None:
600                 return True
601             try:
602                 value = getattr(self, path)
603                 if value == byvalue:
604                     return True
605             except Exception, e: # a property restricts the acces to value
606                 pass
607             return False
608         def _filter_by_type():
609             if bytype is None:
610                 return True
611             if isinstance(option, bytype):
612                 return True
613             return False
614
615         find_results = []
616         paths = self.getpaths(allpaths=True)
617         for path in paths:
618             option = self.unwrap_from_path(path)
619             if not _filter_by_name():
620                 continue
621             if not _filter_by_value():
622                 continue
623             if not _filter_by_type():
624                 continue
625             if not _filter_by_attrs():
626                 continue
627             if first:
628                 return option
629             else:
630                 find_results.append(option)
631         if first:
632             return None
633         else:
634             return find_results
635
636     def find(self, bytype=None, byname=None, byvalue=None, byattrs=None):
637         """
638             finds a list of options recursively in the config
639
640             :param bytype: Option class (BoolOption, StrOption, ...)
641             :param byname: filter by Option._name
642             :param byvalue: filter by the option's value
643             :param byattrs: dict of option attributes (default, callback...)
644             :returns: list of matching Option objects
645         """
646         return self._find(bytype, byname, byvalue, byattrs, first=False)
647
648     def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None):
649         """
650             finds an option recursively in the config
651
652             :param bytype: Option class (BoolOption, StrOption, ...)
653             :param byname: filter by Option._name
654             :param byvalue: filter by the option's value
655             :param byattrs: dict of option attributes (default, callback...)
656             :returns: list of matching Option objects
657         """
658         return self._find(bytype, byname, byvalue, byattrs, first=True)
659
660 def make_dict(config, flatten=False):
661     """export the whole config into a `dict`
662     :returns: dict of Option's name (or path) and values"""
663     paths = config.getpaths()
664     pathsvalues = []
665     for path in paths:
666         if flatten:
667             pathname = path.split('.')[-1]
668         else:
669             pathname = path
670         try:
671             value = getattr(config, path)
672             pathsvalues.append((pathname, value))
673         except:
674             pass # this just a hidden or disabled option
675     options = dict(pathsvalues)
676     return options
677
678 def mandatory_warnings(config):
679     """convenience function to trace Options that are mandatory and
680     where no value has been set
681
682     :returns: generator of mandatory Option's path
683     """
684     mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory
685     config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True
686     for path in config._cfgimpl_descr.getpaths(include_groups=True):
687         try:
688             value = config._getattr(path, permissive=True)
689         except MandatoryError:
690             yield path
691         except PropertiesOptionError:
692             pass
693     config._cfgimpl_get_toplevel()._cfgimpl_mandatory = mandatory