summaryrefslogtreecommitdiffstats
path: root/Lib/newimp.py
blob: e671de8ba2b45ce5e68e3ab7c0f32f1f2da622a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
"""Prototype of 'import' functionality enhanced to implement packages.

Why packages?  Packages enable module nesting and sibling module
imports.  'Til now, the python module namespace was flat, which
means every module had to have a unique name, in order to not
conflict with names of other modules on the load path.  Furthermore,
suites of modules could not be structurally affiliated with one
another.

With packages, a suite of, eg, email-oriented modules can include a
module named 'mailbox', without conflicting with the, eg, 'mailbox'
module of a shared-memory suite - 'email.mailbox' vs
'shmem.mailbox'.  Packages also enable modules within a suite to
load other modules within their package without having the package
name hard-coded.  Similarly, package suites of modules can be loaded
as a unit, by loading the package that contains them.

Usage: once installed (newimp.install(); newimp.revert() to revert to
the prior __import__ routine), 'import ...' and 'from ... import ...'
can be used to:

  - import modules from the search path, as before.

  - import modules from within other directory "packages" on the search
    path using a '.' dot-delimited nesting syntax.  The nesting is fully
    recursive.

    For example, 'import test.test_types' will import the test_types
    module within the 'test' package.  The calling environment would
    then access the module as 'test.test_types', which is the name of
    the fully-loaded 'test_types' module.  It is found contained within
    the stub (ie, only partially loaded) 'test' module, hence accessed as
    'test.test_types'.

  - import siblings from modules within a package, using '__.' as a shorthand
    prefix to refer to the parent package.  This enables referential
    transparency - package modules need not know their package name.

    The '__' package references are actually names assigned within
    modules, to refer to their containing package.  This means that
    variable references can be made to imported modules, or to variables
    defined via 'import ... from', also using the '__.var' shorthand
    notation.  This establishes a proper equivalence between the import
    reference '__.sibling' and the var reference '__.sibling'.  

  - import an entire package as a unit, by importing the package directory.
    If there is a module named '__main__.py' in the package, it controls the
    load.  Otherwise, all the modules in the dir, including packages, are
    inherently loaded into the package module's namespace.

    For example, 'import test' will load the modules of the entire 'test'
    package, at least until a test failure is encountered.

    In a package, a module with the name '__main__' has a special role.
    If present in a package directory, then it is loaded into the package
    module, instead of loading the contents of the directory.  This
    enables the __main__ module to control the load, possibly loading
    the entire directory deliberately (using 'import __', or even
    'from __ import *', to load all the module contents directly into the
    package module).

  - perform any combination of the above - have a package that contains
    packages, etc.

Modules have a few new attributes in support of packages.  As mentioned
above, '__' is a shorthand attribute denoting the modules' parent package,
also denoted in the module by '__package__'.  Additionally, modules have
associated with them a '__pkgpath__', a path by which sibling modules are
found."""

__version__ = "$Revision$"

# $Id$ First release:
# Ken.Manheimer@nist.gov, 5-Apr-1995, for python 1.2

# Developers Notes:
#
# - 'sys.stub_modules' registers "incidental" (partially loaded) modules.
#   A stub module is promoted to the fully-loaded 'sys.modules' list when it is
#   explicitly loaded as a unit.
# - The __main__ loads of '__' have not yet been tested.
# - The test routines are cool, including a transient directory
#   hierarchy facility, and a means of skipping to later tests by giving
#   the test routine a numeric arg.
# - This could be substantially optimized, and there are many loose ends
#   lying around, since i wanted to get this released for 1.2.

VERBOSE = 0

import sys, string, regex, types, os, marshal, new, __main__
try:
    import imp				# Build on this recent addition
except ImportError:
    raise ImportError, 'Pkg import module depends on optional "imp" module'

from imp import SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION
PY_PACKAGE = 4				# In addition to above PY_*

modes = {SEARCH_ERROR: 'SEARCH_ERROR',
	 PY_SOURCE: 'PY_SOURCE',
	 PY_COMPILED: 'PY_COMPILED',
	 C_EXTENSION: 'C_EXTENSION',
	 PY_PACKAGE: 'PY_PACKAGE'}

# sys.stub_modules tracks modules partially loaded modules, ie loaded only
# incidental to load of nested components.

try: sys.stub_modules
except AttributeError:
    sys.stub_modules = {}

