'How do I dynamically import a python module containing imports to sibling packages?

I'm developing a command line tool in python which takes a source directory and build directory as parameters. The tool should walk through all directories in the source dir, find .py files and check for class members of a specific class instance.

For example:

$ my-cli-tool --source-dir app/ --build-dir docs/

With the following file structure:

MyProj
├── app
│   ├── foo
│   │   └── foo.py
│   └── bar
│       └── bar.py  
└── docs
    └─ ...

foo.py:

def foo_method():
    pass

class foo:
    my_member = MyClass()

bar.py:

from app.foo.foo import foo_method

class bar:
    my_member = MyClass()

Results in:

No module named 'app'

And if I use relative imports instead it results in:

attempted relative import beyond top-level package

Expected result:

As the program is intended to be a pip package, I would like to make it as pain free as possible for the users to integrate my package. I would like this cli tool to be able to work out of the box with their preferred style of imports.

My code so far:

import inspect
import os
import sys
from importlib import util
from argparse import ArgumentParser


def dir_path(string):
    if os.path.isdir(string):
        return os.path.abspath(string)
    else:
        raise NotADirectoryError(string)


def import_module(file_name, file_path):
    spec = util.spec_from_file_location(file_name, file_path)
    module = util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    return module


def main(args):
    parser = ArgumentParser(prog='my-cli-tool')
    parser.add_argument('--source-dir', type=dir_path, action='store', default=os.getcwd())
    parser.add_argument('--build-dir', type=dir_path, action='store', default=os.getcwd())
    args = parser.parse_args(args)

    for root, dirs, filenames in os.walk(args.source_dir):
        for file_name in filenames:
            if file_name.endswith('.py'):
                file_path = os.path.join(root, file_name)
                module = import_module(file_name, file_path)
                classes = [obj[1] for obj in inspect.getmembers(module) if inspect.isclass(obj[1])]
                result = []
                for class_ in classes:
                    for i in inspect.getmembers(class_):
                        if isinstance(i[1], MyClass):
                            result.append(getattr(class_, i[0]))
                return result # Then make something with the results....


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source