fix_import.py 3.18 KB
Newer Older
1 2 3 4 5 6 7 8 9
"""Fixer for import statements.
If spam is being imported from the local directory, this import:
    from spam import eggs
Becomes:
    from .spam import eggs

And this import:
    import spam
Becomes:
10
    from . import spam
11 12 13
"""

# Local imports
14
from .. import fixer_base
Benjamin Peterson's avatar
Benjamin Peterson committed
15
from os.path import dirname, join, exists, sep
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
from ..fixer_util import FromImport, syms, token


def traverse_imports(names):
    """
    Walks over all the names imported in a dotted_as_names node.
    """
    pending = [names]
    while pending:
        node = pending.pop()
        if node.type == token.NAME:
            yield node.value
        elif node.type == syms.dotted_name:
            yield "".join([ch.value for ch in node.children])
        elif node.type == syms.dotted_as_name:
            pending.append(node.children[0])
        elif node.type == syms.dotted_as_names:
            pending.extend(node.children[::-2])
        else:
            raise AssertionError("unkown node type")

37

38
class FixImport(fixer_base.BaseFix):
Benjamin Peterson's avatar
Benjamin Peterson committed
39
    BM_compatible = True
40 41

    PATTERN = """
42
    import_from< 'from' imp=any 'import' ['('] any [')'] >
43
    |
44
    import_name< 'import' imp=any >
45 46
    """

Benjamin Peterson's avatar
Benjamin Peterson committed
47 48 49 50
    def start_tree(self, tree, name):
        super(FixImport, self).start_tree(tree, name)
        self.skip = "absolute_import" in tree.future_features

51
    def transform(self, node, results):
Benjamin Peterson's avatar
Benjamin Peterson committed
52 53
        if self.skip:
            return
54 55
        imp = results['imp']

56
        if node.type == syms.import_from:
57 58 59 60 61 62
            # Some imps are top-level (eg: 'import ham')
            # some are first level (eg: 'import ham.eggs')
            # some are third level (eg: 'import ham.eggs as spam')
            # Hence, the loop
            while not hasattr(imp, 'value'):
                imp = imp.children[0]
63
            if self.probably_a_local_import(imp.value):
64
                imp.value = u"." + imp.value
65
                imp.changed()
66
        else:
67 68 69 70 71 72 73 74 75 76 77 78 79 80
            have_local = False
            have_absolute = False
            for mod_name in traverse_imports(imp):
                if self.probably_a_local_import(mod_name):
                    have_local = True
                else:
                    have_absolute = True
            if have_absolute:
                if have_local:
                    # We won't handle both sibling and absolute imports in the
                    # same statement at the moment.
                    self.warning(node, "absolute and local imports together")
                return

Benjamin Peterson's avatar
Benjamin Peterson committed
81
            new = FromImport(u".", [imp])
Benjamin Peterson's avatar
Benjamin Peterson committed
82
            new.prefix = node.prefix
83
            return new
84

85
    def probably_a_local_import(self, imp_name):
Benjamin Peterson's avatar
Benjamin Peterson committed
86 87 88 89
        if imp_name.startswith(u"."):
            # Relative imports are certainly not local imports.
            return False
        imp_name = imp_name.split(u".", 1)[0]
90 91 92 93
        base_path = dirname(self.filename)
        base_path = join(base_path, imp_name)
        # If there is no __init__.py next to the file its not in a package
        # so can't be a relative import.
Benjamin Peterson's avatar
Benjamin Peterson committed
94
        if not exists(join(dirname(base_path), "__init__.py")):
95
            return False
Benjamin Peterson's avatar
Benjamin Peterson committed
96
        for ext in [".py", sep, ".pyc", ".so", ".sl", ".pyd"]:
97 98
            if exists(base_path + ext):
                return True
99
        return False