Break out hooke.util.pluggable.submods.
[hooke.git] / hooke / util / pluggable.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
9 #
10 # Hooke 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 Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke.  If not, see
17 # <http://www.gnu.org/licenses/>.
18
19 """`pluggable`
20 """
21
22 from ..compat.odict import odict
23 from .graph import Node, Graph
24
25
26 class IsSubclass (object):
27     """A safe subclass comparator.
28     
29     Examples
30     --------
31
32     >>> class A (object):
33     ...     pass
34     >>> class B (A):
35     ...     pass
36     >>> C = 5
37     >>> is_subclass = IsSubclass(A)
38     >>> is_subclass(A)
39     True
40     >>> is_subclass = IsSubclass(A, blacklist=[A])
41     >>> is_subclass(A)
42     False
43     >>> is_subclass(B)
44     True
45     >>> is_subclass(C)
46     False
47     """
48     def __init__(self, base_class, blacklist=None):
49         self.base_class = base_class
50         if blacklist == None:
51             blacklist = []
52         self.blacklist = blacklist
53     def __call__(self, other):
54         try:
55             subclass = issubclass(other, self.base_class)
56         except TypeError:
57             return False
58         if other in self.blacklist:
59             return False
60         return subclass
61
62
63 def submods(this_modname, submodnames):
64     """Iterate through (submodname, submod) pairs.
65     """
66     for submodname in submodnames:
67         count = len([s for s in submodnames if s == submodname])
68         assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
69         assert count == 1, 'Multiple (%d) %s entries: %s' \
70             % (count, submodname, submodnames)
71         this_mod = __import__(this_modname, fromlist=[submodname])
72         submod = getattr(this_mod, submodname)
73         yield (submodname, submod)
74
75
76 def construct_odict(this_modname, submodnames, class_selector,
77                     instantiate=True):
78     """Search the submodules `submodnames` of a module `this_modname`
79     for class objects for which `class_selector(class)` returns
80     `True`.  If `instantiate == True` these classes are instantiated
81     and stored in the returned :class:`hooke.compat.odict.odict` in
82     the order in which they were discovered.  Otherwise, the class
83     itself is stored.
84     """
85     objs = odict()
86     for submodname,submod in submods(this_modname, submodnames):
87         for objname in dir(submod):
88             obj = getattr(submod, objname)
89             if class_selector(obj):
90                 if instantiate == True:
91                     obj = obj()
92                 name = getattr(obj, 'name', submodname)
93                 objs[name] = obj
94     return objs
95
96
97 def construct_graph(this_modname, submodnames, class_selector,
98                     assert_name_match=True):
99     """Search the submodules `submodnames` of a module `this_modname`
100     for class objects for which `class_selector(class)` returns
101     `True`.  These classes are instantiated, and the `instance.name`
102     is compared to the `submodname` (if `assert_name_match` is
103     `True`).
104
105     The instances are further arranged into a dependency
106     :class:`hooke.util.graph.Graph` according to their
107     `instance.dependencies()` values.  The topologically sorted graph
108     is returned.
109     """
110     instances = {}
111     for submodname,submod in submods(this_modname, submodnames):
112         for objname in dir(submod):
113             obj = getattr(submod, objname)
114             if class_selector(obj):
115                 instance = obj()
116                 if assert_name_match == True and instance.name != submodname:
117                     raise Exception(
118                         'Instance name %s does not match module name %s'
119                         % (instance.name, submodname))
120                 instances[instance.name] = instance
121     nodes = {}
122     for i in instances.values():     # make nodes for each instance
123         nodes[i.name] = Node(data=i)
124     for n in nodes.values():         # fill in dependencies for each node
125         n.extend([nodes[name] for name in n.data.dependencies()])
126     graph = Graph(nodes.values())
127     graph.topological_sort()
128     return graph