cpython: 07a615a8f9ad (original) (raw)
--- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -1,6 +1,7 @@ import sys import os import marshal +import importlib import importlib.util import struct import time @@ -48,6 +49,7 @@ test_pyc = make_pyc(test_co, NOW, len(te TESTMOD = "ziptestmodule" TESTPACK = "ziptestpackage" TESTPACK2 = "ziptestpackage2" +TEMP_DIR = os.path.abspath("junk95142") TEMP_ZIP = os.path.abspath("junk95142.zip") pyc_file = importlib.util.cache_from_source(TESTMOD + '.py') @@ -77,45 +79,64 @@ class UncompressedZipImportTestCase(Impo def setUp(self): # We're reusing the zip archive path, so we must clear the
# cached directory info and linecache[](#l1.23)
# cached directory info and linecache.[](#l1.24) linecache.clearcache()[](#l1.25) zipimport._zip_directory_cache.clear()[](#l1.26) ImportHooksBaseTestCase.setUp(self)[](#l1.27)
- def doTest(self, expected_ext, files, *modules, **kw):
z = ZipFile(TEMP_ZIP, "w")[](#l1.30)
try:[](#l1.31)
- def makeTree(self, files, dirName=TEMP_DIR):
# Create a filesystem based set of modules/packages[](#l1.33)
# defined by files under the directory dirName.[](#l1.34)
self.addCleanup(support.rmtree, dirName)[](#l1.35)
for name, (mtime, data) in files.items():[](#l1.37)
path = os.path.join(dirName, name)[](#l1.38)
if path[-1] == os.sep:[](#l1.39)
if not os.path.isdir(path):[](#l1.40)
os.makedirs(path)[](#l1.41)
else:[](#l1.42)
dname = os.path.dirname(path)[](#l1.43)
if not os.path.isdir(dname):[](#l1.44)
os.makedirs(dname)[](#l1.45)
with open(path, 'wb') as fp:[](#l1.46)
fp.write(data)[](#l1.47)
- def makeZip(self, files, zipName=TEMP_ZIP, **kw):
# Create a zip archive based set of modules/packages[](#l1.50)
# defined by files in the zip file zipName. If the[](#l1.51)
# key 'stuff' exists in kw it is prepended to the archive.[](#l1.52)
self.addCleanup(support.unlink, zipName)[](#l1.53)
with ZipFile(zipName, "w") as z:[](#l1.55) for name, (mtime, data) in files.items():[](#l1.56) zinfo = ZipInfo(name, time.localtime(mtime))[](#l1.57) zinfo.compress_type = self.compression[](#l1.58) z.writestr(zinfo, data)[](#l1.59)
z.close()[](#l1.60)
stuff = kw.get("stuff", None)[](#l1.62)
if stuff is not None:[](#l1.63)
# Prepend 'stuff' to the start of the zipfile[](#l1.64)
with open(TEMP_ZIP, "rb") as f:[](#l1.65)
data = f.read()[](#l1.66)
with open(TEMP_ZIP, "wb") as f:[](#l1.67)
f.write(stuff)[](#l1.68)
f.write(data)[](#l1.69)
stuff = kw.get("stuff", None)[](#l1.70)
if stuff is not None:[](#l1.71)
# Prepend 'stuff' to the start of the zipfile[](#l1.72)
with open(zipName, "rb") as f:[](#l1.73)
data = f.read()[](#l1.74)
with open(zipName, "wb") as f:[](#l1.75)
f.write(stuff)[](#l1.76)
f.write(data)[](#l1.77)
sys.path.insert(0, TEMP_ZIP)[](#l1.79)
mod = __import__(".".join(modules), globals(), locals(),[](#l1.83)
["__dummy__"])[](#l1.84)
sys.path.insert(0, TEMP_ZIP)[](#l1.85)
call = kw.get('call')[](#l1.87)
if call is not None:[](#l1.88)
call(mod)[](#l1.89)
mod = importlib.import_module(".".join(modules))[](#l1.90)
if expected_ext:[](#l1.92)
file = mod.get_file()[](#l1.93)
self.assertEqual(file, os.path.join(TEMP_ZIP,[](#l1.94)
call = kw.get('call')[](#l1.95)
if call is not None:[](#l1.96)
call(mod)[](#l1.97)
if expected_ext:[](#l1.99)
file = mod.get_file()[](#l1.100)
self.assertEqual(file, os.path.join(TEMP_ZIP,[](#l1.101) *modules) + expected_ext)[](#l1.102)
finally:[](#l1.103)
z.close()[](#l1.104)
os.remove(TEMP_ZIP)[](#l1.105)
def testAFakeZlib(self): # @@ -201,7 +222,9 @@ class UncompressedZipImportTestCase(Impo packdir + TESTMOD + pyc_ext: (NOW, test_pyc)} self.doTest(pyc_ext, files, TESTPACK, TESTMOD)
- def testSubPackage(self):
# Test that subpackages function when loaded from zip[](#l1.115)
# archives.[](#l1.116) packdir = TESTPACK + os.sep[](#l1.117) packdir2 = packdir + TESTPACK2 + os.sep[](#l1.118) files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc),[](#l1.119)
@@ -209,6 +232,167 @@ class UncompressedZipImportTestCase(Impo packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD)
- def testSubNamespacePackage(self):
# Test that implicit namespace subpackages function[](#l1.125)
# when loaded from zip archives.[](#l1.126)
packdir = TESTPACK + os.sep[](#l1.127)
packdir2 = packdir + TESTPACK2 + os.sep[](#l1.128)
# The first two files are just directory entries (so have no data).[](#l1.129)
files = {packdir: (NOW, ""),[](#l1.130)
packdir2: (NOW, ""),[](#l1.131)
packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}[](#l1.132)
self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD)[](#l1.133)
- def testMixedNamespacePackage(self):
# Test implicit namespace packages spread between a[](#l1.136)
# real filesystem and a zip archive.[](#l1.137)
packdir = TESTPACK + os.sep[](#l1.138)
packdir2 = packdir + TESTPACK2 + os.sep[](#l1.139)
packdir3 = packdir2 + TESTPACK + '3' + os.sep[](#l1.140)
files1 = {packdir: (NOW, ""),[](#l1.141)
packdir + TESTMOD + pyc_ext: (NOW, test_pyc),[](#l1.142)
packdir2: (NOW, ""),[](#l1.143)
packdir3: (NOW, ""),[](#l1.144)
packdir3 + TESTMOD + pyc_ext: (NOW, test_pyc),[](#l1.145)
packdir2 + TESTMOD + '3' + pyc_ext: (NOW, test_pyc),[](#l1.146)
packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}[](#l1.147)
files2 = {packdir: (NOW, ""),[](#l1.148)
packdir + TESTMOD + '2' + pyc_ext: (NOW, test_pyc),[](#l1.149)
packdir2: (NOW, ""),[](#l1.150)
packdir2 + TESTMOD + '2' + pyc_ext: (NOW, test_pyc),[](#l1.151)
packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}[](#l1.152)
zip1 = os.path.abspath("path1.zip")[](#l1.154)
self.makeZip(files1, zip1)[](#l1.155)
zip2 = TEMP_DIR[](#l1.157)
self.makeTree(files2, zip2)[](#l1.158)
# zip2 should override zip1.[](#l1.160)
sys.path.insert(0, zip1)[](#l1.161)
sys.path.insert(0, zip2)[](#l1.162)
mod = importlib.import_module(TESTPACK)[](#l1.164)
# if TESTPACK is functioning as a namespace pkg then[](#l1.166)
# there should be two entries in the __path__.[](#l1.167)
# First should be path2 and second path1.[](#l1.168)
self.assertEqual(2, len(mod.__path__))[](#l1.169)
p1, p2 = mod.__path__[](#l1.170)
self.assertEqual(os.path.basename(TEMP_DIR), p1.split(os.sep)[-2])[](#l1.171)
self.assertEqual("path1.zip", p2.split(os.sep)[-2])[](#l1.172)
# packdir3 should import as a namespace package.[](#l1.174)
# Its __path__ is an iterable of 1 element from zip1.[](#l1.175)
mod = importlib.import_module(packdir3.replace(os.sep, '.')[:-1])[](#l1.176)
self.assertEqual(1, len(mod.__path__))[](#l1.177)
mpath = list(mod.__path__)[0].split('path1.zip' + os.sep)[1][](#l1.178)
self.assertEqual(packdir3[:-1], mpath)[](#l1.179)
# TESTPACK/TESTMOD only exists in path1.[](#l1.181)
mod = importlib.import_module('.'.join((TESTPACK, TESTMOD)))[](#l1.182)
self.assertEqual("path1.zip", mod.__file__.split(os.sep)[-3])[](#l1.183)
# And TESTPACK/(TESTMOD + '2') only exists in path2.[](#l1.185)
mod = importlib.import_module('.'.join((TESTPACK, TESTMOD + '2')))[](#l1.186)
self.assertEqual(os.path.basename(TEMP_DIR),[](#l1.187)
mod.__file__.split(os.sep)[-3])[](#l1.188)
# One level deeper...[](#l1.190)
subpkg = '.'.join((TESTPACK, TESTPACK2))[](#l1.191)
mod = importlib.import_module(subpkg)[](#l1.192)
self.assertEqual(2, len(mod.__path__))[](#l1.193)
p1, p2 = mod.__path__[](#l1.194)
self.assertEqual(os.path.basename(TEMP_DIR), p1.split(os.sep)[-3])[](#l1.195)
self.assertEqual("path1.zip", p2.split(os.sep)[-3])[](#l1.196)
# subpkg.TESTMOD exists in both zips should load from zip2.[](#l1.198)
mod = importlib.import_module('.'.join((subpkg, TESTMOD)))[](#l1.199)
self.assertEqual(os.path.basename(TEMP_DIR),[](#l1.200)
mod.__file__.split(os.sep)[-4])[](#l1.201)
# subpkg.TESTMOD + '2' only exists in zip2.[](#l1.203)
mod = importlib.import_module('.'.join((subpkg, TESTMOD + '2')))[](#l1.204)
self.assertEqual(os.path.basename(TEMP_DIR),[](#l1.205)
mod.__file__.split(os.sep)[-4])[](#l1.206)
# Finally subpkg.TESTMOD + '3' only exists in zip1.[](#l1.208)
mod = importlib.import_module('.'.join((subpkg, TESTMOD + '3')))[](#l1.209)
self.assertEqual('path1.zip', mod.__file__.split(os.sep)[-4])[](#l1.210)
- def testNamespacePackage(self):
# Test implicit namespace packages spread between multiple zip[](#l1.213)
# archives.[](#l1.214)
packdir = TESTPACK + os.sep[](#l1.215)
packdir2 = packdir + TESTPACK2 + os.sep[](#l1.216)
packdir3 = packdir2 + TESTPACK + '3' + os.sep[](#l1.217)
files1 = {packdir: (NOW, ""),[](#l1.218)
packdir + TESTMOD + pyc_ext: (NOW, test_pyc),[](#l1.219)
packdir2: (NOW, ""),[](#l1.220)
packdir3: (NOW, ""),[](#l1.221)
packdir3 + TESTMOD + pyc_ext: (NOW, test_pyc),[](#l1.222)
packdir2 + TESTMOD + '3' + pyc_ext: (NOW, test_pyc),[](#l1.223)
packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}[](#l1.224)
zip1 = os.path.abspath("path1.zip")[](#l1.225)
self.makeZip(files1, zip1)[](#l1.226)
files2 = {packdir: (NOW, ""),[](#l1.228)
packdir + TESTMOD + '2' + pyc_ext: (NOW, test_pyc),[](#l1.229)
packdir2: (NOW, ""),[](#l1.230)
packdir2 + TESTMOD + '2' + pyc_ext: (NOW, test_pyc),[](#l1.231)
packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)}[](#l1.232)
zip2 = os.path.abspath("path2.zip")[](#l1.233)
self.makeZip(files2, zip2)[](#l1.234)
# zip2 should override zip1.[](#l1.236)
sys.path.insert(0, zip1)[](#l1.237)
sys.path.insert(0, zip2)[](#l1.238)
mod = importlib.import_module(TESTPACK)[](#l1.240)
# if TESTPACK is functioning as a namespace pkg then[](#l1.242)
# there should be two entries in the __path__.[](#l1.243)
# First should be path2 and second path1.[](#l1.244)
self.assertEqual(2, len(mod.__path__))[](#l1.245)
p1, p2 = mod.__path__[](#l1.246)
self.assertEqual("path2.zip", p1.split(os.sep)[-2])[](#l1.247)
self.assertEqual("path1.zip", p2.split(os.sep)[-2])[](#l1.248)
# packdir3 should import as a namespace package.[](#l1.250)
# Tts __path__ is an iterable of 1 element from zip1.[](#l1.251)
mod = importlib.import_module(packdir3.replace(os.sep, '.')[:-1])[](#l1.252)
self.assertEqual(1, len(mod.__path__))[](#l1.253)
mpath = list(mod.__path__)[0].split('path1.zip' + os.sep)[1][](#l1.254)
self.assertEqual(packdir3[:-1], mpath)[](#l1.255)
# TESTPACK/TESTMOD only exists in path1.[](#l1.257)
mod = importlib.import_module('.'.join((TESTPACK, TESTMOD)))[](#l1.258)
self.assertEqual("path1.zip", mod.__file__.split(os.sep)[-3])[](#l1.259)
# And TESTPACK/(TESTMOD + '2') only exists in path2.[](#l1.261)
mod = importlib.import_module('.'.join((TESTPACK, TESTMOD + '2')))[](#l1.262)
self.assertEqual("path2.zip", mod.__file__.split(os.sep)[-3])[](#l1.263)
# One level deeper...[](#l1.265)
subpkg = '.'.join((TESTPACK, TESTPACK2))[](#l1.266)
mod = importlib.import_module(subpkg)[](#l1.267)
self.assertEqual(2, len(mod.__path__))[](#l1.268)
p1, p2 = mod.__path__[](#l1.269)
self.assertEqual("path2.zip", p1.split(os.sep)[-3])[](#l1.270)
self.assertEqual("path1.zip", p2.split(os.sep)[-3])[](#l1.271)
# subpkg.TESTMOD exists in both zips should load from zip2.[](#l1.273)
mod = importlib.import_module('.'.join((subpkg, TESTMOD)))[](#l1.274)
self.assertEqual('path2.zip', mod.__file__.split(os.sep)[-4])[](#l1.275)
# subpkg.TESTMOD + '2' only exists in zip2.[](#l1.277)
mod = importlib.import_module('.'.join((subpkg, TESTMOD + '2')))[](#l1.278)
self.assertEqual('path2.zip', mod.__file__.split(os.sep)[-4])[](#l1.279)
# Finally subpkg.TESTMOD + '3' only exists in zip1.[](#l1.281)
mod = importlib.import_module('.'.join((subpkg, TESTMOD + '3')))[](#l1.282)
self.assertEqual('path1.zip', mod.__file__.split(os.sep)[-4])[](#l1.283)
+ def testZipImporterMethods(self): packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep @@ -231,7 +415,7 @@ class UncompressedZipImportTestCase(Impo mod = zi.load_module(TESTPACK) self.assertEqual(zi.get_filename(TESTPACK), mod.file)
existing_pack_path = __import__(TESTPACK).__path__[0][](#l1.292)
existing_pack_path = importlib.import_module(TESTPACK).__path__[0][](#l1.293) expected_path_path = os.path.join(TEMP_ZIP, TESTPACK)[](#l1.294) self.assertEqual(existing_pack_path, expected_path_path)[](#l1.295)
@@ -241,8 +425,8 @@ class UncompressedZipImportTestCase(Impo mod_path = packdir2 + TESTMOD mod_name = module_path_to_dotted_name(mod_path)
__import__(mod_name)[](#l1.301)
mod = sys.modules[mod_name][](#l1.302)
mod = importlib.import_module(mod_name)[](#l1.303)
self.assertTrue(mod_name in sys.modules)[](#l1.304) self.assertEqual(zi.get_source(TESTPACK), None)[](#l1.305) self.assertEqual(zi.get_source(mod_path), None)[](#l1.306) self.assertEqual(zi.get_filename(mod_path), mod.__file__)[](#l1.307)
@@ -289,13 +473,13 @@ class UncompressedZipImportTestCase(Impo mod_path = TESTPACK2 + os.sep + TESTMOD mod_name = module_path_to_dotted_name(mod_path)
__import__(mod_name)[](#l1.312)
mod = sys.modules[mod_name][](#l1.313)
mod = importlib.import_module(mod_name)[](#l1.314)
self.assertTrue(mod_name in sys.modules)[](#l1.315) self.assertEqual(zi.get_source(TESTPACK2), None)[](#l1.316) self.assertEqual(zi.get_source(mod_path), None)[](#l1.317) self.assertEqual(zi.get_filename(mod_path), mod.__file__)[](#l1.318) # To pass in the module name instead of the path, we must use the[](#l1.319)
# right importer[](#l1.320)
# right importer.[](#l1.321) loader = mod.__loader__[](#l1.322) self.assertEqual(loader.get_source(mod_name), None)[](#l1.323) self.assertEqual(loader.get_filename(mod_name), mod.__file__)[](#l1.324)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -44,6 +44,8 @@ Core and Builtins Library ------- +- Issue #17633: Improve zipimport's support for namespace packages. +
--- a/Modules/zipimport.c +++ b/Modules/zipimport.c @@ -324,17 +324,14 @@ get_module_info(ZipImporter *self, PyObj } typedef enum {
- FL_ERROR = -1, /* error */
- FL_NOT_FOUND, /* no loader or namespace portions found */
- FL_MODULE_FOUND, /* module/package found */
- FL_NS_FOUND /* namespace portion found: */
/* *namespace_portion will point to the name */[](#l3.15)
} find_loader_result; -/* The guts of "find_loader" and "find_module". Return values:
- -1: error
- 0: no loader or namespace portions found
- 1: module/package found
- 2: namespace portion found: *namespace_portion will point to the name
+/* The guts of "find_loader" and "find_module". */ static find_loader_result find_loader(ZipImporter *self, PyObject *fullname, PyObject **namespace_portion) @@ -349,21 +346,34 @@ find_loader(ZipImporter self, PyObject if (mi == MI_NOT_FOUND) { / Not a module or regular package. See if this is a directory, and therefore possibly a portion of a namespace package. */
int is_dir = check_is_directory(self, self->prefix, fullname);[](#l3.31)
find_loader_result result = FL_NOT_FOUND;[](#l3.32)
PyObject *subname;[](#l3.33)
int is_dir;[](#l3.34)
/* We're only interested in the last path component of fullname;[](#l3.36)
earlier components are recorded in self->prefix. */[](#l3.37)
subname = get_subname(fullname);[](#l3.38)
if (subname == NULL) {[](#l3.39)
return FL_ERROR;[](#l3.40)
}[](#l3.41)
is_dir = check_is_directory(self, self->prefix, subname);[](#l3.43) if (is_dir < 0)[](#l3.44)
return -1;[](#l3.45)
if (is_dir) {[](#l3.46)
result = FL_ERROR;[](#l3.47)
else if (is_dir) {[](#l3.48) /* This is possibly a portion of a namespace[](#l3.49) package. Return the string representing its path,[](#l3.50) without a trailing separator. */[](#l3.51) *namespace_portion = PyUnicode_FromFormat("%U%c%U%U",[](#l3.52) self->archive, SEP,[](#l3.53)
self->prefix, fullname);[](#l3.54)
self->prefix, subname);[](#l3.55) if (*namespace_portion == NULL)[](#l3.56)
return FL_ERROR;[](#l3.57)
return FL_NS_FOUND;[](#l3.58)
result = FL_ERROR;[](#l3.59)
else[](#l3.60)
result = FL_NS_FOUND;[](#l3.61) }[](#l3.62)
return FL_NOT_FOUND;[](#l3.63)
Py_DECREF(subname);[](#l3.64)
} /* This is a module or package. */ return FL_MODULE_FOUND; @@ -397,6 +407,9 @@ zipimporter_find_module(PyObject *obj, P case FL_MODULE_FOUND: result = (PyObject *)self; break;return result;[](#l3.65)
- default:
PyErr_BadInternalCall();[](#l3.74)
} Py_INCREF(result); return result; @@ -433,6 +446,9 @@ zipimporter_find_loader(PyObject *obj, P result = Py_BuildValue("O[O]", Py_None, namespace_portion); Py_DECREF(namespace_portion); return result;return NULL;[](#l3.75)
- default:
PyErr_BadInternalCall();[](#l3.84)
} return result; }return NULL;[](#l3.85)