~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/python/modules.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_modules -*-
 
2
 
 
3
"""
 
4
This module aims to provide a unified, object-oriented view of Python's
 
5
runtime hierarchy.
 
6
 
 
7
Python is a very dynamic language with wide variety of introspection utilities.
 
8
However, these utilities can be hard to use, because there is no consistent
 
9
API.  The introspection API in python is made up of attributes (__name__,
 
10
__module__, func_name, etc) on instances, modules, classes and functions which
 
11
vary between those four types, utility modules such as 'inspect' which provide
 
12
some functionality, the 'imp' module, the "compiler" module, the semantics of
 
13
PEP 302 support, and setuptools, among other things.
 
14
 
 
15
At the top, you have "PythonPath", an abstract representation of sys.path which
 
16
includes methods to locate top-level modules, with or without loading them.
 
17
The top-level exposed functions in this module for accessing the system path
 
18
are "walkModules", "iterModules", and "getModule".
 
19
 
 
20
From most to least specific, here are the objects provided:
 
21
 
 
22
                  PythonPath  # sys.path
 
23
                      |
 
24
                      v
 
25
                  PathEntry   # one entry on sys.path: an importer
 
26
                      |
 
27
                      v
 
28
                 PythonModule # a module or package that can be loaded
 
29
                      |
 
30
                      v
 
31
                 PythonAttribute # an attribute of a module (function or class)
 
32
                      |
 
33
                      v
 
34
                 PythonAttribute # an attribute of a function or class
 
35
                      |
 
36
                      v
 
37
                     ...
 
38
 
 
39
Here's an example of idiomatic usage: this is what you would do to list all of
 
40
the modules outside the standard library's python-files directory.
 
41
 
 
42
    import os
 
43
    stdlibdir = os.path.dirname(os.__file__)
 
44
 
 
45
    from twisted.python.modules import iterModules
 
46
 
 
47
    for modinfo in iterModules():
 
48
        if (modinfo.pathEntry.filePath.path != stdlibdir
 
49
            and not modinfo.isPackage()):
 
50
            print 'unpackaged: %s: %s' % (
 
51
                modinfo.name, modinfo.filePath.path)
 
52
"""
 
53
 
 
54
__metaclass__ = type
 
55
 
 
56
# let's try to keep path imports to a minimum...
 
57
from os.path import dirname, split as splitpath
 
58
 
 
59
import sys
 
60
import zipimport
 
61
import inspect
 
62
from errno import ENOTDIR
 
63
 
 
64
from zope.interface import Interface, implements
 
65
 
 
66
from twisted.python.components import registerAdapter
 
67
from twisted.python.filepath import FilePath
 
68
from twisted.python.zippath import ZipArchive
 
69
from twisted.python.reflect import namedAny
 
70
from twisted.python.win32 import WindowsError
 
71
from twisted.python.win32 import ERROR_DIRECTORY
 
72
 
 
73
_nothing = object()
 
74
 
 
75
PYTHON_EXTENSIONS = ['.py']
 
76
OPTIMIZED_MODE = __doc__ is None
 
77
if OPTIMIZED_MODE:
 
78
    PYTHON_EXTENSIONS.append('.pyo')
 
79
else:
 
80
    PYTHON_EXTENSIONS.append('.pyc')
 
81
 
 
82
def _isPythonIdentifier(string):
 
83
    """
 
84
    cheezy fake test for proper identifier-ness.
 
85
 
 
86
    @param string: a str which might or might not be a valid python identifier.
 
87
 
 
88
    @return: True or False
 
89
    """
 
90
    return (' ' not in string and
 
91
            '.' not in string and
 
92
            '-' not in string)
 
93
 
 
94
 
 
95
 
 
96
def _isPackagePath(fpath):
 
97
    # Determine if a FilePath-like object is a Python package.  TODO: deal with
 
98
    # __init__module.(so|dll|pyd)?
 
99
    extless = fpath.splitext()[0]
 
100
    basend = splitpath(extless)[1]
 
101
    return basend == "__init__"
 
102
 
 
103
 
 
104
 
 
105
class _ModuleIteratorHelper:
 
106
    """
 
107
    This mixin provides common behavior between python module and path entries,
 
108
    since the mechanism for searching sys.path and __path__ attributes is
 
109
    remarkably similar.
 
110
    """
 
