# ubelt.util_import module

Expose functions to simplify importing from module names and paths.

The `ubelt.import_module_from_path()` function does its best to load a python file into th current set of global modules.

The `ubelt.import_module_from_name()` works similarly.

The `ubelt.modname_to_modpath()` and `ubelt.modname_to_modpath()` work statically and convert between module names and file paths on disk.

The `ubelt.split_modpath()` function separates modules into a root and base path depending on where the first `__init__.py` file is.

ubelt.util_import.split_modpath(modpath, check=True)

  Splits the modpath into the dir that must be in PYTHONPATH for the module to be imported and the modulepath relative to this directory.

  Parameters:

    - modpath (str) – module filepath

    - check (bool) – if False, does not raise an error if modpath is a directory and does not contain an `__init__.py` file.

  Returns:

    (directory, rel_modpath)

  Return type:

    Tuple[str, str]

  Raises:

    ValueError – if modpath does not exist or is not a package

  Example

      >>> from xdoctest import static_analysis
      >>> from ubelt.util_import import split_modpath
      >>> modpath = static_analysis.__file__.replace('.pyc', '.py')
      >>> modpath = abspath(modpath)
      >>> dpath, rel_modpath = split_modpath(modpath)
      >>> recon = join(dpath, rel_modpath)
      >>> assert recon == modpath
      >>> assert rel_modpath == join('xdoctest', 'static_analysis.py')

