-
-
Notifications
You must be signed in to change notification settings - Fork 30.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-121111: Fix zipimport
incorrectly dealing with namespace packages
#121161
Conversation
ZeroIntensity
commented
Jun 29, 2024
- Issue: zipimport cannot do a namespace import when a directory has no python files, but it contains nested directories with python files inside of a zip file. #121111
mod = importlib.util.module_from_spec(spec) | ||
importer.exec_module(mod) | ||
self.assertEqual(mod.foo(), "foo") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If here test the namespace module in zip, mkdir()
must be removed, this bug is triggered by no directory entry in zip.
And don't do test via zipimport, that always fail/succeed, because find functons depend the loader's path prefix. Direct import namespace module can cover all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused on what you want here - the test passes, why should mkdir()
be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this bug is triggered by no directory entry in zip. If the directory entry is already exists, everything is OK.
# OK, we found it the old way. Let's return it. | ||
return path | ||
|
||
return self.prefix + fullname.replace(".", path_sep) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As https://github.com/python/cpython/pull/121161/files#r1660062444 described, this patch has no effect, shoud patch _is_dir
or _read_directory
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As https://github.com/python/cpython/pull/121161/files#r1660062444 described, this patch has no effect, shoud patch
_is_dir
or_read_directory
.
From what I found, there's no problem with _read_directory
, and _is_dir
is only relevant to find_spec
(the problem still occurs in get_code
, for example). This patch seems to fix the issue for both. Have you tried this fix on this issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For namespace package's dealing and operation, there has already clearly explained in the source code comment. You can see them in find_loader
(<=py311), find_spec
and _is_dir
.
And, why your test with get_code
(and get_filename
, get_source
, is_package
also) was fail? Because find functons depend the loader's path prefix, different level modules have different level loaders, skip levels will be failed. Yes, this is _get_module_path
which you patched, then it broken, return the full path at once instead of stepwise lookup.
If you like to test into zipimport functions, it should be get_data
:
def testImportSubmodulesInZip(self):
with ZipFile(TEMP_ZIP, "w") as z:
z.writestr("a/__init__.py", b'')
z.writestr("a/b/c/__init__.py", b'def foo(): return "foo"')
importer = zipimport.zipimporter(TEMP_ZIP + "/a/")
spec = importer.find_spec("a.b")
self.assertEqual(spec.loader, None)
self.assertEqual(spec.submodule_search_locations,
[(TEMP_ZIP + "/a/b").replace("/", zipimport.path_sep)])
self.assertRaises(zipimport.ZipImportError, importer.get_code, "a.b")
self.assertEqual(importer.get_data("a/b/"), b"")
sys.path.insert(0, TEMP_ZIP)
try:
import a.b.c
self.assertIn('namespace', str(a.b).lower())
self.assertEqual(a.b.c.foo(), "foo")
finally:
del sys.path[0]
sys.modules.pop('a.b.c', None)
sys.modules.pop('a.b', None)
sys.modules.pop('a', None)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fix worked, Thank you so much. |
@serhiy-storchaka Could you take look at the testcase and patch? |
Can confirm that the test written by @SeaHOH continues to fail on the new patch. There were two bugs at play here, I only patched one of them (my original test case fails on the main branch, as of now). The underlying problem is not directly with |
Sorry for my English is so bad. Seems #121233 fixed the bug. |