111
 
 
112
    def iterModules(self):
 
113
        """
 
114
        Loop over the modules present below this entry or package on PYTHONPATH.
 
115
 
 
116
        For modules which are not packages, this will yield nothing.
 
117
 
 
118
        For packages and path entries, this will only yield modules one level
 
119
        down; i.e. if there is a package a.b.c, iterModules on a will only
 
120
        return a.b.  If you want to descend deeply, use walkModules.
 
121
 
 
122
        @return: a generator which yields PythonModule instances that describe
 
123
        modules which can be, or have been, imported.
 
124
        """
 
125
        yielded = {}
 
126
        if not self.filePath.exists():
 
127
            return
 
128
 
 
129
        for placeToLook in self._packagePaths():
 
130
            try:
 
131
                children = placeToLook.children()
 
132
            except WindowsError, e:
 
133
                errno = getattr(e, 'winerror', e.errno)
 
134
                if errno == ERROR_DIRECTORY:
 
135
                    # It is a non-directory, skip it.
 
136
                    continue
 
137
                raise
 
138
            except OSError, e:
 
139
                if e.errno in (ENOTDIR,):
 
140
                    # It is a non-directory, skip it.
 
141
                    continue
 
142
                raise
 
143
 
 
144
            for potentialTopLevel in children:
 
145
                name, ext = potentialTopLevel.splitext()
 
146
                if ext in PYTHON_EXTENSIONS:
 
147
                    # TODO: this should be a little choosier about which path entry
 
148
                    # it selects first, and it should do all the .so checking and
 
149
                    # crud
 
150
                    potentialBasename = potentialTopLevel.basename()[:-len(ext)]
 
151
                    if not _isPythonIdentifier(potentialBasename):
 
152
                        continue
 
153
                    modname = self._subModuleName(potentialBasename)
 
154
                    if modname.split(".")[-1] == '__init__':
 
155
                        # This marks the directory as a package so it can't be
 
156
                        # a module.
 
157
                        continue
 
158
                    if modname not in yielded:
 
159
                        yielded[modname] = True
 
160
                        pm = PythonModule(modname, potentialTopLevel, self._getEntry())
 
161
                        assert pm != self
 
162
                        yield pm
 
163
                elif potentialTopLevel.isdir():
 
164
                    modname = self._subModuleName(potentialTopLevel.basename())
 
165
                    for ext in PYTHON_EXTENSIONS:
 
166
                        initpy = potentialTopLevel.child("__init__"+ext)
 
167
                        if initpy.exists():
 
168
                            yielded[modname] = True
 
169
                            pm = PythonModule(modname, initpy, self._getEntry())
 
170
                            assert pm != self
 
171
                            yield pm
 
172
                            break
 
173
 
 
174
    def walkModules(self, importPackages=False):
 
175
        """
 
176
        Similar to L{iterModules}, this yields self, and then every module in my
 
177
        package or entry, and every submodule in each package or entry.
 
178
 
 
179
        In other words, this is deep, and L{iterModules} is shallow.
 
180
        """
 
181
        yield self
 
182
        for package in self.iterModules():
 
183
            for module in package.walkModules(importPackages=importPackages):
 
184
                yield module
 
185
 
 
186
    def _subModuleName(self, mn):
 
187
        """
 
188
        This is a hook to provide packages with the ability to specify their names
 
189
        as a prefix to submodules here.
 
190
        """
 
191
        return mn
 
192
 
 
193
    def _packagePaths(self):
 
194
        """
 
195
        Implement in subclasses to specify where to look for modules.
 
196
 
 
197
        @return: iterable of FilePath-like objects.
 
198
        """
 
199
        raise NotImplementedError()
 
200
 
 
201
    def _getEntry(self):
 
202
        """
 
203
        Implement in subclasses to specify what path entry submodules will come
 
204
        from.
 
205
 
 
206
        @return: a PathEntry instance.
 
207
        """
 
208
        raise NotImplementedError()
 
209
 
 
210
 
 
211
    def __getitem__(self, modname):
 
