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