# Environment setup - "root" module, '__python__'

# Establish root package '__python__' in __main__ and newimp envs.

PKG_MAIN_NM = '__main__'		# 'pkg/__main__.py' master, if present.
PKG_NM = '__package__'			# Longhand for module's container.
PKG_SHORT_NM = '__'			# Shorthand for module's container.
PKG_SHORT_NM_LEN = len(PKG_SHORT_NM)
PKG_PATH = '__pkgpath__'		# Var holding package search path,
					# usually just the path of the pkg dir.
__python__ = __main__
sys.modules['__python__'] = __python__	# Register as an importable module.
__python__.__dict__[PKG_PATH] = sys.path

origImportFunc = None
def install():
    """Install newimp import_module() routine, for package support.

    newimp.revert() reverts to __import__ routine that was superceded."""
    import __builtin__
    global origImportFunc
    if not origImportFunc:
	try:
	    origImportFunc = __builtin__.__import__
	except AttributeError:
	    pass
    __builtin__.__import__ = import_module
    print 'Enhanced import functionality installed.'
def revert():
    """Revert to original __builtin__.__import__ func, if newimp.install() has
    been executed."""
    if origImportFunc:
	import __builtin__
	__builtin__.__import__ = origImportFunc
	print 'Original import routine back in place.'

def import_module(name,
		  envLocals=None, envGlobals=None,
		  froms=None,
		  inPkg=None):
    """Primary service routine implementing 'import' with package nesting."""

    # The job is divided into a few distinct steps:
    #
    # - Look for either an already loaded module or a file to be loaded.
    #   * if neither loaded module nor prospect file is found, raise an error.
    #   - If we have a file, not an already loaded module:
    #     - Load the file into a module.
    #     - Register the new module and intermediate package stubs.
    # (We have a module at this point...)
    # - Bind requested syms (module or specified 'from' defs) in calling env.
    # - Return the appropriate component.

    note("import_module: seeking '%s'%s" %
	 (name, ((inPkg and ' (in package %s)' % inPkg.__name__) or '')))

    # We need callers environment dict for local path and resulting module
    # binding.
    if not (envLocals or envGlobals):
	envLocals, envGlobals = exterior()

    modList = theMod = absNm = container = None

    # Get module obj if one already established, or else module file if not:

    if inPkg:
	# We've been invoked with a specific containing package:
	pkg, pkgPath, pkgNm = inPkg, inPkg.__dict__[PKG_PATH], inPkg.__name__
	relNm = name
	absNm = pkgNm + '.' + name
	
    elif name[:PKG_SHORT_NM_LEN+1] != PKG_SHORT_NM + '.':
	# name is NOT '__.something' - setup to seek according to specified
	# absolute name.
	pkg = __python__
	pkgPath = sys.path
	absNm = name
	relNm = absNm

    else:
	# name IS '__.' + something - setup to seek according to relative name,
	# in current package.

	relNm = name[len(PKG_SHORT_NM)+1:]	# Relative portion of name.
	try:
	    pkg = envGlobals[PKG_NM]	# The immediately containing package.
	    pkgPath = pkg.__dict__[PKG_PATH]
	    if pkg == __python__:	# At outermost package.
		absNm = relNm
	    else:
		absNm = (pkg.__name__ + '.' + relNm)
	except KeyError:		# Missing package, path, or name.
	    note("Can't identify parent package, package name, or pkgpath")
	    pass							# ==v

    # Try to find existing module:
    if sys.modules.has_key(absNm):
	note('found ' + absNm + ' already imported')
	theMod = sys.modules[absNm]
    else:
	# Try for builtin or frozen first:
	theMod = imp.init_builtin(absNm)
	if theMod:
	    note('found builtin ' + absNm)
	else:
	    theMod = imp.init_frozen(absNm)
	    if theMod:
		note('found frozen ' + absNm)
	if not theMod:
	    if type(pkgPath) == types.StringType:
		pkgPath = [pkgPath]
	    modList = find_module(relNm, pkgPath, absNm)
	    if not modList:
		raise ImportError, "module '%s' not found" % absNm	# ===X
	    # We have a list of successively nested files leading to the
	    # module, register them as stubs:
	    container = register_module_nesting(modList, pkg)

	    # Load from file if necessary and possible:
	    modNm, modf, path, ty = modList[-1]
	    note('found type ' + modes[ty[2]] + ' - ' + absNm)

	    # Do the load:
	    theMod = load_module(absNm, ty[2], modf, inPkg)

	    # Loaded successfully - promote module to full module status:
	    register_module(theMod, theMod.__name__, pkgPath, pkg)

    # Have a loaded module, impose designated components, and return
    # appropriate thing - according to guido:
    # "Note that for "from spam.ham import bacon" your function should
    #  return the object denoted by 'spam.ham', while for "import
    #  spam.ham" it should return the object denoted by 'spam' -- the
    #  STORE instructions following the import statement expect it this
    #  way."
    if not froms:
	# Establish the module defs in the importing name space:
	(envLocals or envGlobals)[name] = theMod
	return (container or theMod)
    else:
	# Implement 'from': Populate immediate env with module defs:
	if froms == '*':
	    froms = theMod.__dict__.keys()	# resolve '*'
	for item in froms:
	    (envLocals or envGlobals)[item] = theMod.__dict__[item]
	return theMod

