Ensembl/ensembl-hive

View on GitHub
wrappers/python3/wrapper

Summary

Maintainability
Test Coverage
#!/usr/bin/env python3

import os
import sys

import eHive


## Check that the required module is available, is a eHive runnable, etc
def find_module(module_name):

    module = None
    def import_error(msg):
        if msg[-1] != "\n":
            msg += "\n"
        if (module is not None) and hasattr(module, '__file__'):
            msg += module.__name__ + " is in " + module.__file__ + "\n"
        msg += "sys.path is " + str(sys.path)
        print("ImportError: " + msg, file=sys.stderr)
        sys.exit(1)

    # Import the whole module chain
    try:
        module = __import__(module_name)
    except ImportError as e:
        import_error("Cannot import '{0}' : {1}".format(module_name, e))

    # Traverse the namespaces down to the module
    # e.g. If module_name is "eHive.examples.LongMult.AddTogether", module represents "LongMult" at this stage
    for submodule in module_name.split('.')[1:]:
        if hasattr(module, submodule):
            module = getattr(module, submodule)
        else:
            import_error("Cannot find '{0}' in '{1}'".format(submodule, module))
    # e.g. module now represents "AddTogether"

    if not hasattr(module, '__file__'):
       import_error('"{0}" is a namespace, not a module'.format(module.__name__))

    # NB: We assume that the runnable has the same name as the file itself
    class_name = module_name.split('.')[-1]

    # get the class in the module
    if not hasattr(module, class_name):
        # it could be a typo ... Let's print the available modules by decreasing distance to the required name
        import difflib
        possible_modules = [_ for _ in dir(module) if isinstance(getattr(module, _), type) and issubclass(getattr(module, _), eHive.BaseRunnable)]
        possible_modules = sorted(possible_modules, key = lambda _ : difflib.SequenceMatcher(a=class_name, b=_, autojunk=False).ratio(), reverse=True)
        s = "No class named '{0}' in the module '{1}'.\n"
        if len(possible_modules):
            s += "Warning: {1} contains {2} Runnable classes ({3}). Should one of them be renamed ?"
        else:
            s += "Warning: {1} doesn't contain any Runnable classes"
        import_error(s.format(class_name, module_name, len(possible_modules), ', '.join('"%s"' % _ for _ in possible_modules)))

    # Check that the class is a runnable
    c = getattr(module, class_name)
    if not isinstance(c, type):
        import_error("{0} (found in {1}) is not a class but a {2}".format(class_name, module.__file__, type(c)))
    if not issubclass(c, eHive.BaseRunnable):
        import_error("{0} (found in {1}) is not a sub-class of eHive.BaseRunnable".format(class_name, module.__file__))

    return c


## One method per mode

def do_version():
    print(eHive.__version__)

def do_compile():
    find_module(sys.argv[2])

def do_run():
    runnable = find_module(sys.argv[2])
    try:
        fd_in = int(sys.argv[3])
        fd_out = int(sys.argv[4])
    except:
        usage('Cannot read the file descriptors as integers')
    runnable(fd_in, fd_out)


## And here we select the mode

available_modes = {
        'version' : (do_version, []),
        'compile' : (do_compile, ['module_name']),
        'run'     : (do_run, ['module_name', 'fd_in', 'fd_out'])
    }

def usage(msg):
    error = "Command-line error: " + msg + "\nUsage: \n"
    for (mode, (_, args)) in available_modes.items():
        error += "\t" + " ".join([sys.argv[0], mode] + args) + "\n"
    print(error, file=sys.stderr)
    sys.exit(1)

if len(sys.argv) == 1:
    usage('No mode provided')

mode = sys.argv[1]
if mode not in available_modes:
    usage('Unknown mode "{0}"'.format(mode))

if len(sys.argv)-2 != len(available_modes[mode][1]):
    usage('Not enough arguments for mode "' + mode + '". Expecting: ' + ' '.join(available_modes[mode][1]))

available_modes[mode][0]()