From b701f95990801093f30adc75ef8429107d074812 Mon Sep 17 00:00:00 2001 From: Kernc Date: Fri, 7 Feb 2025 00:29:31 +0100 Subject: [PATCH] REF: Replace `import_module_from_file()` with pkgutil-based `iter_modules_from_dir()` Fixes packaging build with PyInstaller >= 6.10. Refs: https://github.com/pyinstaller/pyinstaller/issues/9015#issuecomment-2639398557 Thanks @rokm --- efck/tabs/__init__.py | 13 ++----------- efck/tabs/filters.py | 17 +++-------------- efck/util.py | 31 +++++++------------------------ setup.py | 2 +- 4 files changed, 13 insertions(+), 50 deletions(-) diff --git a/efck/tabs/__init__.py b/efck/tabs/__init__.py index 75e41ca..d0266be 100644 --- a/efck/tabs/__init__.py +++ b/efck/tabs/__init__.py @@ -1,24 +1,15 @@ import logging import typing -from glob import glob -from pathlib import Path from ..tab import Tab -from ..util import import_pyinstaller_bundled_submodules, iter_config_dirs, import_module_from_file +from ..util import iter_config_dirs, iter_modules_from_dir logger = logging.getLogger(__name__) _modules = [] # Fixes missing Filters tab due to lost reference on PySide6 -_modules.extend(import_pyinstaller_bundled_submodules(__name__)) - for dir in iter_config_dirs('tabs'): - for file in sorted(glob(str(dir / '*.py*'))): - basename = Path(file).stem - if basename.startswith('_'): - continue - logger.debug('Loading tab "%s"', file) - module = import_module_from_file(f'efck.tabs.{basename}', file) + for module in iter_modules_from_dir(dir, 'efck.tabs.'): _modules.append(module) # Export tabs here. diff --git a/efck/tabs/filters.py b/efck/tabs/filters.py index 053c93e..a5c9b12 100644 --- a/efck/tabs/filters.py +++ b/efck/tabs/filters.py @@ -1,16 +1,10 @@ import logging from functools import lru_cache -from glob import glob -from pathlib import Path from ..qt import * from ..gui import ICON_DIR from ..tab import Tab -from ..util import ( - import_module_from_file, - import_pyinstaller_bundled_submodules, - iter_config_dirs, -) +from ..util import iter_config_dirs, iter_modules_from_dir logger = logging.getLogger(__name__) @@ -19,14 +13,9 @@ def load_modules(): """Return built-in filter modules, shadowed by name by user's config-local filter modules.""" all_modules = {} - for mod in import_pyinstaller_bundled_submodules('efck.filters'): - all_modules[module_basename(mod)] = mod - for dir in iter_config_dirs('filters'): - for file in sorted(glob(str(dir / '*.py*'))): - logger.debug('Loading filter "%s"', file) - mod = import_module_from_file(f'efck.filters.{Path(file).stem}', file) - all_modules[module_basename(mod)] = mod + for module in iter_modules_from_dir(dir, 'efck.filters.'): + all_modules[module_basename(module)] = module # Empty user's config-local filters by the same name can shadow out builtins for name, mod in tuple(all_modules.items()): diff --git a/efck/util.py b/efck/util.py index 78d09f4..cd979c5 100644 --- a/efck/util.py +++ b/efck/util.py @@ -1,6 +1,6 @@ import importlib.util import logging -import os +import pkgutil from pathlib import Path from . import CONFIG_DIRS @@ -8,13 +8,12 @@ logger = logging.getLogger(__name__) -def import_module_from_file(module_name, path): - path = os.path.abspath(path) - # Should handle all sorts of importable modules (*.py, *.pyc, ...) - spec = importlib.util.spec_from_file_location(module_name, path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module +def iter_modules_from_dir(dir: str, prefix: str): + for modinfo in pkgutil.iter_modules([dir], prefix=prefix): + spec = modinfo.module_finder.find_spec(modinfo.name) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + yield module def iter_config_dirs(subdir): @@ -24,19 +23,3 @@ def iter_config_dirs(subdir): path = Path(dir) / subdir if path.is_dir(): yield path - - -def import_pyinstaller_bundled_submodules(package): - # This works with PyInstaller who compiles all our modules into an - # import-hooked archive rather than keeping non-data files on disk. - # A few alternatives would be: - # - Include these submodules in pyinstaller datas (and remove them from PYZ), - # - List and maintain literal imports - # This is safe. - # https://github.com/pyinstaller/pyinstaller/blame/7875d7684c/PyInstaller/loader/pyimod02_importers.py#L115-L117 - modules = [] - if hasattr(__loader__, 'toc'): - modules = sorted(k for k in __loader__.toc - if k.startswith(f'{package}.') and '._' not in k) - modules = [importlib.import_module(mod) for mod in modules] - return modules diff --git a/setup.py b/setup.py index 1c116d8..cf831fa 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ 'dev': [ 'flake8', 'coverage', - 'pyinstaller == 6.9', + 'pyinstaller', 'pillow', # for pyinstaller ], 'extra': [