Skip to content

Commit 49c94d1

Browse files
ccie18643claude
andcommitted
build(lint): make pyproject.toml the single source of truth for pylint
There were two divergent pylint configs: the `make lint` gate ran `pylint --disable=all --enable=<7 checks>` (an allowlist, in the Makefile via PYLINT_GATE_CHECKS), while `[tool.pylint.master]` in pyproject carried a ~30-entry *denylist* that only an ad-hoc / IDE `pylint <file>` saw. They expressed the same intent ("enforce only a few pylint checks") in opposite directions, so a bare/IDE run actually fired a different, larger set than the gate — the opposite of IDE/gate parity — and the master block needed a warning comment not to disable a check the gate enables. Collapse to one config in pyproject.toml: - `[tool.pylint.messages_control]` now holds `disable = ["all"]` + the same 7-check `enable` allowlist the gate used. Every invocation — `make lint`, ad-hoc `pylint <file>`, IDE/Pylance — reads it and behaves identically. - `[tool.pylint.main] ignore-paths = [".*/tests/.*"]` moves the test exemption (tests reach across the private boundary by design, source_files.md §5.2) into config, so even a directly-opened test file self-exempts in the IDE. - The Makefile no longer defines the check set or a non-test file list. PYLINT_GATE_CHECKS and PYLINT_GATE_FILES are removed; the gate runs `pylint --score=n --recursive=y <3 package roots>` and lets config drive both which checks fire and which paths are skipped. - Dropped the now-dead `max-line-length` (line-too-long isn't enabled under the allowlist; black enforces 120). Verified: gate clean; ad-hoc `pylint <file>` runs only the 7 checks; a mutable-default probe still fires `dangerous-default-value`; a test file self-exempts from `protected-access`. make lint clean. Tooling-only change — no runtime source touched, so the test suite is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d39db78 commit 49c94d1

2 files changed

Lines changed: 54 additions & 64 deletions

File tree

Makefile

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,13 @@ LINT_FILES := $(PYTCP_FILES) $(NET_ADDR_FILES) $(NET_PROTO_FILES) $(EXAMPLES_FIL
1919
# the 'venv' target), so its name is decoupled from its path here.
2020
MYPY_PACKAGES := pytcp net_addr net_proto examples_legacy
2121

22-
# The pylint gate runs a small set of checks NOT covered by flake8 / mypy
23-
# over the three packages' NON-TEST source (tests are exempt — they reach
24-
# across the private boundary by design, source_files.md §5.2):
25-
# protected-access no cross-class '_private' reach-through (§5.2);
26-
# split-class subpackages (protocols/tcp/fsm,
27-
# protocols/tcp/session, runtime/packet_handler)
28-
# carry their own file-level disable, honored here
29-
# raise-missing-from mandatory exception chaining 'raise X from err'
30-
# (net_proto.md §9.2, python_features.md §10a)
31-
# dangerous-default-value no mutable default args ('= []' / '= {}')
32-
# cell-var-from-loop closure-over-loop-variable bug
33-
# useless-super-delegation / super-with-arguments redundant super() forms
34-
# unused-import genuinely-unused imports AND — uniquely — a
35-
# runtime import used only inside a STRING
36-
# annotation, i.e. an unnecessary quote that
37-
# should be unquoted (typing.md §20). flake8's
38-
# F401 reads inside string annotations so it
39-
# misses this; pylint W0611 does not. It does
40-
# NOT fire on TYPE_CHECKING imports used in
41-
# string annotations (the legit circular-import
42-
# case) or on '__all__' re-exports.
43-
# Everything else stays off — full pylint is not part of the gate.
44-
PYLINT_GATE_CHECKS := protected-access,raise-missing-from,dangerous-default-value,cell-var-from-loop,useless-super-delegation,super-with-arguments,unused-import
45-
PYLINT_GATE_FILES := $(shell find $(PYTCP_PATH) $(NET_ADDR_PATH) $(NET_PROTO_PATH) -name '*.py' -not -path '*/tests/*')
22+
# The entire pylint configuration — which checks fire AND which paths
23+
# are exempt — lives in pyproject.toml ([tool.pylint.*]: 'disable = all'
24+
# + a small 'enable' allowlist, and 'ignore-paths' for tests). It is the
25+
# single source of truth shared with ad-hoc / IDE pylint runs. The gate
26+
# below just hands pylint the three package roots and lets config decide
27+
# the rest ('--recursive=y' so pylint walks the tree; tests self-exempt
28+
# via 'ignore-paths').
4629

4730
# If any recipe fails, delete its target file. Without this a
4831
# failed (or interrupted) 'venv' build leaves a half-populated
@@ -128,7 +111,7 @@ lint: venv
128111
@for pkg in $(MYPY_PACKAGES); do PYTHONPATH=$(ROOT_PATH) ./$(VENV)/bin/mypy -p $$pkg || exit 1; done
129112
@PYTHONPATH=$(ROOT_PATH) ./$(VENV)/bin/mypy $(ROOT_FILES)
130113
@echo '<<< PYLINT'
131-
@PYTHONPATH=$(ROOT_PATH) ./$(VENV)/bin/pylint --disable=all --enable=$(PYLINT_GATE_CHECKS) --score=n $(PYLINT_GATE_FILES)
114+
@PYTHONPATH=$(ROOT_PATH) ./$(VENV)/bin/pylint --score=n --recursive=y $(PYTCP_PATH) $(NET_ADDR_PATH) $(NET_PROTO_PATH)
132115
@echo '<<< PYRIGHT'
133116
@PYTHONPATH=$(ROOT_PATH) ./$(VENV)/bin/pyright
134117

pyproject.toml

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -102,42 +102,49 @@ reportCallIssue = "none" # test-side; mypy-covered
102102
reportAbstractUsage = "none" # tests deliberately instantiate abstract classes (with `# type: ignore[abstract]`)
103103
reportPrivateImportUsage = "none" # net_proto re-export shape pyright disagrees with; mypy's no_implicit_reexport is authority
104104

105-
# This block ONLY affects an ad-hoc 'pylint <file>' run (a developer /
106-
# IDE invoking pylint directly); it silences the noisy checks so such a
107-
# run is usable. It does NOT affect 'make lint' — that gate runs
108-
# 'pylint --disable=all --enable=<explicit checks>' (see the Makefile
109-
# 'PYLINT_GATE_CHECKS'), whose CLI '--enable' overrides everything here.
110-
# Do not disable a check the gate enables (e.g. 'dangerous-default-value'),
111-
# or a local run would miss what the gate then rejects.
112-
[tool.pylint.master]
113-
max-line-length = 120
114-
disable = """
115-
super-init-not-called,
116-
no-name-in-module,
117-
import-error,
118-
too-many-instance-attributes,
119-
too-many-public-methods,
120-
too-many-arguments,
121-
too-many-locals,
122-
too-many-branches,
123-
too-many-lines,
124-
too-many-boolean-expressions,
125-
too-many-positional-arguments,
126-
too-many-statements,
127-
too-many-return-statements,
128-
too-few-public-methods,
129-
import-outside-toplevel,
130-
fixme,
131-
multiple-statements,
132-
invalid-name,
133-
redefined-builtin,
134-
superfluous-parens,
135-
expression-not-assigned,
136-
no-member,
137-
too-many-ancestors,
138-
redefined-outer-name,
139-
global-statement,
140-
wrong-import-order,
141-
consider-using-with,
142-
abstract-class-instantiated
143-
"""
105+
# Single source of truth for pylint — every invocation reads this:
106+
# 'make lint', an ad-hoc 'pylint <file>', and the IDE/Pylance pylint
107+
# integration all behave identically. The Makefile gate runs 'pylint'
108+
# with NO check flags, so this config alone decides what fires.
109+
#
110+
# The gate is a small allowlist of checks NOT already covered by
111+
# flake8 / mypy / black; 'disable = all' turns off everything else
112+
# (pylint's many noisy / opinionated checks the project does not
113+
# enforce). Enabled checks:
114+
# protected-access no cross-class '_private' reach-through
115+
# (source_files.md §5.2); split-class
116+
# subpackages (protocols/tcp/fsm,
117+
# protocols/tcp/session, runtime/packet_handler)
118+
# carry their own file-level disable, honored here
119+
# raise-missing-from mandatory exception chaining 'raise X from err'
120+
# (net_proto.md §9.2, python_features.md §10a)
121+
# dangerous-default-value no mutable default args ('= []' / '= {}')
122+
# cell-var-from-loop closure-over-loop-variable bug
123+
# useless-super-delegation / super-with-arguments redundant super() forms
124+
# unused-import genuinely-unused imports AND — uniquely — a
125+
# runtime import used only inside a STRING
126+
# annotation (an unnecessary quote that should
127+
# be unquoted, typing.md §20); flake8's F401
128+
# reads inside string annotations and misses
129+
# this, pylint W0611 does not. Does NOT fire on
130+
# TYPE_CHECKING imports used in string
131+
# annotations (the legit circular-import case)
132+
# or on '__all__' re-exports.
133+
#
134+
# Tests are exempt — they reach across the private boundary by design
135+
# (source_files.md §5.2) — via 'ignore-paths', so even a direct
136+
# 'pylint <test_file>' / IDE-opened test file self-exempts.
137+
[tool.pylint.main]
138+
ignore-paths = [".*/tests/.*"]
139+
140+
[tool.pylint.messages_control]
141+
disable = ["all"]
142+
enable = [
143+
"protected-access",
144+
"raise-missing-from",
145+
"dangerous-default-value",
146+
"cell-var-from-loop",
147+
"useless-super-delegation",
148+
"super-with-arguments",
149+
"unused-import",
150+
]

0 commit comments

Comments
 (0)