PEP 302 + zipimport:

- new import hooks in import.c, exposed in the sys module
- new module called 'zipimport'
- various changes to allow bootstrapping from zip files

I hope I didn't break the Windows build (or anything else for that
matter), but then again, it's been sitting on sf long enough...

Regarding the latest discussions on python-dev: zipimport sets
pkg.__path__ as specified in PEP 273, and likewise, sys.path item such as
/path/to/Archive.zip/subdir/ are supported again.
This commit is contained in:
Just van Rossum 2002-12-30 22:08:05 +00:00
parent 60087fb450
commit 52e14d640b
13 changed files with 1887 additions and 35 deletions

View File

@ -99,6 +99,7 @@ PyAPI_FUNC(PyObject *) _PyBuiltin_Init(void);
PyAPI_FUNC(PyObject *) _PySys_Init(void); PyAPI_FUNC(PyObject *) _PySys_Init(void);
PyAPI_FUNC(void) _PyImport_Init(void); PyAPI_FUNC(void) _PyImport_Init(void);
PyAPI_FUNC(void) _PyExc_Init(void); PyAPI_FUNC(void) _PyExc_Init(void);
PyAPI_FUNC(void) _PyImportHooks_Init(void);
/* Various internal finalizers */ /* Various internal finalizers */
PyAPI_FUNC(void) _PyExc_Fini(void); PyAPI_FUNC(void) _PyExc_Fini(void);

View File

@ -73,16 +73,11 @@ del m
# only absolute pathnames, even if we're running from the build directory. # only absolute pathnames, even if we're running from the build directory.
L = [] L = []
_dirs_in_sys_path = {} _dirs_in_sys_path = {}
dir = dircase = None # sys.path may be empty at this point
for dir in sys.path: for dir in sys.path:
# Filter out paths that don't exist, but leave in the empty string # Filter out duplicate paths (on case-insensitive file systems also
# since it's a special case. We also need to special-case the Mac, # if they only differ in case); turn relative paths into absolute
# as file names are allowed on sys.path there. # paths.
if sys.platform != 'mac':
if dir and not os.path.isdir(dir):
continue
else:
if dir and not os.path.exists(dir):
continue
dir, dircase = makepath(dir) dir, dircase = makepath(dir)
if not dircase in _dirs_in_sys_path: if not dircase in _dirs_in_sys_path:
L.append(dir) L.append(dir)

View File

@ -0,0 +1,204 @@
import sys
import imp
import os
import unittest
from test import test_support
test_src = """\
def get_name():
return __name__
def get_file():
return __file__
"""
test_co = compile(test_src, "<???>", "exec")
test_path = "!!!_test_!!!"
class ImportTracker:
"""Importer that only tracks attempted imports."""
def __init__(self):
self.imports = []
def find_module(self, fullname, path=None):
self.imports.append(fullname)
return None
class TestImporter:
modules = {
"hooktestmodule": (False, test_co),
"hooktestpackage": (True, test_co),
"hooktestpackage.sub": (True, test_co),
"hooktestpackage.sub.subber": (False, test_co),
}
def __init__(self, path=test_path):
if path != test_path:
# if out class is on sys.path_hooks, we must raise
# ImportError for any path item that we can't handle.
raise ImportError
self.path = path
def _get__path__(self):
raise NotImplementedError
def find_module(self, fullname, path=None):
if fullname in self.modules:
return self
else:
return None
def load_module(self, fullname):
ispkg, code = self.modules[fullname]
mod = imp.new_module(fullname)
sys.modules[fullname] = mod
mod.__file__ = "<%s>" % self.__class__.__name__
mod.__loader__ = self
if ispkg:
mod.__path__ = self._get__path__()
exec code in mod.__dict__
return mod
class MetaImporter(TestImporter):
def _get__path__(self):
return []
class PathImporter(TestImporter):
def _get__path__(self):
return [self.path]
class ImportBlocker:
"""Place an ImportBlocker instance on sys.meta_path and you
can be sure the modules you specified can't be imported, even
if it's a builtin."""
def __init__(self, *namestoblock):
self.namestoblock = dict.fromkeys(namestoblock)
def find_module(self, fullname, path=None):
if fullname in self.namestoblock:
return self
return None
def load_module(self, fullname):
raise ImportError, "I dare you"
class ImpWrapper:
def __init__(self, path=None):
if path is not None and not os.path.isdir(path):
raise ImportError
self.path = path
def find_module(self, fullname, path=None):
subname = fullname.split(".")[-1]
if subname != fullname and self.path is None:
return None
if self.path is None:
path = None
else:
path = [self.path]
try:
file, filename, stuff = imp.find_module(subname, path)
except ImportError:
return None
return ImpLoader(file, filename, stuff)
class ImpLoader:
def __init__(self, file, filename, stuff):
self.file = file
self.filename = filename
self.stuff = stuff
def load_module(self, fullname):
mod = imp.load_module(fullname, self.file, self.filename, self.stuff)
if self.file:
self.file.close()
mod.__loader__ = self # for introspection
return mod
class ImportHooksBaseTestCase(unittest.TestCase):
def setUp(self):
self.path = sys.path[:]
self.meta_path = sys.meta_path[:]
self.path_hooks = sys.path_hooks[:]
sys.path_importer_cache.clear()
self.tracker = ImportTracker()
sys.meta_path.insert(0, self.tracker)
def tearDown(self):
sys.path[:] = self.path
sys.meta_path[:] = self.meta_path
sys.path_hooks[:] = self.path_hooks
sys.path_importer_cache.clear()
for fullname in self.tracker.imports:
if fullname in sys.modules:
del sys.modules[fullname]
class ImportHooksTestCase(ImportHooksBaseTestCase):
def doTestImports(self, importer=None):
import hooktestmodule
import hooktestpackage
import hooktestpackage.sub
import hooktestpackage.sub.subber
self.assertEqual(hooktestmodule.get_name(),
"hooktestmodule")
self.assertEqual(hooktestpackage.get_name(),
"hooktestpackage")
self.assertEqual(hooktestpackage.sub.get_name(),
"hooktestpackage.sub")
self.assertEqual(hooktestpackage.sub.subber.get_name(),
"hooktestpackage.sub.subber")
if importer:
self.assertEqual(hooktestmodule.__loader__, importer)
self.assertEqual(hooktestpackage.__loader__, importer)
self.assertEqual(hooktestpackage.sub.__loader__, importer)
self.assertEqual(hooktestpackage.sub.subber.__loader__, importer)
def testMetaPath(self):
i = MetaImporter()
sys.meta_path.append(i)
self.doTestImports(i)
def testPathHook(self):
sys.path_hooks.append(PathImporter)
sys.path.append(test_path)
self.doTestImports()
def testBlocker(self):
mname = "exceptions" # an arbitrary harmless builtin module
if mname in sys.modules:
del sys.modules[mname]
sys.meta_path.append(ImportBlocker(mname))
try:
__import__(mname)
except ImportError:
pass
else:
self.fail("'%s' was not supposed to be importable" % mname)
def testImpWrapper(self):
i = ImpWrapper()
sys.meta_path.append(i)
sys.path_hooks.append(ImpWrapper)
mnames = ("colorsys", "urlparse", "distutils.core", "compiler.misc")
for mname in mnames:
parent = mname.split(".")[0]
for n in sys.modules.keys():
if n.startswith(parent):
del sys.modules[n]
for mname in mnames:
m = __import__(mname, globals(), locals(), ["__dummy__"])
m.__loader__ # to make sure we actually handled the import
if __name__ == "__main__":
test_support.run_unittest(ImportHooksTestCase)