ubelt.util_import.modname_to_modpath(modname, hide_init=True, hide_main=False, 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.

  Parameters:

    - modname (str) – The name of a module in `sys_path`.

    - hide_init (bool) – if False, __init__.py will be returned for packages. Defaults to True.

    - hide_main (bool) – if False, and `hide_init` is True, __main__.py will be returned for packages, if it exists. Defaults to False.

    - sys_path (None | List[str | PathLike]) – The paths to search for the module. If unspecified, defaults to `sys.path`.

  Returns:

    modpath - path to the module, or None if it doesn’t exist

  Return type:

    str | None

  Example

      >>> from ubelt.util_import import modname_to_modpath
      >>> modname = 'xdoctest.__main__'
      >>> modpath = modname_to_modpath(modname, hide_main=False)
      >>> assert modpath.endswith('__main__.py')
      >>> modname = 'xdoctest'
      >>> modpath = modname_to_modpath(modname, hide_init=False)
      >>> assert modpath.endswith('__init__.py')
      >>> # xdoctest: +REQUIRES(CPython)
      >>> modpath = basename(modname_to_modpath('_ctypes'))
      >>> assert 'ctypes' in modpath

ubelt.util_import.modpath_to_modname(modpath, hide_init=True, hide_main=False, check=True, relativeto=None)

  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.

  Parameters:

    - modpath (str) – Module filepath

    - hide_init (bool) – Removes the __init__ suffix. Defaults to True.

    - hide_main (bool) – Removes the __main__ suffix. Defaults to False.

    - check (bool) – If False, does not raise an error if modpath is a dir and does not contain an __init__ file. Defaults to True.

    - relativeto (str | None) – If specified, all checks are ignored and this is considered the path to the root module. Defaults to None.

  Todo

  - [ ] Does this need modification to support PEP 420?

      https://www.python.org/dev/peps/pep-0420/

  Returns:

    modname

  Return type:

    str

  Raises:

    ValueError – if check is True and the path does not exist

  Example

      >>> from ubelt.util_import import modpath_to_modname
      >>> from xdoctest import static_analysis
      >>> modpath = static_analysis.__file__.replace('.pyc', '.py')
      >>> modpath = modpath.replace('.pyc', '.py')
      >>> modname = modpath_to_modname(modpath)
      >>> assert modname == 'xdoctest.static_analysis'

  Example

      >>> from ubelt.util_import import modpath_to_modname
      >>> import xdoctest
      >>> assert modpath_to_modname(xdoctest.__file__.replace('.pyc', '.py')) == 'xdoctest'
      >>> assert modpath_to_modname(dirname(xdoctest.__file__.replace('.pyc', '.py'))) == 'xdoctest'

  Example

      >>> # xdoctest: +REQUIRES(CPython)
      >>> from ubelt.util_import import modpath_to_modname
      >>> from ubelt.util_import import modname_to_modpath
      >>> modpath = modname_to_modpath('_ctypes')
      >>> modname = modpath_to_modname(modpath)
      >>> assert modname == '_ctypes'

  Example

      >>> from ubelt.util_import import modpath_to_modname
      >>> modpath = '/foo/libfoobar.linux-x86_64-3.6.so'
      >>> modname = modpath_to_modname(modpath, check=False)
      >>> assert modname == 'libfoobar'

ubelt.util_import.import_module_from_name(modname)

  Imports a module from its string name (i.e. `__name__`)

  This is a simple wrapper around `importlib.import_module()`, but is provided as a companion function to `import_module_from_path()`, which contains functionality not provided in the Python standard library.

  Parameters:

    modname (str) – module name

  Returns:

    module

  Return type:

    ModuleType

  SeeAlso:

    `import_module_from_path()`

  Example

      >>> # test with modules that won't be imported in normal circumstances
      >>> # todo write a test where we guarantee this
      >>> import ubelt as ub
      >>> import sys
      >>> modname_list = [
      >>>     'pickletools',
      >>>     'email.mime.text',
      >>> ]
      >>> #assert not any(m in sys.modules for m in modname_list)
      >>> modules = [ub.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)

ubelt.util_import.import_module_from_path(modpath, index=-1)

  Imports a module via a filesystem path.

  This works by modifying `sys.path`, importing the module name, and then attempting to undo the change to sys.path. This function may produce unexpected results in the case where the imported module itself itself modifies `sys.path` or if there is another conflicting module with the same name.

  Parameters:

    - modpath (str | PathLike) – Path to the module on disk or within a zipfile. Paths within a zipfile can be given by `<path-to>.zip/<path-inside-zip>.py`.

    - index (int) – Location at which we modify PYTHONPATH if necessary. If your module name does not conflict, the safest value is -1, However, if there is a conflict, then use an index of 0. The default may change to 0 in the future.

  Returns:

    the imported module

  Return type:

    ModuleType

  References

  [SO_67631]

  https://stackoverflow.com/questions/67631/import-module-given-path

  Raises:

    - IOError - when the path to the module does not exist –

    - ImportError - when the module is unable to be imported –

  Note

  If the module is part of a package, the package will be imported first. These modules may cause problems when reloading via IPython magic

  This can import a module from within a zipfile. To do this modpath should specify the path to the zipfile and the path to the module within that zipfile separated by a colon or pathsep. E.g. “/path/to/archive.zip:mymodule.pl”

  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 contains the code `from . import helper`, Python will assume helper belongs to the `pkg` module already in `sys.modules`. This can cause a NameError or worse — an incorrect helper module.

  SeeAlso:

    `import_module_from_name()`

  Example

      >>> import ubelt as ub
      >>> import xdoctest
      >>> modpath = xdoctest.__file__
      >>> module = ub.import_module_from_path(modpath)
      >>> assert module is xdoctest

  Example

      >>> # Test importing a module from within a zipfile
      >>> import ubelt as ub
      >>> import zipfile
      >>> from xdoctest import utils
      >>> import os
      >>> from os.path import join, expanduser, normpath
      >>> dpath = expanduser('~/.cache/xdoctest')
      >>> dpath = utils.ensuredir(dpath)
      >>> #dpath = utils.TempDir().ensure()
      >>> # Write to an external module named bar
      >>> external_modpath = join(dpath, 'bar.py')
      >>> # For pypy support we have to write this using with
      >>> with open(external_modpath, 'w') as file:
      >>>     file.write('testvar = 1')
      >>> internal = 'folder/bar.py'
      >>> # Move the external bar module into a zipfile
      >>> zippath = join(dpath, 'myzip.zip')
      >>> with zipfile.ZipFile(zippath, 'w') as myzip:
      >>>     myzip.write(external_modpath, internal)
      >>> # Import the bar module from within the zipfile
      >>> modpath = zippath + ':' + internal
      >>> modpath = zippath + os.path.sep + internal
      >>> module = ub.import_module_from_path(modpath)
      >>> assert normpath(module.__name__) == normpath('folder/bar')
      >>> assert module.testvar == 1

  Example

      >>> import pytest
      >>> import ubelt as ub
      >>> with pytest.raises(IOError):
      >>>     ub.import_module_from_path('does-not-exist')
      >>> with pytest.raises(IOError):
      >>>     ub.import_module_from_path('does-not-exist.zip/')