PageRenderTime 329ms CodeModel.GetById 80ms app.highlight 147ms RepoModel.GetById 94ms app.codeStats 1ms

/Lib/ihooks.py

http://unladen-swallow.googlecode.com/
Python | 522 lines | 508 code | 0 blank | 14 comment | 7 complexity | 67ed500d4eedd423c860272980edc72f MD5 | raw file
  1"""Import hook support.
  2
  3Consistent use of this module will make it possible to change the
  4different mechanisms involved in loading modules independently.
  5
  6While the built-in module imp exports interfaces to the built-in
  7module searching and loading algorithm, and it is possible to replace
  8the built-in function __import__ in order to change the semantics of
  9the import statement, until now it has been difficult to combine the
 10effect of different __import__ hacks, like loading modules from URLs
 11by rimport.py, or restricted execution by rexec.py.
 12
 13This module defines three new concepts:
 14
 151) A "file system hooks" class provides an interface to a filesystem.
 16
 17One hooks class is defined (Hooks), which uses the interface provided
 18by standard modules os and os.path.  It should be used as the base
 19class for other hooks classes.
 20
 212) A "module loader" class provides an interface to search for a
 22module in a search path and to load it.  It defines a method which
 23searches for a module in a single directory; by overriding this method
 24one can redefine the details of the search.  If the directory is None,
 25built-in and frozen modules are searched instead.
 26
 27Two module loader class are defined, both implementing the search
 28strategy used by the built-in __import__ function: ModuleLoader uses
 29the imp module's find_module interface, while HookableModuleLoader
 30uses a file system hooks class to interact with the file system.  Both
 31use the imp module's load_* interfaces to actually load the module.
 32
 333) A "module importer" class provides an interface to import a
 34module, as well as interfaces to reload and unload a module.  It also
 35provides interfaces to install and uninstall itself instead of the
 36default __import__ and reload (and unload) functions.
 37
 38One module importer class is defined (ModuleImporter), which uses a
 39module loader instance passed in (by default HookableModuleLoader is
 40instantiated).
 41
 42The classes defined here should be used as base classes for extended
 43functionality along those lines.
 44
 45If a module importer class supports dotted names, its import_module()
 46must return a different value depending on whether it is called on
 47behalf of a "from ... import ..." statement or not.  (This is caused
 48by the way the __import__ hook is used by the Python interpreter.)  It
 49would also do wise to install a different version of reload().
 50
 51"""
 52from warnings import warnpy3k
 53warnpy3k("the ihooks module has been removed in Python 3.0", stacklevel=2)
 54del warnpy3k
 55
 56import __builtin__
 57import imp
 58import os
 59import sys
 60
 61__all__ = ["BasicModuleLoader","Hooks","ModuleLoader","FancyModuleLoader",
 62           "BasicModuleImporter","ModuleImporter","install","uninstall"]
 63
 64VERBOSE = 0
 65
 66
 67from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED
 68from imp import C_BUILTIN, PY_FROZEN, PKG_DIRECTORY
 69BUILTIN_MODULE = C_BUILTIN
 70FROZEN_MODULE = PY_FROZEN
 71
 72
 73class _Verbose:
 74
 75    def __init__(self, verbose = VERBOSE):
 76        self.verbose = verbose
 77
 78    def get_verbose(self):
 79        return self.verbose
 80
 81    def set_verbose(self, verbose):
 82        self.verbose = verbose
 83
 84    # XXX The following is an experimental interface
 85
 86    def note(self, *args):
 87        if self.verbose:
 88            self.message(*args)
 89
 90    def message(self, format, *args):
 91        if args:
 92            print format%args
 93        else:
 94            print format
 95
 96
 97class BasicModuleLoader(_Verbose):
 98
 99    """Basic module loader.
100
101    This provides the same functionality as built-in import.  It
102    doesn't deal with checking sys.modules -- all it provides is
103    find_module() and a load_module(), as well as find_module_in_dir()
104    which searches just one directory, and can be overridden by a
105    derived class to change the module search algorithm when the basic
106    dependency on sys.path is unchanged.
107
108    The interface is a little more convenient than imp's:
109    find_module(name, [path]) returns None or 'stuff', and
110    load_module(name, stuff) loads the module.
111
112    """
113
114    def find_module(self, name, path = None):
115        if path is None:
116            path = [None] + self.default_path()
117        for dir in path:
118            stuff = self.find_module_in_dir(name, dir)
119            if stuff: return stuff
120        return None
121
122    def default_path(self):
123        return sys.path
124
125    def find_module_in_dir(self, name, dir):
126        if dir is None:
127            return self.find_builtin_module(name)
128        else:
129            try:
130                return imp.find_module(name, [dir])
131            except ImportError:
132                return None
133
134    def find_builtin_module(self, name):
135        # XXX frozen packages?
136        if imp.is_builtin(name):
137            return None, '', ('', '', BUILTIN_MODULE)
138        if imp.is_frozen(name):
139            return None, '', ('', '', FROZEN_MODULE)
140        return None
141
142    def load_module(self, name, stuff):
143        file, filename, info = stuff
144        try:
145            return imp.load_module(name, file, filename, info)
146        finally:
147            if file: file.close()
148
149
150class Hooks(_Verbose):
151
152    """Hooks into the filesystem and interpreter.
153
154    By deriving a subclass you can redefine your filesystem interface,
155    e.g. to merge it with the URL space.
156
157    This base class behaves just like the native filesystem.
158
159    """
160
161    # imp interface
162    def get_suffixes(self): return imp.get_suffixes()
163    def new_module(self, name): return imp.new_module(name)
164    def is_builtin(self, name): return imp.is_builtin(name)
165    def init_builtin(self, name): return imp.init_builtin(name)
166    def is_frozen(self, name): return imp.is_frozen(name)
167    def init_frozen(self, name): return imp.init_frozen(name)
168    def get_frozen_object(self, name): return imp.get_frozen_object(name)
169    def load_source(self, name, filename, file=None):
170        return imp.load_source(name, filename, file)
171    def load_compiled(self, name, filename, file=None):
172        return imp.load_compiled(name, filename, file)
173    def load_dynamic(self, name, filename, file=None):
174        return imp.load_dynamic(name, filename, file)
175    def load_package(self, name, filename, file=None):
176        return imp.load_module(name, file, filename, ("", "", PKG_DIRECTORY))
177
178    def add_module(self, name):
179        d = self.modules_dict()
180        if name in d: return d[name]
181        d[name] = m = self.new_module(name)
182        return m
183
184    # sys interface
185    def modules_dict(self): return sys.modules
186    def default_path(self): return sys.path
187
188    def path_split(self, x): return os.path.split(x)
189    def path_join(self, x, y): return os.path.join(x, y)
190    def path_isabs(self, x): return os.path.isabs(x)
191    # etc.
192
193    def path_exists(self, x): return os.path.exists(x)
194    def path_isdir(self, x): return os.path.isdir(x)
195    def path_isfile(self, x): return os.path.isfile(x)
196    def path_islink(self, x): return os.path.islink(x)
197    # etc.
198
199    def openfile(self, *x): return open(*x)
200    openfile_error = IOError
201    def listdir(self, x): return os.listdir(x)
202    listdir_error = os.error
203    # etc.
204
205
206class ModuleLoader(BasicModuleLoader):
207
208    """Default module loader; uses file system hooks.
209
210    By defining suitable hooks, you might be able to load modules from
211    other sources than the file system, e.g. from compressed or
212    encrypted files, tar files or (if you're brave!) URLs.
213
214    """
215
216    def __init__(self, hooks = None, verbose = VERBOSE):
217        BasicModuleLoader.__init__(self, verbose)
218        self.hooks = hooks or Hooks(verbose)
219
220    def default_path(self):
221        return self.hooks.default_path()
222
223    def modules_dict(self):
224        return self.hooks.modules_dict()
225
226    def get_hooks(self):
227        return self.hooks
228
229    def set_hooks(self, hooks):
230        self.hooks = hooks
231
232    def find_builtin_module(self, name):
233        # XXX frozen packages?
234        if self.hooks.is_builtin(name):
235            return None, '', ('', '', BUILTIN_MODULE)
236        if self.hooks.is_frozen(name):
237            return None, '', ('', '', FROZEN_MODULE)
238        return None
239
240    def find_module_in_dir(self, name, dir, allow_packages=1):
241        if dir is None:
242            return self.find_builtin_module(name)
243        if allow_packages:
244            fullname = self.hooks.path_join(dir, name)
245            if self.hooks.path_isdir(fullname):
246                stuff = self.find_module_in_dir("__init__", fullname, 0)
247                if stuff:
248                    file = stuff[0]
249                    if file: file.close()
250                    return None, fullname, ('', '', PKG_DIRECTORY)
251        for info in self.hooks.get_suffixes():
252            suff, mode, type = info
253            fullname = self.hooks.path_join(dir, name+suff)
254            try:
255                fp = self.hooks.openfile(fullname, mode)
256                return fp, fullname, info
257            except self.hooks.openfile_error:
258                pass
259        return None
260
261    def load_module(self, name, stuff):
262        file, filename, info = stuff
263        (suff, mode, type) = info
264        try:
265            if type == BUILTIN_MODULE:
266                return self.hooks.init_builtin(name)
267            if type == FROZEN_MODULE:
268                return self.hooks.init_frozen(name)
269            if type == C_EXTENSION:
270                m = self.hooks.load_dynamic(name, filename, file)
271            elif type == PY_SOURCE:
272                m = self.hooks.load_source(name, filename, file)
273            elif type == PY_COMPILED:
274                m = self.hooks.load_compiled(name, filename, file)
275            elif type == PKG_DIRECTORY:
276                m = self.hooks.load_package(name, filename, file)
277            else:
278                raise ImportError, "Unrecognized module type (%r) for %s" % \
279                      (type, name)
280        finally:
281            if file: file.close()
282        m.__file__ = filename
283        return m
284
285
286class FancyModuleLoader(ModuleLoader):
287
288    """Fancy module loader -- parses and execs the code itself."""
289
290    def load_module(self, name, stuff):
291        file, filename, (suff, mode, type) = stuff
292        realfilename = filename
293        path = None
294
295        if type == PKG_DIRECTORY:
296            initstuff = self.find_module_in_dir("__init__", filename, 0)
297            if not initstuff:
298                raise ImportError, "No __init__ module in package %s" % name
299            initfile, initfilename, initinfo = initstuff
300            initsuff, initmode, inittype = initinfo
301            if inittype not in (PY_COMPILED, PY_SOURCE):
302                if initfile: initfile.close()
303                raise ImportError, \
304                    "Bad type (%r) for __init__ module in package %s" % (
305                    inittype, name)
306            path = [filename]
307            file = initfile
308            realfilename = initfilename
309            type = inittype
310
311        if type == FROZEN_MODULE:
312            code = self.hooks.get_frozen_object(name)
313        elif type == PY_COMPILED:
314            import marshal
315            file.seek(8)
316            code = marshal.load(file)
317        elif type == PY_SOURCE:
318            data = file.read()
319            code = compile(data, realfilename, 'exec')
320        else:
321            return ModuleLoader.load_module(self, name, stuff)
322
323        m = self.hooks.add_module(name)
324        if path:
325            m.__path__ = path
326        m.__file__ = filename
327        try:
328            exec code in m.__dict__
329        except:
330            d = self.hooks.modules_dict()
331            if name in d:
332                del d[name]
333            raise
334        return m
335
336
337class BasicModuleImporter(_Verbose):
338
339    """Basic module importer; uses module loader.
340
341    This provides basic import facilities but no package imports.
342
343    """
344
345    def __init__(self, loader = None, verbose = VERBOSE):
346        _Verbose.__init__(self, verbose)
347        self.loader = loader or ModuleLoader(None, verbose)
348        self.modules = self.loader.modules_dict()
349
350    def get_loader(self):
351        return self.loader
352
353    def set_loader(self, loader):
354        self.loader = loader
355
356    def get_hooks(self):
357        return self.loader.get_hooks()
358
359    def set_hooks(self, hooks):
360        return self.loader.set_hooks(hooks)
361
362    def import_module(self, name, globals={}, locals={}, fromlist=[]):
363        name = str(name)
364        if name in self.modules:
365            return self.modules[name] # Fast path
366        stuff = self.loader.find_module(name)
367        if not stuff:
368            raise ImportError, "No module named %s" % name
369        return self.loader.load_module(name, stuff)
370
371    def reload(self, module, path = None):
372        name = str(module.__name__)
373        stuff = self.loader.find_module(name, path)
374        if not stuff:
375            raise ImportError, "Module %s not found for reload" % name
376        return self.loader.load_module(name, stuff)
377
378    def unload(self, module):
379        del self.modules[str(module.__name__)]
380        # XXX Should this try to clear the module's namespace?
381
382    def install(self):
383        self.save_import_module = __builtin__.__import__
384        self.save_reload = __builtin__.reload
385        if not hasattr(__builtin__, 'unload'):
386            __builtin__.unload = None
387        self.save_unload = __builtin__.unload
388        __builtin__.__import__ = self.import_module
389        __builtin__.reload = self.reload
390        __builtin__.unload = self.unload
391
392    def uninstall(self):
393        __builtin__.__import__ = self.save_import_module
394        __builtin__.reload = self.save_reload
395        __builtin__.unload = self.save_unload
396        if not __builtin__.unload:
397            del __builtin__.unload
398
399
400class ModuleImporter(BasicModuleImporter):
401
402    """A module importer that supports packages."""
403
404    def import_module(self, name, globals=None, locals=None, fromlist=None):
405        parent = self.determine_parent(globals)
406        q, tail = self.find_head_package(parent, str(name))
407        m = self.load_tail(q, tail)
408        if not fromlist:
409            return q
410        if hasattr(m, "__path__"):
411            self.ensure_fromlist(m, fromlist)
412        return m
413
414    def determine_parent(self, globals):
415        if not globals or not "__name__" in globals:
416            return None
417        pname = globals['__name__']
418        if "__path__" in globals:
419            parent = self.modules[pname]
420            assert globals is parent.__dict__
421            return parent
422        if '.' in pname:
423            i = pname.rfind('.')
424            pname = pname[:i]
425            parent = self.modules[pname]
426            assert parent.__name__ == pname
427            return parent
428        return None
429
430    def find_head_package(self, parent, name):
431        if '.' in name:
432            i = name.find('.')
433            head = name[:i]
434            tail = name[i+1:]
435        else:
436            head = name
437            tail = ""
438        if parent:
439            qname = "%s.%s" % (parent.__name__, head)
440        else:
441            qname = head
442        q = self.import_it(head, qname, parent)
443        if q: return q, tail
444        if parent:
445            qname = head
446            parent = None
447            q = self.import_it(head, qname, parent)
448            if q: return q, tail
449        raise ImportError, "No module named " + qname
450
451    def load_tail(self, q, tail):
452        m = q
453        while tail:
454            i = tail.find('.')
455            if i < 0: i = len(tail)
456            head, tail = tail[:i], tail[i+1:]
457            mname = "%s.%s" % (m.__name__, head)
458            m = self.import_it(head, mname, m)
459            if not m:
460                raise ImportError, "No module named " + mname
461        return m
462
463    def ensure_fromlist(self, m, fromlist, recursive=0):
464        for sub in fromlist:
465            if sub == "*":
466                if not recursive:
467                    try:
468                        all = m.__all__
469                    except AttributeError:
470                        pass
471                    else:
472                        self.ensure_fromlist(m, all, 1)
473                continue
474            if sub != "*" and not hasattr(m, sub):
475                subname = "%s.%s" % (m.__name__, sub)
476                submod = self.import_it(sub, subname, m)
477                if not submod:
478                    raise ImportError, "No module named " + subname
479
480    def import_it(self, partname, fqname, parent, force_load=0):
481        if not partname:
482            raise ValueError, "Empty module name"
483        if not force_load:
484            try:
485                return self.modules[fqname]
486            except KeyError:
487                pass
488        try:
489            path = parent and parent.__path__
490        except AttributeError:
491            return None
492        partname = str(partname)
493        stuff = self.loader.find_module(partname, path)
494        if not stuff:
495            return None
496        fqname = str(fqname)
497        m = self.loader.load_module(fqname, stuff)
498        if parent:
499            setattr(parent, partname, m)
500        return m
501
502    def reload(self, module):
503        name = str(module.__name__)
504        if '.' not in name:
505            return self.import_it(name, name, None, force_load=1)
506        i = name.rfind('.')
507        pname = name[:i]
508        parent = self.modules[pname]
509        return self.import_it(name[i+1:], name, parent, force_load=1)
510
511
512default_importer = None
513current_importer = None
514
515def install(importer = None):
516    global current_importer
517    current_importer = importer or default_importer or ModuleImporter()
518    current_importer.install()
519
520def uninstall():
521    global current_importer
522    current_importer.uninstall()