def unload(module):
    """Remove registration for a module, so import will do a fresh load."""
    if type(module) == types.ModuleType:
	module = module.__name__
    for m in [sys.modules, sys.stub_modules]:
	try:
	    del m[module]
	except KeyError:
	    pass

def find_module(name, path, absNm=''):
    """Locate module NAME on PATH.  PATH is pathname string or a list of them.

    Note that up-to-date compiled versions of a module are preferred to plain
    source, and compilation is automatically performed when necessary and
    possible.

    Returns a list of the tuples returned by 'find_module_file' (cf), one for
    each nested level, deepest last."""

    checked = []			# For avoiding redundant dir lists.

    if not absNm: absNm = name

    # Parse name into list of nested components, 
    expNm = string.splitfields(name, '.')

    for curPath in path:

	if (type(curPath) != types.StringType) or (curPath in checked):
	    # Disregard bogus or already investigated path elements:
	    continue							# ==^
	else:
	    # Register it for subsequent disregard.
	    checked.append(curPath)

	if len(expNm) == 1:

	    # Non-nested module name:

	    got = find_module_file(curPath, absNm)
	    if got:
		note('using %s' % got[2], 2)
		return [got]						# ===>

	else:

	    # Composite name specifying nested module:

	    gotList = []; nameAccume = expNm[0]

	    got = find_module_file(curPath, nameAccume)
	    if not got:			# Continue to next prospective path.
		continue						# ==^
	    else:
		gotList.append(got)
		nm, file, fullPath, ty = got

	    # Work on successively nested components:
	    for component in expNm[1:]:
		# 'ty'pe of containing component must be package:
		if ty[2] != PY_PACKAGE:
		    gotList, got = [], None
		    break						# ==v^
		if nameAccume:
		    nameAccume = nameAccume + '.' + component
		else:
		    nameAccume = component
		got = find_module_file(fullPath, nameAccume)
		if got:
		    gotList.append(got)
		    # ** have to return the *full* name here:
		    nm, file, fullPath, ty = got
		else:
		    # Clear state vars:
		    gotList, got, nameAccume = [], None, ''
		    break						# ==v^
	    # Found nesting all the way to the specified tip:
	    if got:
		return gotList						# ===>

    # Failed.
    return None

