From f53b7cb94b4be22f1ef60b33fb898ccee930db04 Mon Sep 17 00:00:00 2001 From: Rok Mandeljc Date: Wed, 24 May 2023 16:06:13 +0200 Subject: [PATCH] loader: PyiFrozenImporter: work-around for importlib's LazyLoader When `importlib.util.LazyLoader` is used, the `module` passed to the `PyiFrozenImporter.exec_module` contains modified spec, in which the `loader_state` is a `dict` prepared by the `LazyLoader` code. This means that our original `loader_state` is lost, and we need to determine the module name for look-up in the PYZ by other means. Specifically, we convert the `origin` back to the module name, which allows us to handle aliased modules as well. --- PyInstaller/loader/pyimod02_importers.py | 15 +++++++++++++++ news/7657.bugfix.rst | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 news/7657.bugfix.rst diff --git a/PyInstaller/loader/pyimod02_importers.py b/PyInstaller/loader/pyimod02_importers.py index d21fe541479..4cc26ca619c 100644 --- a/PyInstaller/loader/pyimod02_importers.py +++ b/PyInstaller/loader/pyimod02_importers.py @@ -347,10 +347,25 @@ def exec_module(self, module): # `pkg_resources.extern.jaraco.text`, but the original name stored in `loader_state`, which we need # to use for code look-up, is `pkg_resources._vendor.jaraco.text`. module_name = spec.loader_state.pyz_entry_name + elif isinstance(spec.loader_state, dict): + # This seems to happen when `importlib.util.LazyLoader` is used, and our original `loader_state` is lost. + # We could use `spec.name`, and hope for the best, but that will likely fail with aliased modules (see + # the comment in the branch above for an example). + # + # So, try to reconstruct the original module name from the `origin` - which is essentially the reverse of + # the our `get_filename()` implementation. + assert spec.origin.startswith(SYS_PREFIX) + module_name = spec.origin[SYS_PREFIXLEN:].replace(os.sep, '.') + if module_name.endswith('.pyc'): + module_name = module_name[:-4] + if module_name.endswith('.__init__'): + module_name = module_name[:-9] else: raise RuntimeError(f"Module's spec contains loader_state of incompatible type: {type(spec.loader_state)}") bytecode = self.get_code(module_name) + if bytecode is None: + raise RuntimeError(f"Failed to retrieve bytecode for {spec.name!r}!") # Set by the import machinery assert hasattr(module, '__file__') diff --git a/news/7657.bugfix.rst b/news/7657.bugfix.rst new file mode 100644 index 00000000000..774d103744d --- /dev/null +++ b/news/7657.bugfix.rst @@ -0,0 +1,2 @@ +Attempt to fix compatibility of PyInstaller's ``PyiFrozenImporter`` with +``importlib.util.LazyLoader``.