Search code examples
pythonlintgoogle-style-guide

Require python imports to be modules


The Google Style Guide for python states that one should: "Use imports for packages and modules only."

https://google.github.io/styleguide/pyguide.html#Imports

Is there a tool that flags violations of this suggestion?

Pylint does NOT do it. For example, following: Is there a tool to lint Python based on the Google style guide?

Creating a test.py the violates the guideline (exists is a function, not a module):

"""Test file for pylint"""
from os.path import exists

exists('/home')

Then, running pylint with the rc file does just fine:

$ pylint --rcfile=googlecl-pylint.rc -r n -s n  test.py
$ echo $?
0

Searching through the possible codes: http://pylint-messages.wikidot.com/all-codes, I don't see anything that looks like it would warn against this.

I have also not seen anything in pep8 or pyflakes that will catch this.


Solution

  • I made the following pylint plugin for this purpose:

    import astroid
    from pylint import checkers, interfaces
    from pylint.checkers import utils
    
    
    class ImportOnlyModulesChecked(checkers.BaseChecker):
      __implements__ = interfaces.IAstroidChecker
    
      name = 'import-only-modules'
      priority = -1
      msgs = {
        'W5521': (
          "Import \"%s\" from \"%s\" is not a module.",
          'import-only-modules',
          "Only modules should be imported.",
        ),
      }
    
      @utils.check_messages('import-only-modules')
      def visit_importfrom(self, node):
        try:
          imported_module = node.do_import_module(node.modname)
        except astroid.AstroidBuildingException:
          # Import errors should be checked elsewhere.
          return
    
        if node.level is None:
          modname = node.modname
        else:
          modname = '.' * node.level + node.modname
    
        for (name, alias) in node.names:
          # Wildcard imports should be checked elsewhere.
          if name == '*':
            continue
    
          try:
            imported_module.import_module(name, True)
            # Good, we could import "name" as a module relative to the "imported_module".
          except astroid.AstroidImportError:
            self.add_message(
              'import-only-modules',
              node=node,
              args=(name, modname),
            )
          except astroid.AstroidBuildingException:
            # Some other error.
            pass
    
    
    def register(linter):
      linter.register_checker(ImportOnlyModulesChecked(linter))