212
        """
 
213
        Retrieve a module from below this path or package.
 
214
 
 
215
        @param modname: a str naming a module to be loaded.  For entries, this
 
216
        is a top-level, undotted package name, and for packages it is the name
 
217
        of the module without the package prefix.  For example, if you have a
 
218
        PythonModule representing the 'twisted' package, you could use:
 
219
 
 
220
            twistedPackageObj['python']['modules']
 
221
 
 
222
        to retrieve this module.
 
223
 
 
224
        @raise: KeyError if the module is not found.
 
225
 
 
226
        @return: a PythonModule.
 
227
        """
 
228
        for module in self.iterModules():
 
229
            if module.name == self._subModuleName(modname):
 
230
                return module
 
231
        raise KeyError(modname)
 
232
 
 
233
    def __iter__(self):
 
234
        """
 
235
        Implemented to raise NotImplementedError for clarity, so that attempting to
 
236
        loop over this object won't call __getitem__.
 
237
 
 
238
        Note: in the future there might be some sensible default for iteration,
 
239
        like 'walkEverything', so this is deliberately untested and undefined
 
240
        behavior.
 
241
        """
 
242
        raise NotImplementedError()
 
243
 
 
244
class PythonAttribute:
 
245
    """
 
246
    I represent a function, class, or other object that is present.
 
247
 
 
248
    @ivar name: the fully-qualified python name of this attribute.
 
249
 
 
250
    @ivar onObject: a reference to a PythonModule or other PythonAttribute that
 
251
    is this attribute's logical parent.
 
252
 
 
253
    @ivar name: the fully qualified python name of the attribute represented by
 
254
    this class.
 
255
    """
 
256
    def __init__(self, name, onObject, loaded, pythonValue):
 
257
        """
 
258
        Create a PythonAttribute.  This is a private constructor.  Do not construct
 
259
        me directly, use PythonModule.iterAttributes.
 
260
 
 
261
        @param name: the FQPN
 
262
        @param onObject: see ivar
 
263
        @param loaded: always True, for now
 
264
        @param pythonValue: the value of the attribute we're pointing to.
 
265
        """
 
266
        self.name = name
 
267
        self.onObject = onObject
 
268
        self._loaded = loaded
 
269
        self.pythonValue = pythonValue
 
270
 
 
271
    def __repr__(self):
 
272
        return 'PythonAttribute<%r>'%(self.name,)
 
273
 
 
274
    def isLoaded(self):
 
275
        """
 
276
        Return a boolean describing whether the attribute this describes has
 
277
        actually been loaded into memory by importing its module.
 
278
 
 
279
        Note: this currently always returns true; there is no Python parser
 
280
        support in this module yet.
 
281
        """
 
282
        return self._loaded
 
283
 
 
284
    def load(self, default=_nothing):
 
285
        """
 
286
        Load the value associated with this attribute.
 
287
 
 
288
        @return: an arbitrary Python object, or 'default' if there is an error
 
289
        loading it.
 
290
        """
 
291
        return self.pythonValue
 
292
 
 
293
    def iterAttributes(self):
 
294
        for name, val in inspect.getmembers(self.load()):
 
295
            yield PythonAttribute(self.name+'.'+name, self, True, val)
 
296
 
 
297
class PythonModule(_ModuleIteratorHelper):
 
298
    """
 
299
    Representation of a module which could be imported from sys.path.
 
300
 
 
301
    @ivar name: the fully qualified python name of this module.
 
302
 
 
303
    @ivar filePath: a FilePath-like object which points to the location of this
 
304
    module.
 
305
 
 
306
    @ivar pathEntry: a L{PathEntry} instance which this module was located
 
307
    from.
 
308
    """
 
309
 
 
310
    def __init__(self, name, filePath, pathEntry):
 
311
        """
 
312
        Create a PythonModule.  Do not construct this directly, instead inspect a
 
313
        PythonPath or other PythonModule instances.
 
314
 
 
315
        @param name: see ivar
 
316
        @param filePath: see ivar
 
317
        @param pathEntry: see ivar
 
318
        """
 
319
        assert not name.endswith(".__init__")
 
320
        self.name = name
 
321
        self.filePath = filePath
 
322
        self.pathEntry = pathEntry
 
323
 
 
324
    def _getEntry(self):
 
325
        return self.pathEntry
 
326
 
 
327
    def __repr__(self):
 
328
        """
 
329
        Return a string representation including the module name.
 
330
        """
 
331
        return 'PythonModule<%r>' % (self.name,)
 
332
 
 
333
    def isLoaded(self):
 