def find_module_file(pathNm, modname):
    """Find module file given dir PATHNAME and module NAME.

    If successful, returns quadruple consisting of a mod name, file object,
    PATHNAME for the found file, and a description triple as contained in the
    list returned by get_suffixes.

    Otherwise, returns None.

    Note that up-to-date compiled versions of a module are preferred to plain
    source, and compilation is automatically performed, when necessary and
    possible."""

    relNm = string.splitfields(modname,'.')[-1]

    if pathNm[-1] != '/': pathNm = pathNm + '/'

    for suff, mode, ty in get_suffixes():
	note('trying ' + pathNm + relNm + suff + '...', 3)
	fullPath = pathNm + relNm + suff
	try:
	    modf = open(fullPath, mode)
	except IOError:
	    # ?? Skip unreadable ones.
	    continue							# ==^

	if ty == PY_PACKAGE:
	    # Enforce directory characteristic:
	    if not os.path.isdir(fullPath):
		note('Skipping non-dir match ' + fullPath)
		continue						# ==^
	    else:
		return (modname, modf, fullPath, (suff, mode, ty))	# ===>
	    

	elif ty == PY_SOURCE:
	    # Try for a compiled version:
	    note('found source ' + fullPath, 2)
	    pyc = fullPath + 'c'	# Sadly, we're presuming '.py' suff.
	    if (not os.path.exists(pyc) or
		(os.stat(fullPath)[8] > os.stat(pyc)[8])):
		# Try to compile:
		pyc = compile_source(fullPath, modf)
	    if pyc and (os.stat(fullPath)[8] < os.stat(pyc)[8]):
		# Either pyc was already newer or we just made it so; in either
		# case it's what we crave:
		return (modname, open(pyc, 'rb'), pyc,			# ===>
			('.pyc', 'rb', PY_COMPILED))
	    # Couldn't get a compiled version - return the source:
	    return (modname, modf, fullPath, (suff, mode, ty))		# ===>

	elif ty == PY_COMPILED:
	    # Make sure it is current, trying to compile if necessary, and
	    # prefer source failing that:
	    note('found compiled ' + fullPath, 2)
	    py = fullPath[:-1]		# Sadly again, presuming '.pyc' suff.
	    if not os.path.exists(py):
		note('found pyc sans py: ' + fullPath)
		return (modname, modf, fullPath, (suff, mode, ty))	# ===>
	    elif (os.stat(py)[8] > os.stat(fullPath)[8]):
		note('forced to try compiling: ' + py)
		pyc = compile_source(py, modf)
		if pyc:
		    return (modname, modf, fullPath, (suff, mode, ty))	# ===>
		else:
		    note('failed compile - must use more recent .py')
		    return (modname,					# ===>
			    open(py, 'r'), py, ('.py', 'r', PY_SOURCE))
	    else:
		return (modname, modf, fullPath, (suff, mode, ty))	# ===>

	elif ty == C_EXTENSION:
	    note('found extension ' + fullPath, 2)
	    return (modname, modf, fullPath, (suff, mode, ty))		# ===>

	else:
	    raise SystemError, 'Unanticipated (new?) module type encountered'

    return None


def load_module(name, ty, theFile, fromMod=None):
    """Load module NAME, type TYPE, from file FILE.

    Optional arg fromMod indicated the module from which the load is being done
    - necessary for detecting import of __ from a package's __main__ module.

    Return the populated module object."""

    # Note: we mint and register intermediate package directories, as necessary
    
    # Determine packagepath extension:

    # Establish the module object in question:
    theMod = procure_module(name)
    nameTail = string.splitfields(name, '.')[-1]
    thePath = theFile.name

    if ty == PY_SOURCE:
	exec_into(theFile, theMod, theFile.name)

    elif ty == PY_COMPILED:
	pyc = open(theFile.name, 'rb').read()
	if pyc[0:4] != imp.get_magic():
	    raise ImportError, 'bad magic number: ' + theFile.name	# ===>
	code = marshal.loads(pyc[8:])
	exec_into(code, theMod, theFile.name)

    elif ty == C_EXTENSION:
	try:
	    theMod = imp.load_dynamic(nameTail, thePath, theFile)
	except:
	    # ?? Ok to embellish the error message?
	    raise sys.exc_type, ('%s (from %s)' %
				 (str(sys.exc_value), theFile.name))

    elif ty == PY_PACKAGE:
	# Load constituents:
	if (os.path.exists(thePath + '/' + PKG_MAIN_NM) and
	    # pkg has a __main__, and this import not already from __main__, so
	    # __main__ can 'import __', or even better, 'from __ import *'
	    ((theMod.__name__ != PKG_MAIN_NM) and (fromMod.__ == theMod))):
	    exec_into(thePath + '/' + PKG_MAIN_NM, theMod, theFile.name)
	else:
	    # ... or else recursively load constituent modules.
	    prospects = mod_prospects(thePath)
	    for item in prospects:
		theMod.__dict__[item] = import_module(item,
						      theMod.__dict__,
						      theMod.__dict__,
						      None,
						      theMod)
		
    else:
	raise ImportError, 'Unimplemented import type: %s' % ty		# ===>
	
    return theMod
    
