Source code for ubelt.util_import

# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import sys


__all__ = [
    'split_modpath',
    'modname_to_modpath',
    'modpath_to_modname',
    'import_module_from_name',
    'import_module_from_path',
]


[docs]def split_modpath(modpath): r""" Splits the modpath into the dir that must be in PYTHONPATH for the module to be imported and the modulepath relative to this directory. Args: modpath (str): module filepath Returns: tuple: (directory, rel_modpath) Example: >>> from xdoctest import static_analysis >>> from os.path import join >>> modpath = static_analysis.__file__ >>> modpath = modpath.replace('.pyc', '.py') >>> dpath, rel_modpath = split_modpath(modpath) >>> assert join(dpath, rel_modpath) == modpath >>> assert rel_modpath == join('xdoctest', 'static_analysis.py') """ from xdoctest import static_analysis as static directory, rel_modpath = static.split_modpath(modpath) return directory, rel_modpath
[docs]def modpath_to_modname(modpath, hide_init=True, hide_main=False): """ Determines importable name from file path Converts the path to a module (__file__) to the importable python name (__name__) without importing the module. The filename is converted to a module name, and parent directories are recursively included until a directory without an __init__.py file is encountered. Args: modpath (str): module filepath hide_init (bool): removes the __init__ suffix (default True) hide_main (bool): removes the __main__ suffix (default False) Returns: str: modname Example: >>> import ubelt.util_import >>> modpath = ubelt.util_import.__file__ >>> print(modpath_to_modname(modpath)) ubelt.util_import """ from xdoctest import static_analysis as static return static.modpath_to_modname(modpath, hide_init, hide_main)
[docs]def modname_to_modpath(modname, hide_init=True, hide_main=True, sys_path=None): """ Finds the path to a python module from its name. Determines the path to a python module without directly import it Converts the name of a module (__name__) to the path (__file__) where it is located without importing the module. Returns None if the module does not exist. Args: modname (str): module filepath hide_init (bool): if False, __init__.py will be returned for packages hide_main (bool): if False, and hide_init is True, __main__.py will be returned for packages, if it exists. sys_path (list): if specified overrides `sys.path` (default None) Returns: str: modpath - path to the module, or None if it doesn't exist CommandLine: python -m ubelt.util_import modname_to_modpath Example: >>> from ubelt.util_import import * # NOQA >>> import sys >>> modname = 'ubelt.progiter' >>> already_exists = modname in sys.modules >>> modpath = modname_to_modpath(modname) >>> print('modpath = {!r}'.format(modpath)) >>> assert already_exists or modname not in sys.modules Example: >>> from ubelt.util_import import * # NOQA >>> import sys >>> modname = 'ubelt.__main__' >>> modpath = modname_to_modpath(modname, hide_main=False) >>> print('modpath = {!r}'.format(modpath)) >>> assert modpath.endswith('__main__.py') >>> modname = 'ubelt' >>> modpath = modname_to_modpath(modname, hide_init=False) >>> print('modpath = {!r}'.format(modpath)) >>> assert modpath.endswith('__init__.py') >>> modname = 'ubelt' >>> modpath = modname_to_modpath(modname, hide_init=False, hide_main=False) >>> print('modpath = {!r}'.format(modpath)) >>> assert modpath.endswith('__init__.py') """ from xdoctest import static_analysis as static return static.modname_to_modpath(modname, hide_init, hide_main, sys_path)
class PythonPathContext(object): """ Context for temporarily adding a dir to the PYTHONPATH. Args: dpath (str): directory to insert into the PYTHONPATH index (int): position to add to. Typically either -1 or 0. Example: >>> with PythonPathContext('foo', -1): >>> assert sys.path[-1] == 'foo' >>> assert sys.path[-1] != 'foo' >>> with PythonPathContext('bar', 0): >>> assert sys.path[0] == 'bar' >>> assert sys.path[0] != 'bar' """ def __init__(self, dpath, index=-1): self.dpath = dpath self.index = index def __enter__(self): if self.index < 0: self.index = len(sys.path) + self.index + 1 sys.path.insert(self.index, self.dpath) def __exit__(self, type, value, trace): if len(sys.path) <= self.index or sys.path[self.index] != self.dpath: raise AssertionError( 'sys.path significantly changed while in PythonPathContext') sys.path.pop(self.index)
[docs]def import_module_from_name(modname): """ Imports a module from its string name (__name__) Args: modname (str): module name Returns: module: module Example: >>> # test with modules that wont be imported in normal circumstances >>> # todo write a test where we gaurentee this >>> modname_list = [ >>> #'test', >>> 'pickletools', >>> 'lib2to3.fixes.fix_apply', >>> ] >>> #assert not any(m in sys.modules for m in modname_list) >>> modules = [import_module_from_name(modname) for modname in modname_list] >>> assert [m.__name__ for m in modules] == modname_list >>> assert all(m in sys.modules for m in modname_list) """ # The __import__ statment is weird if '.' in modname: fromlist = modname.split('.')[-1] fromlist_ = list(map(str, fromlist)) # needs to be ascii for python2.7 module = __import__(modname, {}, {}, fromlist_, 0) else: module = __import__(modname, {}, {}, [], 0) return module
[docs]def import_module_from_path(modpath): """ Imports a module via its path Args: modpath (str): path to the module Returns: module: the imported module References: https://stackoverflow.com/questions/67631/import-module-given-path Notes: If the module is part of a package, the package will be imported first. These modules may cause problems when reloading via IPython magic Warning: It is best to use this with paths that will not conflict with previously existing modules. If the modpath conflicts with a previously existing module name. And the target module does imports of its own relative to this conflicting path. In this case, the module that was loaded first will win. For example if you try to import '/foo/bar/pkg/mod.py' from the folder structure: - foo/ +- bar/ +- pkg/ + __init__.py |- mod.py |- helper.py If there exists another module named `pkg` already in sys.modules and mod.py does something like `from . import helper`, Python will assume helper belongs to the `pkg` module already in sys.modules. This can cause a NameError or worse --- a incorrect helper module. TODO: handle modules inside of zipfiles Example: >>> from ubelt import util_import >>> modpath = util_import.__file__ >>> module = import_module_from_path(modpath) >>> assert module is util_import """ # the importlib version doesnt work in pytest module = _custom_import_modpath(modpath) # TODO: use this implementation once pytest fixes importlib # module = _pkgutil_import_modpath(modpath) return module
def _custom_import_modpath(modpath): import xdoctest.static_analysis as static dpath, rel_modpath = static.split_modpath(modpath) modname = static.modpath_to_modname(modpath) with PythonPathContext(dpath, index=0): module = import_module_from_name(modname) return module def _pkgutil_import_modpath(modpath): # nocover import six import xdoctest.static_analysis as static dpath, rel_modpath = static.split_modpath(modpath) modname = static.modpath_to_modname(modpath) if six.PY2: # nocover import imp module = imp.load_source(modname, modpath) elif sys.version_info[0:2] <= (3, 4): # nocover assert sys.version_info[0:2] <= (3, 2), '3.0 to 3.2 is not supported' from importlib.machinery import SourceFileLoader module = SourceFileLoader(modname, modpath).load_module() else: import importlib.util spec = importlib.util.spec_from_file_location(modname, modpath) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module