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 the
site-packagesfolder, but strangely, it was being registered as
site_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.