Source code for pylablib.core.utils.module

"""
Library for dealing with python module properties.
"""

try:
    from importlib import reload
except ImportError:
    from imp import reload  # pylint: disable=deprecated-module

try:
    from importlib import metadata
except ImportError:
    metadata=None
try:
    import pkg_resources
except ImportError:
    pkg_resources=None
import sys
import subprocess
import os
import inspect

from . import general, files as file_utils



[docs] def get_package_version(pkg): """ Get the version of the package. If the package version is unavailable, return ``None``. """ if metadata is not None: try: return metadata.metadata(pkg)["version"] except metadata.PackageNotFoundError: return None try: return pkg_resources.get_distribution(pkg).version except (pkg_resources.DistributionNotFound,pkg_resources.VersionConflict): return None
def _tryint(v): try: return int(v) except ValueError: return v
[docs] def cmp_versions(ver1, ver2): """ Compare two package versions. Return ``'<'`` if the first version is older (smaller), ``'>'`` if it's younger (larger) or ``'='`` if it's the same. """ ver1=[_tryint(v.strip()) for v in str(ver1).split(".")] ver2=[_tryint(v.strip()) for v in str(ver2).split(".")] if ver1>ver2: return ">" if ver1<ver2: return "<" return "="
[docs] def cmp_package_version(pkg, ver): """ Compare current package version to `ver`. `ver` should be a name of the package (rather than the module). Return ``'<'`` if current version is older (smaller), ``'>'`` if it's younger (larger) or ``'='`` if it's the same. If the package version is unavailable, return ``None``. """ cver=get_package_version(pkg) if cver is None: return None if ver=="": return ">" return cmp_versions(cver,ver)
[docs] def expand_relative_path(module_name, rel_path): """ Turn a relative module path into an absolute one. `module_name` is the absolute name of the reference module, `rel_path` is the path relative to this module. """ module_path=module_name.split(".") if not rel_path.startswith("."): return rel_path else: while rel_path.startswith("."): rel_path=rel_path[1:] module_path=module_path[:-1] return ".".join(module_path)+"."+rel_path
[docs] def get_loaded_package_modules(pkg_name): """ Get all modules in the package `pkg_name`. Returns a dict ``{name: module}``. """ prefix=pkg_name+"." return dict([(name,module) for name,module in sys.modules.items() if (name.startswith(prefix) or name==pkg_name) and module is not None])
[docs] def get_imported_modules(module, explicit=False): """ Get modules imported within a given module. If ``explicit==True``, take into account only toplevel objects which are modules (corresponds to ``import module`` or ``from package import module`` statements) If ``explicit==False``, also include all modules containing toplevel objects (corresponds to ``from module import Class`` or ``from package import function`` statements). Return a dictionary ``{name: module}`` (modules with the same name are considered to be the same). """ imported={} for n in module.__dict__: v=getattr(module,n,None) if v is not None: if inspect.ismodule(v): imported[v.__name__]=v elif not explicit: cm=inspect.getmodule(v) if cm is not None and cm is not module: imported[cm.__name__]=cm return imported
[docs] def get_reload_order(modules): """ Find reload order for modules which respects dependencies (a module is loaded before its dependents). `modules` is a dict ``{name: module}``. The module dependencies (i.e., the modules which the current module depends on) are determined based on imported modules and modules containing toplevel module objects. """ deps={} for name,module in modules.items(): deps[name]=deps.get(name,set())|{m for m in get_imported_modules(module) if m in modules} for ch_name in modules: if ch_name.startswith(name+"."): deps.setdefault(name,set()).add(ch_name) for name in deps: deps[name]=set(deps[name]) order=general.topological_order(deps) order=[name for name in modules if not name in order]+order return order
[docs] def reload_package_modules(pkg_name, ignore_errors=False): """ Reload package `pkg_name`, while respecting dependencies of its submodules. If ``ignore_errors=True``, ignore :exc:`ImportError` exceptions during the reloading process. """ modules=get_loaded_package_modules(pkg_name) order=get_reload_order(modules) for name in order: try: reload(modules[name]) except ImportError: if not ignore_errors: raise
[docs] def unload_package_modules(pkg_name, ignore_errors=False): """ Reload package `pkg_name`, while respecting dependencies of its submodules. If ``ignore_errors=True``, ignore :exc:`ImportError` exceptions during the reloading process. """ modules=get_loaded_package_modules(pkg_name) order=get_reload_order(modules) for name in order: try: del sys.modules[name] except IndexError: if not ignore_errors: raise
[docs] def get_library_path(): """ Get a filesystem path for the pyLabLib library (the one containing current the module). """ module_path=sys.modules[__name__].__file__ module_path=file_utils.normalize_path(module_path) return os.path.join(*file_utils.fullsplit(module_path)[:-3]) # pylint: disable=no-value-for-parameter
[docs] def get_library_name(): """ Get the name for the pyLabLib library (the one containing current the module). """ module_name=__name__ return ".".join(module_name.split(".")[:-3])
[docs] def get_executable(console=False): """ Get Python executable. If ``console==True`` and the current executable is windowed (i.e., ``"pythonw.exe"``), return the corresponding ``"python.exe"`` instead. """ folder,file=os.path.split(sys.executable) if file.lower()=="pythonw.exe" and console: return os.path.join(folder,"python.exe") return sys.executable
[docs] def get_python_folder(): """Return Python interpreter folder (the folder containing the python executable)""" return os.path.split(os.path.abspath(sys.executable))[0]
[docs] def pip_install(pkg, upgrade=False): """ Call ``pip install`` for a given package. If ``upgrade==True``, call with ``--upgrade`` key (upgrade current version if it is already installed). """ if upgrade: subprocess.call([get_executable(console=True), "-m", "pip", "install", "--upgrade", pkg]) else: subprocess.call([get_executable(console=True), "-m", "pip", "install", pkg])
[docs] def install_if_older(pkg, min_ver=""): """ Install `pkg` from the default PyPI repository if its version is lower that `min_ver` If `min_ver` is ``None``, upgrade to the newest version regardless; if ``min_ver==""``, install only if no version is installed. Return ``True`` if the package was installed. """ if get_package_version(pkg) is None or cmp_package_version(pkg,min_ver)=="<": pip_install(pkg,upgrade=True) return True return False