0a491cb67b9491f4719292c86697422a6fd749a6
[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 ..util.graph import Node, Graph
23
24
25 class IsSubclass (object):
26     """A safe subclass comparator.
27     
28     Examples
29     --------
30
31     >>> class A (object):
32     ...     pass
33     >>> class B (A):
34     ...     pass
35     >>> C = 5
36     >>> is_subclass = IsSubclass(A)
37     >>> is_subclass(A)
38     True
39     >>> is_subclass = IsSubclass(A, blacklist=[A])
40     >>> is_subclass(A)
41     False
42     >>> is_subclass(B)
43     True
44     >>> is_subclass(C)
45     False
46     """
47     def __init__(self, base_class, blacklist=None):
48         self.base_class = base_class
49         if blacklist == None:
50             blacklist = []
51         self.blacklist = blacklist
52     def __call__(self, other):
53         try:
54             subclass = issubclass(other, self.base_class)
55         except TypeError:
56             return False
57         if other in self.blacklist:
58             return False
59         return subclass
60
61 def construct_graph(this_modname, submodnames, class_selector,
62                     assert_name_match=True):
63     """Search the submodules `submodnames` of a module `this_modname`
64     for class objects for which `class_selector(class)` returns
65     `True`.  These classes are instantiated, and the `instance.name`
66     is compared to the `submodname` (if `assert_name_match` is
67     `True`).
68
69     The instances are further arranged into a dependency
70     :class:`hooke.util.graph.Graph` according to their
71     `instance.dependencies()` values.  The topologically sorted graph
72     is returned.
73     """
74     instances = {}
75     for submodname in submodnames:
76         count = len([s for s in submodnames if s == submodname])
77         assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
78         assert count == 1, 'Multiple (%d) %s entries: %s' \
79             % (count, submodname, submodnames)
80         this_mod = __import__(this_modname, fromlist=[submodname])
81         submod = getattr(this_mod, submodname)
82         for objname in dir(submod):
83             obj = getattr(submod, objname)
84             if class_selector(obj):
85                 instance = obj()
86                 if assert_name_match == True and instance.name != submodname:
87                     raise Exception(
88                         'Instance name %s does not match module name %s'
89                         % (instance.name, submodname))
90                 instances[instance.name] = instance
91     nodes = {}
92     for i in instances.values():     # make nodes for each instance
93         nodes[i.name] = Node(data=i)
94     for n in nodes.values():         # fill in dependencies for each node
95         n.extend([nodes[name] for name in n.data.dependencies()])
96     graph = Graph(nodes.values())
97     graph.topological_sort()
98     return graph