Opinionated pylint checkers for code that says what it means.
A pylint plugin encoding the df12 house style: prefer match statements over
imperative type dispatch, make assertions explain themselves, and never silence
a diagnostic without saying why. It also ships ambrleaks, a scanner that
catches unredacted values hiding in syrupy snapshots.
Review feedback is cheapest when a machine gives it before a human has to:
- Structure over ceremony:
isinstanceladders and constant comparison chains becomematch/case, which states the accepted shapes and values directly. - Assertions that testify: a bare
assertfailure echoes an expression; an assert with a message names the violated expectation — invaluable when a property test shrinks to a counterexample. - No silent suppressions: every
noqa,pylint: disable, ortype: ignoremust record a reason the next reader can audit. - Snapshots kept honest: big inline expected values move into syrupy snapshots, and the snapshots themselves are swept for hex ids, emails, URLs, and absolute paths that should have been redacted.
uv add --dev df12-python-lintsLoad the plugin in pyproject.toml:
[tool.pylint.main]
load-plugins = ["df12_python_lints"]Then run pylint as usual:
pylint my_package testsA dispatch chain like this:
if isinstance(value, dict):
handle_mapping(value)
elif isinstance(value, list):
handle_sequence(value)is reported as:
R9101: Type dispatch on 'value' would be clearer as a match statement
(prefer-structural-pattern-matching)
To sweep syrupy snapshots, install the package as a tool and point ambrleaks
at your tests:
uv tool install df12-python-lints
ambrleaks testsThirteen pylint messages:
prefer-structural-pattern-matching(R9101) —isinstancedispatch on one subject should be amatchstatement with class patterns.assert-missing-message(C9102) — everyassertcarries a failure message naming the violated expectation.prefer-match-over-constant-chain(R9103) —if/elifchains comparing one subject with constants, enum members, or literals should be amatchstatement over an enumeration.trivial-attribute-wrapper(R9104) andtrivial-alias-wrapper(R9110) — functions with no logic beyond attribute access, a proxied call, or forwarding to another function add a name without adding behaviour.reexport-by-assignment(C9105) — re-export withfrom ... import ... as ...rather than assignment.lint-suppression-without-explanation(C9106) andtypecheck-suppression-without-explanation(C9107) — suppression pragmas must record a reason.prefer-snapshot-assertion(R9108) andprefer-snapshot-substring(R9109) — tests asserting against large inline literals or repeatedly probing substrings should use a syrupy snapshot.prefer-slots-for-dataclass(R9111) — closed standard-library dataclasses should request generated slots or declare an explicit slot layout.prefer-type-statement(R9112) — module-level type aliases should use the PEP 695typestatement on a 3.12+ baseline.redundant-future-annotations(C9112) —from __future__ import annotationsshould be removed on a 3.14+ baseline, where deferred evaluation is the default.
Both baseline-gated messages respect pylint's py-version option.
And one companion tool:
ambrleaks— scans syrupy.ambrsnapshot files for unredacted hex strings, UUIDs, emails, phone numbers, URLs, and absolute paths, with entropy gating, allowlists, and a baseline that survives snapshot regeneration.
- Users' Guide — every checker, every rule, and how to suppress findings without touching your snapshots
- Developers' Guide — contributing and development workflow
- Documentation contents — the full documentation set
ISC — see LICENSE for details.
Contributions welcome! Please see AGENTS.md for guidelines, and run
make all before proposing a change.