334
        """
 
335
        Determine if the module is loaded into sys.modules.
 
336
 
 
337
        @return: a boolean: true if loaded, false if not.
 
338
        """
 
339
        return self.name in self.pathEntry.pythonPath.moduleDict
 
340
 
 
341
    def iterAttributes(self):
 
342
        """
 
343
        List all the attributes defined in this module.
 
344
 
 
345
        Note: Future work is planned here to make it possible to list python
 
346
        attributes on a module without loading the module by inspecting ASTs or
 
347
        bytecode, but currently any iteration of PythonModule objects insists
 
348
        they must be loaded, and will use inspect.getmodule.
 
349
 
 
350
        @raise NotImplementedError: if this module is not loaded.
 
351
 
 
352
        @return: a generator yielding PythonAttribute instances describing the
 
353
        attributes of this module.
 
354
        """
 
355
        if not self.isLoaded():
 
356
            raise NotImplementedError(
 
357
                "You can't load attributes from non-loaded modules yet.")
 
358
        for name, val in inspect.getmembers(self.load()):
 
359
            yield PythonAttribute(self.name+'.'+name, self, True, val)
 
360
 
 
361
    def isPackage(self):
 
362
        """
 
363
        Returns true if this module is also a package, and might yield something
 
364
        from iterModules.
 
365
        """
 
366
        return _isPackagePath(self.filePath)
 
367
 
 
368
    def load(self, default=_nothing):
 
369
        """
 
370
        Load this module.
 
371
 
 
372
        @param default: if specified, the value to return in case of an error.
 
373
 
 
374
        @return: a genuine python module.
 
375
 
 
376
        @raise: any type of exception.  Importing modules is a risky business;
 
377
        the erorrs of any code run at module scope may be raised from here, as
 
378
        well as ImportError if something bizarre happened to the system path
 
379
        between the discovery of this PythonModule object and the attempt to
 
380
        import it.  If you specify a default, the error will be swallowed
 
381
        entirely, and not logged.
 
382
 
 
383
        @rtype: types.ModuleType.
 
384
        """
 
385
        try:
 
386
            return self.pathEntry.pythonPath.moduleLoader(self.name)
 
387
        except:                 # this needs more thought...
 
388
            if default is not _nothing:
 
389
                return default
 
390
            raise
 
391
 
 
392
    def __eq__(self, other):
 
393
        """
 
394
        PythonModules with the same name are equal.
 
395
        """
 
396
        if not isinstance(other, PythonModule):
 
397
            return False
 
398
        return other.name == self.name
 
399
 
 
400
    def __ne__(self, other):
 
401
        """
 
402
        PythonModules with different names are not equal.
 
403
        """
 
404
        if not isinstance(other, PythonModule):
 
405
            return True
 
406
        return other.name != self.name
 
407
 
 
408
    def walkModules(self, importPackages=False):
 
409
        if importPackages and self.isPackage():
 
410
            self.load()
 
411
        return super(PythonModule, self).walkModules(importPackages=importPackages)
 
412
 
 
413
    def _subModuleName(self, mn):
 
414
        """
 
415
        submodules of this module are prefixed with our name.
 
416
        """
 
417
        return self.name + '.' + mn
 
418
 
 
419
    def _packagePaths(self):
 
420
        """
 
421
        Yield a sequence of FilePath-like objects which represent path segments.
 
422
        """
 
423
        if not self.isPackage():
 
424
            return
 
425
        if self.isLoaded():
 
426
            for fn in self.load().__path__:
 
427
                sfpp = self.filePath.parent()
 
428
                if fn == sfpp.path:
 
429
                    # this should _really_ exist.
 
430
                    assert sfpp.exists()
 
431
                    yield sfpp
 
432
                else:
 
433
                    smp = self.pathEntry.pythonPath._smartPath(fn)
 
434
                    if smp.exists():
 
435
                        yield smp
 
436
        else:
 
437
            yield self.filePath.parent()
 
438
 
 
439
 
 
440
class PathEntry(_ModuleIteratorHelper):
 
441
    """
 
442
    I am a proxy for a single entry on sys.path.
 
443
 
 
444
    @ivar filePath: a FilePath-like object pointing at the filesystem location
 
445
    or archive file where this path entry is stored.
 
446
 
 
447
    @ivar pythonPath: a PythonPath instance.
 
448
    """
 
