List all functions in a module given fullpath
<p>I am trying to list all the functions in every encountered module to enforce the writing of test cases. However, I am having issues doing this with just the fullpath to the file as a string and not importing it. When I use <code>inspect.getmembers(fullpath, inspect.isfunction)</code> it returns an empty array. Is there any way to accomplish this? Here is my code so far:</p>

<pre><code>import glob
import os
import inspect

def count_tests(moduletestdict):
    for key,value in moduletestdict.items():
        print("Key: {}".format(key))
        print(inspect.getmembers(key, inspect.isfunction))

def find_functions(base_dir):
    """moduletestdict has key value pairs where the key is the module
    to be tested and the value is the testing module"""
    moduletestdict = {}
    for roots, dirs, files in os.walk(base_dir):
        for f in files:
            if (".py" in f) and (".pyc" not in f) and ("test_" not in f) and (f != "__init__.py"):
                if "test_{}".format(f) in files:
                    moduletestdict["{}/{}".format(roots,f)] = "{}/{}".format(roots, "test_{}".format(f))
                else:   
                    moduletestdict["{}/{}".format(roots,f)] = "NOTEST"
    return moduletestdict

if __name__ == "__main__":
    base_dir = "/Users/MyName/Documents/Work/MyWork/local-dev/sns"
    moduletestdict = find_functions(base_dir)
    count_tests(moduletestdict)
</code></pre>

<p>You do need to import the modules somehow but the ârecommendedâ way is a bit tricky since things kept changing with new versions of Python, see <a href="http://stackoverflow.com/a/67692/1640404">this answer</a>.</p>

<p>Here's an example based on the test runner of the <a href="https://github.com/christophercrouzet/gorilla" rel="nofollow">gorilla library</a> that is compatible with Python 2 and Python 3:</p>

<pre><code>import inspect
import os
import sys


def module_iterator(directory, package_dotted_path):
    paths = os.listdir(directory)
    for path in paths:
        full_path = os.path.join(directory, path)
        basename, tail = os.path.splitext(path)
        if basename == '__init__':
            dotted_path = package_dotted_path
        elif package_dotted_path:
            dotted_path = "%s.%s" % (package_dotted_path, basename)
        else:
            dotted_path = basename

        if os.path.isfile(full_path):
            if tail != '.py':
                continue

            __import__(dotted_path)
            module = sys.modules[dotted_path]
            yield module
        elif os.path.isdir(full_path):
            if not os.path.isfile(os.path.join(full_path, '__init__.py')):
                continue

            __import__(dotted_path)
            package = sys.modules[dotted_path]
            yield package
            for module in module_iterator(full_path, dotted_path):
                yield module

def main():
    directory_path = '/path/to/your/package'
    package_name = 'package'
    sys.path.insert(0, directory_path)
    modules = module_iterator(directory_path, package_name)
    for module in modules:
        functions = inspect.getmembers(module, inspect.isfunction)
        print("functions for module '{0}': {1}".format(
            module.__name__, functions)
        )

if __name__ == "__main__":
    main()
</code></pre>

<p>Here <code>/path/to/your/package</code> would be the repository to the package, that is the directory containing the readme, the docs, and so on like it is commonly the case with Python's packages, and <code>package_name</code> would be the directory name inside the repository that contains the root <code>__init__.py</code> file.</p>

<p>In fact the function <code>module_iterator</code> also accepts a module for its parameter <code>package_dotted_path</code>, such as <code>'package.subpackage.module'</code> for example, but then only the functions from this specific module would be retrieved. If a package is passed instead, then all the recursively nested modules will be inspected.</p>

python