~washort/exocet/exocet-lite

« back to all changes in this revision

Viewing changes to exocet/_exocet.py

  • Committer: Allen Short
  • Date: 2011-01-23 21:17:11 UTC
  • mfrom: (17.1.3 trunk)
  • Revision ID: washort@divmod.com-20110123211711-bfow83ktepkn83v2
mergeĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- test-case-name: exocet.test.test_exocet -*-
2
 
# Copyright (c) 2010 Allen Short. See LICENSE file for details.
 
2
# Copyright (c) 2010-2011 Allen Short. See LICENSE file for details.
3
3
 
4
4
 
5
5
import sys, __builtin__, itertools
8
8
from zope.interface import Interface, implements
9
9
 
10
10
DEBUG = False
11
 
 
 
11
_sysModulesSpecialCases = {"os": ['path']}
12
12
 
13
13
def trace(*args):
14
14
    if DEBUG:
15
15
        print ' '.join(str(x) for x in args)
16
16
 
17
17
 
18
 
def proxyModule(original, **replacements):
19
 
    """
20
 
    Create a proxy for a module object, overriding some of its attributes with
21
 
    replacement objects.
22
 
 
23
 
    @param original: A module.
24
 
    @param replacements: Attribute names and objects to associate with them.
25
 
 
26
 
    @returns: A module proxy with attributes containing the replacement
27
 
    objects; other attribute accesses are delegated to the original module.
28
 
    """
29
 
    class _ModuleProxy(object):
30
 
       def __getattribute__(self, name):
31
 
           if name in replacements:
32
 
               return replacements[name]
33
 
           else:
34
 
               return getattr(original, name)
35
 
 
36
 
       def __repr__(self):
37
 
           return "<Proxy for %r: %s replaced>" % (
38
 
               original, ', '.join(replacements.keys()))
39
 
    return _ModuleProxy()
40
 
 
41
 
 
42
 
def redirectLocalImports(name, globals=None, *a, **kw):
43
 
    """
44
 
    Catch function-level imports in modules loaded via Exocet. This ensures
45
 
    that any imports done after module load time look up imported names in the
46
 
    same context the module was originally loaded in.
47
 
    """
48
 
 
49
 
    if globals is not None:
50
 
        mf = globals.get('__exocet_context__', None)
51
 
        if mf is not None:
52
 
            trace("__import__ of", name,  "called in exocet module", mf, mf.mapper)
53
 
            return _isolateImports(mf, _originalImport, name, globals, *a, **kw)
54
 
        else:
55
 
            return _originalImport(name, globals, *a, **kw)
56
 
    else:
57
 
        return _originalImport(name, globals, *a, **kw)
58
 
 
59
 
 
60
 
_originalImport = None
61
 
 
62
 
def installExocetGlobalHook():
63
 
    """
64
 
    Install the global Exocet import hook.
65
 
    """
66
 
    global _originalImport
67
 
    _originalImport = __builtin__.__import__
68
 
    __builtin__.__import__ = redirectLocalImports
69
 
 
70
 
 
71
 
 
72
 
def uninstallExocetGlobalHook():
73
 
    __builtin__.__import__ = _originalImport
74
 
 
75
 
 
76
 
installExocetGlobalHook()
77
 
 
78
18
class IMapper(Interface):
79
19
    """
80
20
    An object that maps names used in C{import} statements to objects (such as
274
214
            sys.modules.clear()
275
215
            sys.modules.update(self._oldSysModules)
276
216
            topLevel = _originalImport(name)
 
217
            trace("pep302Mapper imported %r as %r@%d" % (name, topLevel, id(topLevel)))
277
218
            packages = name.split(".")[1:]
278
219
            m = topLevel
 
220
            trace("subelements:", packages)
279
221
            for p in packages:
 
222
                trace("getattr", m, p)
280
223
                m = getattr(m, p)
 
224
            trace("done:", m, id(m))
281
225
            return m
282
226
        finally:
 
227
            self._oldSysModules.update(sys.modules)
283
228
            sys.meta_path[:] = prevMetaPath
284
229
            sys.modules.clear()
285
230
            sys.modules.update(prevSysModules)
325
270
        trace("load_module", fqn)
326
271
        trace("sys.modules", sys.modules)
327
272
        p = self.mapper.lookup(fqn)
 
273
        trace("load_module", fqn , "done", id(p))
 
274
 
 
275
        if fqn in _sysModulesSpecialCases:
 
276
        # This module didn't have access to our isolated sys.modules when it
 
277
        # did its sys.modules modification. Replicate it here.
 
278
            for submoduleName in _sysModulesSpecialCases[fqn]:
 
279
                subfqn = '.'.join([fqn, submoduleName])
 
280
                sys.modules[subfqn] = getattr(p, submoduleName, None)
328
281
        return p
329
282
 
330
283
 
333
286
        Wrapper around C{__import__}. Needed to ensure builtin modules aren't
334
287
        loaded from the global context.
335
288
        """
336
 
        trace("Import invoked:", name)
 
289
        trace("Import invoked:", name, kwargs.keys())
337
290
        if name in sys.builtin_module_names:
338
291
            trace("Loading builtin module", name)
339
292
            return self.load_module(name)
696
649
 
697
650
    return topological_sort(component_graph)
698
651
 
 
652
 
 
653
 
 
654
def proxyModule(original, **replacements):
 
655
    """
 
656
    Create a proxy for a module object, overriding some of its attributes with
 
657
    replacement objects.
 
658
 
 
659
    @param original: A module.
 
660
    @param replacements: Attribute names and objects to associate with them.
 
661
 
 
662
    @returns: A module proxy with attributes containing the replacement
 
663
    objects; other attribute accesses are delegated to the original module.
 
664
    """
 
665
    class _ModuleProxy(object):
 
666
       def __getattribute__(self, name):
 
667
           if name in replacements:
 
668
               return replacements[name]
 
669
           else:
 
670
               return getattr(original, name)
 
671
 
 
672
       def __repr__(self):
 
673
           return "<Proxy for %r: %s replaced>" % (
 
674
               original, ', '.join(replacements.keys()))
 
675
    return _ModuleProxy()
 
676
 
 
677
 
 
678
def redirectLocalImports(name, globals=None, *a, **kw):
 
679
    """
 
680
    Catch function-level imports in modules loaded via Exocet. This ensures
 
681
    that any imports done after module load time look up imported names in the
 
682
    same context the module was originally loaded in.
 
683
    """
 
684
 
 
685
    if globals is not None:
 
686
        mf = globals.get('__exocet_context__', None)
 
687
        if mf is not None:
 
688
            trace("isolated __import__ of", name,  "called in exocet module", mf, mf.mapper)
 
689
            return _isolateImports(mf, _originalImport, name, globals, *a, **kw)
 
690
        else:
 
691
            return _originalImport(name, globals, *a, **kw)
 
692
    else:
 
693
        return _originalImport(name, globals, *a, **kw)
 
694
 
 
695
 
 
696
_originalImport = None
 
697
 
 
698
def installExocetGlobalHook():
 
699
    """
 
700
    Install the global Exocet import hook.
 
701
    """
 
702
    global _originalImport
 
703
    _originalImport = __builtin__.__import__
 
704
    __builtin__.__import__ = redirectLocalImports
 
705
 
 
706
 
 
707
 
 
708
def uninstallExocetGlobalHook():
 
709
    __builtin__.__import__ = _originalImport
 
710
 
 
711
 
 
712
installExocetGlobalHook()
 
713