diff --git a/dist_assets/pyinstaller_hooks/hook-matplotlib.backends.py b/dist_assets/pyinstaller_hooks/hook-matplotlib.backends.py deleted file mode 100644 index d73ba1ec3..000000000 --- a/dist_assets/pyinstaller_hooks/hook-matplotlib.backends.py +++ /dev/null @@ -1,78 +0,0 @@ -# This apes hook-matplotlib.backends.py, but REMOVES backends, all but -# the ones in the list below. -# Courtesy of https://github.com/bpteague/cytoflow/blob/70f9291/packaging/hook-matplotlib.backends.py - -KEEP = ["WXAgg", "WX", "agg"] - -from PyInstaller.compat import is_darwin -from PyInstaller.utils.hooks import ( - eval_statement, exec_statement, logger) - - -def get_matplotlib_backend_module_names(): - """ - List the names of all matplotlib backend modules importable under the - current Python installation. - Returns - ---------- - list - List of the fully-qualified names of all such modules. - """ - # Statement safely importing a single backend module. - import_statement = """ -import os, sys -# Preserve stdout. -sys_stdout = sys.stdout -try: - # Redirect output printed by this importation to "/dev/null", preventing - # such output from being erroneously interpreted as an error. - with open(os.devnull, 'w') as dev_null: - sys.stdout = dev_null - __import__('%s') -# If this is an ImportError, print this exception's message without a traceback. -# ImportError messages are human-readable and require no additional context. -except ImportError as exc: - sys.stdout = sys_stdout - print(exc) -# Else, print this exception preceded by a traceback. traceback.print_exc() -# prints to stderr rather than stdout and must not be called here! -except Exception: - sys.stdout = sys_stdout - import traceback - print(traceback.format_exc()) -""" - - # List of the human-readable names of all available backends. - backend_names = eval_statement( - 'import matplotlib; print(matplotlib.rcsetup.all_backends)') - - # List of the fully-qualified names of all importable backend modules. - module_names = [] - - # If the current system is not OS X and the "CocoaAgg" backend is available, - # remove this backend from consideration. Attempting to import this backend - # on non-OS X systems halts the current subprocess without printing output - # or raising exceptions, preventing its reliable detection. - if not is_darwin and 'CocoaAgg' in backend_names: - backend_names.remove('CocoaAgg') - - # For safety, attempt to import each backend in a unique subprocess. - for backend_name in backend_names: - if backend_name in KEEP: - continue - - module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() - stdout = exec_statement(import_statement % module_name) - - # If no output was printed, this backend is importable. - if not stdout: - module_names.append(module_name) - logger.info(' Matplotlib backend "%s": removed' % backend_name) - - return module_names - -# Freeze all importable backends, as PyInstaller is unable to determine exactly -# which backends are required by the current program. -e=get_matplotlib_backend_module_names() -print(e) -excludedimports = e \ No newline at end of file diff --git a/eos/db/migrations/__init__.py b/eos/db/migrations/__init__.py index e9797c352..b5034ac03 100644 --- a/eos/db/migrations/__init__.py +++ b/eos/db/migrations/__init__.py @@ -8,43 +8,25 @@ many upgrade files as there are database versions (version 5 would include upgrade files 1-5) """ -import pkgutil import re +from eos.utils.pyinst_support import iterNamespace + updates = {} appVersion = 0 prefix = __name__ + "." -# load modules to work based with and without pyinstaller -# from: https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py -# see: https://github.com/pyinstaller/pyinstaller/issues/1905 - -# load modules using iter_modules() -# (should find all filters in normal build, but not pyinstaller) -module_names = [m[1] for m in pkgutil.iter_modules(__path__, prefix)] - -# special handling for PyInstaller -importers = map(pkgutil.get_importer, __path__) -toc = set() -for i in importers: - if hasattr(i, 'toc'): - toc |= i.toc - -for elm in toc: - if elm.startswith(prefix): - module_names.append(elm) - -for modname in module_names: +for modName in iterNamespace(__name__, __path__): # loop through python files, extracting update number and function, and # adding it to a list - modname_tail = modname.rsplit('.', 1)[-1] - module = __import__(modname, fromlist=True) + modname_tail = modName.rsplit('.', 1)[-1] m = re.match("^upgrade(?P\d+)$", modname_tail) if not m: continue index = int(m.group("index")) appVersion = max(appVersion, index) + module = __import__(modName, fromlist=True) upgrade = getattr(module, "upgrade", False) if upgrade: updates[index] = upgrade diff --git a/eos/utils/pyinst_support.py b/eos/utils/pyinst_support.py new file mode 100644 index 000000000..6b0345da2 --- /dev/null +++ b/eos/utils/pyinst_support.py @@ -0,0 +1,39 @@ +""" +Slightly modified version of function taken from here: +https://github.com/pyinstaller/pyinstaller/issues/1905#issuecomment-525221546 +""" + + +import pkgutil + + +def iterNamespace(name, path): + """Pyinstaller-compatible namespace iteration. + + Yields the name of all modules found at a given Fully-qualified path. + + To have it running with pyinstaller, it requires to ensure a hook inject the + "hidden" modules from your plugins folder inside the executable: + + - if your plugins are under the ``myappname/pluginfolder`` module + - create a file ``specs/hook-.py`` + - content of this file should be: + + .. code-block:: python + + from PyInstaller.utils.hooks import collect_submodules + hiddenimports = collect_submodules('') + """ + prefix = name + "." + for p in pkgutil.iter_modules(path, prefix): + yield p[1] + + # special handling when the package is bundled with PyInstaller 3.5 + # See https://github.com/pyinstaller/pyinstaller/issues/1905#issuecomment-445787510 + toc = set() + for importer in pkgutil.iter_importers(name.partition(".")[0]): + if hasattr(importer, 'toc'): + toc |= importer.toc + for name in toc: + if name.startswith(prefix): + yield name diff --git a/service/conversions/__init__.py b/service/conversions/__init__.py index b3263339c..53ee84692 100644 --- a/service/conversions/__init__.py +++ b/service/conversions/__init__.py @@ -7,37 +7,17 @@ item's name. The name of the file is usually arbitrary unless it's used in logic elsewhere (in which case can be accessed with packs[name]) """ -import pkgutil + +from eos.utils.pyinst_support import iterNamespace + # init parent dict all = {} - # init container to store the separate conversion packs in case we need them packs = {} -prefix = __name__ + "." - -# load modules to work based with and without pyinstaller -# from: https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py -# see: https://github.com/pyinstaller/pyinstaller/issues/1905 - -# load modules using iter_modules() -# (should find all filters in normal build, but not pyinstaller) -module_names = [m[1] for m in pkgutil.iter_modules(__path__, prefix)] - -# special handling for PyInstaller -importers = map(pkgutil.get_importer, __path__) -toc = set() -for i in importers: - if hasattr(i, 'toc'): - toc |= i.toc - -for elm in toc: - if elm.startswith(prefix): - module_names.append(elm) - -for modname in module_names: - conversionPack = __import__(modname, fromlist="dummy") +for modName in iterNamespace(__name__, __path__): + conversionPack = __import__(modName, fromlist="dummy") all.update(conversionPack.CONVERSIONS) - modname_tail = modname.rsplit('.', 1)[-1] + modname_tail = modName.rsplit('.', 1)[-1] packs[modname_tail] = conversionPack.CONVERSIONS