pytest-pylint
Run pylint checks as pytest test items, failing your build on configurable pylint rule severities.
Repository Health
Technical Analysis
pytest-pylint is a pytest plugin that turns pylint into a first-class part of your test suite. Enable it with the —pylint flag and every collected Python file becomes its own pytest item, so lint failures show up in your test report exactly like any other pytest failure. Results are cached by file modification time via pytest’s cache plugin, so unchanged files are skipped on subsequent runs to keep repeated invocations fast.
You control severity with —pylint-error-types to decide which pylint message categories (Convention, Refactor, Warning, Error, Fatal) actually fail the build, and can point —pylint-rcfile at a specific configuration file, including pyproject.toml-based configuration discovered automatically via tomli/tomllib. Ignore lists and ignore-patterns from pylintrc are respected, results can be restricted to a pylint marker with -m pylint, and output can be written to a report file with —pylint-output-file.
What You Get
- Automatic pylint execution across your test target, enabled with a single —pylint flag
- Per-file pytest items so lint failures appear in normal pytest output and exit codes
- Configurable failure severity via —pylint-error-types (Convention/Refactor/Warning/Error/Fatal)
- mtime-based result caching that skips re-linting files that already passed
- Automatic discovery of pylintrc and pyproject.toml-based pylint configuration
Common Use Cases
- Enforcing lint-clean code as part of the normal CI pytest run
- Restricting failures to only Error/Fatal levels via —pylint-error-types=EF during incremental adoption
- Running only pylint checks in isolation with -m pylint, separate from the rest of the test suite
- Writing pylint results to a dedicated report file for CI artifact collection via —pylint-output-file
Under The Hood
Architecture
pytest-pylint is a thin pytest plugin registered through the pytest11 setuptools entry point (pytest_pylint.plugin). pytest_addoption/pytest_configure register a PylintPlugin only when --pylint is passed and --no-pylint is not, keeping the plugin fully inert otherwise. The plugin hooks pytest_collect_file to wrap every .py file in a PylintFile, which in turn yields a single PyLintItem leaf node (pytest_pylint/plugin.py); pytest_collection_finish then runs pylint once, in a single batched lint.Run call, over every collected file using a custom ProgrammaticReporter (pytest_pylint/pylint_util.py) that stores structured message objects instead of printing them. Each PyLintItem.runtest looks up its file’s messages from that shared dictionary and raises a PyLintException if any message’s category is in --pylint-error-types, keeping the per-file test items lightweight consumers of one shared lint pass rather than re-invoking pylint per file. What breaks if this changes: the one-batched-run-then-fan-out model is central to performance — running pylint once per file instead would multiply process startup cost across a large repo.
Tech Stack
The project is a small, dependency-light Python package built with plain setuptools (setup.py, setup.cfg), no build backend abstraction. Runtime dependencies are pytest>=7.0, pylint>=2.15.0, and tomli>=1.1.0 as a tomllib backport for Python <3.11 (pytest_pylint/plugin.py branches on sys.version_info). It targets Python 3.8-3.12 per setup.py classifiers, tests across that matrix via tox.ini and GitHub Actions (.github/workflows/tests.yml), and has no runtime dependencies beyond the pytest/pylint ecosystem itself — no database, no network calls, no external services.
Code Quality
Testing relies on pytest’s own pytester fixture (pytest_plugins = ("pytester",)) to spawn isolated sub-pytest runs and assert on their captured stdout — a pattern well suited to testing a pytest plugin, exercised across two files (test_pytest_pylint.py, test_util.py) covering flag behavior, caching, ignore patterns, rcfile/pyproject.toml discovery, and multi-process (-j) runs. CI (.github/workflows/tests.yml) runs the full tox matrix plus a tox -e qa linting pass and a coverage job, and the source itself is written with pylint: disable= comments showing the maintainers dogfood their own tool. There is no static type checking (no mypy, no type hints in the core modules) and error handling is narrow — mostly try/except (NoSectionError, NoOptionError) around config parsing — but exceptions are explicit and named (PyLintException) rather than swallowed.
What Makes It Unique
Rather than shelling out to pylint per file or wrapping its CLI output, pytest-pylint integrates at the pytest collection-hook level so a single pylint invocation’s structured message objects get distributed across many individually reportable, individually cacheable pytest test items — giving you pylint failures with pytest’s native -k/-m selection, parallelization, and reporting for free. The mtime-based skip-if-unchanged caching (keyed through pytest’s own cache plugin) is a deliberate performance choice absent from most one-shot lint runners, making it practical to run lint checks on every local test invocation rather than only in CI.