1 # Copyright (C) 2010 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
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
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/>.
19 """``pluggable`` provides utility functions for extensible plugin modules.
24 from ..compat.odict import odict
25 from .graph import Node, Graph
28 class IsSubclass (object):
29 """A safe subclass comparator.
39 >>> is_subclass = IsSubclass(A)
42 >>> is_subclass = IsSubclass(A, blacklist=[A])
50 def __init__(self, base_class, blacklist=None):
51 self.base_class = base_class
54 self.blacklist = blacklist
55 def __call__(self, other):
57 subclass = issubclass(other, self.base_class)
60 if other in self.blacklist:
65 def submods(this_modname, submodnames):
66 """Iterate through (submodname, submod) pairs.
68 for submodname in submodnames:
69 count = len([s for s in submodnames if s == submodname])
70 assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
71 assert count == 1, 'Multiple (%d) %s entries: %s' \
72 % (count, submodname, submodnames)
74 this_mod = __import__(this_modname, fromlist=[submodname])
75 except ImportError, e:
76 # Use the root logger because the 'hooke' logger is
77 # configured by a Hooke instance after module imports.
78 logging.warn('could not import %s from %s: %s'
79 % (submodname, this_modname, e))
81 submod = getattr(this_mod, submodname)
82 yield (submodname, submod)
85 def construct_odict(this_modname, submodnames, class_selector,
87 """Search the submodules `submodnames` of a module `this_modname`
88 for class objects for which `class_selector(class)` returns
89 `True`. If `instantiate == True` these classes are instantiated
90 and stored in the returned :class:`hooke.compat.odict.odict` in
91 the order in which they were discovered. Otherwise, the class
95 for submodname,submod in submods(this_modname, submodnames):
96 for objname in dir(submod):
97 obj = getattr(submod, objname)
98 if class_selector(obj):
99 if instantiate == True:
101 name = getattr(obj, 'name', submodname)
106 def construct_graph(this_modname, submodnames, class_selector,
107 assert_name_match=True):
108 """Search the submodules `submodnames` of a module `this_modname`
109 for class objects for which `class_selector(class)` returns
110 `True`. These classes are instantiated, and the `instance.name`
111 is compared to the `submodname` (if `assert_name_match` is
114 The instances are further arranged into a dependency
115 :class:`hooke.util.graph.Graph` according to their
116 `instance.dependencies()` values. The topologically sorted graph
120 for submodname,submod in submods(this_modname, submodnames):
121 for objname in dir(submod):
122 obj = getattr(submod, objname)
123 if class_selector(obj):
125 if assert_name_match == True and instance.name != submodname:
127 'Instance name %s does not match module name %s'
128 % (instance.name, submodname))
129 instances[instance.name] = instance
131 for i in instances.values(): # make nodes for each instance
132 nodes[i.name] = Node(data=i)
133 for n in nodes.values(): # fill in dependencies for each node
134 n.extend([nodes[name] for name in n.data.dependencies()])
135 graph = Graph(nodes.values())
136 graph.topological_sort()