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