summaryrefslogtreecommitdiffstats
path: root/Lib/ni.py
blob: fa24d30c3d950a09020115dcd5ef3c5a44c53368 (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
"""New import scheme with package support.

Quick Reference
---------------

- To enable package support, execute "import ni" before importing any
  packages.  Importing this module automatically installs the relevant
  import hooks.

- To create a package named spam containing sub-modules ham, bacon and
  eggs, create a directory spam somewhere on Python's module search
  path (i.e. spam's parent directory must be one of the directories in
  sys.path or $PYTHONPATH); then create files ham.py, bacon.py and
  eggs.py inside spam.

- To import module ham from package spam and use function hamneggs()
  from that module, you can either do

    import spam.ham		# *not* "import spam" !!!
    spam.ham.hamneggs()

  or

    from spam import ham
    ham.hamneggs()

  or

    from spam.ham import hamneggs
    hamneggs()

- Importing just "spam" does not do what you expect: it creates an
  empty package named spam if one does not already exist, but it does
  not import spam's submodules.  The only submodule that is guaranteed
  to be imported is spam.__init__, if it exists.  Note that
  spam.__init__ is a submodule of package spam.  It can reference to
  spam's namespace via the '__.' prefix, for instance

    __.spam_inited = 1		# Set a package-level variable



Theory of Operation
-------------------

A Package is a module that can contain other modules.  Packages can be
nested.  Package introduce dotted names for modules, like P.Q.M, which
could correspond to a file P/Q/M.py found somewhere on sys.path.  It
is possible to import a package itself, though this makes little sense
unless the package contains a module called __init__.

A package has two variables that control the namespace used for
packages and modules, both initialized to sensible defaults the first
time the package is referenced.

(1) A package's *module search path*, contained in the per-package
variable __path__, defines a list of *directories* where submodules or
subpackages of the package are searched.  It is initialized to the
directory containing the package.  Setting this variable to None makes
the module search path default to sys.path (this is not quite the same
as setting it to sys.path, since the latter won't track later
assignments to sys.path).

(2) A package's *import domain*, contained in the per-package variable
__domain__, defines a list of *packages* that are searched (using
their respective module search paths) to satisfy imports.  It is
initialized to the list cosisting of the package itself, its parent
package, its parent's parent, and so on, ending with the root package
(the nameless package containing all top-level packages and modules,
whose module search path is None, implying sys.path).

The default domain implements a search algorithm called "expanding
search".  An alternative search algorithm called "explicit search"
fixes the import search path to contain only the root package,
requiring the modules in the package to name all imported modules by
their full name.  The convention of using '__' to refer to the current
package (both as a per-module variable and in module names) can be
used by packages using explicit search to refer to modules in the same
package; this combination is known as "explicit-relative search".

The PackageImporter and PackageLoader classes together implement the
following policies:

- There is a root package, whose name is ''.  It cannot be imported
  directly but may be referenced, e.g. by using '__' from a top-level
  module.

- In each module or package, the variable '__' contains a reference to
  the parent package; in the root package, '__' points to itself.

- In the name for imported modules (e.g. M in "import M" or "from M
  import ..."), a leading '__' refers to the current package (i.e.
  the package containing the current module); leading '__.__' and so
  on refer to the current package's parent, and so on.  The use of
  '__' elsewhere in the module name is not supported.

- Modules are searched using the "expanding search" algorithm by
  virtue of the default value for __domain__.

- If A.B.C is imported, A is searched using __domain__; then
  subpackage B is searched in A using its __path__, and so on.

- Built-in modules have priority: even if a file sys.py exists in a
  package, "import sys" imports the built-in sys module.

- The same holds for frozen modules, for better or for worse.

- Submodules and subpackages are not automatically loaded when their
  parent packages is loaded.

- The construct "from package import *" is illegal.  (It can still be
  used to import names from a module.)

- When "from package import module1, module2, ..." is used, those
    modules are explicitly loaded.

- When a package is loaded, if it has a submodule __init__, that
  module is loaded.  This is the place where required submodules can
  be loaded, the __path__ variable extended, etc.  The __init__ module
  is loaded even if the package was loaded only in order to create a
  stub for a sub-package: if "import P.Q.R" is the first reference to
  P, and P has a submodule __init__, P.__init__ is loaded before P.Q
  is even searched.

Caveats:

- It is possible to import a package that has no __init__ submodule;
  this is not particularly useful but there may be useful applications
  for it (e.g. to manipulate its search paths from the outside!).

- There are no special provisions for os.chdir().  If you plan to use
  os.chdir() before you have imported all your modules, it is better
  not to have relative pathnames in sys.path.  (This could actually be
  fixed by changing the implementation of path_join() in the hook to
  absolutize paths.)

- Packages and modules are introduced in sys.modules as soon as their
  loading is started.  When the loading is terminated by an exception,
  the sys.modules entries remain around.

- There are no special measures to support mutually recursive modules,
  but it will work under the same conditions where it works in the
  flat module space system.

- Sometimes dummy entries (whose value is None) are entered in
  sys.modules, to indicate that a particular module does not exist --
  this is done to speed up the expanding search algorithm when a
  module residing at a higher level is repeatedly imported (Python
  promises that importing a previously imported module is cheap!)

- Although dynamically loaded extensions are allowed inside packages,
  the current implementation (hardcoded in the interpreter) of their
  initialization may cause problems if an extension invokes the
  interpreter during its initialization.

- reload() may find another version of the module only if it occurs on
  the package search path.  Thus, it keeps the connection to the
  package to which the module belongs, but may find a different file.

XXX Need to have an explicit name for '', e.g. '__root__'.

"""


import imp
import string
import sys
import __builtin__

import ihooks
from ihooks import ModuleLoader, ModuleImporter


class PackageLoader(ModuleLoader):

    """A subclass of ModuleLoader with package support.

    find_module_in_dir() will succeed if there's a subdirectory with
    the given name; load_module() will create a stub for a package and
    load its __init__ module if it exists.

    """

    def find_module_in_dir(self, name, dir):
	if dir is not None:
	    dirname = self.hooks.path_join(dir, name)
	    if self.hooks.path_isdir(dirname):
		return None, dirname, ('', '', 'PACKAGE')
	return ModuleLoader.find_module_in_dir(self, name, dir)

    def load_module(self, name, stuff):
	file, filename, info = stuff
	suff, mode, type = info
	if type == 'PACKAGE':
	    return self.load_package(name, stuff)
	if sys.modules.has_key(name):
	    m = sys.modules[name]
	else:
	    sys.modules[name] = m = imp.new_module(name)
	self.set_parent(m)
	if type == imp.C_EXTENSION and '.' in name:
	    return self.load_dynamic(name, stuff)
	else:
	    return ModuleLoader.load_module(self, name, stuff)

    def load_dynamic(self, name, stuff):
	file, filename, (suff, mode, type) = stuff
	# Hack around restriction in imp.load_dynamic()
	i = string.rfind(name, '.')
	tail = name[i+1:]
	if sys.modules.has_key(tail):
	    save = sys.modules[tail]
	else:
	    save = None
	sys.modules[tail] = imp.new_module(name)
	try:
	    m = imp.load_dynamic(tail, filename, file)
	finally:
	    if save:
		sys.modules[tail] = save
	    else:
		del sys.modules[tail]
	sys.modules[name] = m
	return m

    def load_package(self, name, stuff):
	file, filename, info = stuff
	if sys.modules.has_key(name):
	    package = sys.modules[name]
	else:
	    sys.modules[name] = package = imp.new_module(name)
	package.__path__ = [filename]
	self.init_package(package)
	return package

    def init_package(self, package):
	self.set_parent(package)
	self.set_domain(package)
	self.call_init_module(package)

    def set_parent(self, m):
	name = m.__name__
	if '.' in name:
	    name = name[:string.rfind(name, '.')]
	else:
	    name = ''
	m.__ = sys.modules[name]

    def set_domain(self, package):
	name = package.__name__
	package.__domain__ = domain = [name]
	while '.' in name:
	    name = name[:string.rfind(name, '.')]
	    domain.append(name)
	if name:
	    domain.append('')

    def call_init_module(self, package):
	stuff = self.find_module('__init__', package.__path__)
	if stuff:
	    m = self.load_module(package.__name__ + '.__init__', stuff)
	    package.__init__ = m


class PackageImporter(ModuleImporter):

    """Importer that understands packages and '__'."""

    def __init__(self, loader = None, verbose = 0):
	ModuleImporter.__init__(self,
	loader or PackageLoader(None, verbose), verbose)

    def import_module(self, name, globals={}, locals={}, fromlist=[]):
	if globals.has_key('__'):
	    package = globals['__']
	else:
	    # No calling context, assume in root package
	    package = sys.modules['']
	if name[:3] in ('__.', '__'):
	    p = package
	    name = name[3:]
	    while name[:3] in ('__.', '__'):
		p = p.__
		name = name[3:]
	    if not name:
		return self.finish(package, p, '', fromlist)
	    if '.' in name:
		i = string.find(name, '.')
		name, tail = name[:i], name[i:]
	    else:
		tail = ''
	    mname = p.__name__ and p.__name__+'.'+name or name
	    m = self.get1(mname)
	    return self.finish(package, m, tail, fromlist)
	if '.' in name:
	    i = string.find(name, '.')
	    name, tail = name[:i], name[i:]
	else:
	    tail = ''
	for pname in package.__domain__:
	    mname = pname and pname+'.'+name or name
	    m = self.get0(mname)
	    if m: break
	else:
	    raise ImportError, "No such module %s" % name
	return self.finish(m, m, tail, fromlist)

    def finish(self, module, m, tail, fromlist):
	# Got ....A; now get ....A.B.C.D
	yname = m.__name__
	if tail and sys.modules.has_key(yname + tail): # Fast path
	    yname, tail = yname + tail, ''
	    m = self.get1(yname)
	while tail:
	    i = string.find(tail, '.', 1)
	    if i > 0:
		head, tail = tail[:i], tail[i:]
	    else:
		head, tail = tail, ''
	    yname = yname + head
	    m = self.get1(yname)

	# Got ....A.B.C.D; now finalize things depending on fromlist
	if not fromlist:
	    return module
	if '__' in fromlist:
	    raise ImportError, "Can't import __ from anywhere"
	if not hasattr(m, '__path__'): return m
	if '*' in fromlist:
	    raise ImportError, "Can't import * from a package"
	for f in fromlist:
	    if hasattr(m, f): continue
	    fname = yname + '.' + f
	    self.get1(fname)
	return m

    def get1(self, name):
	m = self.get(name)
	if not m:
	    raise ImportError, "No module named %s" % name
	return m

    def get0(self, name):
	m = self.get(name)
	if not m:
	    sys.modules[name] = None
	return m

    def get(self, name):
	# Internal routine to get or load a module when its parent exists
	if sys.modules.has_key(name):
	    return sys.modules[name]
	if '.' in name:
	    i = string.rfind(name, '.')
	    head, tail = name[:i], name[i+1:]
	else:
	    head, tail = '', name
	path = sys.modules[head].__path__
	stuff = self.loader.find_module(tail, path)
	if not stuff:
	    return None
	sys.modules[name] = m = self.loader.load_module(name, stuff)
	if head:
	    setattr(sys.modules[head], tail, m)
	return m

    def reload(self, module):
	name = module.__name__
	if '.' in name:
	    i = string.rfind(name, '.')
	    head, tail = name[:i], name[i+1:]
	    path = sys.modules[head].__path__
	else:
	    tail = name
	    path = sys.modules[''].__path__
	stuff = self.loader.find_module(tail, path)
	if not stuff:
	    raise ImportError, "No module named %s" % name
	return self.loader.load_module(name, stuff)

    def unload(self, module):
	if hasattr(module, '__path__'):
	    raise ImportError, "don't know how to unload packages yet"
	PackageImporter.unload(self, module)

    def install(self):
	if not sys.modules.has_key(''):
	    sys.modules[''] = package = imp.new_module('')
	    package.__path__ = None
	    self.loader.init_package(package)
	    for m in sys.modules.values():
		if not m: continue
		if not hasattr(m, '__'):
		    self.loader.set_parent(m)
	ModuleImporter.install(self)


def install(v = 0):
    ihooks.install(PackageImporter(None, v))

def uninstall():
    ihooks.uninstall()

def ni(v = 0):
    install(v)

def no():
    uninstall()

def test():
    import pdb
    try:
	testproper()
    except:
	sys.last_type, sys.last_value, sys.last_traceback = (
		sys.exc_type, sys.exc_value, sys.exc_traceback)
	print
	print sys.last_type, ':', sys.last_value
	print
	pdb.pm()

def testproper():
    install(1)
    try:
	import mactest
	print dir(mactest)
	raw_input('OK?')
    finally:
	uninstall()


if __name__ == '__main__':
    test()
else:
    install()