180
Lib/test/test_zipimport.py Normal file
View File

@ -0,0 +1,180 @@
import sys
import os
import marshal
import imp
import struct
import time
import zlib # implied prerequisite
from zipfile import ZipFile, ZipInfo, ZIP_STORED, ZIP_DEFLATED
from test import test_support
from test.test_importhooks import ImportHooksBaseTestCase, test_src, test_co
import zipimport
def make_pyc(co, mtime):
data = marshal.dumps(co)
pyc = imp.get_magic() + struct.pack("<i", mtime) + data
return pyc
NOW = time.time()
test_pyc = make_pyc(test_co, NOW)
if __debug__:
pyc_ext = ".pyc"
else:
pyc_ext = ".pyo"
TESTMOD = "ziptestmodule"
TESTPACK = "ziptestpackage"
TEMP_ZIP = "junk95142.zip"
class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
compression = ZIP_STORED
def setUp(self):
# We're reusing the zip archive path, so we must clear the
# cached directory info.
zipimport._zip_directory_cache.clear()
ImportHooksBaseTestCase.setUp(self)
def doTest(self, expected_ext, files, *modules):
z = ZipFile(TEMP_ZIP, "w")
try:
for name, (mtime, data) in files.items():
zinfo = ZipInfo(name, time.localtime(mtime))
zinfo.compress_type = self.compression
z.writestr(zinfo, data)
z.close()
sys.path.insert(0, TEMP_ZIP)
mod = __import__(".".join(modules), globals(), locals(),
["__dummy__"])
file = mod.get_file()
self.assertEquals(file, os.path.join(TEMP_ZIP,
os.sep.join(modules) + expected_ext))
finally:
z.close()
os.remove(TEMP_ZIP)
def testAFakeZlib(self):
#
# This could cause a stack overflow before: importing zlib.py
# from a compressed archive would cause zlib to be imported
# which would find zlib.py in the archive, which would... etc.
#
# This test *must* be executed first: it must be the first one
# to trigger zipimport to import zlib (zipimport caches the
# zlib.decompress function object, after which the problem being
# tested here wouldn't be a problem anymore...
# (Hence the 'A' in the test method name: to make it the first
# item in a list sorted by name, like unittest.makeSuite() does.)
#
if "zlib" in sys.modules:
del sys.modules["zlib"]
files = {"zlib.py": (NOW, test_src)}
try:
self.doTest(".py", files, "zlib")
except ImportError:
if self.compression != ZIP_DEFLATED:
self.fail("expected test to not raise ImportError")
else:
if self.compression != ZIP_STORED:
self.fail("expected test to raise ImportError")
def testPy(self):
files = {TESTMOD + ".py": (NOW, test_src)}
self.doTest(".py", files, TESTMOD)
def testPyc(self):
files = {TESTMOD + pyc_ext: (NOW, test_pyc)}
self.doTest(pyc_ext, files, TESTMOD)
def testBoth(self):
files = {TESTMOD + ".py": (NOW, test_src),
TESTMOD + pyc_ext: (NOW, test_pyc)}
self.doTest(pyc_ext, files, TESTMOD)
def testBadMagic(self):
# make pyc magic word invalid, forcing loading from .py
m0 = ord(test_pyc[0])
m0 ^= 0x04 # flip an arbitrary bit
badmagic_pyc = chr(m0) + test_pyc[1:]
files = {TESTMOD + ".py": (NOW, test_src),
TESTMOD + pyc_ext: (NOW, badmagic_pyc)}
self.doTest(".py", files, TESTMOD)
def testBadMagic2(self):
# make pyc magic word invalid, causing an ImportError
m0 = ord(test_pyc[0])
m0 ^= 0x04 # flip an arbitrary bit
badmagic_pyc = chr(m0) + test_pyc[1:]
files = {TESTMOD + pyc_ext: (NOW, badmagic_pyc)}
try:
self.doTest(".py", files, TESTMOD)
except ImportError:
pass
else:
self.fail("expected ImportError; import from bad pyc")
def testBadMTime(self):
t3 = ord(test_pyc[7])
t3 ^= 0x02 # flip the second bit -- not the first as that one
# isn't stored in the .py's mtime in the zip archive.
badtime_pyc = test_pyc[:7] + chr(t3) + test_pyc[8:]
files = {TESTMOD + ".py": (NOW, test_src),
TESTMOD + pyc_ext: (NOW, badtime_pyc)}
self.doTest(".py", files, TESTMOD)
def testPackage(self):
packdir = TESTPACK + os.sep
files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc),
packdir + TESTMOD + pyc_ext: (NOW, test_pyc)}
self.doTest(pyc_ext, files, TESTPACK, TESTMOD)
def testDeepPackage(self):
packdir = TESTPACK + os.sep
packdir2 = packdir + packdir
files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc),
packdir2 + "__init__" + pyc_ext: (NOW, test_pyc),
packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}
self.doTest(pyc_ext, files, TESTPACK, TESTPACK, TESTMOD)
def testGetData(self):
z = ZipFile(TEMP_ZIP, "w")
z.compression = self.compression
try:
name = "testdata.dat"
data = "".join([chr(x) for x in range(256)]) * 500
z.writestr(name, data)
z.close()
zi = zipimport.zipimporter(TEMP_ZIP)
self.assertEquals(data, zi.get_data(name))
finally:
z.close()
os.remove(TEMP_ZIP)
def testImporterAttr(self):
src = """if 1: # indent hack
def get_file():
return __file__
if __importer__.get_data("some.data") != "some data":
raise AssertionError, "bad data"\n"""
pyc = make_pyc(compile(src, "<???>", "exec"), NOW)
files = {TESTMOD + pyc_ext: (NOW, pyc),
"some.data": (NOW, "some data")}
self.doTest(pyc_ext, files, TESTMOD)
class CompressedZipImportTestCase(UncompressedZipImportTestCase):
compression = ZIP_DEFLATED
if __name__ == "__main__":
test_support.run_unittest(UncompressedZipImportTestCase)
test_support.run_unittest(CompressedZipImportTestCase)

