0

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 inspect.getmembers(fullpath, inspect.isfunction) it returns an empty array. Is there any way to accomplish this? Here is my code so far:

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)
asdf
  • 2,927
  • 2
  • 21
  • 42

1 Answers1

0

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 this answer.

Here's an example based on the test runner of the gorilla library that is compatible with Python 2 and Python 3:

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()

Here /path/to/your/package 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 package_name would be the directory name inside the repository that contains the root __init__.py file.

In fact the function module_iterator also accepts a module for its parameter package_dotted_path, such as 'package.subpackage.module' 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.

Community
  • 1
  • 1
ChristopherC
  • 1,635
  • 16
  • 31
  • Just to be clear, `package_dotted_path` is expecting the name of the module with the `.py` file extension, correct? – asdf Aug 31 '16 at 01:31
  • I have edited (and fixed) my answer to help clearing this out. Basically the `package_dotted_path` that you pass to the function has to be the package name containing all your modules. For example it could simply be `package`, or `package.subpackage`, or even directly a module such as `package.subpackage.module`. The only condition is that importing such a dotted path has to work. This is ensured here by adding the parent directory of the root package to `sys.path`. – ChristopherC Aug 31 '16 at 01:46