*** empty log message ***
[pli.git] / pli / apps / pluginloader2.py
blobc8cdb19723ad2f77a679af3a07a91024f2988793
1 #=======================================================================
3 __version__ = '''0.1.02'''
4 __sub_version__ = '''20051204044022'''
5 __copyright__ = '''(c) Alex A. Nannou 2004'''
7 __doc__ = '''\
8 '''
11 #-----------------------------------------------------------------------
13 import os
14 import sys
15 import types
17 import pli.importutils as importutils
18 import pli.event as event
19 import pli.misc.extendedtypes as exttypes
22 #-----------------------------------------------------------------------
23 #-----------------------------------------------PluginDependencyError---
24 class PluginDependencyError(Exception):
25 '''
26 '''
27 pass
30 #--------------------------------------------------------onPluginLoad---
31 class onPluginLoad(event.Event):
32 '''
34 the callback will recive the name of the plugin package and the
35 module object....
36 '''
40 #-----------------------------------------------------------------------
41 # TODO a more basic version with method callbacks intead of events...
42 # TODO more docs...
43 # TODO cleanup code...
44 #-------------------------------------------------------------Plugins---
45 class Plugins(object):
46 '''
47 '''
48 __packageiter__ = staticmethod(importutils.packageiter)
50 def __init__(self, path=None, prefix=None):
51 '''
52 '''
53 if path == None:
54 # by default get path relative to the module this was
55 # called from.... (????)
56 # (e.g. import all that is on callres level...)
57 i = 0
58 while True:
59 f_locals = sys._getframe(frame_depth+i).f_locals
60 if '__file__' in f_locals:
61 break
62 i += 1
63 path = os.path.dirname(f_locals['__file__'])
64 if prefix == None:
65 # the prefix is same as caller...
66 n = f_locals['__name__']
67 if n != '__main__':
68 prefix = n
69 elif type(path) == types.ModuleType:
70 mod = path
71 # get path...
72 path = os.path.dirname(mod.__file__)
73 if prefix == None and mod.__name__ != '__main__':
74 # get prefix...
75 prefix = mod.__name__
76 elif exttypes.isstring(path):
77 pass
78 ## self.path = path
79 if prefix == None:
80 # normalize the path...
81 f = os.path.normpath(path)
82 # guess the prefix
83 # see if it is in sys.modules...
84 for n, v in sys.modules.iteritems():
85 if hasattr(v, '__file__') and v.__file__.startswith(f) and v.__name__ != None:
86 prefix = v.__name__
87 break
88 # Q: is there anythong else we could do here???
89 ##!!!
90 else:
91 raise TypeError, 'the path may either be a string (or unicode), a package or None (got: %s).' % path
93 self.path = path
94 self.prefix = prefix
95 self.plugins = {}
96 ##!!!
97 self.sorted_plugins = None
99 # custumization methods...
100 def __getplugins__(self):
102 this will get a list of loadable plugins.
104 NOTE: this will override the old list.
105 NOTE: this will actually import all the found modules, regardless of
106 if they are plugins or not.
108 prefix = self.prefix
109 plugins = self.plugins
110 plugins.clear()
111 isplugin = self.__isplugin__
112 for n in self.__packageiter__(self.path):
113 # import the plugin...
114 name = prefix + '.' + n
115 ##!!! correct the context...
116 o = __import__(name)
117 for nn in name.split('.')[1:]:
118 o = getattr(o, nn)
119 # check the module...
120 if isplugin(o):
121 ## plugins[n] = o
122 plugins[name] = o
123 return plugins
125 # the folowing are stubs...
126 def __isplugin__(self, obj):
128 this will test a plugin for compliancy.
130 NOTE: this will not load the plugin.
132 return True
133 def __cmpplugins__(self, a, b):
136 ##!!!
137 return 0
138 # NOTE: this should call the onPluginLoad event...
139 def __loadplugin__(self, plugin):
142 raise NotImplementedError
143 def __unloadplugin__(self, plugin):
146 raise NotImplementedError
148 def __sortplugins__(self):
151 plugins = self.sorted_plugins = self.plugins.values()
152 if plugins == []:
153 return
154 plugins.sort(cmp=self.__cmpplugins__)
156 # actions....
157 def read(self):
159 this will read the plugins, their configuration and dependencies.
161 self.__getplugins__()
162 self.__sortplugins__()
163 # XXX do more checks... (are the plugins read before this??)
164 def load(self):
166 this will load the plugins in order of dependency.
168 load = self.__loadplugin__
169 for p in self.sorted_plugins:
170 load(p)
171 # XXX should the order of the unload be reversed???
172 def unload(self):
175 load = self.__unloadplugin__
176 for p in self.sorted_plugins:
177 unload(p)
179 # interface methods...
180 ##!!!
183 #-----------------------------------------------PluginsDependencyErro---
184 class PluginsDependencyErro(Exception):
187 pass
190 #---------------------------------------------PluginsWithDependencies---
191 class PluginsWithDependencies(Plugins):
194 # NOTE: __plugindepends__ and __builddependencies__ are called
195 # AFTER all plugins are imported...
196 def __plugindepends__(self, plugin):
198 this will get object dependencies.
200 NOTE: this should only provide direct dependencies.
202 return []
203 def __builddependencies__(self):
205 this will build the dependency tree.
207 NOTE: this will break if a dependency loop is detected.
209 res = self._dependency_cache = {}
210 getdeps = self.__plugindepends__
211 for plugin in self.plugins.values():
212 # NOTE: here we will exclude the root from the tree...
213 ##!!! itertree
214 deps = res[plugin] = list(itertree(plugin, getdeps))[1:]
215 return res
216 def __cmpplugins__(self, a, b):
219 res = 0
220 if b in self._dependency_cache[a]:
221 res = 1
222 if a in self._dependency_cache[b]:
223 if res != 0:
224 raise PluginDependencyError, 'cyclic dependency error (%s, %s).' % (a, b)
225 res = -1
226 return res
227 def __sortplugins__(self):
230 self.__builddependencies__()
231 super(PluginsWithDependencies, self).__sortplugins__()
241 #-----------------------------------------------------------------------
242 ##!!! MOVE THIS TO A SEPORATE MODULE...
243 #------------------------------------------------------------TreeErro---
244 class TreeError(Exception):
247 pass
250 #-----------------------------------------------------------------------
251 WALK_DEPTH = 0
252 WALK_BREADTH = 1
254 LOOP_ERR = 0
255 LOOP_IGNORE = 1
257 #------------------------------------------------------------itertree---
258 def itertree(tree, getchildren=None, mode=WALK_BREADTH, onloop=LOOP_ERR):
260 return a list of nodes of a tree.
262 seen = [tree]
263 yield tree
264 l = getchildren(tree)
265 while l != []:
266 c = l.pop(0)
267 if c in seen:
268 # complain...
269 if onloop == LOOP_ERR:
270 raise TreeError, 'loop detected.'
271 # ignore the looping branch and continue...
272 if onLoop == LOOP_IGNORE:
273 continue
274 # sanity check...
275 raise TypeError, 'unknown loop handler event code.'
276 seen += [c]
277 yield c
278 if mode == WALK_BREADTH:
279 l += getchildren(c)
280 elif mode == WALK_DEPTH:
281 l[:0] = getchildren(c)
282 else:
283 raise TypeError, 'unknown mode (%s).' % mode
287 #-----------------------------------------------------------------------
288 if __name__ == '__main__':
290 t = [((1, 2), (3, (4, 5)),), 6]
291 ## t += [t]
293 def gc(o):
296 if type(o) is int:
297 return []
298 return list(o)
300 print list(itertree(t, gc, WALK_BREADTH))
301 print list(itertree(t, gc, WALK_DEPTH))
306 #=======================================================================
307 # vim:set ts=4 sw=4 nowrap :