osdir.com

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Import module from file path


Oscar Benjamin wrote:

> Hi all,
> 
> I'm looking to import a module given a string representing the path to
> the .py file defining the module. For example given this setup
> 
> mkdir -p a/b/c
> touch a/__init__.py
> touch a/b/__init__.py
> touch a/b/c/__init__.py
> touch a/b/c/stuff.py
> 
> I have a module a.b.c.stuff which is defined in the file
> '/home/oscar/work/project/a/b/c/stuff.py'. Given that a.b.c.stuff is
> importable and I have the (relative or absolute) path of stuff.py as a
> string I would like to import that module.
> 
> I want this to work in 2.7 and 3.4+ and have come up with the
> following which works for valid inputs:
> 
> import os.path
> 
> def import_submodule(filename, rootmodule):
>     # Convert from path to module name
>     rootdir = os.path.dirname(os.path.dirname(rootmodule.__path__[0]))
>     filepath = os.path.relpath(filename, rootdir)
>     basename, ext = os.path.splitext(filepath)
>     modname = basename.replace('/', '.').replace('\\', '.')
>     subattr = modname.split(rootmodule.__name__ + '.')[-1]
>     modname = rootmodule.__name__ + '.' + subattr
> 
>     # Now import the module
>     import importlib
>     mod = importlib.import_module(modname)
>     return mod
> 
> import a
> mod = import_submodule('a/b/c/stuff.py', a)
> print(dir(mod))
> 
> The first part of the above function is the bit that bothers me. I
> think there are ways that it could import and run the wrong code if
> accidentally given the wrong input (malevolent input is unimportant
> here). Also it seems as if there should be a simpler way to get from
> the path to the module name...

I am not aware of a clean way. I have used


def guess_modulename(filename):
    """Infer module name from filename.

    >>> guess_modulename("/foo/bar/baz.py")
    'baz'
    >>> guess_modulename("/usr/lib/python3.4/logging/handlers.py")
    'logging.handlers'
    """
    if not filename.endswith(".py"):
        raise ValueError("expecting .py file, but got %r" % filename)
    filename = filename[:-3]
    folder, name = os.path.split(filename)
    names = [name]
    while os.path.isfile(os.path.join(folder, "__init__.py")):
        folder, name = os.path.split(folder)
        names.append(name)
    return ".".join(reversed(names))


which unfortunately does not work with namespace packages.