def exec_into(obj, module, path):
    """Helper for load_module, execfile/exec path or code OBJ within MODULE."""

    # This depends on ability of exec and execfile to mutilate, erhm, mutate
    # the __dict__ of a module.  It will not work if/when this becomes
    # disallowed, as it is for normal assignments.

    try:
	if type(obj) == types.FileType:
	    execfile(path, module.__dict__, module.__dict__)
	elif type(obj) in [types.CodeType, types.StringType]:
	    exec obj in module.__dict__, module.__dict__
    except:
	# ?? Ok to embellish the error message?
	raise sys.exc_type, ('%s (from %s)' %
			     (str(sys.exc_value), path))
	

def mod_prospects(path):
    """Return a list of prospective modules within directory PATH.

    We actually return the distinct names resulting from stripping the dir
    entries (excluding '.' and '..') of their suffixes (as represented by
    'get_suffixes').

    (Note that matches for the PY_PACKAGE type with null suffix are
    implicitly constrained to be directories.)"""

    # We actually strip the longest matching suffixes, so eg 'dbmmodule.so'
    # mates with 'module.so' rather than '.so'.

    dirList = os.listdir(path)
    excludes = ['.', '..']
    sortedSuffs = sorted_suffixes()
    entries = []
    for item in dirList:
	if item in excludes: continue					# ==^
	for suff in sortedSuffs:
	    sub = -1 * len(suff)
	    if sub == 0:
		if os.path.isdir(os.path.join(path, item)):
		    entries.append(item)
	    elif item[sub:] == suff:
		it = item[:sub]
		if not it in entries:
		    entries.append(it)
		break							# ==v^
    return entries
		


def procure_module(name):
    """Return an established or else new module object having NAME.

    First checks sys.modules, then sys.stub_modules."""

    if sys.modules.has_key(name):
	it = sys.modules[name]
    elif sys.stub_modules.has_key(name):
	it = sys.stub_modules[name]
    else:
	it = new.module(name)
    return it								# ===>

def register_module_nesting(modList, pkg):
    """Given a find_module()-style NESTING and a parent PACKAGE, register
    components as stub modules."""
    container = None
    for stubModNm, stubModF, stubPath, stubTy in modList:
	relStubNm = string.splitfields(stubModNm, '.')[-1]
	if sys.modules.has_key(stubModNm):
	    # Nestle in containing package:
	    stubMod = sys.modules[stubModNm]
	    pkg.__dict__[relStubNm] = stubMod
	    pkg = stubMod	# will be parent for next in sequence.
	elif sys.stub_modules.has_key(stubModNm):
	    stubMod = sys.stub_modules[stubModNm]
	    pkg.__dict__[relStubNm] = stubMod
	    pkg = stubMod
	else:
	    stubMod = procure_module(stubModNm)
	    # Register as a stub:
	    register_module(stubMod, stubModNm, stubPath, pkg, 1)
	    pkg.__dict__[relStubNm] = stubMod
	    pkg = stubMod
	if not container:
	    container = stubMod
    return container

def register_module(theMod, name, path, package, stub=0):
    """Properly register MODULE, w/ name, path, package, opt, as stub."""
    
    if stub:
	sys.stub_modules[name] = theMod
    else:
	sys.modules[name] = theMod
	if sys.stub_modules.has_key(name):
	    del sys.stub_modules[name]
    theMod.__ = theMod.__dict__[PKG_NM] = package
    theMod.__dict__[PKG_PATH] = path


def compile_source(sourcePath, sourceFile):
    """Given python code source path and file obj, Create a compiled version.

    Return path of compiled version, or None if file creation is not
    successful.  (Compilation errors themselves are passed without restraint.)

    This is an import-private interface, and not well-behaved for general use.
    
    In particular, we presume the validity of the sourcePath, and that it
    includes a '.py' extension."""

    compiledPath = sourcePath[:-3] + '.pyc'
    try:
	compiledFile = open(compiledPath, 'wb')
    except IOError:
	note("write permission denied to " + compiledPath)
	return None
    mtime = os.stat(sourcePath)[8]
    sourceFile.seek(0)			# rewind
    try:
	compiledFile.write(imp.get_magic())		# compiled magic number
	compiledFile.seek(8, 0)				# mtime space holder
	# We let compilation errors go their own way...
	compiled = compile(sourceFile.read(), sourcePath, 'exec')
	marshal.dump(compiled, compiledFile)		# write the code obj
	compiledFile.seek(4, 0)				# position for mtime
	compiledFile.write(marshal.dumps(mtime)[1:])	# register mtime
	compiledFile.flush()
	compiledFile.close()
	return compiledPath
    except IOError:
	return None