449
    def __init__(self, filePath, pythonPath):
 
450
        """
 
451
        Create a PathEntry.  This is a private constructor.
 
452
        """
 
453
        self.filePath = filePath
 
454
        self.pythonPath = pythonPath
 
455
 
 
456
    def _getEntry(self):
 
457
        return self
 
458
 
 
459
    def __repr__(self):
 
460
        return 'PathEntry<%r>' % (self.filePath,)
 
461
 
 
462
    def _packagePaths(self):
 
463
        yield self.filePath
 
464
 
 
465
class IPathImportMapper(Interface):
 
466
    """
 
467
    This is an internal interface, used to map importers to factories for
 
468
    FilePath-like objects.
 
469
    """
 
470
    def mapPath(self, pathLikeString):
 
471
        """
 
472
        Return a FilePath-like object.
 
473
 
 
474
        @param pathLikeString: a path-like string, like one that might be
 
475
        passed to an import hook.
 
476
 
 
477
        @return: a L{FilePath}, or something like it (currently only a
 
478
        L{ZipPath}, but more might be added later).
 
479
        """
 
480
 
 
481
class _DefaultMapImpl:
 
482
    """ Wrapper for the default importer, i.e. None.  """
 
483
    implements(IPathImportMapper)
 
484
    def mapPath(self, fsPathString):
 
485
        return FilePath(fsPathString)
 
486
_theDefaultMapper = _DefaultMapImpl()
 
487
 
 
488
class _ZipMapImpl:
 
489
    """ IPathImportMapper implementation for zipimport.ZipImporter.  """
 
490
    implements(IPathImportMapper)
 
491
    def __init__(self, importer):
 
492
        self.importer = importer
 
493
 
 
494
    def mapPath(self, fsPathString):
 
495
        """
 
496
        Map the given FS path to a ZipPath, by looking at the ZipImporter's
 
497
        "archive" attribute and using it as our ZipArchive root, then walking
 
498
        down into the archive from there.
 
499
 
 
500
        @return: a L{zippath.ZipPath} or L{zippath.ZipArchive} instance.
 
501
        """
 
502
        za = ZipArchive(self.importer.archive)
 
503
        myPath = FilePath(self.importer.archive)
 
504
        itsPath = FilePath(fsPathString)
 
505
        if myPath == itsPath:
 
506
            return za
 
507
        # This is NOT a general-purpose rule for sys.path or __file__:
 
508
        # zipimport specifically uses regular OS path syntax in its pathnames.
 
509
        segs = itsPath.segmentsFrom(myPath)
 
510
        zp = za
 
511
        for seg in segs:
 
512
            zp = zp.child(seg)
 
513
        return zp
 
514
 
 
515
registerAdapter(_ZipMapImpl, zipimport.zipimporter, IPathImportMapper)
 
516
 
 
517
def _defaultSysPathFactory():
 
518
    """
 
519
    Provide the default behavior of PythonPath's sys.path factory, which is to
 
520
    return the current value of sys.path.
 
521
 
 
522
    @return: L{sys.path}
 
523
    """
 
524
    return sys.path
 
525
 
 
526
 
 
527
class PythonPath:
 
528
    """
 
529
    I represent the very top of the Python object-space, the module list in
 
530
    sys.path and the modules list in sys.modules.
 
531
 
 
532
    @ivar sysPath: a sequence of strings like sys.path.  This attribute is
 
533
    read-only.
 
534
 
 
535
    @ivar moduleDict: a dictionary mapping string module names to module
 
536
    objects, like sys.modules.
 
537
 
 
538
    @ivar sysPathHooks: a list of PEP-302 path hooks, like sys.path_hooks.
 
539
 
 
540
    @ivar moduleLoader: a function that takes a fully-qualified python name and
 
541
    returns a module, like twisted.python.reflect.namedAny.
 
542
    """
 
543
 
 
544
    def __init__(self,
 
545
                 sysPath=None,
 
546
                 moduleDict=sys.modules,
 
547
                 sysPathHooks=sys.path_hooks,
 
548
                 importerCache=sys.path_importer_cache,
 
549
                 moduleLoader=namedAny,
 
550
                 sysPathFactory=None):
 
