python-linters 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_linters/__init__.py +0 -0
- python_linters/__main__.py +11 -0
- python_linters/cli_commands.py +103 -0
- python_linters/config_check.py +413 -0
- python_linters/lint_report.py +273 -0
- python_linters/reference_files.py +25 -0
- python_linters/references/ruff.reference.toml +505 -0
- python_linters/references/ty.reference.toml +203 -0
- python_linters/tool_errors.py +44 -0
- python_linters-0.2.0.dist-info/METADATA +44 -0
- python_linters-0.2.0.dist-info/RECORD +13 -0
- python_linters-0.2.0.dist-info/WHEEL +4 -0
- python_linters-0.2.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Ruff Reference Configuration
|
|
3
|
+
# ============================================================================
|
|
4
|
+
#
|
|
5
|
+
# Canonical starting point for new Python projects. Copy this file as
|
|
6
|
+
# `ruff.toml` into your project root and adjust:
|
|
7
|
+
#
|
|
8
|
+
# 1. target-version — match your project's minimum Python version
|
|
9
|
+
# 2. known-first-party — list your project's own packages
|
|
10
|
+
# 3. exclude — add project-specific paths if needed
|
|
11
|
+
# 4. ignore — add project-specific ignores WITH justification
|
|
12
|
+
# 5. flake8-type-checking — list your runtime-evaluated base classes and
|
|
13
|
+
# decorators (pydantic, FastAPI preset)
|
|
14
|
+
#
|
|
15
|
+
# Last triaged against: ruff 0.17.3
|
|
16
|
+
# Rule names in `select`/`ignore` and in `# ruff: ignore[...]` comments need
|
|
17
|
+
# `preview = true`: without it ruff rejects the config and treats name-based
|
|
18
|
+
# suppression comments as inert.
|
|
19
|
+
# ============================================================================
|
|
20
|
+
|
|
21
|
+
# Older ruff fails on the first rule name it does not know, with a cryptic
|
|
22
|
+
# `Unknown rule selector`. This guard names the version instead.
|
|
23
|
+
required-version = ">=0.17.3"
|
|
24
|
+
|
|
25
|
+
target-version = "py312"
|
|
26
|
+
|
|
27
|
+
# Ruff/Black default: 88 ("~10% over 80 — the wise choice" per Black docs).
|
|
28
|
+
# Common alternatives:
|
|
29
|
+
# 79 — PEP 8 stdlib standard
|
|
30
|
+
# 88 — Black/Ruff default (this config)
|
|
31
|
+
# 100 — PEP 8 allows up to 99 for teams; popular with type-heavy codebases
|
|
32
|
+
# 119 — Django project standard (79 for docstrings)
|
|
33
|
+
# Pick one for the project and stick with it. 88 is the safe default;
|
|
34
|
+
# 100 is reasonable if long signatures or type annotations cause excessive wrapping.
|
|
35
|
+
line-length = 88
|
|
36
|
+
|
|
37
|
+
# ── Lint settings ───────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
[lint]
|
|
40
|
+
preview = true
|
|
41
|
+
# A preview rule runs only when `select` names it. A category select brings its
|
|
42
|
+
# stable rules alone, so a ruff bump cannot switch on an unreviewed preview rule.
|
|
43
|
+
explicit-preview-rules = true
|
|
44
|
+
# No `extend-safe-fixes`: an unsafe fix guesses intent and silences the rule, so the finding must be resolved by a decision.
|
|
45
|
+
|
|
46
|
+
# `RUF100` (unused-noqa) stays enabled: it is the only automated check that a
|
|
47
|
+
# `# ruff: ignore[<rule-name>]` cites the rule that actually fires there, and
|
|
48
|
+
# a suppression naming the wrong rule is inert — it silently suppresses
|
|
49
|
+
# nothing while reading as justified. Enable it and it also flags every
|
|
50
|
+
# suppression made redundant by a broader `ignore` / `per-file-ignores` entry.
|
|
51
|
+
#
|
|
52
|
+
# It does NOT inspect other tools' suppressions: `# type: ignore` (mypy),
|
|
53
|
+
# `# ty: ignore` (ty), and `# nosemgrep` use their own comment syntax. A
|
|
54
|
+
# `# noqa` carrying a code of a tool ruff does not implement (vulture's `V…`)
|
|
55
|
+
# trips RUF100 and noqa-comments (RUF105) — list that prefix here rather than
|
|
56
|
+
# ignoring either rule:
|
|
57
|
+
#
|
|
58
|
+
# external = ["V"] # keep vulture's `# noqa: V…` directives
|
|
59
|
+
|
|
60
|
+
# ── Rule categories ─────────────────────────────────────────────────────────
|
|
61
|
+
#
|
|
62
|
+
# Every ruff category must appear below — either enabled in `select` or
|
|
63
|
+
# commented out with `# excluded` and a reason. A rule without a code (ruff
|
|
64
|
+
# ships new rules without one since 0.16.5) has no prefix to inherit, so it
|
|
65
|
+
# appears by name under the same convention. This makes it easy to
|
|
66
|
+
# audit coverage and triage new categories when ruff is updated.
|
|
67
|
+
#
|
|
68
|
+
# Categories are grouped: core → style → security → complexity → framework.
|
|
69
|
+
|
|
70
|
+
select = [
|
|
71
|
+
# ── Core correctness ──
|
|
72
|
+
"F", # Pyflakes — undefined names, unused imports, unreachable code
|
|
73
|
+
"E", # pycodestyle errors — basic PEP 8 violations
|
|
74
|
+
"W", # pycodestyle warnings — whitespace, deprecation warnings
|
|
75
|
+
"B", # flake8-bugbear — common pitfalls (mutable defaults, assert misuse, …)
|
|
76
|
+
"PLE", # Pylint errors — actual bugs (bad super(), invalid __all__, …)
|
|
77
|
+
"UP", # pyupgrade — modernize syntax for target-version (f-strings, type unions, …)
|
|
78
|
+
|
|
79
|
+
# ── Imports ──
|
|
80
|
+
"I", # isort — consistent import ordering
|
|
81
|
+
"FA", # flake8-future-annotations — fires only for target-version <= py39
|
|
82
|
+
"ICN", # flake8-import-conventions — `import numpy as np` etc.
|
|
83
|
+
"TID", # flake8-tidy-imports — TID252 catches parent-relative `from ..x` by default; sibling `from .x` needs [lint.flake8-tidy-imports] ban-relative-imports = "all". Also: banned-api markers.
|
|
84
|
+
"TC", # flake8-type-checking — move typing-only imports behind TYPE_CHECKING
|
|
85
|
+
|
|
86
|
+
# ── Type annotations ──
|
|
87
|
+
"ANN", # flake8-annotations — enforce type annotations on public API
|
|
88
|
+
# Noisy on first adoption. Consider ignoring ANN201 temporarily,
|
|
89
|
+
# but aim to enable it — return types are the highest-value annotations.
|
|
90
|
+
|
|
91
|
+
# ── Naming ──
|
|
92
|
+
"N", # pep8-naming — PEP 8 naming conventions (classes, functions, constants)
|
|
93
|
+
# Watch out for N815 on Pydantic models with camelCase JSON fields.
|
|
94
|
+
|
|
95
|
+
# ── Security ──
|
|
96
|
+
"S", # flake8-bandit — security antipatterns (hardcoded passwords, exec, …)
|
|
97
|
+
"EXE", # flake8-executable — shebang correctness
|
|
98
|
+
|
|
99
|
+
# ── Error handling ──
|
|
100
|
+
"BLE", # flake8-blind-except — no bare `except:` or `except Exception:`
|
|
101
|
+
"EM", # flake8-errmsg — don't embed f-strings directly in exception constructors
|
|
102
|
+
"RSE", # flake8-raise — correct raise usage
|
|
103
|
+
"TRY", # tryceratops — try/except hygiene (too broad except, reraise properly, …)
|
|
104
|
+
|
|
105
|
+
# ── Code style & simplification ──
|
|
106
|
+
"SIM", # flake8-simplify — simplifiable if/else, context managers, boolean ops
|
|
107
|
+
"PIE", # flake8-pie — misc. simplifications (no-op pass, unnecessary spread, …)
|
|
108
|
+
"RET", # flake8-return — unnecessary else-after-return, implicit return values
|
|
109
|
+
"C4", # flake8-comprehensions — prefer list/dict/set comprehensions over map/filter
|
|
110
|
+
"FLY", # flynt — prefer f-strings over .format() and % formatting
|
|
111
|
+
"Q", # flake8-quotes — consistent quote style
|
|
112
|
+
"ISC", # flake8-implicit-str-concat — catch accidental string concatenation
|
|
113
|
+
"SLOT", # flake8-slots — suggest __slots__ on simple classes
|
|
114
|
+
|
|
115
|
+
# ── Unused code & arguments ──
|
|
116
|
+
"ARG", # flake8-unused-arguments — flag unused function/method arguments
|
|
117
|
+
|
|
118
|
+
# ── Pathlib & modernization ──
|
|
119
|
+
"PTH", # flake8-use-pathlib — prefer pathlib over os.path
|
|
120
|
+
"FURB", # refurb — more modernization suggestions
|
|
121
|
+
|
|
122
|
+
# ── Logging ──
|
|
123
|
+
"LOG", # flake8-logging — correct logging module usage
|
|
124
|
+
"G", # flake8-logging-format — logging format string issues
|
|
125
|
+
|
|
126
|
+
# ── Async ──
|
|
127
|
+
"ASYNC", # flake8-async — async pitfalls (blocking calls in async, …)
|
|
128
|
+
|
|
129
|
+
# ── Testing ──
|
|
130
|
+
"PT", # flake8-pytest-style — pytest best practices
|
|
131
|
+
"pytest-fixture-autouse", # code-less rule, no prefix selects it: an autouse
|
|
132
|
+
# fixture injects hidden setup into every test in scope
|
|
133
|
+
"shared-clock-mock", # code-less rule: patching `worker.time.sleep` patches the
|
|
134
|
+
# process-wide `time.sleep`, which other threads and tasks
|
|
135
|
+
# share. Inject a clock or patch `worker.time` instead
|
|
136
|
+
|
|
137
|
+
# ── Complexity ──
|
|
138
|
+
"unnecessary-regular-expression-compile", # code-less rule:
|
|
139
|
+
# `re.compile(p).match(s)` used once is `re.match(p, s)`
|
|
140
|
+
"C90", # mccabe — cyclomatic complexity threshold
|
|
141
|
+
"PLC", # Pylint convention — import placement, naming, …
|
|
142
|
+
"PLR", # Pylint refactor — too many args/branches/returns, magic values, …
|
|
143
|
+
"PLW", # Pylint warnings — global statements, unreachable code, …
|
|
144
|
+
"PERF", # Perflint — performance antipatterns
|
|
145
|
+
|
|
146
|
+
# ── Framework-specific ──
|
|
147
|
+
"FAST", # FastAPI — common FastAPI mistakes (missing Annotated, …)
|
|
148
|
+
"DTZ", # flake8-datetimez — require timezone-aware datetimes
|
|
149
|
+
"PYI", # flake8-pyi — type stub (.pyi) correctness
|
|
150
|
+
"INP", # flake8-no-pep420 — implicit namespace packages
|
|
151
|
+
"INT", # flake8-gettext — i18n correctness
|
|
152
|
+
|
|
153
|
+
# ── Self-access ──
|
|
154
|
+
"SLF", # flake8-self — flag access to private members from outside
|
|
155
|
+
|
|
156
|
+
# ── Builtins ──
|
|
157
|
+
"A", # flake8-builtins — don't shadow Python builtins
|
|
158
|
+
|
|
159
|
+
# ── Debugger ──
|
|
160
|
+
"T10", # flake8-debugger — no leftover breakpoint() / pdb calls
|
|
161
|
+
|
|
162
|
+
# ── Ruff-specific ──
|
|
163
|
+
"RUF", # Ruff-specific rules — parenthesized generators, ambiguous unicode, …
|
|
164
|
+
"PGH", # pygrep-hooks — blanket noqa, type: ignore without code
|
|
165
|
+
|
|
166
|
+
# ── Numeric / data libraries (harmless if unused — rules only fire on relevant code) ──
|
|
167
|
+
"NPY", # NumPy-specific rules — deprecated NumPy aliases
|
|
168
|
+
"PD", # pandas-vet — common pandas antipatterns
|
|
169
|
+
"YTT", # flake8-2020 — outdated sys.version checks
|
|
170
|
+
|
|
171
|
+
# ── Preview rules, selected by name ──────────────────────────────────
|
|
172
|
+
# `explicit-preview-rules = true`: a category select above turns on only its
|
|
173
|
+
# stable rules. Each preview rule below was triaged on ruff 0.17.3.
|
|
174
|
+
# A preview rule in a selected category that is neither listed
|
|
175
|
+
# here nor covered by an `# excluded` line below is untriaged.
|
|
176
|
+
|
|
177
|
+
# Ruff-specific (25)
|
|
178
|
+
# bugs: the code runs but does the wrong thing
|
|
179
|
+
"missing-f-string-syntax", # `"{x}"` without `f` prints the braces
|
|
180
|
+
"unraw-re-pattern", # `"\b"` in a non-raw pattern is a backspace, not a word boundary, and nothing else warns
|
|
181
|
+
"implicit-class-var-in-dataclass", # an unannotated field is a class var shared by all instances
|
|
182
|
+
"property-without-return", # the property silently returns None
|
|
183
|
+
"float-equality-comparison", # `==` on floats depends on rounding; tests ignore it
|
|
184
|
+
"f-string-percent-format", # `%` on an f-string mixes two interpolations
|
|
185
|
+
"incorrect-decorator-order", # decorators apply bottom-up, the wrong order breaks at runtime
|
|
186
|
+
"fallible-context-manager", # cleanup after an unguarded `yield` skips on exception
|
|
187
|
+
"method-receiver-default", # a default on `self`/`cls` applies only to `A.method()` calls, almost always a mistake
|
|
188
|
+
"unused-async", # forces callers into async and hides a missing await; tests ignore it
|
|
189
|
+
# dead code: removing it changes no behavior, mostly auto-fixable
|
|
190
|
+
"needless-else",
|
|
191
|
+
"unnecessary-if",
|
|
192
|
+
"useless-finally",
|
|
193
|
+
"unnecessary-assign-before-yield",
|
|
194
|
+
"falsy-dict-get-fallback",
|
|
195
|
+
"redundant-bool-literal",
|
|
196
|
+
"unnecessary-regular-expression", # a str method replaces the regex
|
|
197
|
+
"logging-eager-conversion", # `%s` already calls str(), and `repr(x)` with `%s` is `%r`
|
|
198
|
+
# readability
|
|
199
|
+
"used-dummy-variable", # an `_x` that is read lies about being unused
|
|
200
|
+
"non-empty-init-module", # code in `__init__.py` runs on import and gets overlooked
|
|
201
|
+
"incorrectly-parenthesized-tuple-in-subscript", # one tuple style in subscripts
|
|
202
|
+
"indented-form-feed", # the language reference leaves a form feed mid-indentation undefined
|
|
203
|
+
# rule names over codes: a name says what it checks, a code needs a lookup
|
|
204
|
+
"noqa-comments",
|
|
205
|
+
"rule-codes-in-suppression-comments",
|
|
206
|
+
"rule-codes-in-selectors",
|
|
207
|
+
|
|
208
|
+
# Pylint (18)
|
|
209
|
+
# bugs: runtime errors or silently lost behavior
|
|
210
|
+
"dict-iter-missing-items", # `for k, v in d` unpacks keys, not items
|
|
211
|
+
"modified-iterating-set", # RuntimeError
|
|
212
|
+
"redefined-slots-in-subclass", # makes the base class slot unreachable
|
|
213
|
+
"bad-dunder-method-name", # a misspelled dunder is never called
|
|
214
|
+
"unspecified-encoding", # `open()` without encoding uses the platform locale
|
|
215
|
+
# size and nesting limits, every threshold set under [lint.pylint]
|
|
216
|
+
"too-many-public-methods",
|
|
217
|
+
"too-many-locals",
|
|
218
|
+
"too-many-boolean-expressions",
|
|
219
|
+
"too-many-nested-blocks", # the nesting axis C901 cannot see
|
|
220
|
+
"too-many-statements-in-try-clause", # the except also catches failures from statements nobody expected to raise
|
|
221
|
+
# design: private names stay private (as SLF), functions over methods
|
|
222
|
+
"import-private-name", # `_name` is private to its module
|
|
223
|
+
"no-self-use", # a method without `self` is a function; tests ignore it
|
|
224
|
+
# idioms, mostly auto-fixable
|
|
225
|
+
"unnecessary-dunder-call",
|
|
226
|
+
"no-classmethod-decorator",
|
|
227
|
+
"no-staticmethod-decorator",
|
|
228
|
+
"swap-with-temporary-variable",
|
|
229
|
+
"non-augmented-assignment",
|
|
230
|
+
"literal-membership",
|
|
231
|
+
|
|
232
|
+
# flake8-bandit (4): each flags an insecure library or protocol at import,
|
|
233
|
+
# because no stable call-level rule covers these
|
|
234
|
+
"suspicious-xmlrpc-import", # XML attacks over the network
|
|
235
|
+
"suspicious-httpoxy-import", # CGI handlers, httpoxy
|
|
236
|
+
"suspicious-pycrypto-import", # pycrypto: unmaintained, known buffer overflow. Maintained
|
|
237
|
+
# pycryptodome shares the `Crypto` namespace and also fires:
|
|
238
|
+
# check the distribution, then suppress with a reason
|
|
239
|
+
"suspicious-pyghmi-import", # IPMI is unencrypted
|
|
240
|
+
|
|
241
|
+
# refurb (10): stdlib idioms that say the intent, mostly auto-fixable.
|
|
242
|
+
# Unlike the 3 excluded refurb rules, no fix here loses a type or breaks isinstance
|
|
243
|
+
"read-whole-file", # Path.read_text(), same direction as PTH
|
|
244
|
+
"write-whole-file", # Path.write_text(), same direction as PTH
|
|
245
|
+
"delete-full-slice",
|
|
246
|
+
"for-loop-set-mutations",
|
|
247
|
+
"slice-copy",
|
|
248
|
+
"unnecessary-enumerate",
|
|
249
|
+
"math-constant", # catches a truncated pi
|
|
250
|
+
"repeated-global",
|
|
251
|
+
"hardcoded-string-charset",
|
|
252
|
+
"meta-class-abc-meta",
|
|
253
|
+
|
|
254
|
+
# flake8-bugbear (4)
|
|
255
|
+
"return-in-generator", # a for loop drops the returned value
|
|
256
|
+
"loop-iterator-mutation", # mutating the iterable skips elements or loops forever
|
|
257
|
+
"class-as-data-structure", # an `__init__`-only class is a dataclass
|
|
258
|
+
"del-attr-with-constant", # `delattr(o, "x")` is `del o.x`, auto-fixable
|
|
259
|
+
|
|
260
|
+
# pyupgrade (3): legacy spellings, mostly auto-fixable
|
|
261
|
+
"while-one", # Python 2 idiom
|
|
262
|
+
"deprecated-abc-decorator", # deprecated since 3.3
|
|
263
|
+
"context-manager-iterator", # @contextmanager needs Generator, Iterator lacks `throw`
|
|
264
|
+
|
|
265
|
+
# flake8-async (1): a context manager's cleanup is delayed until the
|
|
266
|
+
# generator closes, when `await` is no longer allowed
|
|
267
|
+
"yield-in-context-manager-in-async-generator",
|
|
268
|
+
|
|
269
|
+
# flake8-pytest-style (1): `pytest.warns()` without a warning fails at runtime
|
|
270
|
+
"pytest-warns-without-warning",
|
|
271
|
+
|
|
272
|
+
# flake8-type-checking (1): quotes in an explicit type alias are needless. A quoted
|
|
273
|
+
# `TypeAlias` is a str at runtime, so `Alias | None` raises TypeError
|
|
274
|
+
"quoted-type-alias",
|
|
275
|
+
|
|
276
|
+
# ── Excluded categories ──────────────────────────────────────────────
|
|
277
|
+
# Each must have a reason.
|
|
278
|
+
|
|
279
|
+
# excluded "AIR", # Airflow — not relevant outside Airflow projects
|
|
280
|
+
# excluded "COM", # flake8-commas — conflicts with `ruff format` (COM812)
|
|
281
|
+
# excluded "CPY", # flake8-copyright — copyright headers not enforced
|
|
282
|
+
# excluded "D", # pydocstyle — docstrings rarely improve well-named code;
|
|
283
|
+
# enforce selectively via per-file if needed
|
|
284
|
+
# excluded "DJ", # flake8-django — only for Django projects
|
|
285
|
+
# excluded "DOC", # pydoclint — docstring param/return validation; enable
|
|
286
|
+
# once D is adopted
|
|
287
|
+
# excluded "ERA", # eradicate — commented-out code is sometimes intentional
|
|
288
|
+
# (bookmarks, alternative approaches being tested)
|
|
289
|
+
# excluded "FBT", # flake8-boolean-trap — philosophically sound but hard to
|
|
290
|
+
# retrofit; `def foo(*, verbose: bool)` is fine
|
|
291
|
+
# excluded "FIX", # flake8-fixme — TODOs/FIXMEs in code are legitimate
|
|
292
|
+
# bookmarks during development
|
|
293
|
+
# excluded "T20", # flake8-print — print() is fine in scripts, CLIs, and
|
|
294
|
+
# debugging; logging.info isn't always better
|
|
295
|
+
# excluded "TD", # flake8-todos — enforces TODO format (author, link);
|
|
296
|
+
# too bureaucratic for most projects
|
|
297
|
+
|
|
298
|
+
# ── Excluded preview rules ───────────────────────────────────────────
|
|
299
|
+
# Preview rules stay off unless selected by name. Each prefix needs a reason.
|
|
300
|
+
|
|
301
|
+
# excluded "E11", "E2", "E3", "E502", "W391", # pycodestyle preview (42): `ruff format` settles all but E262/E265/E266, which only police comment prefixes (`#:` Sphinx, `##` banners), 0 findings outside generated `*_pb2.py`, E111/E114/E117 conflict with it
|
|
302
|
+
# excluded "S401", "S402", "S403", "S404", "S405", "S406", "S407", "S408", "S409", # bandit import bans (9): importing is not the risk, stable S3xx/S60x rules flag the risky calls
|
|
303
|
+
# excluded "FURB113", "FURB118", "FURB189", # refurb (3): fix erases types (itemgetter -> Any), reads worse (extend), or breaks isinstance (UserList/UserDict)
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
# ── Ignored rules ───────────────────────────────────────────────────────────
|
|
307
|
+
#
|
|
308
|
+
# Every ignore must have a justification. Group by theme.
|
|
309
|
+
# Rules listed here are GLOBALLY disabled. Prefer per-file-ignores for
|
|
310
|
+
# rules that only need suppression in specific contexts (tests, scripts).
|
|
311
|
+
|
|
312
|
+
ignore = [
|
|
313
|
+
# ── Line length ──
|
|
314
|
+
"line-too-long", # the formatter handles wrappable lines; remaining
|
|
315
|
+
# violations are long strings/URLs that would be worse
|
|
316
|
+
# if broken up
|
|
317
|
+
|
|
318
|
+
# ── Type annotations ──
|
|
319
|
+
# We enforce annotations broadly but exempt specific patterns that
|
|
320
|
+
# add noise without value.
|
|
321
|
+
# ANN401 (typing.Any disallowed) stays ON: with basedpyright dropped
|
|
322
|
+
# in favor of ty, ANN401 is the gate's only coverage for explicit
|
|
323
|
+
# Any (basedpyright's reportExplicitAny): ty has no equivalent rule.
|
|
324
|
+
# missing return type on __init__, __enter__, __exit__ — always obvious
|
|
325
|
+
# from context
|
|
326
|
+
"missing-return-type-special-method",
|
|
327
|
+
"missing-type-args", # missing type on *args — typically pass-through,
|
|
328
|
+
# type depends on caller
|
|
329
|
+
"missing-type-kwargs", # missing type on **kwargs — same reasoning as
|
|
330
|
+
# missing-type-args
|
|
331
|
+
|
|
332
|
+
# ── Naming ──
|
|
333
|
+
# prevents CONSTANT_IN_FUNCTION convention, which is useful for thresholds
|
|
334
|
+
# and config values inside functions
|
|
335
|
+
"non-lowercase-variable-in-function",
|
|
336
|
+
"invalid-function-name", # test names like `test_ASRDecoder` embed class
|
|
337
|
+
# names for grep-ability
|
|
338
|
+
"invalid-argument-name", # abbreviations like max_SNR are more readable
|
|
339
|
+
# than max_snr
|
|
340
|
+
|
|
341
|
+
# ── Code style ──
|
|
342
|
+
"lambda-assignment", # `transform = lambda x: x.strip()` is sometimes
|
|
343
|
+
# clearer than a named function for one-off local
|
|
344
|
+
# transforms
|
|
345
|
+
"in-dict-keys", # use `key in dict` — `key in dict.keys()` is more
|
|
346
|
+
# explicit about intent; Python Zen says explicit >
|
|
347
|
+
# implicit
|
|
348
|
+
"superfluous-else-return", # the else branch makes the two paths visually
|
|
349
|
+
# symmetric and easier to read
|
|
350
|
+
"superfluous-else-raise", # same reasoning as superfluous-else-return
|
|
351
|
+
# super() issues with dataclass(slots=True), see
|
|
352
|
+
# https://stackoverflow.com/questions/73268995
|
|
353
|
+
"super-call-with-parameters",
|
|
354
|
+
"compare-to-empty-string", # `s == ""` is more explicit than `not s` (the
|
|
355
|
+
# latter also matches None, 0, [])
|
|
356
|
+
|
|
357
|
+
# ── Security ──
|
|
358
|
+
"hardcoded-bind-all-interfaces", # hardcoded 0.0.0.0 binding —
|
|
359
|
+
# intentional for dev servers
|
|
360
|
+
# non-cryptographic random — not relevant when random is used for jitter,
|
|
361
|
+
# sampling, or test data (not tokens/keys)
|
|
362
|
+
"suspicious-non-cryptographic-random-usage",
|
|
363
|
+
|
|
364
|
+
# ── Logging ──
|
|
365
|
+
"logging-f-string", # f-string in logging — f-strings in log calls are
|
|
366
|
+
# fine; the lazy-% argument only matters at high log
|
|
367
|
+
# volume where the string wouldn't be emitted anyway
|
|
368
|
+
|
|
369
|
+
# ── Performance ──
|
|
370
|
+
"reimplemented-starmap", # starmap slower than comprehension — confirmed
|
|
371
|
+
# regression in Python 3.12, comprehensions are
|
|
372
|
+
# faster
|
|
373
|
+
|
|
374
|
+
# ── Complexity / false positives ──
|
|
375
|
+
"open-file-with-context-handler", # false positives with lambdas and
|
|
376
|
+
# callback-based APIs
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
# ── Per-file ignores ────────────────────────────────────────────────────────
|
|
380
|
+
#
|
|
381
|
+
# Rules that should only be suppressed in specific file patterns.
|
|
382
|
+
# Prefer this over global ignore when the rule IS useful in production code.
|
|
383
|
+
|
|
384
|
+
[lint.per-file-ignores]
|
|
385
|
+
"**/tests/**/*.py" = [ # any depth: monorepos nest tests per package
|
|
386
|
+
"assert", # pytest's testing idiom
|
|
387
|
+
"hardcoded-temp-file", # hardcoded /tmp — fine in tests
|
|
388
|
+
"magic-value-comparison", # `assert len(x) == 3` is clear
|
|
389
|
+
"no-self-use", # pytest classes group tests, methods don't need self
|
|
390
|
+
"too-many-public-methods", # a pytest class groups tests and has no
|
|
391
|
+
# callers, so its method count is not an
|
|
392
|
+
# interface
|
|
393
|
+
# missing return type — test functions always return None
|
|
394
|
+
"missing-return-type-undocumented-public-function",
|
|
395
|
+
"import-private-name", # testing internal functions is legitimate
|
|
396
|
+
"unused-async", # async test doubles mirror the awaited signature they replace
|
|
397
|
+
"float-equality-comparison", # asserting an exactly representable result is correct
|
|
398
|
+
"too-many-locals", # an integration test sets up many real collaborators
|
|
399
|
+
]
|
|
400
|
+
"test_*.py" = [
|
|
401
|
+
"assert",
|
|
402
|
+
"hardcoded-temp-file",
|
|
403
|
+
"magic-value-comparison",
|
|
404
|
+
"no-self-use",
|
|
405
|
+
"too-many-public-methods",
|
|
406
|
+
"missing-return-type-undocumented-public-function",
|
|
407
|
+
"implicit-namespace-package", # standalone test scripts
|
|
408
|
+
"import-private-name",
|
|
409
|
+
"unused-async",
|
|
410
|
+
"float-equality-comparison",
|
|
411
|
+
"too-many-locals",
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
# ── isort ───────────────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
[lint.isort]
|
|
417
|
+
known-first-party = [] # ← EDIT: add your project's packages
|
|
418
|
+
|
|
419
|
+
# ── Type checking imports ───────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
[lint.flake8-type-checking]
|
|
422
|
+
# TC001-003 move a typing-only import behind TYPE_CHECKING. Classes and
|
|
423
|
+
# functions whose annotations a library reads at runtime are exempt only if
|
|
424
|
+
# listed here. Both lists default to empty.
|
|
425
|
+
# ← EDIT: add your own BaseModel subclasses and runtime-evaluating decorators.
|
|
426
|
+
runtime-evaluated-base-classes = [
|
|
427
|
+
"pydantic.BaseModel",
|
|
428
|
+
"pydantic_settings.BaseSettings",
|
|
429
|
+
]
|
|
430
|
+
# ruff resolves `@app.get` / `@router.get` on instances in the same module.
|
|
431
|
+
runtime-evaluated-decorators = [
|
|
432
|
+
"fastapi.FastAPI.get", "fastapi.FastAPI.post", "fastapi.FastAPI.put",
|
|
433
|
+
"fastapi.FastAPI.patch", "fastapi.FastAPI.delete",
|
|
434
|
+
"fastapi.FastAPI.websocket",
|
|
435
|
+
"fastapi.APIRouter.get", "fastapi.APIRouter.post", "fastapi.APIRouter.put",
|
|
436
|
+
"fastapi.APIRouter.patch", "fastapi.APIRouter.delete",
|
|
437
|
+
"fastapi.APIRouter.websocket",
|
|
438
|
+
]
|
|
439
|
+
# Blind spots: ruff sees declarations, never the call site that reads
|
|
440
|
+
# annotations. With deferred annotations (py314+ or `from __future__ import
|
|
441
|
+
# annotations`), it still flags a parameter type of a `Depends(dep)` function
|
|
442
|
+
# and a field type of a dataclass passed to `TypeAdapter`. The fix is unsafe,
|
|
443
|
+
# so check each move for these before applying it.
|
|
444
|
+
|
|
445
|
+
# ── Complexity thresholds ───────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
[lint.mccabe]
|
|
448
|
+
# Cyclomatic complexity. Radon: A ≤ 5, B ≤ 10. Ruff/flake8 default: 10.
|
|
449
|
+
# Counts branches, and is *blind to nesting*: a 4-deep nest of `if`s and the
|
|
450
|
+
# flat guard-clause rewrite of the same logic both measure 5, so C901 cannot
|
|
451
|
+
# distinguish them at any threshold. Nesting depth is a separate axis —
|
|
452
|
+
# see `max-nested-blocks` below (PLR1702), the complement to this rule.
|
|
453
|
+
max-complexity = 10
|
|
454
|
+
|
|
455
|
+
[lint.pylint]
|
|
456
|
+
# pydantic hooks, not typos: bad-dunder-method-name (PLW3201) flags them otherwise.
|
|
457
|
+
allow-dunder-method-names = ["__get_pydantic_core_schema__", "__get_pydantic_json_schema__"]
|
|
458
|
+
# max-args: default 5 — raise only if your domain genuinely needs it
|
|
459
|
+
max-args = 5
|
|
460
|
+
# max-branches: default 12
|
|
461
|
+
max-branches = 12
|
|
462
|
+
# max-returns: ruff/pylint default 6, wemake default 5.
|
|
463
|
+
# Keep at default 6, but strive for fewer — each return is an exit path
|
|
464
|
+
# the reader must track. Prefer early-return guard clauses over deep nesting,
|
|
465
|
+
# but consolidate where possible.
|
|
466
|
+
max-returns = 6
|
|
467
|
+
# max-statements: ruff default 50 — rarely hit because C901 triggers first
|
|
468
|
+
max-statements = 50
|
|
469
|
+
# max-nested-blocks (PLR1702, preview — selected by name in `select`).
|
|
470
|
+
# Pylint default 5 is lax enough to be effectively dead; 3 is the enforced cap.
|
|
471
|
+
# This is the nesting axis C901 cannot see (see the note under [lint.mccabe]).
|
|
472
|
+
# Counts *every* block type — `if`/`for`/`while`/`with`/`try` — so a plain
|
|
473
|
+
# `for` + `with` + `try` + `if` I/O function already measures 4 and must be
|
|
474
|
+
# extracted. That is the intended pressure: the cure is a named helper (or
|
|
475
|
+
# guard clauses), not a raised threshold.
|
|
476
|
+
# Flattening only fixes the *shape*. When the levels correspond to genuinely
|
|
477
|
+
# different input worlds, the depth was un-modeled state — model it upstream.
|
|
478
|
+
max-nested-blocks = 3
|
|
479
|
+
# max-public-methods (PLR0904, preview): ruff default 20, enforced cap 10.
|
|
480
|
+
# A class is a smaller unit than a file, so its interface gets no more room
|
|
481
|
+
# than a file's: 10 public names.
|
|
482
|
+
# A ceiling, not a target: below 10, a narrower interface is still better.
|
|
483
|
+
# No decorator exception: a pydantic Settings class with many validators can
|
|
484
|
+
# trip it — `# ruff: ignore[too-many-public-methods]` with a reason there, not a raised cap.
|
|
485
|
+
max-public-methods = 10
|
|
486
|
+
# max-locals (PLR0914, preview): ruff default 15. Tests are exempt: an
|
|
487
|
+
# integration test sets up many real collaborators.
|
|
488
|
+
max-locals = 15
|
|
489
|
+
# max-bool-expr (PLR0916, preview): ruff default 5. Past 5 terms, name the
|
|
490
|
+
# condition or its parts as variables.
|
|
491
|
+
max-bool-expr = 5
|
|
492
|
+
# max-statements-in-try (PLW0717, preview): ruff default 5. A `try` without
|
|
493
|
+
# `except` is not counted, so cleanup in `finally` stays free.
|
|
494
|
+
max-statements-in-try = 5
|
|
495
|
+
|
|
496
|
+
# ── Format settings ─────────────────────────────────────────────────────────
|
|
497
|
+
|
|
498
|
+
[format]
|
|
499
|
+
# Format code examples in docstrings (`>>>` doctests, Markdown and reST code
|
|
500
|
+
# blocks) like the code around them, so the examples cannot drift from the
|
|
501
|
+
# project's style. Ruff skips an example that is not valid Python.
|
|
502
|
+
# Cost: a hand-aligned layout inside an example, such as a table of `case`
|
|
503
|
+
# arms with lined-up comments, gets reflowed. Write that example as a plain
|
|
504
|
+
# text block instead.
|
|
505
|
+
docstring-code-format = true
|