1"""
2Provides an emulator/replacement for Python's standard import system.
3
4@@TR: Be warned that Import Hooks are in the deepest, darkest corner of Python's
5jungle.  If you need to start hacking with this, be prepared to get lost for a
6while. Also note, this module predates the newstyle import hooks in Python 2.3
7http://www.python.org/peps/pep-0302.html.
8
9
10This is a hacked/documented version of Gordon McMillan's iu.py. I have:
11
12  - made it a little less terse
13
14  - added docstrings and explanatations
15
16  - standardized the variable naming scheme
17
18  - reorganized the code layout to enhance readability
19
20"""
21
22import sys
23import imp
24import marshal
25
26_installed = False
27
28# _globalOwnerTypes is defined at the bottom of this file
29
30_os_stat = _os_path_join = _os_getcwd = _os_path_dirname = None
31
32##################################################
33## FUNCTIONS
34
35def _os_bootstrap():
36    """Set up 'os' module replacement functions for use during import bootstrap."""
37
38    names = sys.builtin_module_names
39
40    join = dirname = None
41    if 'posix' in names:
42        sep = '/'
43        from posix import stat, getcwd
44    elif 'nt' in names:
45        sep = '\\'
46        from nt import stat, getcwd
47    elif 'dos' in names:
48        sep = '\\'
49        from dos import stat, getcwd
50    elif 'os2' in names:
51        sep = '\\'
52        from os2 import stat, getcwd
53    elif 'mac' in names:
54        from mac import stat, getcwd
55        def join(a, b):
56            if a == '':
57                return b
58            if ':' not in a:
59                a = ':' + a
60            if a[-1:] != ':':
61                a = a + ':'
62            return a + b
63    else:
64        raise ImportError('no os specific module found')
65
66    if join is None:
67        def join(a, b, sep=sep):
68            if a == '':
69                return b
70            lastchar = a[-1:]
71            if lastchar == '/' or lastchar == sep:
72                return a + b
73            return a + sep + b
74
75    if dirname is None:
76        def dirname(a, sep=sep):
77            for i in range(len(a)-1, -1, -1):
78                c = a[i]
79                if c == '/' or c == sep:
80                    return a[:i]
81            return ''
82
83    global _os_stat
84    _os_stat = stat
85
86    global _os_path_join
87    _os_path_join = join
88
89    global _os_path_dirname
90    _os_path_dirname = dirname
91
92    global _os_getcwd
93    _os_getcwd = getcwd
94
95_os_bootstrap()
96
97def packageName(s):
98    for i in range(len(s)-1, -1, -1):
99        if s[i] == '.':
100            break
101    else:
102        return ''
103    return s[:i]
104
105def nameSplit(s):
106    rslt = []
107    i = j = 0
108    for j in range(len(s)):
109        if s[j] == '.':
110            rslt.append(s[i:j])
111            i = j+1
112    if i < len(s):
113        rslt.append(s[i:])
114    return rslt
115
116def getPathExt(fnm):
117    for i in range(len(fnm)-1, -1, -1):
118        if fnm[i] == '.':
119            return fnm[i:]
120    return ''
121
122def pathIsDir(pathname):
123    "Local replacement for os.path.isdir()."
124    try:
125        s = _os_stat(pathname)
126    except OSError:
127        return None
128    return (s[0] & 0170000) == 0040000
129
130def getDescr(fnm):
131    ext = getPathExt(fnm)
132    for (suffix, mode, typ) in imp.get_suffixes():
133        if suffix == ext:
134            return (suffix, mode, typ)
135
136##################################################
137## CLASSES
138
139class Owner:
140
141    """An Owner does imports from a particular piece of turf That is, there's
142    an Owner for each thing on sys.path There are owners for directories and
143    .pyz files.  There could be owners for zip files, or even URLs.  A
144    shadowpath (a dictionary mapping the names in sys.path to their owners) is
145    used so that sys.path (or a package's __path__) is still a bunch of strings,
146    """
147
148    def __init__(self, path):
149        self.path = path
150
151    def __str__(self):
152        return self.path
153
154    def getmod(self, nm):
155        return None
156
157class DirOwner(Owner):
158
159    def __init__(self, path):
160        if path == '':
161            path = _os_getcwd()
162        if not pathIsDir(path):
163            raise ValueError("%s is not a directory" % path)
164        Owner.__init__(self, path)
165
166    def getmod(self, nm,
167               getsuffixes=imp.get_suffixes, loadco=marshal.loads, newmod=imp.new_module):
168
169        pth =  _os_path_join(self.path, nm)
170
171        possibles = [(pth, 0, None)]
172        if pathIsDir(pth):
173            possibles.insert(0, (_os_path_join(pth, '__init__'), 1, pth))
174        py = pyc = None
175        for pth, ispkg, pkgpth in possibles:
176            for ext, mode, typ in getsuffixes():
177                attempt = pth+ext
178                try:
179                    st = _os_stat(attempt)
180                except:
181                    pass
182                else:
183                    if typ == imp.C_EXTENSION:
184                        fp = open(attempt, 'rb')
185                        mod = imp.load_module(nm, fp, attempt, (ext, mode, typ))
186                        mod.__file__ = attempt
187                        return mod
188                    elif typ == imp.PY_SOURCE:
189                        py = (attempt, st)
190                    else:
191                        pyc = (attempt, st)
192            if py or pyc:
193                break
194        if py is None and pyc is None:
195            return None
196        while True:
197            if pyc is None or py and pyc[1][8] < py[1][8]:
198                try:
199                    co = compile(open(py[0], 'r').read()+'\n', py[0], 'exec')
200                    break
201                except SyntaxError, e:
202                    print("Invalid syntax in %s" % py[0])
203                    print(e.args)
204                    raise
205            elif pyc:
206                stuff = open(pyc[0], 'rb').read()
207                try:
208                    co = loadco(stuff[8:])
209                    break
210                except (ValueError, EOFError):
211                    pyc = None
212            else:
213                return None
214        mod = newmod(nm)
215        mod.__file__ = co.co_filename
216        if ispkg:
217            mod.__path__ = [pkgpth]
218            subimporter = PathImportDirector(mod.__path__)
219            mod.__importsub__ = subimporter.getmod
220        mod.__co__ = co
221        return mod
222
223
224class ImportDirector(Owner):
225    """ImportDirectors live on the metapath There's one for builtins, one for
226    frozen modules, and one for sys.path Windows gets one for modules gotten
227    from the Registry Mac would have them for PY_RESOURCE modules etc.  A
228    generalization of Owner - their concept of 'turf' is broader"""
229
230    pass
231
232class BuiltinImportDirector(ImportDirector):
233    """Directs imports of builtin modules"""
234    def __init__(self):
235        self.path = 'Builtins'
236
237    def getmod(self, nm, isbuiltin=imp.is_builtin):
238        if isbuiltin(nm):
239            mod = imp.load_module(nm, None, nm, ('', '', imp.C_BUILTIN))
240            return mod
241        return None
242
243class FrozenImportDirector(ImportDirector):
244    """Directs imports of frozen modules"""
245
246    def __init__(self):
247        self.path = 'FrozenModules'
248
249    def getmod(self, nm,
250               isFrozen=imp.is_frozen, loadMod=imp.load_module):
251        if isFrozen(nm):
252            mod = loadMod(nm, None, nm, ('', '', imp.PY_FROZEN))
253            if hasattr(mod, '__path__'):
254                mod.__importsub__ = lambda name, pname=nm, owner=self: owner.getmod(pname+'.'+name)
255            return mod
256        return None
257
258
259class RegistryImportDirector(ImportDirector):
260    """Directs imports of modules stored in the Windows Registry"""
261
262    def __init__(self):
263        self.path = "WindowsRegistry"
264        self.map = {}
265        try:
266            import win32api
267            ## import win32con
268        except ImportError:
269            pass
270        else:
271            HKEY_CURRENT_USER = -2147483647
272            HKEY_LOCAL_MACHINE = -2147483646
273            KEY_ALL_ACCESS = 983103
274            subkey = r"Software\Python\PythonCore\%s\Modules" % sys.winver
275            for root in (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE):
276                try:
277                    hkey = win32api.RegOpenKeyEx(root, subkey, 0, KEY_ALL_ACCESS)
278                except:
279                    pass
280                else:
281                    numsubkeys, numvalues, lastmodified = win32api.RegQueryInfoKey(hkey)
282                    for i in range(numsubkeys):
283                        subkeyname = win32api.RegEnumKey(hkey, i)
284                        hskey = win32api.RegOpenKeyEx(hkey, subkeyname, 0, KEY_ALL_ACCESS)
285                        val = win32api.RegQueryValueEx(hskey, '')
286                        desc = getDescr(val[0])
287                        self.map[subkeyname] = (val[0], desc)
288                        hskey.Close()
289                    hkey.Close()
290                    break
291
292    def getmod(self, nm):
293        stuff = self.map.get(nm)
294        if stuff:
295            fnm, desc = stuff
296            fp = open(fnm, 'rb')
297            mod = imp.load_module(nm, fp, fnm, desc)
298            mod.__file__ = fnm
299            return mod
300        return None
301
302class PathImportDirector(ImportDirector):
303    """Directs imports of modules stored on the filesystem."""
304
305    def __init__(self, pathlist=None, importers=None, ownertypes=None):
306        if pathlist is None:
307            self.path = sys.path
308        else:
309            self.path = pathlist
310        if ownertypes == None:
311            self._ownertypes = _globalOwnerTypes
312        else:
313            self._ownertypes = ownertypes
314        if importers:
315            self._shadowPath = importers
316        else:
317            self._shadowPath = {}
318        self._inMakeOwner = False
319        self._building = {}
320
321    def getmod(self, nm):
322        mod = None
323        for thing in self.path:
324            if isinstance(thing, basestring):
325                owner = self._shadowPath.get(thing, -1)
326                if owner == -1:
327                    owner = self._shadowPath[thing] = self._makeOwner(thing)
328                if owner:
329                    mod = owner.getmod(nm)
330            else:
331                mod = thing.getmod(nm)
332            if mod:
333                break
334        return mod
335
336    def _makeOwner(self, path):
337        if self._building.get(path):
338            return None
339        self._building[path] = 1
340        owner = None
341        for klass in self._ownertypes:
342            try:
343                # this may cause an import, which may cause recursion
344                # hence the protection
345                owner = klass(path)
346            except:
347                pass
348            else:
349                break
350        del self._building[path]
351        return owner
352
353#=================ImportManager============================#
354# The one-and-only ImportManager
355# ie, the builtin import
356
357UNTRIED = -1
358
359class ImportManager:
360    # really the equivalent of builtin import
361    def __init__(self):
362        self.metapath = [
363            BuiltinImportDirector(),
364            FrozenImportDirector(),
365            RegistryImportDirector(),
366            PathImportDirector()
367        ]
368        self.threaded = 0
369        self.rlock = None
370        self.locker = None
371        self.setThreaded()
372
373    def setThreaded(self):
374        thread = sys.modules.get('thread', None)
375        if thread and not self.threaded:
376            self.threaded = 1
377            self.rlock = thread.allocate_lock()
378            self._get_ident = thread.get_ident
379
380    def install(self):
381        import __builtin__
382        __builtin__.__import__ = self.importHook
383        __builtin__.reload = self.reloadHook
384
385    def importHook(self, name, globals=None, locals=None, fromlist=None, level=-1):
386        '''
387            NOTE: Currently importHook will accept the keyword-argument "level"
388            but it will *NOT* use it (currently). Details about the "level" keyword
389            argument can be found here: http://www.python.org/doc/2.5.2/lib/built-in-funcs.html
390        '''
391        # first see if we could be importing a relative name
392        #print "importHook(%s, %s, locals, %s)" % (name, globals['__name__'], fromlist)
393        _sys_modules_get = sys.modules.get
394        contexts = [None]
395        if globals:
396            importernm = globals.get('__name__', '')
397            if importernm:
398                if hasattr(_sys_modules_get(importernm), '__path__'):
399                    contexts.insert(0, importernm)
400                else:
401                    pkgnm = packageName(importernm)
402                    if pkgnm:
403                        contexts.insert(0, pkgnm)
404        # so contexts is [pkgnm, None] or just [None]
405        # now break the name being imported up so we get:
406        # a.b.c -> [a, b, c]
407        nmparts = nameSplit(name)
408        _self_doimport = self.doimport
409        threaded = self.threaded
410        for context in contexts:
411            ctx = context
412            for i in range(len(nmparts)):
413                nm = nmparts[i]
414                #print " importHook trying %s in %s" % (nm, ctx)
415                if ctx:
416                    fqname = ctx + '.' + nm
417                else:
418                    fqname = nm
419                if threaded:
420                    self._acquire()
421                mod = _sys_modules_get(fqname, UNTRIED)
422                if mod is UNTRIED:
423                    mod = _self_doimport(nm, ctx, fqname)
424                if threaded:
425                    self._release()
426                if mod:
427                    ctx = fqname
428                else:
429                    break
430            else:
431                # no break, point i beyond end
432                i = i + 1
433            if i:
434                break
435
436        if i<len(nmparts):
437            if ctx and hasattr(sys.modules[ctx], nmparts[i]):
438                #print "importHook done with %s %s %s (case 1)" % (name, globals['__name__'], fromlist)
439                return sys.modules[nmparts[0]]
440            del sys.modules[fqname]
441            raise ImportError("No module named %s" % fqname)
442        if fromlist is None:
443            #print "importHook done with %s %s %s (case 2)" % (name, globals['__name__'], fromlist)
444            if context:
445                return sys.modules[context+'.'+nmparts[0]]
446            return sys.modules[nmparts[0]]
447        bottommod = sys.modules[ctx]
448        if hasattr(bottommod, '__path__'):
449            fromlist = list(fromlist)
450            i = 0
451            while i < len(fromlist):
452                nm = fromlist[i]
453                if nm == '*':
454                    fromlist[i:i+1] = list(getattr(bottommod, '__all__', []))
455                    if i >= len(fromlist):
456                        break
457                    nm = fromlist[i]
458                i = i + 1
459                if not hasattr(bottommod, nm):
460                    if self.threaded:
461                        self._acquire()
462                    mod = self.doimport(nm, ctx, ctx+'.'+nm)
463                    if self.threaded:
464                        self._release()
465                    if not mod:
466                        raise ImportError("%s not found in %s" % (nm, ctx))
467        #print "importHook done with %s %s %s (case 3)" % (name, globals['__name__'], fromlist)
468        return bottommod
469
470    def doimport(self, nm, parentnm, fqname):
471        # Not that nm is NEVER a dotted name at this point
472        #print "doimport(%s, %s, %s)" % (nm, parentnm, fqname)
473        if parentnm:
474            parent = sys.modules[parentnm]
475            if hasattr(parent, '__path__'):
476                importfunc = getattr(parent, '__importsub__', None)
477                if not importfunc:
478                    subimporter = PathImportDirector(parent.__path__)
479                    importfunc = parent.__importsub__ = subimporter.getmod
480                mod = importfunc(nm)
481                if mod:
482                    setattr(parent, nm, mod)
483            else:
484                #print "..parent not a package"
485                return None
486        else:
487            # now we're dealing with an absolute import
488            for director in self.metapath:
489                mod = director.getmod(nm)
490                if mod:
491                    break
492        if mod:
493            mod.__name__ = fqname
494            sys.modules[fqname] = mod
495            if hasattr(mod, '__co__'):
496                co = mod.__co__
497                del mod.__co__
498                exec(co, mod.__dict__)
499            if fqname == 'thread' and not self.threaded:
500##                print "thread detected!"
501                self.setThreaded()
502        else:
503            sys.modules[fqname] = None
504        #print "..found %s" % mod
505        return mod
506
507    def reloadHook(self, mod):
508        fqnm = mod.__name__
509        nm = nameSplit(fqnm)[-1]
510        parentnm = packageName(fqnm)
511        newmod = self.doimport(nm, parentnm, fqnm)
512        mod.__dict__.update(newmod.__dict__)
513##        return newmod
514
515    def _acquire(self):
516        if self.rlock.locked():
517            if self.locker == self._get_ident():
518                self.lockcount = self.lockcount + 1
519##                print "_acquire incrementing lockcount to", self.lockcount
520                return
521        self.rlock.acquire()
522        self.locker = self._get_ident()
523        self.lockcount = 0
524##        print "_acquire first time!"
525
526    def _release(self):
527        if self.lockcount:
528            self.lockcount = self.lockcount - 1
529##            print "_release decrementing lockcount to", self.lockcount
530        else:
531            self.rlock.release()
532##            print "_release releasing lock!"
533
534
535##################################################
536## MORE CONSTANTS & GLOBALS
537
538_globalOwnerTypes = [
539    DirOwner,
540    Owner,
541]
542