551
        """
 
552
        Create a PythonPath.  You almost certainly want to use
 
553
        modules.theSystemPath, or its aliased methods, rather than creating a
 
554
        new instance yourself, though.
 
555
 
 
556
        All parameters are optional, and if unspecified, will use 'system'
 
557
        equivalents that makes this PythonPath like the global L{theSystemPath}
 
558
        instance.
 
559
 
 
560
        @param sysPath: a sys.path-like list to use for this PythonPath, to
 
561
        specify where to load modules from.
 
562
 
 
563
        @param moduleDict: a sys.modules-like dictionary to use for keeping
 
564
        track of what modules this PythonPath has loaded.
 
565
 
 
566
        @param sysPathHooks: sys.path_hooks-like list of PEP-302 path hooks to
 
567
        be used for this PythonPath, to determie which importers should be
 
568
        used.
 
569
 
 
570
        @param importerCache: a sys.path_importer_cache-like list of PEP-302
 
571
        importers.  This will be used in conjunction with the given
 
572
        sysPathHooks.
 
573
 
 
574
        @param moduleLoader: a module loader function which takes a string and
 
575
        returns a module.  That is to say, it is like L{namedAny} - *not* like
 
576
        L{__import__}.
 
577
 
 
578
        @param sysPathFactory: a 0-argument callable which returns the current
 
579
        value of a sys.path-like list of strings.  Specify either this, or
 
580
        sysPath, not both.  This alternative interface is provided because the
 
581
        way the Python import mechanism works, you can re-bind the 'sys.path'
 
582
        name and that is what is used for current imports, so it must be a
 
583
        factory rather than a value to deal with modification by rebinding
 
584
        rather than modification by mutation.  Note: it is not recommended to
 
585
        rebind sys.path.  Although this mechanism can deal with that, it is a
 
586
        subtle point which some tools that it is easy for tools which interact
 
587
        with sys.path to miss.
 
588
        """
 
589
        if sysPath is not None:
 
590
            sysPathFactory = lambda : sysPath
 
591
        elif sysPathFactory is None:
 
592
            sysPathFactory = _defaultSysPathFactory
 
593
        self._sysPathFactory = sysPathFactory
 
594
        self._sysPath = sysPath
 
595
        self.moduleDict = moduleDict
 
596
        self.sysPathHooks = sysPathHooks
 
597
        self.importerCache = importerCache
 
598
        self._moduleLoader = moduleLoader
 
599
 
 
600
 
 
601
    def _getSysPath(self):
 
602
        """
 
603
        Retrieve the current value of sys.path.
 
604
        """
 
605
        return self._sysPathFactory()
 
606
 
 
607
    sysPath = property(_getSysPath)
 
608
 
 
609
    def moduleLoader(self, modname):
 
610
        """
 
611
        Replicate python2.4+ sys.modules preservation behavior.
 
612
 
 
613
        @param modname: a str module name.
 
614
 
 
615
        @return: an imported module.
 
616
 
 
617
        @raise: any type of exception that may arise from importing.
 
618
        """
 
619
        freezeModules = self.moduleDict.copy()
 
620
        try:
 
621
            return self._moduleLoader(modname)
 
622
        except:
 
623
            self.moduleDict.clear()
 
624
            self.moduleDict.update(freezeModules)
 
625
            raise
 
626
 
 
627
    def _findEntryPathString(self, modobj):
 
628
        """
 
629
        Determine where a given Python module object came from by looking at path
 
630
        entries.
 
631
        """
 
632
        topPackageObj = modobj
 
633
        while '.' in topPackageObj.__name__:
 
634
            topPackageObj = self.moduleDict['.'.join(
 
635
                    topPackageObj.__name__.split('.')[:-1])]
 
636
        if _isPackagePath(FilePath(topPackageObj.__file__)):
 
637
            # if package 'foo' is on sys.path at /a/b/foo, package 'foo's
 
638
            # __file__ will be /a/b/foo/__init__.py, and we are looking for
 
639
            # /a/b here, the path-entry; so go up two steps.
 
640
            rval = dirname(dirname(topPackageObj.__file__))
 
641
        else:
 
642
            # the module is completely top-level, not within any packages.  The
 
643
            # path entry it's on is just its dirname.
 
644
            rval = dirname(topPackageObj.__file__)
 
645
        # There are probably some awful tricks that an importer could pull
 
646
        # which would break this, so let's just make sure... it's a loaded
 
647
        # module after all, which means that its path MUST be in
 
