-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
gh-59110: zipimport: support namespace packages when no directory entry exists #121233
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-59110: zipimport: support namespace packages when no directory entry exists #121233
Conversation
Lib/test/test_zipimport.py
Outdated
| 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"") |
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 don't know if this is a necessary test case for get_data, maybe directory path will be never passed to it.
Interesting, this was not a bug before, someone who wrote the test knows how it is working. |
Lib/test/test_zipimport.py
Outdated
| 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.
I see the ImportHooksBaseTestCase has setUp and tearDown to restore the environ, so the finally section could be removed.
|
I did a little test and it seems that this change also fixed my use case as well. |
|
Changed the reference to the older issue #59110. |
| for name in list(files): | ||
| while True: | ||
| i = name.rstrip(path_sep).rfind(path_sep) | ||
| if i < 0: | ||
| break | ||
| name = name[:i + 1] | ||
| if name in files: | ||
| break | ||
| files[name] = None | ||
| count += 1 |
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 probably would have written this as something like:
files.update(dict.fromkeys(zipfile._path.CompleteDirs._implicit_dirs(files)))
(or some variation that ports the CompleteDirs._implicit_dirs without itertools). The advantages of using that implementation are:
- it's a proven implementation and has already encountered real-world edge cases
- it uses lazy evaluation to ensure compact memory usage
- it applies functional principles, avoiding mutating structures in place and simplifying the logic
If you want the count of files added:
updates = dict.fromkeys(zipfile._path.CompleteDirs._implicit_dirs(files))
files.update(updates)
count = len(updates)
Of course, CompleteDirs operates on posixpath.sep and files here uses pathsep for separators, so maybe it's not worth porting CompleteDirs. Still, I think it would be nice if this new behavior were factored out so as to limit making this already enormous function much larger.
I set out to apply this approach in #121378.
|
This change feels like a bugfix. The bug is affecting users of Setuptools in pypa/setuptools#4641. Could the change be backported to bugfix Pythons? |
Test written by @SeaHOH.