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>
File "/usr/lib/python3.6/site-packages/pylint/__init__.py", line 16, in run_pylint
File "/usr/lib/python3.6/site-packages/pylint/lint.py", line 1312, in __init__
File "/usr/lib/python3.6/site-packages/pylint/lint.py", line 495, in load_plugin_modules
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
pylint_plugin_utils.NoSuchChecker: <class 'pylint.checkers.base.NameChecker'>
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-packages
folder, 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
/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?
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.