blob: 614ce941e292f53c797c1044b6985b52dad9603a [file] [log] [blame]
Guido van Rossum52325901995-04-07 09:03:10 +00001"""Prototype of 'import' functionality enhanced to implement packages.
2
3Why packages? Packages enable module nesting and sibling module
4imports. 'Til now, the python module namespace was flat, which
5means every module had to have a unique name, in order to not
6conflict with names of other modules on the load path. Furthermore,
7suites of modules could not be structurally affiliated with one
8another.
9
10With packages, a suite of, eg, email-oriented modules can include a
11module named 'mailbox', without conflicting with the, eg, 'mailbox'
12module of a shared-memory suite - 'email.mailbox' vs
13'shmem.mailbox'. Packages also enable modules within a suite to
14load other modules within their package without having the package
15name hard-coded. Similarly, package suites of modules can be loaded
16as a unit, by loading the package that contains them.
17
18Usage: once installed (newimp.install(); newimp.revert() to revert to
19the prior __import__ routine), 'import ...' and 'from ... import ...'
20can be used to:
21
22 - import modules from the search path, as before.
23
24 - import modules from within other directory "packages" on the search
25 path using a '.' dot-delimited nesting syntax. The nesting is fully
26 recursive.
27
28 - import an entire package as a unit, by importing the package directory.
29 If there is a module named '__main__.py' in the package, it controls the
30 load. Otherwise, all the modules in the dir, including packages, are
31 inherently loaded into the package module's namespace.
32
33 __main__.py can load the entire directory, by loading the package
34 itself, via eg 'import __', or even 'from __ import *'. The benefit
35 is (1) the ability to do additional things before and after the loads
36 of the other modules, and (2) the ability to populate the package
37 module with the *contents* of the component modules, ie with a
38 'from __ import *'.)
39
40 - import siblings from modules within a package, using '__.' as a shorthand
41 prefix to refer to the parent package. This enables referential
42 transparency - package modules need not know their package name.
43
44 - The '__' package references are actually names assigned within
45 modules, to refer to their containing package. This means that
46 variable references can be made to imported modules, or variables
47 defined via 'import ... from' of the modules, also using the '__.var'
48 shorthand notation. This establishes an proper equivalence between
49 the import reference '__.sibling' and the var reference '__.sibling'.
50
51Modules have a few new attributes, in support of packages. As
52mentioned above, '__' is a shorthand attribute denoting the
53modules' parent package, also denoted in the module by
54'__package__'. Additionally, modules have associated with them a
55'__pkgpath__', a path by which sibling modules are found."""
56
57__version__ = "$Revision$"
58
59# $Id$
60# First release: Ken.Manheimer@nist.gov, 5-Apr-1995, for python 1.2
61
62# Developers Notes:
63#
64# - 'sys.stub_modules' registers "incidental" (partially loaded) modules.
65# A stub module is promoted to the fully-loaded 'sys.modules' list when it is
66# explicitly loaded as a unit.
67# - The __main__ loads of '__' have not yet been tested.
68# - The test routines are cool, including a transient directory
69# hierarchy facility, and a means of skipping to later tests by giving
70# the test routine a numeric arg.
71# - This could be substantially optimized, and there are many loose ends
72# lying around, since i wanted to get this released for 1.2.
73
74VERBOSE = 0
75
76import sys, string, regex, types, os, marshal, new, __main__
77try:
78 import imp # Build on this recent addition
79except ImportError:
80 raise ImportError, 'Pkg import module depends on optional "imp" module'
81
82from imp import SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION
83PY_PACKAGE = 4 # In addition to above PY_*
84
85modes = {SEARCH_ERROR: 'SEARCH_ERROR',
86 PY_SOURCE: 'PY_SOURCE',
87 PY_COMPILED: 'PY_COMPILED',
88 C_EXTENSION: 'C_EXTENSION',
89 PY_PACKAGE: 'PY_PACKAGE'}
90
91# sys.stub_modules tracks modules partially loaded modules, ie loaded only
92# incidental to load of nested components.
93
94try: sys.stub_modules
95except AttributeError:
96 sys.stub_modules = {}
97
98# Environment setup - "root" module, '__python__'
99
100# Establish root package '__python__' in __main__ and newimp envs.
101
102PKG_MAIN_NM = '__main__' # 'pkg/__main__.py' master, if present.
103PKG_NM = '__package__' # Longhand for module's container.
104PKG_SHORT_NM = '__' # Shorthand for module's container.
105PKG_SHORT_NM_LEN = len(PKG_SHORT_NM)
106PKG_PATH = '__pkgpath__' # Var holding package search path,
107 # usually just the path of the pkg dir.
108__python__ = __main__
109sys.modules['__python__'] = __python__ # Register as an importable module.
110__python__.__dict__[PKG_PATH] = sys.path
111
112origImportFunc = None
113def install():
114 """Install newimp import_module() routine, for package support.
115
116 newimp.revert() reverts to __import__ routine that was superceded."""
117 global origImportFunc
118 if not origImportFunc:
119 try:
120 import __builtin__
121 origImportFunc = __builtin__.__import__
122 except AttributeError:
123 pass
124 __builtin__.__import__ = import_module
125 print 'Enhanced import functionality installed.'
126def revert():
127 """Revert to original __builtin__.__import__ func, if newimp.install() has
128 been executed."""
129 if origImportFunc:
130 import __builtin__
131 __builtin__.__import__ = origImportFunc
132 print 'Original import routine back in place.'
133
134def import_module(name,
135 envLocals=None, envGlobals=None,
136 froms=None,
137 inPkg=None):
138 """Primary service routine implementing 'import' with package nesting."""
139
140 # The job is divided into a few distinct steps:
141 #
142 # - Look for either an already loaded module or a file to be loaded.
143 # * if neither loaded module nor prospect file is found, raise an error.
144 # - If we have a file, not an already loaded module:
145 # - Load the file into a module.
146 # - Register the new module and intermediate package stubs.
147 # (We have a module at this point...)
148 # - Bind requested syms (module or specified 'from' defs) in calling env.
149 # - Return the appropriate component.
150
151 note("import_module: seeking '%s'%s" %
152 (name, ((inPkg and ' (in package %s)' % inPkg.__name__) or '')))
153
154 # We need callers environment dict for local path and resulting module
155 # binding.
156 if not (envLocals or envGlobals):
157 envLocals, envGlobals = exterior()
158
159 modList = theMod = absNm = container = None
160
161 # Get module obj if one already established, or else module file if not:
162
163 if inPkg:
164 # We've been invoked with a specific containing package:
165 pkg, pkgPath, pkgNm = inPkg, inPkg.__dict__[PKG_PATH], inPkg.__name__
166 relNm = name
167 absNm = pkgNm + '.' + name
168
169 elif name[:PKG_SHORT_NM_LEN+1] != PKG_SHORT_NM + '.':
170 # name is NOT '__.something' - setup to seek according to specified
171 # absolute name.
172 pkg = __python__
173 pkgPath = sys.path
174 absNm = name
175 relNm = absNm
176
177 else:
178 # name IS '__.' + something - setup to seek according to relative name,
179 # in current package.
180
181 relNm = name[len(PKG_SHORT_NM)+1:] # Relative portion of name.
182 try:
183 pkg = envGlobals[PKG_NM] # The immediately containing package.
184 pkgPath = pkg.__dict__[PKG_PATH]
185 if pkg == __python__: # At outermost package.
186 absNm = relNm
187 else:
188 absNm = (pkg.__name__ + '.' + relNm)
189 except KeyError: # Missing package, path, or name.
190 note("Can't identify parent package, package name, or pkgpath")
191 pass # ==v
192
193 # Try to find existing module:
194 if sys.modules.has_key(absNm):
195 note('found ' + absNm + ' already imported')
196 theMod = sys.modules[absNm]
197 else:
198 # Try for builtin or frozen first:
199 theMod = imp.init_builtin(absNm)
200 if theMod:
201 note('found builtin ' + absNm)
202 else:
203 theMod = imp.init_frozen(absNm)
204 if theMod:
205 note('found frozen ' + absNm)
206 if not theMod:
207 if type(pkgPath) == types.StringType:
208 pkgPath = [pkgPath]
209 modList = find_module(relNm, pkgPath, absNm)
210 if not modList:
211 raise ImportError, "module '%s' not found" % absNm # ===X
212 # We have a list of successively nested files leading to the
213 # module, register them as stubs:
214 container = register_module_nesting(modList, pkg)
215
216 # Load from file if necessary and possible:
217 modNm, modf, path, ty = modList[-1]
218 note('found type ' + modes[ty[2]] + ' - ' + absNm)
219
220 # Do the load:
221 theMod = load_module(absNm, ty[2], modf, inPkg)
222
223 # Loaded successfully - promote module to full module status:
224 register_module(theMod, theMod.__name__, pkgPath, pkg)
225
226 # Have a loaded module, impose designated components, and return
227 # appropriate thing - according to guido:
228 # "Note that for "from spam.ham import bacon" your function should
229 # return the object denoted by 'spam.ham', while for "import
230 # spam.ham" it should return the object denoted by 'spam' -- the
231 # STORE instructions following the import statement expect it this
232 # way."
233 if not froms:
234 # Establish the module defs in the importing name space:
235 (envLocals or envGlobals)[name] = theMod
236 return (container or theMod)
237 else:
238 # Implement 'from': Populate immediate env with module defs:
239 if froms == '*':
240 froms = theMod.__dict__.keys() # resolve '*'
241 for item in froms:
242 (envLocals or envGlobals)[item] = theMod.__dict__[item]
243 return theMod
244
245def unload(module):
246 """Remove registration for a module, so import will do a fresh load."""
247 if type(module) == types.ModuleType:
248 module = module.__name__
249 for m in [sys.modules, sys.stub_modules]:
250 try:
251 del m[module]
252 except KeyError:
253 pass
254
255def find_module(name, path, absNm=''):
256 """Locate module NAME on PATH. PATH is pathname string or a list of them.
257
258 Note that up-to-date compiled versions of a module are preferred to plain
259 source, and compilation is automatically performed when necessary and
260 possible.
261
262 Returns a list of the tuples returned by 'find_module_file' (cf), one for
263 each nested level, deepest last."""
264
265 checked = [] # For avoiding redundant dir lists.
266
267 if not absNm: absNm = name
268
269 # Parse name into list of nested components,
270 expNm = string.splitfields(name, '.')
271
272 for curPath in path:
273
274 if (type(curPath) != types.StringType) or (curPath in checked):
275 # Disregard bogus or already investigated path elements:
276 continue # ==^
277 else:
278 # Register it for subsequent disregard.
279 checked.append(curPath)
280
281 if len(expNm) == 1:
282
283 # Non-nested module name:
284
285 got = find_module_file(curPath, absNm)
286 if got:
287 note('using %s' % got[2], 2)
288 return [got] # ===>
289
290 else:
291
292 # Composite name specifying nested module:
293
294 gotList = []; nameAccume = expNm[0]
295
296 got = find_module_file(curPath, nameAccume)
297 if not got: # Continue to next prospective path.
298 continue # ==^
299 else:
300 gotList.append(got)
301 nm, file, fullPath, ty = got
302
303 # Work on successively nested components:
304 for component in expNm[1:]:
305 # 'ty'pe of containing component must be package:
306 if ty[2] != PY_PACKAGE:
307 gotList, got = [], None
308 break # ==v^
309 if nameAccume:
310 nameAccume = nameAccume + '.' + component
311 else:
312 nameAccume = component
313 got = find_module_file(fullPath, nameAccume)
314 if got:
315 gotList.append(got)
316 # ** have to return the *full* name here:
317 nm, file, fullPath, ty = got
318 else:
319 # Clear state vars:
320 gotList, got, nameAccume = [], None, ''
321 break # ==v^
322 # Found nesting all the way to the specified tip:
323 if got:
324 return gotList # ===>
325
326 # Failed.
327 return None
328
329def find_module_file(pathNm, modname):
330 """Find module file given dir PATHNAME and module NAME.
331
332 If successful, returns quadruple consisting of a mod name, file object,
333 PATHNAME for the found file, and a description triple as contained in the
334 list returned by get_suffixes.
335
336 Otherwise, returns None.
337
338 Note that up-to-date compiled versions of a module are preferred to plain
339 source, and compilation is automatically performed, when necessary and
340 possible."""
341
342 relNm = string.splitfields(modname,'.')[-1]
343
344 if pathNm[-1] != '/': pathNm = pathNm + '/'
345
346 for suff, mode, ty in get_suffixes():
347 note('trying ' + pathNm + relNm + suff + '...', 3)
348 fullPath = pathNm + relNm + suff
349 try:
350 modf = open(fullPath, mode)
351 except IOError:
352 # ?? Skip unreadable ones.
353 continue # ==^
354
355 if ty == PY_PACKAGE:
356 # Enforce directory characteristic:
357 if not os.path.isdir(fullPath):
358 note('Skipping non-dir match ' + fullPath)
359 continue # ==^
360 else:
361 return (modname, modf, fullPath, (suff, mode, ty)) # ===>
362
363
364 elif ty == PY_SOURCE:
365 # Try for a compiled version:
366 note('found source ' + fullPath, 2)
367 pyc = fullPath + 'c' # Sadly, we're presuming '.py' suff.
368 if (not os.path.exists(pyc) or
369 (os.stat(fullPath)[8] > os.stat(pyc)[8])):
370 # Try to compile:
371 pyc = compile_source(fullPath, modf)
372 if pyc and (os.stat(fullPath)[8] < os.stat(pyc)[8]):
373 # Either pyc was already newer or we just made it so; in either
374 # case it's what we crave:
375 return (modname, open(pyc, 'rb'), pyc, # ===>
376 ('.pyc', 'rb', PY_COMPILED))
377 # Couldn't get a compiled version - return the source:
378 return (modname, modf, fullPath, (suff, mode, ty)) # ===>
379
380 elif ty == PY_COMPILED:
381 # Make sure it is current, trying to compile if necessary, and
382 # prefer source failing that:
383 note('found compiled ' + fullPath, 2)
384 py = fullPath[:-1] # Sadly again, presuming '.pyc' suff.
385 if not os.path.exists(py):
386 note('found pyc sans py: ' + fullPath)
387 return (modname, modf, fullPath, (suff, mode, ty)) # ===>
388 elif (os.stat(py)[8] > os.stat(fullPath)[8]):
389 note('forced to try compiling: ' + py)
390 pyc = compile_source(py, modf)
391 if pyc:
392 return (modname, modf, fullPath, (suff, mode, ty)) # ===>
393 else:
394 note('failed compile - must use more recent .py')
395 return (modname, # ===>
396 open(py, 'r'), py, ('.py', 'r', PY_SOURCE))
397 else:
398 return (modname, modf, fullPath, (suff, mode, ty)) # ===>
399
400 elif ty == C_EXTENSION:
401 note('found extension ' + fullPath, 2)
402 return (modname, modf, fullPath, (suff, mode, ty)) # ===>
403
404 else:
405 raise SystemError, 'Unanticipated (new?) module type encountered'
406
407 return None
408
409
410def load_module(name, ty, theFile, fromMod=None):
411 """Load module NAME, type TYPE, from file FILE.
412
413 Optional arg fromMod indicated the module from which the load is being done
414 - necessary for detecting import of __ from a package's __main__ module.
415
416 Return the populated module object."""
417
418 # Note: we mint and register intermediate package directories, as necessary
419
420 # Determine packagepath extension:
421
422 # Establish the module object in question:
423 theMod = procure_module(name)
424 nameTail = string.splitfields(name, '.')[-1]
425 thePath = theFile.name
426
427 if ty == PY_SOURCE:
428 exec_into(theFile, theMod, theFile.name)
429
430 elif ty == PY_COMPILED:
431 pyc = open(theFile.name, 'rb').read()
432 if pyc[0:4] != imp.get_magic():
433 raise ImportError, 'bad magic number: ' + theFile.name # ===>
434 code = marshal.loads(pyc[8:])
435 exec_into(code, theMod, theFile.name)
436
437 elif ty == C_EXTENSION:
438 try:
439 theMod = imp.load_dynamic(nameTail, thePath, theFile)
440 except:
441 # ?? Ok to embellish the error message?
442 raise sys.exc_type, ('%s (from %s)' %
443 (str(sys.exc_value), theFile.name))
444
445 elif ty == PY_PACKAGE:
446 # Load constituents:
447 if (os.path.exists(thePath + '/' + PKG_MAIN_NM) and
448 # pkg has a __main__, and this import not already from __main__, so
449 # __main__ can 'import __', or even better, 'from __ import *'
450 ((theMod.__name__ != PKG_MAIN_NM) and (fromMod.__ == theMod))):
451 exec_into(thePath + '/' + PKG_MAIN_NM, theMod, theFile.name)
452 else:
453 # ... or else recursively load constituent modules.
454 prospects = mod_prospects(thePath)
455 for item in prospects:
456 theMod.__dict__[item] = import_module(item,
457 theMod.__dict__,
458 theMod.__dict__,
459 None,
460 theMod)
461
462 else:
463 raise ImportError, 'Unimplemented import type: %s' % ty # ===>
464
465 return theMod
466
467def exec_into(obj, module, path):
468 """Helper for load_module, execfile/exec path or code OBJ within MODULE."""
469
470 # This depends on ability of exec and execfile to mutilate, erhm, mutate
471 # the __dict__ of a module. It will not work if/when this becomes
472 # disallowed, as it is for normal assignments.
473
474 try:
475 if type(obj) == types.FileType:
476 execfile(path, module.__dict__, module.__dict__)
477 elif type(obj) in [types.CodeType, types.StringType]:
478 exec obj in module.__dict__, module.__dict__
479 except:
480 # ?? Ok to embellish the error message?
481 raise sys.exc_type, ('%s (from %s)' %
482 (str(sys.exc_value), path))
483
484
485def mod_prospects(path):
486 """Return a list of prospective modules within directory PATH.
487
488 We actually return the distinct names resulting from stripping the dir
489 entries (excluding '.' and '..') of their suffixes (as represented by
490 'get_suffixes').
491
492 (Note that matches for the PY_PACKAGE type with null suffix are
493 implicitly constrained to be directories.)"""
494
495 # We actually strip the longest matching suffixes, so eg 'dbmmodule.so'
496 # mates with 'module.so' rather than '.so'.
497
498 dirList = os.listdir(path)
499 excludes = ['.', '..']
500 sortedSuffs = sorted_suffixes()
501 entries = []
502 for item in dirList:
503 if item in excludes: continue # ==^
504 for suff in sortedSuffs:
505 sub = -1 * len(suff)
506 if sub == 0:
507 if os.path.isdir(os.path.join(path, item)):
508 entries.append(item)
509 elif item[sub:] == suff:
510 it = item[:sub]
511 if not it in entries:
512 entries.append(it)
513 break # ==v^
514 return entries
515
516
517
518def procure_module(name):
519 """Return an established or else new module object having NAME.
520
521 First checks sys.modules, then sys.stub_modules."""
522
523 if sys.modules.has_key(name):
524 it = sys.modules[name]
525 elif sys.stub_modules.has_key(name):
526 it = sys.stub_modules[name]
527 else:
528 it = new.module(name)
529 return it # ===>
530
531def register_module_nesting(modList, pkg):
532 """Given a find_module()-style NESTING and a parent PACKAGE, register
533 components as stub modules."""
534 container = None
535 for stubModNm, stubModF, stubPath, stubTy in modList:
536 relStubNm = string.splitfields(stubModNm, '.')[-1]
537 if sys.modules.has_key(stubModNm):
538 # Nestle in containing package:
539 stubMod = sys.modules[stubModNm]
540 pkg.__dict__[relStubNm] = stubMod
541 pkg = stubMod # will be parent for next in sequence.
542 elif sys.stub_modules.has_key(stubModNm):
543 stubMod = sys.stub_modules[stubModNm]
544 pkg.__dict__[relStubNm] = stubMod
545 pkg = stubMod
546 else:
547 stubMod = procure_module(stubModNm)
548 # Register as a stub:
549 register_module(stubMod, stubModNm, stubPath, pkg, 1)
550 pkg.__dict__[relStubNm] = stubMod
551 pkg = stubMod
552 if not container:
553 container = stubMod
554 return container
555
556def register_module(theMod, name, path, package, stub=0):
557 """Properly register MODULE, w/ name, path, package, opt, as stub."""
558
559 if stub:
560 sys.stub_modules[name] = theMod
561 else:
562 sys.modules[name] = theMod
563 if sys.stub_modules.has_key(name):
564 del sys.stub_modules[name]
565 theMod.__ = theMod.__dict__[PKG_NM] = package
566 theMod.__dict__[PKG_PATH] = path
567
568
569def compile_source(sourcePath, sourceFile):
570 """Given python code source path and file obj, Create a compiled version.
571
572 Return path of compiled version, or None if file creation is not
573 successful. (Compilation errors themselves are passed without restraint.)
574
575 This is an import-private interface, and not well-behaved for general use.
576
577 In particular, we presume the validity of the sourcePath, and that it
578 includes a '.py' extension."""
579
580 compiledPath = sourcePath[:-3] + '.pyc'
581 try:
582 compiledFile = open(compiledPath, 'wb')
583 except IOError:
584 note("write permission denied to " + compiledPath)
585 return None
586 mtime = os.stat(sourcePath)[8]
587 sourceFile.seek(0) # rewind
588 try:
589 compiledFile.write(imp.get_magic()) # compiled magic number
590 compiledFile.seek(8, 0) # mtime space holder
591 # We let compilation errors go their own way...
592 compiled = compile(sourceFile.read(), sourcePath, 'exec')
593 marshal.dump(compiled, compiledFile) # write the code obj
594 compiledFile.seek(4, 0) # position for mtime
595 compiledFile.write(marshal.dumps(mtime)[1:]) # register mtime
596 compiledFile.flush()
597 compiledFile.close()
598 return compiledPath
599 except IOError:
600 return None
601
602
603def PathExtension(locals, globals): # Probably obsolete.
604 """Determine import search path extension vis-a-vis __pkgpath__ entries.
605
606 local dict __pkgpath__ will preceed global dict __pkgpath__."""
607
608 pathadd = []
609 if globals and globals.has_key(PKG_PATH):
610 pathadd = PrependPath(pathadd, globals[PKG_PATH], 'global')
611 if locals and locals.has_key(PKG_PATH):
612 pathadd = PrependPath(pathadd, locals[PKG_PATH], 'local')
613 if pathadd:
614 note(PKG_PATH + ' extension: ' + pathadd)
615 return pathadd
616
617def PrependPath(path, pre, whence): # Probably obsolete
618 """Return copy of PATH list with string or list-of-strings PRE prepended.
619
620 If PRE is neither a string nor list-of-strings, print warning that
621 locality WHENCE has malformed value."""
622
623 # (There is probably a better way to handle malformed PREs, but raising an
624 # error seems too severe - in that case, a bad setting for
625 # sys.__pkgpath__ would prevent any imports!)
626
627 if type(pre) == types.StringType: return [pre] + path[:] # ===>
628 elif type(pre) == types.ListType: return pre + path[:] # ===>
629 else:
630 print "**Ignoring '%s' bad %s value**" % (whence, PKG_PATH)
631 return path # ===>
632
633got_suffixes = None
634def get_suffixes():
635 """Produce a list of triples, each describing a type of import file.
636
637 Triples have the form '(SUFFIX, MODE, TYPE)', where:
638
639 SUFFIX is a string found appended to a module name to make a filename for
640 that type of import file.
641
642 MODE is the mode string to be passed to the built-in 'open' function - "r"
643 for text files, "rb" for binary.
644
645 TYPE is the file type:
646
647 PY_SOURCE: python source code,
648 PY_COMPILED: byte-compiled python source,
649 C_EXTENSION: compiled-code object file,
650 PY_PACKAGE: python library directory, or
651 SEARCH_ERROR: no module found. """
652
653 # Note: sorted_suffixes() depends on this function's value being invariant.
654 # sorted_suffixes() must be revised if this becomes untrue.
655
656 global got_suffixes
657
658 if got_suffixes:
659 return got_suffixes
660 else:
661 # Ensure that the .pyc suffix precedes the .py:
662 got_suffixes = [('', 'r', PY_PACKAGE)]
663 py = pyc = None
664 for suff in imp.get_suffixes():
665 if suff[0] == '.py':
666 py = suff
667 elif suff[0] == '.pyc':
668 pyc = suff
669 else:
670 got_suffixes.append(suff)
671 got_suffixes.append(pyc)
672 got_suffixes.append(py)
673 return got_suffixes
674
675
676sortedSuffs = [] # State vars for sorted_suffixes(). Go
677def sorted_suffixes():
678 """Helper function ~efficiently~ tracks sorted list of module suffixes."""
679
680 # Produce sortedSuffs once - this presumes that get_suffixes does not
681 # change from call to call during a python session. Needs to be
682 # corrected if that becomes no longer true.
683
684 global sortedsuffs
685 if not sortedSuffs: # do compute only the "first" time
686 for item in get_suffixes():
687 sortedSuffs.append(item[0])
688 # Sort them in descending order:
689 sortedSuffs.sort(lambda x, y: (((len(x) > len(y)) and 1) or
690 ((len(x) < len(y)) and -1)))
691 sortedSuffs.reverse()
692 return sortedSuffs
693
694
695# exterior(): Utility routine, obtain local and global dicts of environment
696# containing/outside the callers environment, ie that of the
697# caller's caller. Routines can use exterior() to determine the
698# environment from which they were called.
699
700def exterior():
701 """Return dyad containing locals and globals of caller's caller.
702
703 Locals will be None if same as globals, ie env is global env."""
704
705 bogus = 'bogus' # A locally usable exception
706 try: raise bogus # Force an exception object
707 except bogus:
708 at = sys.exc_traceback.tb_frame.f_back # The external frame.
709 if at.f_back: at = at.f_back # And further, if any.
710 globals, locals = at.f_globals, at.f_locals
711 if locals == globals: # Exterior is global?
712 locals = None
713 return (locals, globals)
714
715#########################################################################
716# TESTING FACILITIES #
717
718def note(msg, threshold=1):
719 if VERBOSE >= threshold: print '(import:', msg, ')'
720
721class TestDirHier:
722 """Populate a transient directory hierarchy according to a definition
723 template - so we can create package/module hierarchies with which to
724 exercise the new import facilities..."""
725
726 def __init__(self, template, where='/var/tmp'):
727 """Establish a dir hierarchy, according to TEMPLATE, that will be
728 deleted upon deletion of this object (or deliberate invocation of the
729 __del__ method)."""
730 self.PKG_NM = 'tdh_'
731 rev = 0
732 while os.path.exists(os.path.join(where, self.PKG_NM+str(rev))):
733 rev = rev + 1
734 sys.exc_traceback = None # Ensure Discard of try/except obj ref
735 self.PKG_NM = self.PKG_NM + str(rev)
736 self.root = os.path.join(where, self.PKG_NM)
737 self.createDir(self.root)
738 self.add(template)
739
740 def __del__(self):
741 """Cleanup the test hierarchy."""
742 self.remove()
743 def add(self, template, root=None):
744 """Populate directory according to template dictionary.
745
746 Keys indicate file names, possibly directories themselves.
747
748 String values dictate contents of flat files.
749
750 Dictionary values dictate recursively embedded dictionary templates."""
751 if root == None: root = self.root
752 for key, val in template.items():
753 name = os.path.join(root, key)
754 if type(val) == types.StringType: # flat file
755 self.createFile(name, val)
756 elif type(val) == types.DictionaryType: # embedded dir
757 self.createDir(name)
758 self.add(val, name)
759 else:
760 raise ValueError, 'invalid file-value type, %s' % type(val)
761 def remove(self, name=''):
762 """Dispose of the NAME (or keys in dictionary), using 'rm -r'."""
763 name = os.path.join(self.root, name)
764 sys.exc_traceback = None # Ensure Discard of try/except obj ref
765 if os.path.exists(name):
766 print '(TestDirHier: deleting %s)' % name
767 os.system('rm -r ' + name)
768 else:
769 raise IOError, "can't remove non-existent " + name
770 def createFile(self, name, contents=None):
771 """Establish file NAME with CONTENTS.
772
773 If no contents specfied, contents will be 'print NAME'."""
774 f = open(name, 'w')
775 if not contents:
776 f.write("print '" + name + "'\n")
777 else:
778 f.write(contents)
779 f.close
780 def createDir(self, name):
781 """Create dir with NAME."""
782 return os.mkdir(name, 0755)
783
784skipToTest = 0
785atTest = 1
786def testExec(msg, execList, locals, globals):
787 global skipToTest, atTest
788 print 'Import Test:', '(' + str(atTest) + ')', msg, '...'
789 atTest = atTest + 1
790 if skipToTest > (atTest - 1):
791 print ' ... skipping til test', skipToTest
792 return
793 else:
794 print ''
795 for stmt in execList:
796 exec stmt in locals, globals
797
798def test(number=0):
799 """Exercise import functionality, creating a transient dir hierarchy for
800 the purpose.
801
802 We actually install the new import functionality, temporarily, resuming the
803 existing function on cleanup."""
804
805 import __builtin__
806
807 global skipToTest, atTest
808 skipToTest = number
809 hier = None
810
811 def confPkgVars(mod, locals, globals):
812 if not sys.modules.has_key(mod):
813 print 'import test: missing module "%s"' % mod
814 else:
815 modMod = sys.modules[mod]
816 if not modMod.__dict__.has_key(PKG_SHORT_NM):
817 print ('import test: module "%s" missing %s pkg shorthand' %
818 (mod, PKG_SHORT_NM))
819 if not modMod.__dict__.has_key(PKG_PATH):
820 print ('import test: module "%s" missing %s package path' %
821 (mod, PKG_PATH))
822 def unloadFull(mod):
823 """Unload module and offspring submodules, if any."""
824 modMod = ''
825 if type(mod) == types.StringType:
826 modNm = mod
827 elif type(mod) == types.ModuleType:
828 modNm = modMod.__name__
829 for subj in sys.modules.keys() + sys.stub_modules.keys():
830 if subj[0:len(modNm)] == modNm:
831 unload(subj)
832
833 # First, get the globals and locals to pass to our testExec():
834 exec 'import ' + __name__
835 globals, locals = eval(__name__ + '.__dict__'), vars()
836
837 try:
838 __main__.testMods
839 except AttributeError:
840 __main__.testMods = []
841 testMods = __main__.testMods
842
843
844 # Install the newimp routines, within a try/finally:
845 try:
846 sys.exc_traceback = None
847 wasImport = __builtin__.__import__ # Stash default
848 wasPath = sys.path
849 except AttributeError:
850 wasImport = None
851 try:
852 hiers = []; modules = []
853 global VERBOSE
854 wasVerbose, VERBOSE = VERBOSE, 2
855 __builtin__.__import__ = import_module # Install new version
856
857 if testMods: # Clear out imports from previous tests
858 for m in testMods[:]:
859 unloadFull(m)
860 testMods.remove(m)
861
862 testExec("already imported module: %s" % sys.modules.keys()[0],
863 ['import ' + sys.modules.keys()[0]],
864 locals, globals)
865 try:
866 no_sirree = 'no_sirree_does_not_exist'
867 testExec("non-existent module: %s" % no_sirree,
868 ['import ' + no_sirree],
869 locals, globals)
870 except ImportError:
871 testExec("ok", ['pass'], locals, globals)
872 got = None
873 for mod in ['Complex', 'UserDict', 'UserList', 'calendar',
874 'cmd', 'dis', 'mailbox', 'profile', 'random', 'rfc822']:
875 if not (mod in sys.modules.keys()):
876 got = mod
877 break # ==v
878 if got:
879 testExec("not-yet loaded module: %s" % mod,
880 ['import ' + mod, 'modules.append(got)'],
881 locals, globals)
882 else:
883 print "Import Test: couldn't find unimported module from list"
884
885 # Now some package stuff.
886
887 # First change the path to include our temp dir, copying so the
888 # addition can be revoked on cleanup in the finally, below:
889 sys.path = ['/var/tmp'] + sys.path[:]
890 # Now create a trivial package:
891 stmts = ["hier1 = TestDirHier({'a.py': 'print \"a.py executing\"'})",
892 "hiers.append(hier1)",
893 "root = hier1.PKG_NM",
894 "exec 'import ' + root",
895 "testMods.append(root)",
896 "confPkgVars(sys.modules[root].__name__, locals, globals)",
897 "confPkgVars(sys.modules[root].__name__+'.a',locals,globals)"]
898 testExec("trivial package, with one module, a.py",
899 stmts, locals, globals)
900 # Slightly less trivial package - reference to '__':
901 stmts = [("hier2 = TestDirHier({'ref.py': 'print \"Pkg __:\", __'})"),
902 "root = hier2.PKG_NM",
903 "hiers.append(hier2)",
904 "exec 'import ' + root",
905 "testMods.append(root)"]
906 testExec("trivial package, with module that has pkg shorthand ref",
907 stmts, locals, globals)
908 # Nested package, plus '__' references:
909
910 complexTemplate = {'ref.py': 'print "ref.py loading..."',
911 'suite': {'s1.py': 'print "s1.py, in pkg:", __',
912 'subsuite': {'sub1.py':
913 'print "sub1.py"'}}}
914 stmts = [('print """%s\n%s\n%s\n%s\n%s\n%s"""' %
915 ('.../',
916 ' ref.py\t\t\t"ref.py loading..."',
917 ' suite/',
918 ' s1.py \t\t"s1.py, in pkg: xxxx.suite"',
919 ' subsuite/',
920 ' sub1.py "sub1.py" ')),
921 "hier3 = TestDirHier(complexTemplate)",
922 "root = hier3.PKG_NM",
923 "hiers.append(hier3)",
924 "exec 'import ' + root",
925 "testMods.append(root)"]
926 testExec("Significantly nestled package:",
927 stmts, locals, globals)
928
929 # Now try to do an embedded sibling import, using '__' shorthand -
930 # alter our complexTemplate for a new dirHier:
931 complexTemplate['suite']['s1.py'] = 'import __.subsuite'
932 stmts = ["hier4 = TestDirHier(complexTemplate)",
933 "root = hier4.PKG_NM",
934 "testMods.append(root)",
935 "hiers.append(hier4)",
936 "exec 'import %s.suite.s1' % root",
937 "testMods.append(root)"]
938 testExec("Similar structure, but suite/s1.py imports '__.subsuite'",
939 stmts, locals, globals)
940
941 sys.exc_traceback = None # Signify clean conclusion.
942
943 finally:
944 if sys.exc_traceback:
945 print ' ** Import test FAILURE... cleanup.'
946 else:
947 print ' Import test SUCCESS... cleanup'
948 VERBOSE = wasVerbose
949 skipToTest = 0
950 atTest = 1
951 sys.path = wasPath
952 for h in hiers: h.remove(); del h # Dispose of test directories
953 if wasImport: # Resurrect prior routine
954 __builtin__.__import__ = wasImport
955 else:
956 del __builtin__.__import__
Guido van Rossumfa486a21995-04-07 09:04:01 +0000957
958if __name__ == '__main__':
959 test()