1 """
2 Classes associated with dynamic module loading
3
4 Copyright: John Stowers, 2006
5 License: GPLv2
6 """
7
8 import gobject
9 import os, os.path
10 import traceback
11 import pydoc
12 import logging
13 log = logging.getLogger("Module")
14
15 import conduit.dataproviders
16 import conduit.ModuleWrapper as ModuleWrapper
17 import conduit.Vfs as Vfs
18
20 """
21 Generic dynamic module loader for conduit. Given a path
22 it loads all modules in that directory, keeping them in an
23 internam array which may be returned via get_modules
24
25 Also manages dataprovider factories which make dataproviders available
26 at runtime
27 """
28 __gsignals__ = {
29
30
31 "dataprovider-available" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
32 gobject.TYPE_PYOBJECT]),
33 "dataprovider-unavailable" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
34 gobject.TYPE_PYOBJECT]),
35
36 "all-modules-loaded" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
37
38 "syncset-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
39 gobject.TYPE_PYOBJECT]),
40 }
41
43 """
44 @param dirs: A list of directories to search. Relative pathnames and paths
45 containing ~ will be expanded. If dirs is None the
46 ModuleLoader will not search for modules.
47 @type dirs: C{string[]}
48 """
49 gobject.GObject.__init__(self)
50
51
52
53 self.moduleWrappers = {}
54
55 self.invalidFiles = []
56
57 self.dataproviderFactories = []
58
59 self.filelist = self._build_filelist_from_directories(dirs)
60
62 """
63 Store the ipod so it can be retrieved later by the treeview/model
64 emit a signal so it is added to the GUI
65 """
66 log.info("Dynamic dataprovider (%s) available by %s" % (dpw, monitor))
67 self._append_module(dpw, klass)
68
70 log.info("Dynamic dataprovider (%s) unavailable by %s" % (key, monitor))
71 self._remove_module(key)
72
74 if dataproviderWrapper.module_type in ["source", "sink", "twoway"]:
75 self.emit("dataprovider-available", dataproviderWrapper)
76
78 if dataproviderWrapper.module_type in ["source", "sink", "twoway"]:
79 self.emit("dataprovider-unavailable", dataproviderWrapper)
80
82 """
83 Converts a given array of directories into a list
84 containing the filenames of all qualified modules. Recurses into
85 directories and adds files if they have the same name as the
86 directory in which they reside.
87 This method is automatically invoked by the constructor.
88 """
89 res = []
90 if not directories:
91 return res
92
93
94 directories = [os.path.abspath(os.path.expanduser(s)) for s in directories]
95
96 while len(directories) > 0:
97 d = directories.pop(0)
98 try:
99 if not os.path.exists(d):
100 continue
101 for i in os.listdir(d):
102 f = os.path.join(d,i)
103 if os.path.isfile(f) and self._is_module(f):
104 if os.path.basename(f) not in [os.path.basename(j) for j in res]:
105 res.append(f)
106 elif os.path.isdir(f) and self._is_module_dir(f):
107 directories.append(f)
108 except OSError, err:
109 log.warn("Error reading directory %s, skipping." % (d))
110 return res
111
113 return filename.endswith("Module.py")
114
116 return dirname.endswith("Module")
117
119
120 key = wrapper.get_dnd_key()
121 if key not in self.moduleWrappers:
122 self.moduleWrappers[key] = wrapper
123
124 self._emit_available(wrapper)
125 else:
126 log.warn("Wrapper with key %s allready loaded" % key)
127
129 """
130 Looks for a given key in the class registry and attempts to remove it
131
132 @param key: The key of the class to remove
133 """
134
135 if not key in self.moduleWrappers:
136 log.warn("Unable to remove class - it isn't available! (%s)" % key)
137 return
138
139
140 dpw = self.moduleWrappers[key]
141
142
143 del self.moduleWrappers[key]
144
145 self._emit_unavailable(dpw)
146
148 """
149 Tries to import the specified file. Returns the python module on succes.
150 Primarily for internal use. Note that the python module returned may actually
151 contain several more loadable modules.
152 """
153 mods = pydoc.importfile (filename)
154 try:
155 if (mods.MODULES): pass
156 except AttributeError:
157 log.warn("The file %s is not a valid module. Skipping." % (filename))
158 log.warn("A module must have the variable MODULES defined as a dictionary.")
159 raise
160 for modules, infos in mods.MODULES.items():
161 for i in ModuleWrapper.COMPULSORY_ATTRIBUTES:
162 if i not in infos:
163 log.warn("Class %s in file %s does define a %s attribute. Skipping." % (modules, filename, i))
164 raise Exception
165 return mods
166
168 """
169 Loads all modules in the given file
170 """
171 try:
172 mod = self._import_file(filename)
173 for modules, infos in mod.MODULES.items():
174 try:
175 klass = getattr(mod, modules)
176 if infos["type"] == "dataprovider" or infos["type"] == "converter":
177 mod_wrapper = ModuleWrapper.ModuleWrapper(
178 klass=klass,
179 initargs=(),
180 category=getattr(klass, "_category_", conduit.dataproviders.CATEGORY_TEST)
181 )
182
183 self._append_module(
184 mod_wrapper,
185 klass
186 )
187 elif infos["type"] == "dataprovider-factory":
188
189 kwargs = {
190 "moduleManager": self,
191 }
192
193 instance = klass(**kwargs)
194 self.dataproviderFactories.append(instance)
195 else:
196 log.warn("Class is an unknown type: %s" % klass)
197 except AttributeError:
198 log.warn("Could not find module %s in %s\n%s" % (modules,filename,traceback.format_exc()))
199 except Exception, e:
200 log.warn("Error loading the file: %s\n%s" % (filename, traceback.format_exc()))
201 self.invalidFiles.append(os.path.basename(filename))
202
203 - def load_all(self, whitelist, blacklist):
204 """
205 Loads all classes in the configured paths.
206
207 If whitelist and blacklist are supplied then the name of the file
208 is tested against them. Default policy is to load all modules unless
209 """
210 for f in self.filelist:
211 name