pylint_django error – NoSuchChecker

I just spent an inordinate amount of time tracking down an issue with pylint-django that I had a hard time finding any clues on the internet about, so I’m documenting it here.

I use pylint and pylint-django to perform automated checks on my Django projects and ensure a certain code quality is maintained. This combination has proven very useful. Recently, though, I ran into an issue where one project would fail to validate with a strange error:

$ pylint --rcfile pylintrc myproject
Using config file /app/pylintrc
Traceback (most recent call last):
  File "/usr/bin/pylint", line 11, in <module>
    sys.exit(run_pylint())
  File "/usr/lib/python3.6/site-packages/pylint/__init__.py", line 16, in run_pylint
    Run(sys.argv[1:])
  File "/usr/lib/python3.6/site-packages/pylint/lint.py", line 1312, in __init__
    linter.load_plugin_modules(plugins)
  File "/usr/lib/python3.6/site-packages/pylint/lint.py", line 495, in load_plugin_modules
    module.register(self)
  File "/usr/lib/python3.6/site-packages/pylint_django/plugin.py", line 18, in register
    name_checker = get_checker(linter, NameChecker)
  File "/usr/lib/python3.6/site-packages/pylint_plugin_utils/__init__.py", line 30, in get_checker
    raise NoSuchChecker(checker_class)
pylint_plugin_utils.NoSuchChecker: <class 'pylint.checkers.base.NameChecker'>

Investigation

I thought maybe this had to do with some incompatibilities between pylint, pylint-django, and possibly astroid, but strangely, the versions in this project were exactly the same as the other project. So I dug into the code where the exception was being reported.

get_checker is a part of pylint_plugin_utils, and it is used by pylint-django to augment the base PyLint checkers. It was trying to find pylint.checkers.base.NameChecker in the list of registered “checkers” for pylint. The file pylint/checkers/base.py" did exist in thesite-packagesfolder, but strangely, it was being registered assite_packages.pylint.checkers.base.NameChecker`.

Pylint has a register_plugins function that it uses to register all the default plugins. It does this by calling modutils.load_module_from_file on each of the files it finds starting from the checkers directory included with the library.

load_module_from_file figures out the proper import path using a function modpath_from_file, which in turn uses modpath_from_file_with_callback to check that each path it traverses has a __init__.py file. It looks at each path in sys.path in order to determine if the file attempting to be loaded has a valid import path.

What was throwing things off was the presence of __init__.py in the site-packages directory. /usr/lib/python3.6 was in sys.path before /usr/lib/python3.6/site-packages, and because there was a __init__.py file in site-packages, the import mechanism thought that site-packages was itself a module in the /usr/lib/python3.6 directory. It was therefore assigning this as the module name. Now where was that coming from?

Fix

It turns out it was a rogue package that stuck that empty file there (singletons). I removed that file (and thankfully had control over the project where that file originated and removed it from the source), and that fixed the issue.

Leave a Reply