def PathExtension(locals, globals):	# Probably obsolete.
    """Determine import search path extension vis-a-vis __pkgpath__ entries.

    local dict __pkgpath__ will preceed global dict __pkgpath__."""

    pathadd = []
    if globals and globals.has_key(PKG_PATH):
	pathadd = PrependPath(pathadd, globals[PKG_PATH], 'global')
    if locals and locals.has_key(PKG_PATH):
	pathadd = PrependPath(pathadd, locals[PKG_PATH], 'local')
    if pathadd:
	note(PKG_PATH + ' extension: ' + pathadd)
    return pathadd

def PrependPath(path, pre, whence):	# Probably obsolete
    """Return copy of PATH list with string or list-of-strings PRE prepended.

    If PRE is neither a string nor list-of-strings, print warning that
    locality WHENCE has malformed value."""

    # (There is probably a better way to handle malformed PREs, but raising an
    # error seems too severe - in that case, a bad setting for
    # sys.__pkgpath__ would prevent any imports!)

    if type(pre) == types.StringType: return [pre] + path[:]		# ===>
    elif type(pre) == types.ListType: return pre + path[:]		# ===>
    else:
	print "**Ignoring '%s' bad %s value**" % (whence, PKG_PATH)
	return path							# ===>

got_suffixes = None
def get_suffixes():
    """Produce a list of triples, each describing a type of import file.

    Triples have the form '(SUFFIX, MODE, TYPE)', where:

    SUFFIX is a string found appended to a module name to make a filename for
    that type of import file.

    MODE is the mode string to be passed to the built-in 'open' function - "r"
    for text files, "rb" for binary.

    TYPE is the file type:

     PY_SOURCE:		python source code,
     PY_COMPILED:	byte-compiled python source,
     C_EXTENSION:	compiled-code object file,
     PY_PACKAGE:	python library directory, or
     SEARCH_ERROR:	no module found. """

    # Note: sorted_suffixes() depends on this function's value being invariant.
    # sorted_suffixes() must be revised if this becomes untrue.
    
    global got_suffixes

    if got_suffixes:
	return got_suffixes
    else:
	# Ensure that the .pyc suffix precedes the .py:
	got_suffixes = [('', 'r', PY_PACKAGE)]
	py = pyc = None
	for suff in imp.get_suffixes():
	    if suff[0] == '.py':
		py = suff
	    elif suff[0] == '.pyc':
		pyc = suff
	    else:
		got_suffixes.append(suff)
	got_suffixes.append(pyc)
	got_suffixes.append(py)
	return got_suffixes
		

sortedSuffs = []			# State vars for sorted_suffixes().  Go
def sorted_suffixes():
    """Helper function ~efficiently~ tracks sorted list of module suffixes."""

    # Produce sortedSuffs once - this presumes that get_suffixes does not
    # change from call to call during a python session.  Needs to be
    # corrected if that becomes no longer true.

    global sortedsuffs
    if not sortedSuffs:			# do compute only the "first" time
	for item in get_suffixes():
	    sortedSuffs.append(item[0])
	# Sort them in descending order:
	sortedSuffs.sort(lambda x, y: (((len(x) > len(y)) and 1) or
				       ((len(x) < len(y)) and -1)))
	sortedSuffs.reverse()
    return sortedSuffs


# exterior(): Utility routine, obtain local and global dicts of environment
#	      containing/outside the callers environment, ie that of the
#	      caller's caller.  Routines can use exterior() to determine the
#	      environment from which they were called. 

def exterior():
    """Return dyad containing locals and globals of caller's caller.

    Locals will be None if same as globals, ie env is global env."""

    bogus = 'bogus'			# A locally usable exception
    try: raise bogus			# Force an exception object
    except bogus:
	at = sys.exc_traceback.tb_frame.f_back		# The external frame.
	if at.f_back: at = at.f_back			# And further, if any.
	globals, locals = at.f_globals, at.f_locals
	if locals == globals:				# Exterior is global?
	    locals = None
	return (locals, globals)

#########################################################################
#			      TESTING FACILITIES			#

def note(msg, threshold=1):
    if VERBOSE >= threshold: sys.stderr.write('(import: ' + msg + ')\n')