648
        # path_importer_cache according to PEP 302 -glyph
 
649
        from pprint import pformat
 
650
        assert rval in self.importerCache, '%r for %r not in import cache %s' % (
 
651
            rval, modobj, pformat(self.importerCache))
 
652
        return rval
 
653
 
 
654
    def _smartPath(self, pathName):
 
655
        """
 
656
        Given a path entry from sys.path which may refer to an importer,
 
657
        return the appropriate FilePath-like instance.
 
658
 
 
659
        @param pathName: a str describing the path.
 
660
 
 
661
        @return: a FilePath-like object.
 
662
        """
 
663
        importr = self.importerCache.get(pathName, _nothing)
 
664
        if importr is _nothing:
 
665
            for hook in self.sysPathHooks:
 
666
                try:
 
667
                    importr = hook(pathName)
 
668
                except ImportError, ie:
 
669
                    pass
 
670
            if importr is _nothing: # still
 
671
                importr = None
 
672
        return IPathImportMapper(importr, _theDefaultMapper).mapPath(pathName)
 
673
 
 
674
    def iterEntries(self):
 
675
        """
 
676
        Iterate the entries on my sysPath.
 
677
 
 
678
        @return: a generator yielding PathEntry objects
 
679
        """
 
680
        for pathName in self.sysPath:
 
681
            fp = self._smartPath(pathName)
 
682
            yield PathEntry(fp, self)
 
683
 
 
684
    def __getitem__(self, modname):
 
685
        """
 
686
        Get a python module by a given fully-qualified name.
 
687
 
 
688
        @return: a PythonModule object.
 
689
 
 
690
        @raise: KeyError, if the module name is a module name.
 
691
        """
 
692
        # See if the module is already somewhere in Python-land.
 
693
        if modname in self.moduleDict:
 
694
            # we need 2 paths; one of the path entry and one for the module.
 
695
            moduleObject = self.moduleDict[modname]
 
696
            pe = PathEntry(
 
697
                self._smartPath(
 
698
                    self._findEntryPathString(moduleObject)),
 
699
                self)
 
700
            mp = self._smartPath(moduleObject.__file__)
 
701
            return PythonModule(modname, mp, pe)
 
702
 
 
703
        # Recurse if we're trying to get a submodule.
 
704
        if '.' in modname:
 
705
            pkg = self
 
706
            for name in modname.split('.'):
 
707
                pkg = pkg[name]
 
708
            return pkg
 
709
 
 
710
        # Finally do the slowest possible thing and iterate
 
711
        for module in self.iterModules():
 
712
            if module.name == modname:
 
713
                return module
 
714
        raise KeyError(modname)
 
715
 
 
716
    def __repr__(self):
 
717
        """
 
718
        Display my sysPath and moduleDict in a string representation.
 
719
        """
 
720
        return "PythonPath(%r,%r)" % (self.sysPath, self.moduleDict)
 
721
 
 
722
    def iterModules(self):
 
723
        """
 
724
        Yield all top-level modules on my sysPath.
 
725
        """
 
726
        for entry in self.iterEntries():
 
727
            for module in entry.iterModules():
 
728
                yield module
 
729
 
 
730
    def walkModules(self, importPackages=False):
 
731
        """
 
732
        Similar to L{iterModules}, this yields every module on the path, then every
 
733
        submodule in each package or entry.
 
734
        """
 
735
        for package in self.iterModules():
 
736
            for module in package.walkModules(importPackages=False):
 
737
                yield module
 
738
 
 
739
theSystemPath = PythonPath()
 
740
 
 
741
def walkModules(importPackages=False):
 
742
    """
 
743
    Deeply iterate all modules on the global python path.
 
744
 
 
745
    @param importPackages: Import packages as they are seen.
 
746
    """
 
747
    return theSystemPath.walkModules(importPackages=importPackages)
 
748
 
 
749
def iterModules():
 
750
    """
 
751
    Iterate all modules and top-level packages on the global Python path, but
 
752
    do not descend into packages.
 
753
 
 
754
    @param importPackages: Import packages as they are seen.
 
755
    """
 
756
    return theSystemPath.iterModules()
 
757
 
 
758
def getModule(moduleName):
 
759
    """
 
760
    Retrieve a module from the system path.
 
761
    """
 
762
    return theSystemPath[moduleName]