View File

@ -113,6 +113,10 @@ errno errnomodule.c # posix (UNIX) errno values
_sre _sre.c # Fredrik Lundh's new regular expressions _sre _sre.c # Fredrik Lundh's new regular expressions
_codecs _codecsmodule.c # access to the builtin codecs and codec registry _codecs _codecsmodule.c # access to the builtin codecs and codec registry
# The zipimport module is always imported at startup. Having it as a
# builtin module avoids some bootstrapping problems and reduces overhead.
zipimport zipimport.c
# The rest of the modules listed in this file are all commented out by # The rest of the modules listed in this file are all commented out by
# default. Usually they can be detected and built as dynamically # default. Usually they can be detected and built as dynamically
# loaded modules by the new setup.py script added in Python 2.1. If # loaded modules by the new setup.py script added in Python 2.1. If

View File

@ -365,6 +365,7 @@ calculate_path(void)
char *path = getenv("PATH"); char *path = getenv("PATH");
char *prog = Py_GetProgramName(); char *prog = Py_GetProgramName();
char argv0_path[MAXPATHLEN+1]; char argv0_path[MAXPATHLEN+1];
char zip_path[MAXPATHLEN+1];
int pfound, efound; /* 1 if found; -1 if found build directory */ int pfound, efound; /* 1 if found; -1 if found build directory */
char *buf; char *buf;
size_t bufsz; size_t bufsz;
@ -483,6 +484,18 @@ calculate_path(void)
else else
reduce(prefix); reduce(prefix);
strncpy(zip_path, prefix, MAXPATHLEN);
if (pfound > 0) { /* Use the reduced prefix returned by Py_GetPrefix() */
reduce(zip_path);
reduce(zip_path);
}
else
strncpy(zip_path, PREFIX, MAXPATHLEN);
joinpath(zip_path, "lib/python00.zip");
bufsz = strlen(zip_path); /* Replace "00" with version */
zip_path[bufsz - 6] = VERSION[0];
zip_path[bufsz - 5] = VERSION[2];
if (!(efound = search_for_exec_prefix(argv0_path, home))) { if (!(efound = search_for_exec_prefix(argv0_path, home))) {
if (!Py_FrozenFlag) if (!Py_FrozenFlag)
fprintf(stderr, fprintf(stderr,
@ -521,6 +534,7 @@ calculate_path(void)
defpath = delim + 1; defpath = delim + 1;
} }
bufsz += strlen(zip_path) + 1;
bufsz += strlen(exec_prefix) + 1; bufsz += strlen(exec_prefix) + 1;
/* This is the only malloc call in this file */ /* This is the only malloc call in this file */
@ -541,6 +555,10 @@ calculate_path(void)
else else
buf[0] = '\0'; buf[0] = '\0';
/* Next is the default zip path */
strcat(buf, zip_path);
strcat(buf, delimiter);
/* Next goes merge of compile-time $PYTHONPATH with /* Next goes merge of compile-time $PYTHONPATH with
* dynamically located prefix. * dynamically located prefix.
*/ */

1187
Modules/zipimport.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@ extern void initxreadlines(void);
extern void init_weakref(void); extern void init_weakref(void);
extern void init_hotshot(void); extern void init_hotshot(void);
extern void initxxsubtype(void); extern void initxxsubtype(void);
extern void initzipimport(void);
extern void init_random(void); extern void init_random(void);
/* XXX tim: what's the purpose of ADDMODULE MARKER? */ /* XXX tim: what's the purpose of ADDMODULE MARKER? */
@ -98,6 +99,7 @@ struct _inittab _PyImport_Inittab[] = {
{"_random", init_random}, {"_random", init_random},
{"xxsubtype", initxxsubtype}, {"xxsubtype", initxxsubtype},
{"zipimport", initzipimport},
/* XXX tim: what's the purpose of ADDMODULE MARKER? */ /* XXX tim: what's the purpose of ADDMODULE MARKER? */
/* -- ADDMODULE MARKER 2 -- */ /* -- ADDMODULE MARKER 2 -- */

View File

@ -81,6 +81,7 @@
static char prefix[MAXPATHLEN+1]; static char prefix[MAXPATHLEN+1];
static char progpath[MAXPATHLEN+1]; static char progpath[MAXPATHLEN+1];
static char dllpath[MAXPATHLEN+1];
static char *module_search_path = NULL; static char *module_search_path = NULL;
@ -350,6 +351,7 @@ get_progpath(void)
char *prog = Py_GetProgramName(); char *prog = Py_GetProgramName();
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
extern HANDLE PyWin_DLLhModule;
#ifdef UNICODE #ifdef UNICODE
WCHAR wprogpath[MAXPATHLEN+1]; WCHAR wprogpath[MAXPATHLEN+1];
/* Windows documents that GetModuleFileName() will "truncate", /* Windows documents that GetModuleFileName() will "truncate",
@ -357,6 +359,14 @@ get_progpath(void)
PLUS Windows itself defines MAX_PATH as the same, but anyway... PLUS Windows itself defines MAX_PATH as the same, but anyway...
*/ */
wprogpath[MAXPATHLEN]=_T('\0'); wprogpath[MAXPATHLEN]=_T('\0');
if (PyWin_DLLhModule &&
GetModuleFileName(PyWin_DLLhModule, wprogpath, MAXPATHLEN)) {
WideCharToMultiByte(CP_ACP, 0,
wprogpath, -1,
dllpath, MAXPATHLEN+1,
NULL, NULL);
}
wprogpath[MAXPATHLEN]=_T('\0')';
if (GetModuleFileName(NULL, wprogpath, MAXPATHLEN)) { if (GetModuleFileName(NULL, wprogpath, MAXPATHLEN)) {
WideCharToMultiByte(CP_ACP, 0, WideCharToMultiByte(CP_ACP, 0,
wprogpath, -1, wprogpath, -1,
@ -366,6 +376,9 @@ get_progpath(void)
} }
#else #else
/* static init of progpath ensures final char remains \0 */ /* static init of progpath ensures final char remains \0 */
if (PyWin_DLLhModule)
if (!GetModuleFileName(PyWin_DLLhModule, dllpath, MAXPATHLEN))
dllpath[0] = 0;
if (GetModuleFileName(NULL, progpath, MAXPATHLEN)) if (GetModuleFileName(NULL, progpath, MAXPATHLEN))
return; return;
#endif #endif
@ -427,6 +440,8 @@ calculate_path(void)
int skiphome, skipdefault; int skiphome, skipdefault;
char *machinepath = NULL; char *machinepath = NULL;
char *userpath = NULL; char *userpath = NULL;
char zip_path[MAXPATHLEN+1];
size_t len;
#endif #endif
get_progpath(); get_progpath();
@ -447,6 +462,21 @@ calculate_path(void)
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
/* Calculate zip archive path */
if (dllpath[0]) /* use name of python DLL */
strncpy(zip_path, dllpath, MAXPATHLEN);
else /* use name of executable program */
strncpy(zip_path, progpath, MAXPATHLEN);
len = strlen(zip_path);
if (len > 4) {
zip_path[len-3] = 'z'; /* change ending to "zip" */
zip_path[len-2] = 'i';
zip_path[len-1] = 'p';
}
else {
zip_path[0] = 0;
}
skiphome = pythonhome==NULL ? 0 : 1; skiphome = pythonhome==NULL ? 0 : 1;
machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome); machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome);
userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome); userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome);
@ -458,14 +488,15 @@ calculate_path(void)
/* We need to construct a path from the following parts. /* We need to construct a path from the following parts.
(1) the PYTHONPATH environment variable, if set; (1) the PYTHONPATH environment variable, if set;
(2) for Win32, the machinepath and userpath, if set; (2) for Win32, the zip archive file path;
(3) the PYTHONPATH config macro, with the leading "." (3) for Win32, the machinepath and userpath, if set;
(4) the PYTHONPATH config macro, with the leading "."
of each component replaced with pythonhome, if set; of each component replaced with pythonhome, if set;
(4) the directory containing the executable (argv0_path). (5) the directory containing the executable (argv0_path).
The length calculation calculates #3 first. The length calculation calculates #4 first.
Extra rules: Extra rules:
- If PYTHONHOME is set (in any way) item (2) is ignored. - If PYTHONHOME is set (in any way) item (3) is ignored.
- If registry values are used, (3) and (4) are ignored. - If registry values are used, (4) and (5) are ignored.
*/ */
/* Calculate size of return buffer */ /* Calculate size of return buffer */
@ -487,6 +518,7 @@ calculate_path(void)
bufsz += strlen(userpath) + 1; bufsz += strlen(userpath) + 1;
if (machinepath) if (machinepath)
bufsz += strlen(machinepath) + 1; bufsz += strlen(machinepath) + 1;
bufsz += strlen(zip_path) + 1;
#endif #endif
if (envpath != NULL) if (envpath != NULL)
bufsz += strlen(envpath) + 1; bufsz += strlen(envpath) + 1;
@ -518,6 +550,11 @@ calculate_path(void)
*buf++ = DELIM; *buf++ = DELIM;
} }
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (zip_path[0]) {
strcpy(buf, zip_path);
buf = strchr(buf, '\0');
*buf++ = DELIM;
}
if (userpath) { if (userpath) {
strcpy(buf, userpath); strcpy(buf, userpath);
buf = strchr(buf, '\0'); buf = strchr(buf, '\0');

View File

@ -549,5 +549,9 @@ SOURCE=..\Modules\xxsubtype.c
SOURCE=..\Modules\yuvconvert.c SOURCE=..\Modules\yuvconvert.c
# End Source File # End Source File
# Begin Source File
SOURCE=..\Modules\zipimport.c
# End Source File
# End Target # End Target
# End Project # End Project

View File

@ -152,6 +152,72 @@ _PyImport_Init(void)
} }
} }
void
_PyImportHooks_Init(void)
{
PyObject *v, *path_hooks = NULL, *zimpimport;
int err = 0;
/* adding sys.path_hooks and sys.path_importer_cache, setting up
zipimport */
if (Py_VerboseFlag)
PySys_WriteStderr("# installing zipimport hook\n");
v = PyList_New(0);
if (v == NULL)
goto error;
err = PySys_SetObject("meta_path", v);
Py_DECREF(v);
if (err)
goto error;
v = PyDict_New();
if (v == NULL)
goto error;
err = PySys_SetObject("path_importer_cache", v);
Py_DECREF(v);
if (err)
goto error;
path_hooks = PyList_New(0);
if (path_hooks == NULL)
goto error;
err = PySys_SetObject("path_hooks", path_hooks);
if (err) {
error:
PyErr_Print();
Py_FatalError("initializing sys.meta_path, sys.path_hooks or "
"path_importer_cache failed");
}
zimpimport = PyImport_ImportModule("zipimport");
if (zimpimport == NULL) {
PyErr_Clear(); /* No zip import module -- okay */
if (Py_VerboseFlag)
PySys_WriteStderr("# can't import zipimport\n");
}
else {
PyObject *zipimporter = PyObject_GetAttrString(zimpimport,
"zipimporter");
Py_DECREF(zimpimport);
if (zipimporter == NULL) {
PyErr_Clear(); /* No zipimporter object -- okay */
if (Py_VerboseFlag)
PySys_WriteStderr(
"# can't import zipimport.zimimporter\n");
}
else {
/* sys.path_hooks.append(zipimporter) */
err = PyList_Append(path_hooks, zipimporter);
Py_DECREF(zipimporter);
if (err)
goto error;
if (Py_VerboseFlag)
PySys_WriteStderr(
"# installed zipimport hook\n");
}
}
Py_DECREF(path_hooks);
}
void void
_PyImport_Fini(void) _PyImport_Fini(void)
{ {
@ -246,6 +312,7 @@ static char* sys_deletes[] = {
"path", "argv", "ps1", "ps2", "exitfunc", "path", "argv", "ps1", "ps2", "exitfunc",
"exc_type", "exc_value", "exc_traceback", "exc_type", "exc_value", "exc_traceback",
"last_type", "last_value", "last_traceback", "last_type", "last_value", "last_traceback",
"path_hooks", "path_importer_cache", "meta_path",
NULL NULL
}; };
@ -808,9 +875,9 @@ load_source_module(char *name, char *pathname, FILE *fp)
/* Forward */ /* Forward */
static PyObject *load_module(char *, FILE *, char *, int); static PyObject *load_module(char *, FILE *, char *, int, PyObject *);
static struct filedescr *find_module(char *, PyObject *, static struct filedescr *find_module(char *, char *, PyObject *,
char *, size_t, FILE **); char *, size_t, FILE **, PyObject **);
static struct _frozen *find_frozen(char *name); static struct _frozen *find_frozen(char *name);
/* Load a package and return its module object WITH INCREMENTED /* Load a package and return its module object WITH INCREMENTED
@ -848,7 +915,7 @@ load_package(char *name, char *pathname)
goto cleanup; goto cleanup;
} }
buf[0] = '\0'; buf[0] = '\0';
fdp = find_module("__init__", path, buf, sizeof(buf), &fp); fdp = find_module(name, "__init__", path, buf, sizeof(buf), &fp, NULL);
if (fdp == NULL) { if (fdp == NULL) {
if (PyErr_ExceptionMatches(PyExc_ImportError)) { if (PyErr_ExceptionMatches(PyExc_ImportError)) {
PyErr_Clear(); PyErr_Clear();
@ -857,7 +924,7 @@ load_package(char *name, char *pathname)
m = NULL; m = NULL;
goto cleanup; goto cleanup;
} }
m = load_module(name, fp, buf, fdp->type); m = load_module(name, fp, buf, fdp->type, NULL);
if (fp != NULL) if (fp != NULL)
fclose(fp); fclose(fp);
cleanup: cleanup:
@ -885,6 +952,61 @@ is_builtin(char *name)
} }
/* Return an importer object for a sys.path/pkg.__path__ item 'p',
possibly by fetching it from the path_importer_cache dict. If it
wasn't yet cached, traverse path_hooks until it a hook is found
that can handle the path item. Return None if no hook could;
this tells our caller it should fall back to the builtin
import mechanism. Cache the result in path_importer_cache.
Returns a borrowed reference. */
static PyObject *
get_path_importer(PyObject *path_importer_cache, PyObject *path_hooks,
PyObject *p)
{
PyObject *importer;
int j, nhooks;
/* These conditions are the caller's responsibility: */
assert(PyList_Check(path_hooks));
assert(PyDict_Check(path_importer_cache));
nhooks = PyList_Size(path_hooks);
if (nhooks < 0)
return NULL; /* Shouldn't happen */
importer = PyDict_GetItem(path_importer_cache, p);
if (importer != NULL)
return importer;
/* set path_importer_cache[p] to None to avoid recursion */
if (PyDict_SetItem(path_importer_cache, p, Py_None) != 0)
return NULL;
for (j = 0; j < nhooks; j++) {
PyObject *hook = PyList_GetItem(path_hooks, j);
if (hook == NULL)
return NULL;
importer = PyObject_CallFunction(hook, "O", p);
if (importer != NULL)
break;
if (!PyErr_ExceptionMatches(PyExc_ImportError)) {
return NULL;
}
PyErr_Clear();
}
if (importer == NULL)
importer = Py_None;
else if (importer != Py_None) {
int err = PyDict_SetItem(path_importer_cache, p, importer);
Py_DECREF(importer);
if (err != 0)
return NULL;
}
return importer;
}
/* Search the path (default sys.path) for a module. Return the /* Search the path (default sys.path) for a module. Return the
corresponding filedescr struct, and (via return arguments) the corresponding filedescr struct, and (via return arguments) the
pathname and an open file. Return NULL if the module is not found. */ pathname and an open file. Return NULL if the module is not found. */
@ -896,16 +1018,18 @@ extern FILE *PyWin_FindRegisteredModule(const char *, struct filedescr **,
static int case_ok(char *, int, int, char *); static int case_ok(char *, int, int, char *);
static int find_init_module(char *); /* Forward */ static int find_init_module(char *); /* Forward */
static struct filedescr importhookdescr = {"", "", IMP_HOOK};
static struct filedescr * static struct filedescr *
find_module(char *realname, PyObject *path, char *buf, size_t buflen, find_module(char *fullname, char *subname, PyObject *path, char *buf,
FILE **p_fp) size_t buflen, FILE **p_fp, PyObject **p_loader)
{ {
int i, npath; int i, npath;
size_t len, namelen; size_t len, namelen;
struct filedescr *fdp = NULL; struct filedescr *fdp = NULL;
char *filemode; char *filemode;
FILE *fp = NULL; FILE *fp = NULL;
PyObject *path_hooks, *path_importer_cache;
#ifndef RISCOS #ifndef RISCOS
struct stat statbuf; struct stat statbuf;
#endif #endif
@ -918,13 +1042,50 @@ find_module(char *realname, PyObject *path, char *buf, size_t buflen,
size_t saved_namelen; size_t saved_namelen;
char *saved_buf = NULL; char *saved_buf = NULL;
#endif #endif
if (p_loader != NULL)
*p_loader = NULL;
if (strlen(realname) > MAXPATHLEN) { if (strlen(subname) > MAXPATHLEN) {
PyErr_SetString(PyExc_OverflowError, PyErr_SetString(PyExc_OverflowError,
"module name is too long"); "module name is too long");
return NULL; return NULL;
} }
strcpy(name, realname); strcpy(name, subname);
/* sys.meta_path import hook */
if (p_loader != NULL) {
PyObject *meta_path;
meta_path = PySys_GetObject("meta_path");
if (meta_path == NULL || !PyList_Check(meta_path)) {
PyErr_SetString(PyExc_ImportError,
"sys.meta_path must be a list of "
"import hooks");
return NULL;
}
Py_INCREF(meta_path); /* zap guard */
npath = PyList_Size(meta_path);
for (i = 0; i < npath; i++) {
PyObject *loader;
PyObject *hook = PyList_GetItem(meta_path, i);
loader = PyObject_CallMethod(hook, "find_module",
"sO", fullname,
path != NULL ?
path : Py_None);
if (loader == NULL) {
Py_DECREF(meta_path);
return NULL; /* true error */
}
if (loader != Py_None) {
/* a loader was found */
*p_loader = loader;
Py_DECREF(meta_path);
return &importhookdescr;
}
Py_DECREF(loader);
}
Py_DECREF(meta_path);
}
if (path != NULL && PyString_Check(path)) { if (path != NULL && PyString_Check(path)) {
/* The only type of submodule allowed inside a "frozen" /* The only type of submodule allowed inside a "frozen"
@ -978,6 +1139,22 @@ find_module(char *realname, PyObject *path, char *buf, size_t buflen,
"sys.path must be a list of directory names"); "sys.path must be a list of directory names");
return NULL; return NULL;
} }
path_hooks = PySys_GetObject("path_hooks");
if (path_hooks == NULL || !PyList_Check(path_hooks)) {
PyErr_SetString(PyExc_ImportError,
"sys.path_hooks must be a list of "
"import hooks");
return NULL;
}
path_importer_cache = PySys_GetObject("path_importer_cache");
if (path_importer_cache == NULL ||
!PyDict_Check(path_importer_cache)) {
PyErr_SetString(PyExc_ImportError,
"sys.path_importer_cache must be a dict");
return NULL;
}
npath = PyList_Size(path); npath = PyList_Size(path);
namelen = strlen(name); namelen = strlen(name);
for (i = 0; i < npath; i++) { for (i = 0; i < npath; i++) {
@ -1005,6 +1182,33 @@ find_module(char *realname, PyObject *path, char *buf, size_t buflen,
Py_XDECREF(copy); Py_XDECREF(copy);
continue; /* v contains '\0' */ continue; /* v contains '\0' */
} }
/* sys.path_hooks import hook */
if (p_loader != NULL) {
PyObject *importer;
importer = get_path_importer(path_importer_cache,
path_hooks, v);
if (importer == NULL)
return NULL;
/* Note: importer is a borrowed reference */
if (importer != Py_None) {
PyObject *loader;
loader = PyObject_CallMethod(importer,
"find_module",
"s", fullname);
if (loader == NULL)
return NULL; /* error */
if (loader != Py_None) {
/* a loader was found */
*p_loader = loader;
return &importhookdescr;
}
Py_DECREF(loader);
}
/* no hook was successful, use builtin import */
}
#ifdef macintosh #ifdef macintosh
/* /*
** Speedup: each sys.path item is interned, and ** Speedup: each sys.path item is interned, and
@ -1079,7 +1283,7 @@ find_module(char *realname, PyObject *path, char *buf, size_t buflen,
* dynamically loaded module we're going to try, * dynamically loaded module we're going to try,
* truncate the name before trying * truncate the name before trying
*/ */
if (strlen(realname) > 8) { if (strlen(subname) > 8) {
/* is this an attempt to load a C extension? */ /* is this an attempt to load a C extension? */
const struct filedescr *scan; const struct filedescr *scan;
scan = _PyImport_DynLoadFiletab; scan = _PyImport_DynLoadFiletab;
@ -1092,7 +1296,7 @@ find_module(char *realname, PyObject *path, char *buf, size_t buflen,
if (scan->suffix != NULL) { if (scan->suffix != NULL) {
/* yes, so truncate the name */ /* yes, so truncate the name */
namelen = 8; namelen = 8;
len -= strlen(realname) - namelen; len -= strlen(subname) - namelen;
buf[len] = '\0'; buf[len] = '\0';
} }
} }
@ -1444,7 +1648,7 @@ static int init_builtin(char *); /* Forward */
its module object WITH INCREMENTED REFERENCE COUNT */ its module object WITH INCREMENTED REFERENCE COUNT */
static PyObject * static PyObject *
load_module(char *name, FILE *fp, char *buf, int type) load_module(char *name, FILE *fp, char *buf, int type, PyObject *loader)
{ {
PyObject *modules; PyObject *modules;
PyObject *m; PyObject *m;
@ -1523,6 +1727,16 @@ load_module(char *name, FILE *fp, char *buf, int type)
Py_INCREF(m); Py_INCREF(m);
break; break;
case IMP_HOOK: {
if (loader == NULL) {
PyErr_SetString(PyExc_ImportError,
"import hook without loader");
return NULL;
}
m = PyObject_CallMethod(loader, "load_module", "s", name);
break;
}
default: default:
PyErr_Format(PyExc_ImportError, PyErr_Format(PyExc_ImportError,
"Don't know how to import %.200s (type code %d)", "Don't know how to import %.200s (type code %d)",
@ -1978,7 +2192,7 @@ import_submodule(PyObject *mod, char *subname, char *fullname)
Py_INCREF(m); Py_INCREF(m);
} }
else { else {
PyObject *path; PyObject *path, *loader = NULL;
char buf[MAXPATHLEN+1]; char buf[MAXPATHLEN+1];
struct filedescr *fdp; struct filedescr *fdp;
FILE *fp = NULL; FILE *fp = NULL;
@ -1995,7 +2209,8 @@ import_submodule(PyObject *mod, char *subname, char *fullname)
} }
buf[0] = '\0'; buf[0] = '\0';
fdp = find_module(subname, path, buf, MAXPATHLEN+1, &fp); fdp = find_module(fullname, subname, path, buf, MAXPATHLEN+1,
&fp, &loader);
Py_XDECREF(path); Py_XDECREF(path);
if (fdp == NULL) { if (fdp == NULL) {
if (!PyErr_ExceptionMatches(PyExc_ImportError)) if (!PyErr_ExceptionMatches(PyExc_ImportError))
@ -2004,7 +2219,8 @@ import_submodule(PyObject *mod, char *subname, char *fullname)
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
m = load_module(fullname, fp, buf, fdp->type); m = load_module(fullname, fp, buf, fdp->type, loader);
Py_XDECREF(loader);
if (fp) if (fp)
fclose(fp); fclose(fp);
if (mod != Py_None) { if (mod != Py_None) {
@ -2080,11 +2296,11 @@ PyImport_ReloadModule(PyObject *m)
PyErr_Clear(); PyErr_Clear();
} }
buf[0] = '\0'; buf[0] = '\0';
fdp = find_module(subname, path, buf, MAXPATHLEN+1, &fp); fdp = find_module(name, subname, path, buf, MAXPATHLEN+1, &fp, NULL);
Py_XDECREF(path); Py_XDECREF(path);
if (fdp == NULL) if (fdp == NULL)
return NULL; return NULL;
m = load_module(name, fp, buf, fdp->type); m = load_module(name, fp, buf, fdp->type, NULL);
if (fp) if (fp)
fclose(fp); fclose(fp);
return m; return m;
@ -2228,7 +2444,7 @@ call_find_module(char *name, PyObject *path)
pathname[0] = '\0'; pathname[0] = '\0';
if (path == Py_None) if (path == Py_None)
path = NULL; path = NULL;
fdp = find_module(name, path, pathname, MAXPATHLEN+1, &fp); fdp = find_module(NULL, name, path, pathname, MAXPATHLEN+1, &fp, NULL);
if (fdp == NULL) if (fdp == NULL)
return NULL; return NULL;
if (fp != NULL) { if (fp != NULL) {
@ -2465,7 +2681,7 @@ imp_load_module(PyObject *self, PyObject *args)
if (fp == NULL) if (fp == NULL)
return NULL; return NULL;
} }
return load_module(name, fp, pathname, type); return load_module(name, fp, pathname, type, NULL);
} }
static PyObject * static PyObject *
@ -2579,6 +2795,7 @@ initimp(void)
if (setint(d, "C_BUILTIN", C_BUILTIN) < 0) goto failure; if (setint(d, "C_BUILTIN", C_BUILTIN) < 0) goto failure;
if (setint(d, "PY_FROZEN", PY_FROZEN) < 0) goto failure; if (setint(d, "PY_FROZEN", PY_FROZEN) < 0) goto failure;
if (setint(d, "PY_CODERESOURCE", PY_CODERESOURCE) < 0) goto failure; if (setint(d, "PY_CODERESOURCE", PY_CODERESOURCE) < 0) goto failure;
if (setint(d, "IMP_HOOK", IMP_HOOK) < 0) goto failure;
failure: failure:
; ;

View File

@ -16,7 +16,8 @@ enum filetype {
PKG_DIRECTORY, PKG_DIRECTORY,
C_BUILTIN, C_BUILTIN,
PY_FROZEN, PY_FROZEN,
PY_CODERESOURCE /* Mac only */ PY_CODERESOURCE, /* Mac only */
IMP_HOOK
}; };
struct filedescr { struct filedescr {

View File

@ -161,6 +161,8 @@ Py_Initialize(void)
/* phase 2 of builtins */ /* phase 2 of builtins */
_PyImport_FixupExtension("__builtin__", "__builtin__"); _PyImport_FixupExtension("__builtin__", "__builtin__");
_PyImportHooks_Init();
initsigs(); /* Signal handling stuff, including initintr() */ initsigs(); /* Signal handling stuff, including initintr() */
initmain(); /* Module __main__ */ initmain(); /* Module __main__ */