class TestDirHier:
    """Populate a transient directory hierarchy according to a definition
    template - so we can create package/module hierarchies with which to
    exercise the new import facilities..."""

    def __init__(self, template, where='/var/tmp'):
	"""Establish a dir hierarchy, according to TEMPLATE, that will be
	deleted upon deletion of this object (or deliberate invocation of the
	__del__ method)."""
	self.PKG_NM = 'tdh_'
	rev = 0
	while os.path.exists(os.path.join(where, self.PKG_NM+str(rev))):
	    rev = rev + 1
	sys.exc_traceback = None	# Ensure Discard of try/except obj ref
	self.PKG_NM = self.PKG_NM + str(rev)
	self.root = os.path.join(where, self.PKG_NM)
	self.createDir(self.root)
	self.add(template)

    def __del__(self):
	"""Cleanup the test hierarchy."""
	self.remove()
    def add(self, template, root=None):
	"""Populate directory according to template dictionary.

	Keys indicate file names, possibly directories themselves.

	String values dictate contents of flat files.

	Dictionary values dictate recursively embedded dictionary templates."""
	if root == None: root = self.root
	for key, val in template.items():
	    name = os.path.join(root, key)
	    if type(val) == types.StringType:	# flat file
		self.createFile(name, val)
	    elif type(val) == types.DictionaryType:	# embedded dir
		self.createDir(name)
		self.add(val, name)
	    else:
		raise ValueError, 'invalid file-value type, %s' % type(val)
    def remove(self, name=''):
	"""Dispose of the NAME (or keys in dictionary), using 'rm -r'."""
	name = os.path.join(self.root, name)
	sys.exc_traceback = None	# Ensure Discard of try/except obj ref
	if os.path.exists(name):
	    print '(TestDirHier: deleting %s)' % name
	    os.system('rm -r ' + name)
	else:
	    raise IOError, "can't remove non-existent " + name
    def createFile(self, name, contents=None):
	"""Establish file NAME with CONTENTS.

	If no contents specfied, contents will be 'print NAME'."""
	f = open(name, 'w')
	if not contents:
	    f.write("print '" + name + "'\n")
	else:
	    f.write(contents)
	f.close
    def createDir(self, name):
	"""Create dir with NAME."""
	return os.mkdir(name, 0755)

skipToTest = 0
atTest = 1
def testExec(msg, execList, locals, globals):
    global skipToTest, atTest
    print 'Import Test:', '(' + str(atTest) + ')', msg, '...'
    atTest = atTest + 1
    if skipToTest > (atTest - 1):
	print ' ... skipping til test', skipToTest
	return
    else:
	print ''
    for stmt in execList:
	exec stmt in locals, globals

