Skip to content

Commit

Permalink
REF: Replace import_module_from_file() with pkgutil-based `iter_mod…
Browse files Browse the repository at this point in the history
…ules_from_dir()`

Fixes packaging build with PyInstaller >= 6.10.

Refs: pyinstaller/pyinstaller#9015 (comment)

Thanks @rokm
  • Loading branch information
kernc committed Feb 6, 2025
1 parent 8a2a0fd commit b701f95
Show file tree
Hide file tree
Showing 4 changed files with 13 additions and 50 deletions.
13 changes: 2 additions & 11 deletions efck/tabs/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
17 changes: 3 additions & 14 deletions efck/tabs/filters.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand All @@ -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()):
Expand Down
31 changes: 7 additions & 24 deletions efck/util.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import importlib.util
import logging
import os
import pkgutil
from pathlib import Path

from . import CONFIG_DIRS

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):
Expand All @@ -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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
'dev': [
'flake8',
'coverage',
'pyinstaller == 6.9',
'pyinstaller',
'pillow', # for pyinstaller
],
'extra': [
Expand Down

0 comments on commit b701f95

Please sign in to comment.