Package conduit :: Module Module
[hide private]

Source Code for Module conduit.Module

  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   
19 -class ModuleManager(gobject.GObject):
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 #Fired when a new instantiatable DP becomes available. It is described via 30 #a wrapper because we do not actually instantiate it till later - to save memory 31 "dataprovider-available" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 32 gobject.TYPE_PYOBJECT]), #The DPW describing the new DP class 33 "dataprovider-unavailable" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 34 gobject.TYPE_PYOBJECT]), #The DPW describing the DP class which is now unavailable 35 # Fired when load_all has loaded every available modules 36 "all-modules-loaded" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), 37 # Fired when a syncset is created 38 "syncset-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 39 gobject.TYPE_PYOBJECT]), #The syncset that was added 40 } 41
42 - def __init__(self, dirs=None):
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 #Dict of loaded modulewrappers. key is wrapper.get_key() 51 #Stored seperate to the classes because dynamic dataproviders may 52 #use the same class but with different initargs (diff keys) 53 self.moduleWrappers = {} 54 #Files that could not be loaded properly 55 self.invalidFiles = [] 56 #Keep a ref to dataprovider factories so they are not collected 57 self.dataproviderFactories = [] 58 #scan all dirs for files in the right format (*Module/*Module.py) 59 self.filelist = self._build_filelist_from_directories(dirs)
60
61 - def _on_dynamic_dataprovider_added(self, monitor, dpw, klass):
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
69 - def _on_dynamic_dataprovider_removed(self, monitor, key):
70 log.info("Dynamic dataprovider (%s) unavailable by %s" % (key, monitor)) 71 self._remove_module(key)
72
73 - def _emit_available(self, dataproviderWrapper):
74 if dataproviderWrapper.module_type in ["source", "sink", "twoway"]: 75 self.emit("dataprovider-available", dataproviderWrapper)
76
77 - def _emit_unavailable(self, dataproviderWrapper):
78 if dataproviderWrapper.module_type in ["source", "sink", "twoway"]: 79 self.emit("dataprovider-unavailable", dataproviderWrapper)
80
81 - def _build_filelist_from_directories(self, directories=None):
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 #convert to abs path 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
112 - def _is_module(self, filename):
113 return filename.endswith("Module.py")
114
115 - def _is_module_dir(self, dirname):
116 return dirname.endswith("Module")
117
118 - def _append_module(self, wrapper, klass):
119 #Check if the wrapper is unique 120 key = wrapper.get_dnd_key() 121 if key not in self.moduleWrappers: 122 self.moduleWrappers[key] = wrapper 123 #Emit a signal because this wrapper is new 124 self._emit_available(wrapper) 125 else: 126 log.warn("Wrapper with key %s allready loaded" % key)
127
128 - def _remove_module(self, key):
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 #keep a ref for the signal emission 140 dpw = self.moduleWrappers[key] 141 142 # remove from moduleWrappers... 143 del self.moduleWrappers[key] 144 # notify everything that dp is no longer available 145 self._emit_unavailable(dpw)
146
147 - def _import_file(self, filename):
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
167 - def _load_modules_in_file(self, filename):
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 #Save the module (signal is emitted in _append_module) 183 self._append_module( 184 mod_wrapper, 185 klass 186 ) 187 elif infos["type"] == "dataprovider-factory": 188 # build a dict of kwargs to pass to factories 189 kwargs = { 190 "moduleManager": self, 191 } 192 #instantiate and store the factory 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