def test(number=0):
    """Exercise import functionality, creating a transient dir hierarchy for
    the purpose.

    We actually install the new import functionality, temporarily, resuming the
    existing function on cleanup."""

    import __builtin__

    global skipToTest, atTest
    skipToTest = number
    hier = None

    def confPkgVars(mod, locals, globals):
	if not sys.modules.has_key(mod):
	    print 'import test: missing module "%s"' % mod
	else:
	    modMod = sys.modules[mod]
	    if not modMod.__dict__.has_key(PKG_SHORT_NM):
		print ('import test: module "%s" missing %s pkg shorthand' %
		       (mod, PKG_SHORT_NM))
	    if not modMod.__dict__.has_key(PKG_PATH):
		print ('import test: module "%s" missing %s package path' %
		       (mod, PKG_PATH))
    def unloadFull(mod):
	"""Unload module and offspring submodules, if any."""
	modMod = ''
	if type(mod) == types.StringType:
	    modNm = mod
	elif type(mod) == types.ModuleType:
	    modNm = modMod.__name__
	for subj in sys.modules.keys() + sys.stub_modules.keys():
	    if subj[0:len(modNm)] == modNm:
		unload(subj)

    # First, get the globals and locals to pass to our testExec():
    exec 'import ' + __name__
    globals, locals = eval(__name__ + '.__dict__'), vars()

    try:
	__main__.testMods
    except AttributeError:
	__main__.testMods = []
    testMods = __main__.testMods
	

    # Install the newimp routines, within a try/finally:
    try:
	sys.exc_traceback = None
	wasImport = __builtin__.__import__	# Stash default
	wasPath = sys.path
    except AttributeError:
	wasImport = None
    try:
	hiers = []; modules = []
	global VERBOSE
	wasVerbose, VERBOSE = VERBOSE, 2
	__builtin__.__import__ = import_module	# Install new version

	if testMods:		# Clear out imports from previous tests
	    for m in testMods[:]:
		unloadFull(m)
		testMods.remove(m)

	testExec("already imported module: %s" % sys.modules.keys()[0],
		 ['import ' + sys.modules.keys()[0]],
		 locals, globals)
	try:
	    no_sirree = 'no_sirree_does_not_exist'
	    testExec("non-existent module: %s" % no_sirree,
		     ['import ' + no_sirree],
		     locals, globals)
	except ImportError:
	    testExec("ok", ['pass'], locals, globals)
	got = None
	for mod in ['Complex', 'UserDict', 'UserList', 'calendar',
		    'cmd', 'dis', 'mailbox', 'profile', 'random', 'rfc822']:
	    if not (mod in sys.modules.keys()):
		got = mod
		break							# ==v
	if got:
	    testExec("not-yet loaded module: %s" % mod,
		     ['import ' + mod, 'modules.append(got)'],
		     locals, globals)
	else:
	    print "Import Test: couldn't find unimported module from list"

	# Now some package stuff.
	
	# First change the path to include our temp dir, copying so the
	# addition can be revoked on cleanup in the finally, below:
	sys.path = ['/var/tmp'] + sys.path[:]
	# Now create a trivial package:
	stmts = ["hier1 = TestDirHier({'a.py': 'print \"a.py executing\"'})",
		 "hiers.append(hier1)",
		 "root = hier1.PKG_NM",
		 "exec 'import ' + root",
		 "testMods.append(root)",
		 "confPkgVars(sys.modules[root].__name__, locals, globals)",
		 "confPkgVars(sys.modules[root].__name__+'.a',locals,globals)"]
	testExec("trivial package, with one module, a.py",
		 stmts, locals, globals)
	# Slightly less trivial package - reference to '__':
	stmts = [("hier2 = TestDirHier({'ref.py': 'print \"Pkg __:\", __'})"),
		 "root = hier2.PKG_NM",
		 "hiers.append(hier2)",
		 "exec 'import ' + root",
		 "testMods.append(root)"]
	testExec("trivial package, with module that has pkg shorthand ref",
		 stmts, locals, globals)
	# Nested package, plus '__' references:

	complexTemplate = {'ref.py': 'print "ref.py loading..."',
			    'suite': {'s1.py': 'print "s1.py, in pkg:", __',
				      'subsuite': {'sub1.py':
						   'print "sub1.py"'}}}
	stmts = [('print """%s\n%s\n%s\n%s\n%s\n%s"""' %
		  ('.../',
		   '    ref.py\t\t\t"ref.py loading..."',
		   '    suite/',
		   '	    s1.py \t\t"s1.py, in pkg: xxxx.suite"',
		   '	    subsuite/',
		   '		sub1.py		"sub1.py" ')),
		 "hier3 = TestDirHier(complexTemplate)",
		 "root = hier3.PKG_NM",
		 "hiers.append(hier3)",
		 "exec 'import ' + root",
		 "testMods.append(root)"]
	testExec("Significantly nestled package:",
		 stmts, locals, globals)

	# Now try to do an embedded sibling import, using '__' shorthand -
	# alter our complexTemplate for a new dirHier:
	complexTemplate['suite']['s1.py'] = 'import __.subsuite'
	stmts = ["hier4 = TestDirHier(complexTemplate)",
		 "root = hier4.PKG_NM",
		 "testMods.append(root)",
		 "hiers.append(hier4)",
		 "exec 'import %s.suite.s1' % root",
		 "testMods.append(root)"]
	testExec("Similar structure, but suite/s1.py imports '__.subsuite'",
		 stmts, locals, globals)

	sys.exc_traceback = None	# Signify clean conclusion.

    finally:
	if sys.exc_traceback:
	    print ' ** Import test FAILURE... cleanup.'
	else:
	    print ' Import test SUCCESS... cleanup'
	VERBOSE = wasVerbose
	skipToTest = 0
	atTest = 1
	sys.path = wasPath
	for h in hiers: h.remove(); del h	# Dispose of test directories
	if wasImport:				# Resurrect prior routine
	    __builtin__.__import__ = wasImport
	else:
	    del __builtin__.__import__

if __name__ == '__main__':
	test()