1 # Copyright (C) 2010-2012 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with Hooke. If not, see <http://www.gnu.org/licenses/>.
18 """``pluggable`` provides utility functions for extensible plugin modules.
23 from ..compat.odict import odict
24 from .graph import Node, Graph
27 class IsSubclass (object):
28 """A safe subclass comparator.
38 >>> is_subclass = IsSubclass(A)
41 >>> is_subclass = IsSubclass(A, blacklist=[A])
49 def __init__(self, base_class, blacklist=None):
50 self.base_class = base_class
53 self.blacklist = blacklist
54 def __call__(self, other):
56 subclass = issubclass(other, self.base_class)
59 if other in self.blacklist:
64 def submods(this_modname, submodnames):
65 """Iterate through (submodname, submod) pairs.
67 for submodname in submodnames:
68 count = len([s for s in submodnames if s == submodname])
69 assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
70 assert count == 1, 'Multiple (%d) %s entries: %s' \
71 % (count, submodname, submodnames)
73 this_mod = __import__(this_modname, fromlist=[submodname])
74 except ImportError, e:
75 # Use the root logger because the 'hooke' logger is
76 # configured by a Hooke instance after module imports.
77 logging.warn('could not import %s from %s: %s'
78 % (submodname, this_modname, e))
80 submod = getattr(this_mod, submodname)
81 yield (submodname, submod)
84 def construct_odict(this_modname, submodnames, class_selector,
86 """Search the submodules `submodnames` of a module `this_modname`
87 for class objects for which `class_selector(class)` returns
88 `True`. If `instantiate == True` these classes are instantiated
89 and stored in the returned :class:`hooke.compat.odict.odict` in
90 the order in which they were discovered. Otherwise, the class
94 for submodname,submod in submods(this_modname, submodnames):
95 for objname in dir(submod):
96 obj = getattr(submod, objname)
97 if class_selector(obj):
98 if instantiate == True:
102 logging.error('could not instantiate %s from %s: %s'
103 % (obj, submodname, e))
105 name = getattr(obj, 'name', submodname)
110 def construct_graph(this_modname, submodnames, class_selector,
111 assert_name_match=True):
112 """Search the submodules `submodnames` of a module `this_modname`
113 for class objects for which `class_selector(class)` returns
114 `True`. These classes are instantiated, and the `instance.name`
115 is compared to the `submodname` (if `assert_name_match` is
118 The instances are further arranged into a dependency
119 :class:`hooke.util.graph.Graph` according to their
120 `instance.dependencies()` values. The topologically sorted graph
124 for submodname,submod in submods(this_modname, submodnames):
125 for objname in dir(submod):
126 obj = getattr(submod, objname)
127 if class_selector(obj):
131 logging.error('could not instantiate %s from %s: %s'
132 % (obj, submodname, e))
134 if assert_name_match == True and instance.name != submodname:
136 'Instance name %s does not match module name %s'
137 % (instance.name, submodname))
138 instances[instance.name] = instance
140 for i in instances.values(): # make nodes for each instance
141 nodes[i.name] = Node(data=i)
142 for n in nodes.values(): # fill in dependencies for each node
143 n.extend([nodes[name] for name in n.data.dependencies()])
144 graph = Graph(nodes.values())
145 graph.topological_sort()