permissive in the getattr
[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 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 __getattr__(self, name):
213         return self._getattr(name)
214
215     def _getattr(self, name, permissive=False):
216         "attribute notation mechanism for accessing the value of an option"
217         # attribute access by passing a path,
218         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
219         if '.' in name:
220             homeconfig, name = self._cfgimpl_get_home_by_path(name)
221             return homeconfig._getattr(name, permissive)
222         opt_or_descr = getattr(self._cfgimpl_descr, name)
223         # symlink options
224         if type(opt_or_descr) == SymLinkOption:
225             return getattr(self, opt_or_descr.path)
226         if name not in self._cfgimpl_values:
227             raise AttributeError("%s object has no attribute %s" %
228                                  (self.__class__, name))
229         self._validate(name, opt_or_descr, permissive)
230         # special attributes
231         if name.startswith('_cfgimpl_'):
232             # if it were in __dict__ it would have been found already
233             return self.__dict__[name]
234             raise AttributeError("%s object has no attribute %s" %
235                                  (self.__class__, name))
236         if not isinstance(opt_or_descr, OptionDescription):
237             # options with callbacks (fill or auto)
238             if opt_or_descr.has_callback():
239                 value = self._cfgimpl_values[name]
240                 if (not opt_or_descr.is_frozen() or \
241                         not opt_or_descr.is_forced_on_freeze()) and \
242                         not opt_or_descr.getowner(self) == 'default':
243                     if opt_or_descr.is_multi():
244                         if None not in value:
245                             return value
246                     else:
247                         return value
248                 try:
249                     result = opt_or_descr.getcallback_value(
250                             self._cfgimpl_get_toplevel())
251                 except NoValueReturned, err:
252                     pass
253                 else:
254                     # this result **shall not** be a list
255                     # for example, [1, 2, 3, None] -> [1, 2, 3, result]
256                     if isinstance(result, list):
257                         raise ConfigError('invalid calculated value returned'
258                             ' for option {0} : shall not be a list'.format(name))
259                     if result != None and not opt_or_descr._validate(result):
260                         raise ConfigError('invalid calculated value returned'
261                             ' for option {0}'.format(name))
262                     if opt_or_descr.is_multi():
263                         if value == []:
264                             _result = Multi([result], value.config, value.child)
265                         else:
266                             _result = Multi([], value.config, value.child)
267                             #for val in value:
268                             owners = opt_or_descr.getowner(self)
269                             for cpt in range(len(value)):
270                                 val = value[cpt]
271                                 if owners[cpt] == 'default':
272                                     val = result
273                                 _result.append(val)
274                     else:
275                         _result = result
276                     self._cfgimpl_values[name] = _result
277
278             # mandatory options
279             homeconfig = self._cfgimpl_get_toplevel()
280             mandatory = homeconfig._cfgimpl_mandatory
281             if opt_or_descr.is_mandatory() and mandatory:
282                 if self._is_empty(opt_or_descr) and \
283                         opt_or_descr.is_empty_by_default():
284                     raise MandatoryError("option: {0} is mandatory "
285                                           "and shall have a value".format(name))
286             # frozen and force default
287             if not opt_or_descr.has_callback() and opt_or_descr.is_forced_on_freeze():
288                 return opt_or_descr.getdefault()
289         return self._cfgimpl_values[name]
290
291     def unwrap_from_name(self, name):
292         """convenience method to extract and Option() object from the Config()
293         **and it is slow**: it recursively searches into the namespaces
294
295         :returns: Option()
296         """
297         paths = self.getpaths(allpaths=True)
298         opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
299         all_paths = [p.split(".") for p in self.getpaths()]
300         for pth in all_paths:
301             if name in pth:
302                 return opts[".".join(pth)]
303         raise NotFoundError("name: {0} not found".format(name))
304
305     def unwrap_from_path(self, path):
306         """convenience method to extract and Option() object from the Config()
307         and it is **fast**: finds the option directly in the appropriate
308         namespace
309
310         :returns: Option()
311         """
312         if '.' in path:
313             homeconfig, path = self._cfgimpl_get_home_by_path(path)
314             return getattr(homeconfig._cfgimpl_descr, path)
315         return getattr(self._cfgimpl_descr, path)
316
317     def __delattr__(self, name):
318         "if you use delattr you are responsible for all bad things happening"
319         if name.startswith('_cfgimpl_'):
320             del self.__dict__[name]
321             return
322         self._cfgimpl_value_owners[name] = 'default'
323         opt = getattr(self._cfgimpl_descr, name)
324         if isinstance(opt, OptionDescription):
325             raise AttributeError("can't option subgroup")
326         self._cfgimpl_values[name] = getattr(opt, 'default', None)
327
328     def setoption(self, name, value, who=None):
329         """effectively modifies the value of an Option()
330         (typically called by the __setattr__)
331
332         :param who: is an owner's name
333         who is **not necessarily** a owner, because it cannot be a list
334         :type who: string
335         """
336         child = getattr(self._cfgimpl_descr, name)
337         if who == None:
338             if child.is_multi():
339                 newowner = [self._cfgimpl_owner for i in range(len(value))]
340             else:
341                 newowner = self._cfgimpl_owner
342         else:
343             if type(child) != SymLinkOption:
344                 if child.is_multi():
345                     if type(value) != Multi:
346                         if type(value) == list:
347                             value = Multi(value, self, child)
348                         else:
349                             raise ConfigError("invalid value for option:"
350                                        " {0} that is set to multi".format(name))
351                     newowner = [who for i in range(len(value))]
352                 else:
353                     newowner = who
354         if type(child) != SymLinkOption:
355             #if child.has_callback() and who=='default':
356             #    raise TypeError("trying to set a default value to an option "
357             #        "which has a callback: {0}".format(name))
358             child.setoption(self, value, who)
359             if (value is None and who != 'default' and not child.is_multi()):
360                 child.setowner(self, 'default')
361                 self._cfgimpl_values[name] = copy(child.getdefault())
362             elif (value == [] and who != 'default' and child.is_multi()):
363                 child.setowner(self, ['default' for i in range(len(child.getdefault()))])
364                 self._cfgimpl_values[name] = Multi(copy(child.getdefault()),
365                             config=self, child=child)
366             else:
367                 child.setowner(self, newowner)
368         else:
369             homeconfig = self._cfgimpl_get_toplevel()
370             child.setoption(homeconfig, value, who)
371
372     def set(self, **kwargs):
373         """
374         "do what I mean"-interface to option setting. Searches all paths
375         starting from that config for matches of the optional arguments
376         and sets the found option if the match is not ambiguous.
377         :param kwargs: dict of name strings to values.
378         """
379         all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
380         for key, value in kwargs.iteritems():
381             key_p = key.split('.')
382             candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
383             if len(candidates) == 1:
384                 name = '.'.join(candidates[0])
385                 homeconfig, name = self._cfgimpl_get_home_by_path(name)
386                 try:
387                     getattr(homeconfig, name)
388                 except MandatoryError:
389                     pass
390                 except Exception, e:
391                     raise e # HiddenOptionError or DisabledOptionError
392                 homeconfig.setoption(name, value, self._cfgimpl_owner)
393             elif len(candidates) > 1:
394                 raise AmbigousOptionError(
395                     'more than one option that ends with %s' % (key, ))
396             else:
397                 raise NoMatchingOptionFound(
398                     'there is no option that matches %s'
399                     ' or the option is hidden or disabled'% (key, ))
400
401     def get(self, name):
402         """
403         same as a find_first() method in a config that has identical names
404         that is : Returns the first item of an option named 'name'
405
406         much like the attribute access way, except that
407         the search for the option is performed recursively in the whole
408         configuration tree.
409         **carefull**: very slow !
410
411         :returns: option value.
412         """
413         paths = self.getpaths(allpaths=True)
414         pathsvalues = []
415         for path in paths:
416             pathname = path.split('.')[-1]
417             if pathname == name:
418                 try:
419                     value = getattr(self, path)
420                     return value
421                 except Exception, e:
422                     raise e
423         raise NotFoundError("option {0} not found in config".format(name))
424
425     def _cfgimpl_get_home_by_path(self, path):
426         """:returns: tuple (config, name)"""
427         path = path.split('.')
428         for step in path[:-1]:
429             self = getattr(self, step)
430         return self, path[-1]
431
432     def _cfgimpl_get_toplevel(self):
433         ":returns: root config"
434         while self._cfgimpl_parent is not None:
435             self = self._cfgimpl_parent
436         return self
437
438     def _cfgimpl_get_path(self):
439         "the path in the attribute access meaning."
440         subpath = []
441         obj = self
442         while obj._cfgimpl_parent is not None:
443             subpath.insert(0, obj._cfgimpl_descr._name)
444             obj = obj._cfgimpl_parent
445         return ".".join(subpath)
446     # ______________________________________________________________________
447     def cfgimpl_previous_value(self, path):
448         "stores the previous value"
449         home, name = self._cfgimpl_get_home_by_path(path)
450         return home._cfgimpl_previous_values[name]
451
452     def get_previous_value(self, name):
453         "for the time being, only the previous Option's value is accessible"
454         return self._cfgimpl_previous_values[name]
455     # ______________________________________________________________________
456     def add_warning(self, warning):
457         "Config implements its own warning pile. Could be useful"
458         self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
459
460     def get_warnings(self):
461         "Config implements its own warning pile"
462         return self._cfgimpl_get_toplevel()._cfgimpl_warnings
463     # ____________________________________________________________
464     # Config()'s status
465     def cfgimpl_freeze(self):
466         "cannot modify the frozen `Option`'s"
467         rootconfig = self._cfgimpl_get_toplevel()
468         rootconfig._cfgimpl_frozen = True
469         self._cfgimpl_frozen = True
470
471     def cfgimpl_unfreeze(self):
472         "can modify the Options that are frozen"
473         rootconfig = self._cfgimpl_get_toplevel()
474         rootconfig._cfgimpl_frozen = False
475         self._cfgimpl_frozen = False
476
477     def is_frozen(self):
478         "freeze flag at Config level"
479         rootconfig = self._cfgimpl_get_toplevel()
480         return rootconfig._cfgimpl_frozen
481
482     def cfgimpl_read_only(self):
483         "convenience method to freeze, hidde and disable"
484         self.cfgimpl_freeze()
485         rootconfig = self._cfgimpl_get_toplevel()
486         rootconfig.cfgimpl_disable_property('hidden')
487         rootconfig.cfgimpl_enable_property('disabled')
488         rootconfig._cfgimpl_mandatory = True
489
490     def cfgimpl_read_write(self):
491         "convenience method to freeze, hidde and disable"
492         self.cfgimpl_freeze()
493         rootconfig = self._cfgimpl_get_toplevel()
494         rootconfig.cfgimpl_enable_property('hidden')
495         rootconfig.cfgimpl_enable_property('disabled')
496         rootconfig._cfgimpl_mandatory = False
497
498     def cfgimpl_non_mandatory(self):
499         """mandatory at the Config level means that the Config raises an error
500         if a mandatory option is found"""
501         if self._cfgimpl_parent != None:
502             raise MethodCallError("this method root_mandatory shall"
503                                   " not be used with non-root Confit() object")
504         rootconfig = self._cfgimpl_get_toplevel()
505         rootconfig._cfgimpl_mandatory = False
506
507     def cfgimpl_mandatory(self):
508         """mandatory at the Config level means that the Config raises an error
509         if a mandatory option is found"""
510         if self._cfgimpl_parent != None:
511             raise MethodCallError("this method root_mandatory shall"
512                                   " not be used with non-root Confit() object")
513         rootconfig = self._cfgimpl_get_toplevel()
514         rootconfig._cfgimpl_mandatory = True
515
516     def is_mandatory(self):
517         "all mandatory Options shall have a value"
518         rootconfig = self._cfgimpl_get_toplevel()
519         return rootconfig._cfgimpl_mandatory
520     # ____________________________________________________________
521     def getkey(self):
522         return self._cfgimpl_descr.getkey(self)
523
524     def __hash__(self):
525         return hash(self.getkey())
526
527     def __eq__(self, other):
528         "Config comparison"
529         return self.getkey() == other.getkey()
530
531     def __ne__(self, other):
532         "Config comparison"
533         return not self == other
534     # ______________________________________________________________________
535     def __iter__(self):
536         "iteration only on Options (not OptionDescriptions)"
537         for child in self._cfgimpl_descr._children:
538             if isinstance(child, Option):
539                 try:
540                     yield child._name, getattr(self, child._name)
541                 except:
542                     pass # option with properties
543
544     def iter_groups(self, group_type=None):
545         "iteration on OptionDescriptions"
546         if group_type == None:
547             groups = group_types
548         else:
549             if group_type not in group_types:
550                 raise TypeError("Unknown group_type: {0}".format(group_type))
551             groups = [group_type]
552         for child in self._cfgimpl_descr._children:
553             if isinstance(child, OptionDescription):
554                     try:
555                         if child.get_group_type() in groups:
556                             yield child._name, getattr(self, child._name)
557                     except:
558                         pass # hidden, disabled option
559     # ______________________________________________________________________
560     def __str__(self, indent=""):
561         "Config's string representation"
562         lines = []
563         children = [(child._name, child)
564                     for child in self._cfgimpl_descr._children]
565         children.sort()
566         for name, child in children:
567             if self._cfgimpl_value_owners.get(name, None) == 'default':
568                 continue
569             value = getattr(self, name)
570             if isinstance(value, Config):
571                 substr = value.__str__(indent + "    ")
572             else:
573                 substr = "%s    %s = %s" % (indent, name, value)
574             if substr:
575                 lines.append(substr)
576         if indent and not lines:
577             return ''   # hide subgroups with all default values
578         lines.insert(0, "%s[%s]" % (indent, self._cfgimpl_descr._name,))
579         return '\n'.join(lines)
580
581     def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
582         """returns a list of all paths in self, recursively, taking care of
583         the context of properties (hidden/disabled)
584
585         :param include_groups: if true, OptionDescription are included
586         :param allpaths: all the options (event the properties protected ones)
587         :param mandatory: includes the mandatory options
588         :returns: list of all paths
589         """
590         paths = []
591         for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
592             try:
593                 value = getattr(self, path)
594
595             except MandatoryError:
596                 if mandatory or allpaths:
597                     paths.append(path)
598             except PropertiesOptionError:
599                 if allpaths:
600                     paths.append(path) # option which have properties added
601             else:
602                  paths.append(path)
603         return paths
604
605     def _find(self, bytype, byname, byvalue, byattrs, first):
606         """
607             :param first: return only one option if True, a list otherwise
608         """
609         def _filter_by_attrs():
610             if byattrs is None:
611                 return True
612             for key, value in byattrs.items():
613                 if not hasattr(option, key):
614                     return False
615                 else:
616                     if getattr(option, key) != value:
617                         return False
618                     else:
619                         continue
620             return True
621         def _filter_by_name():
622             if byname is None:
623                 return True
624             pathname = path.split('.')[-1]
625             if pathname == byname:
626                 return True
627             else:
628                 return False
629         def _filter_by_value():
630             if byvalue is None:
631                 return True
632             try:
633                 value = getattr(self, path)
634                 if value == byvalue:
635                     return True
636             except Exception, e: # a property restricts the acces to value
637                 pass
638             return False
639         def _filter_by_type():
640             if bytype is None:
641                 return True
642             if isinstance(option, bytype):
643                 return True
644             return False
645
646         find_results = []
647         paths = self.getpaths(allpaths=True)
648         for path in paths:
649             option = self.unwrap_from_path(path)
650             if not _filter_by_name():
651                 continue
652             if not _filter_by_value():
653                 continue
654             if not _filter_by_type():
655                 continue
656             if not _filter_by_attrs():
657                 continue
658             if first:
659                 return option
660             else:
661                 find_results.append(option)
662         if first:
663             return None
664         else:
665             return find_results
666
667     def find(self, bytype=None, byname=None, byvalue=None, byattrs=None):
668         """
669             finds a list of options recursively in the config
670
671             :param bytype: Option class (BoolOption, StrOption, ...)
672             :param byname: filter by Option._name
673             :param byvalue: filter by the option's value
674             :param byattrs: dict of option attributes (default, callback...)
675             :returns: list of matching Option objects
676         """
677         return self._find(bytype, byname, byvalue, byattrs, first=False)
678
679     def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None):
680         """
681             finds an option recursively in the config
682
683             :param bytype: Option class (BoolOption, StrOption, ...)
684             :param byname: filter by Option._name
685             :param byvalue: filter by the option's value
686             :param byattrs: dict of option attributes (default, callback...)
687             :returns: list of matching Option objects
688         """
689         return self._find(bytype, byname, byvalue, byattrs, first=True)
690
691 def make_dict(config, flatten=False):
692     """export the whole config into a `dict`
693     :returns: dict of Option's name (or path) and values"""
694     paths = config.getpaths()
695     pathsvalues = []
696     for path in paths:
697         if flatten:
698             pathname = path.split('.')[-1]
699         else:
700             pathname = path
701         try:
702             value = getattr(config, path)
703             pathsvalues.append((pathname, value))
704         except:
705             pass # this just a hidden or disabled option
706     options = dict(pathsvalues)
707     return options
708
709 def mandatory_warnings(config):
710     """convenience function to trace Options that are mandatory and
711     where no value has been set
712
713     :returns: generator of mandatory Option's path
714     """
715     mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory
716     config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True
717     for path in config._cfgimpl_descr.getpaths(include_groups=True):
718         try:
719             value = getattr(config, path)
720         except MandatoryError:
721             yield path
722         except PropertiesOptionError:
723             pass
724     config._cfgimpl_get_toplevel()._cfgimpl_mandatory = mandatory