python-linters 0.2.0__tar.gz → 0.2.2__tar.gz

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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-linters
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Run ruff, ty and pymodularity on a Python project and print one JSON report grouped by rule.
5
5
  Author: Tilo Himmelsbach
6
6
  License-Expression: MIT
7
- Requires-Dist: ruff==0.17.3
8
- Requires-Dist: ty==0.0.90
7
+ Requires-Dist: ruff==0.17.5
8
+ Requires-Dist: ty==0.0.103
9
9
  Requires-Dist: pymodularity==0.5.0
10
10
  Requires-Python: >=3.11
11
11
  Description-Content-Type: text/markdown
@@ -25,9 +25,9 @@ uv add --dev python-linters
25
25
 
26
26
  ## Commands
27
27
 
28
- - `uv run python-linters lint [path] [--out FILE]` — runs ruff, ty and
29
- pymodularity (default path: `.`) and prints the merged JSON report to
30
- stdout, or writes it to `FILE` with `--out`.
28
+ - `uv run python-linters lint [path] [--out FILE]` — runs ruff, `ruff
29
+ format --check`, ty and pymodularity (default path: `.`) and prints the
30
+ merged JSON report to stdout, or writes it to `FILE` with `--out`.
31
31
  - `uv run python-linters reference ruff|ty` — prints this package's shipped
32
32
  reference config for the given tool to stdout. The package never writes
33
33
  it into your project — copying it into `ruff.toml`/`ty.toml` (or
@@ -13,9 +13,9 @@ uv add --dev python-linters
13
13
 
14
14
  ## Commands
15
15
 
16
- - `uv run python-linters lint [path] [--out FILE]` — runs ruff, ty and
17
- pymodularity (default path: `.`) and prints the merged JSON report to
18
- stdout, or writes it to `FILE` with `--out`.
16
+ - `uv run python-linters lint [path] [--out FILE]` — runs ruff, `ruff
17
+ format --check`, ty and pymodularity (default path: `.`) and prints the
18
+ merged JSON report to stdout, or writes it to `FILE` with `--out`.
19
19
  - `uv run python-linters reference ruff|ty` — prints this package's shipped
20
20
  reference config for the given tool to stdout. The package never writes
21
21
  it into your project — copying it into `ruff.toml`/`ty.toml` (or
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "python-linters"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Run ruff, ty and pymodularity on a Python project and print one JSON report grouped by rule."
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  requires-python = ">=3.11"
8
8
  dependencies = [
9
- "ruff==0.17.3",
10
- "ty==0.0.90",
9
+ "ruff==0.17.5",
10
+ "ty==0.0.103",
11
11
  "pymodularity==0.5.0",
12
12
  ]
13
13
 
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "python-linters"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Run ruff, ty and pymodularity on a Python project and print one JSON report grouped by rule."
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  requires-python = ">=3.11"
8
8
  authors = [{ name = "Tilo Himmelsbach" }]
9
9
  dependencies = [
10
- "ruff==0.17.3",
11
- "ty==0.0.90",
10
+ "ruff==0.17.5",
11
+ "ty==0.0.103",
12
12
  "pymodularity==0.5.0",
13
13
  ]
14
14
 
@@ -169,7 +169,9 @@ def _covers_rule(
169
169
  return False
170
170
  if selector in meta_prefixes:
171
171
  return True
172
- selector_linters = {r["linter"] for r in rules if r["code"] and r["code"].startswith(selector)}
172
+ selector_linters = {
173
+ r["linter"] for r in rules if r["code"] and r["code"].startswith(selector)
174
+ }
173
175
  return selector_linters == {linter}
174
176
 
175
177
 
@@ -221,9 +223,14 @@ def _get_new_categories(
221
223
  for prefix, linter in sorted(categories.items()):
222
224
  if any(_covers(k, prefix, category_linters, meta_prefixes) for k in known):
223
225
  continue
224
- prefix_rules = [r for r in rules if r["code"] and r["code"].rstrip(string.digits) == prefix]
226
+ prefix_rules = [
227
+ r for r in rules if r["code"] and r["code"].rstrip(string.digits) == prefix
228
+ ]
225
229
  if all(
226
- any(_covers_rule(k, r["code"], r["linter"], rules, meta_prefixes) for k in known)
230
+ any(
231
+ _covers_rule(k, r["code"], r["linter"], rules, meta_prefixes)
232
+ for k in known
233
+ )
227
234
  for r in prefix_rules
228
235
  ):
229
236
  continue
@@ -255,9 +262,13 @@ def check_ruff_config(toml_path: Path) -> RuffConfigCheck:
255
262
  commented = _parse_toml_comments(toml_path)
256
263
 
257
264
  known = selected | commented
258
- meta_prefixes = {"PL"} | {p for p, linters in category_linters.items() if len(linters) > 1}
265
+ meta_prefixes = {"PL"} | {
266
+ p for p, linters in category_linters.items() if len(linters) > 1
267
+ }
259
268
 
260
- new_categories = _get_new_categories(rules, categories, category_linters, known, meta_prefixes)
269
+ new_categories = _get_new_categories(
270
+ rules, categories, category_linters, known, meta_prefixes
271
+ )
261
272
  removed = _get_removed(rules, selected)
262
273
 
263
274
  untriaged_codeless = [n for n in _get_codeless_rules(rules) if n not in known]
@@ -293,24 +304,36 @@ def format_ruff_report(toml_path: Path, check: RuffConfigCheck) -> str:
293
304
  if check.new_categories:
294
305
  lines.append(f"NEW categories not yet triaged ({len(check.new_categories)}):")
295
306
  lines.append(" Add to select or comment out with '# excluded' in ruff.toml:\n")
296
- lines.extend(f' "{prefix}",\t# {linter}' for prefix, linter in check.new_categories)
307
+ lines.extend(
308
+ f' "{prefix}",\t# {linter}' for prefix, linter in check.new_categories
309
+ )
297
310
  lines.append("")
298
311
 
299
312
  if check.removed:
300
- lines.append(f"REMOVED/RENAMED categories still in select ({len(check.removed)}):")
313
+ lines.append(
314
+ f"REMOVED/RENAMED categories still in select ({len(check.removed)}):"
315
+ )
301
316
  lines.extend(f" {code}" for code in check.removed)
302
317
  lines.append("")
303
318
 
304
319
  if check.untriaged_codeless:
305
- lines.append(f"Code-less rules not yet triaged ({len(check.untriaged_codeless)}):")
306
- lines.append(" Add to select by name or comment out with '# excluded' in ruff.toml:\n")
320
+ lines.append(
321
+ f"Code-less rules not yet triaged ({len(check.untriaged_codeless)}):"
322
+ )
323
+ lines.append(
324
+ " Add to select by name or comment out with '# excluded' in ruff.toml:\n"
325
+ )
307
326
  lines.extend(f' "{name}",' for name in check.untriaged_codeless)
308
327
  lines.append("")
309
328
 
310
329
  if check.untriaged_preview:
311
330
  lines.append(f"Preview rules not yet triaged ({len(check.untriaged_preview)}):")
312
- lines.append(" Add to select by name or cover with an '# excluded' line in ruff.toml:\n")
313
- lines.extend(f' "{name}",\t# {code}' for code, name in check.untriaged_preview)
331
+ lines.append(
332
+ " Add to select by name or cover with an '# excluded' line in ruff.toml:\n"
333
+ )
334
+ lines.extend(
335
+ f' "{name}",\t# {code}' for code, name in check.untriaged_preview
336
+ )
314
337
  lines.append("")
315
338
 
316
339
  if check.is_clean:
@@ -395,7 +418,9 @@ def format_ty_report(config_path: Path, check: TyConfigCheck) -> str:
395
418
  ]
396
419
 
397
420
  if check.untriaged:
398
- lines.append(f"UNTRIAGED rules ty knows that the config omits ({len(check.untriaged)}):")
421
+ lines.append(
422
+ f"UNTRIAGED rules ty knows that the config omits ({len(check.untriaged)}):"
423
+ )
399
424
  lines.extend(
400
425
  f' {name} = "???" # default: {check.default_levels[name]}'
401
426
  for name in check.untriaged
@@ -403,7 +428,9 @@ def format_ty_report(config_path: Path, check: TyConfigCheck) -> str:
403
428
  lines.append("")
404
429
 
405
430
  if check.stale:
406
- lines.append(f"STALE rules named in config that ty no longer knows ({len(check.stale)}):")
431
+ lines.append(
432
+ f"STALE rules named in config that ty no longer knows ({len(check.stale)}):"
433
+ )
407
434
  lines.extend(f" {name}" for name in check.stale)
408
435
  lines.append("")
409
436
 
@@ -1,4 +1,4 @@
1
- """Run ruff, ty and pymodularity and group their findings by rule.
1
+ """Run ruff, ruff format --check, ty and pymodularity and group their findings by rule.
2
2
 
3
3
  Ported from lint_grouped.py (quality-and-security-tools howtos), minus eslint.
4
4
 
@@ -7,6 +7,7 @@ Output structure:
7
7
  "project": "/abs/path",
8
8
  "summary": {
9
9
  "ruff": {"total": 100, "rules": 12},
10
+ "ruff-format": {"total": 2, "rules": 1},
10
11
  "ty": {"total": 50, "rules": 8},
11
12
  "pymodularity": {"total": 5, "rules": 2}
12
13
  },
@@ -17,6 +18,12 @@ Output structure:
17
18
  "locations": [{"file": "rel/path.py", "line": 12, "col": 5, "message": "..."}, ...]
18
19
  }
19
20
  },
21
+ "ruff-format": {
22
+ "unformatted": {
23
+ "count": 2,
24
+ "locations": [{"file": "rel/path.py", "line": 1, "col": 1, "message": "would be reformatted"}, ...]
25
+ }
26
+ },
20
27
  "ty": {
21
28
  "invalid-return-type": {
22
29
  "severity": "major",
@@ -40,18 +47,23 @@ from __future__ import annotations
40
47
 
41
48
  import json
42
49
  import os
50
+ import re
43
51
  import sys
44
52
  from dataclasses import dataclass, field
45
53
  from pathlib import Path
46
54
  from typing import TYPE_CHECKING
47
55
 
48
- from python_linters.tool_errors import run_checked
56
+ from python_linters.tool_errors import ToolFailedError, run_checked
49
57
 
50
58
  if TYPE_CHECKING:
51
59
  from collections.abc import Iterable
52
60
 
53
61
  _TY_SEVERITY_MAP = {"major": "error", "minor": "warning", "info": "info"}
54
62
 
63
+ # Matches ruff format --check's per-file location line, e.g.:
64
+ # --> my_package/main_module.py:1:2
65
+ _RUFF_FORMAT_LOCATION_RE = re.compile(r"^ --> (?P<file>.+):(?P<line>\d+):(?P<col>\d+)$")
66
+
55
67
 
56
68
  @dataclass
57
69
  class Location:
@@ -61,7 +73,12 @@ class Location:
61
73
  message: str
62
74
 
63
75
  def to_dict(self) -> dict[str, object]:
64
- return {"file": self.file, "line": self.line, "col": self.col, "message": self.message}
76
+ return {
77
+ "file": self.file,
78
+ "line": self.line,
79
+ "col": self.col,
80
+ "message": self.message,
81
+ }
65
82
 
66
83
 
67
84
  @dataclass
@@ -115,7 +132,7 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
115
132
  # Resolution: the python-linters environment's own pinned ruff, run as
116
133
  # `sys.executable -m ruff` so it never falls through to PATH. In
117
134
  # dev-dependency mode that environment is the linted project's venv, so
118
- # ruff==0.17.3 (pinned in python-linters' own pyproject.toml) is what
135
+ # ruff==0.17.5 (pinned in python-linters' own pyproject.toml) is what
119
136
  # actually runs — never a PATH ruff of another version, which lints
120
137
  # with other rules and may refuse the reference config.
121
138
  #
@@ -123,7 +140,16 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
123
140
  # selector, bad config, ...). Empty stdout on exit 2 must never read
124
141
  # as "0 findings" — `run_checked` raises instead of returning in that case.
125
142
  result = run_checked(
126
- [sys.executable, "-m", "ruff", "check", "--no-cache", "--output-format", "json", "."],
143
+ [
144
+ sys.executable,
145
+ "-m",
146
+ "ruff",
147
+ "check",
148
+ "--no-cache",
149
+ "--output-format",
150
+ "json",
151
+ ".",
152
+ ],
127
153
  project_path,
128
154
  "ruff",
129
155
  ok_codes=(0, 1),
@@ -147,6 +173,53 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
147
173
  return _group_findings(findings())
148
174
 
149
175
 
176
+ def run_ruff_format(project_path: Path) -> dict[str, FindingGroup]:
177
+ # Resolution: same as run_ruff above — `sys.executable -m ruff`, never
178
+ # PATH, `--no-cache` so a read-only linted project tree is never an
179
+ # issue.
180
+ #
181
+ # `ruff format --check` has no JSON output mode (unlike `ruff check`):
182
+ # `ruff format --help` lists `--output-format` only under `check`, not
183
+ # under `format`. Its human-readable stdout does carry one `-->
184
+ # path:line:col` line per unformatted file, which is parsed below
185
+ # instead.
186
+ #
187
+ # ruff format's exit codes: 0 all formatted, 1 some file would be
188
+ # reformatted, 2 tool failure (bad config, crash, ...). A failure must
189
+ # never read as "0 findings" — `run_checked` raises instead of
190
+ # returning in that case.
191
+ result = run_checked(
192
+ [sys.executable, "-m", "ruff", "format", "--check", "--no-cache", "."],
193
+ project_path,
194
+ "ruff-format",
195
+ ok_codes=(0, 1),
196
+ )
197
+ if not result.stdout.strip():
198
+ return {}
199
+
200
+ def findings() -> Iterable[_Finding]:
201
+ for line in result.stdout.splitlines():
202
+ match = _RUFF_FORMAT_LOCATION_RE.match(line)
203
+ if match is None:
204
+ continue
205
+ location = Location(
206
+ file=_relative_to(match["file"], project_path),
207
+ line=int(match["line"]),
208
+ col=int(match["col"]),
209
+ message="would be reformatted",
210
+ )
211
+ yield "unformatted", None, None, location
212
+
213
+ groups = _group_findings(findings())
214
+ # Exit 1 with nothing parsed means ruff changed its text output: fail
215
+ # loudly instead of reporting a clean project.
216
+ if result.returncode == 1 and not groups:
217
+ detail = f"exit 1 but no ' --> path:line:col' line parsed:\n{result.stdout}"
218
+ tool = "ruff-format"
219
+ raise ToolFailedError(tool, detail)
220
+ return groups
221
+
222
+
150
223
  def run_ty(project_path: Path) -> dict[str, FindingGroup]:
151
224
  # Resolution: `sys.executable -m ty` from the python-linters
152
225
  # environment, never a bare `ty` entry point (that one falls through to
@@ -226,8 +299,15 @@ def run_pymodularity(project_path: Path) -> dict[str, FindingGroup]:
226
299
  # 2 `uv` missing or an uncaught exception (docs/rules.md "JSON output").
227
300
  # A failure must never read as "0 findings".
228
301
  result = run_checked(
229
- [sys.executable, "-m", "pymodularity", "check", "--output-format", "json",
230
- str(project_path)],
302
+ [
303
+ sys.executable,
304
+ "-m",
305
+ "pymodularity",
306
+ "check",
307
+ "--output-format",
308
+ "json",
309
+ str(project_path),
310
+ ],
231
311
  project_path,
232
312
  "pymodularity",
233
313
  ok_codes=(0, 1),
@@ -259,7 +339,12 @@ def build_report(project_path: Path) -> tuple[dict[str, object], int]:
259
339
  sections: dict[str, object] = {}
260
340
  grand_total = 0
261
341
 
262
- for tool, run in (("ruff", run_ruff), ("ty", run_ty), ("pymodularity", run_pymodularity)):
342
+ for tool, run in (
343
+ ("ruff", run_ruff),
344
+ ("ruff-format", run_ruff_format),
345
+ ("ty", run_ty),
346
+ ("pymodularity", run_pymodularity),
347
+ ):
263
348
  print(f"Running {tool} on {project_path} ...", file=sys.stderr)
264
349
  violations = run(project_path)
265
350
  total = sum(group.count for group in violations.values())
@@ -12,7 +12,7 @@
12
12
  # 5. flake8-type-checking — list your runtime-evaluated base classes and
13
13
  # decorators (pydantic, FastAPI preset)
14
14
  #
15
- # Last triaged against: ruff 0.17.3
15
+ # Last triaged against: ruff 0.17.5
16
16
  # Rule names in `select`/`ignore` and in `# ruff: ignore[...]` comments need
17
17
  # `preview = true`: without it ruff rejects the config and treats name-based
18
18
  # suppression comments as inert.
@@ -20,7 +20,7 @@
20
20
 
21
21
  # Older ruff fails on the first rule name it does not know, with a cryptic
22
22
  # `Unknown rule selector`. This guard names the version instead.
23
- required-version = ">=0.17.3"
23
+ required-version = ">=0.17.5"
24
24
 
25
25
  target-version = "py312"
26
26
 
@@ -94,6 +94,8 @@ select = [
94
94
 
95
95
  # ── Security ──
96
96
  "S", # flake8-bandit — security antipatterns (hardcoded passwords, exec, …)
97
+ "tarfile-ignore-zeros-true", # code-less rule: `ignore_zeros=True` reads past the
98
+ # end-of-archive marker, so appended members get extracted too
97
99
  "EXE", # flake8-executable — shebang correctness
98
100
 
99
101
  # ── Error handling ──
@@ -170,11 +172,11 @@ select = [
170
172
 
171
173
  # ── Preview rules, selected by name ──────────────────────────────────
172
174
  # `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.
175
+ # stable rules. Each preview rule below was triaged on ruff 0.17.5.
174
176
  # A preview rule in a selected category that is neither listed
175
177
  # here nor covered by an `# excluded` line below is untriaged.
176
178
 
177
- # Ruff-specific (25)
179
+ # Ruff-specific (26)
178
180
  # bugs: the code runs but does the wrong thing
179
181
  "missing-f-string-syntax", # `"{x}"` without `f` prints the braces
180
182
  "unraw-re-pattern", # `"\b"` in a non-raw pattern is a backspace, not a word boundary, and nothing else warns
@@ -204,13 +206,15 @@ select = [
204
206
  "noqa-comments",
205
207
  "rule-codes-in-suppression-comments",
206
208
  "rule-codes-in-selectors",
209
+ "renamed-rule", # a config selector that only works through a rename redirect
207
210
 
208
- # Pylint (18)
211
+ # Pylint (19)
209
212
  # bugs: runtime errors or silently lost behavior
210
213
  "dict-iter-missing-items", # `for k, v in d` unpacks keys, not items
211
214
  "modified-iterating-set", # RuntimeError
212
215
  "redefined-slots-in-subclass", # makes the base class slot unreachable
213
216
  "bad-dunder-method-name", # a misspelled dunder is never called
217
+ "no-method-argument", # a method without `self`/`cls` raises TypeError when called on an instance
214
218
  "unspecified-encoding", # `open()` without encoding uses the platform locale
215
219
  # size and nesting limits, every threshold set under [lint.pylint]
216
220
  "too-many-public-methods",
@@ -262,9 +266,11 @@ select = [
262
266
  "deprecated-abc-decorator", # deprecated since 3.3
263
267
  "context-manager-iterator", # @contextmanager needs Generator, Iterator lacks `throw`
264
268
 
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",
269
+ # flake8-async (2)
270
+ "yield-in-context-manager-in-async-generator", # the context manager's cleanup waits until
271
+ # the generator closes, when `await` is no longer allowed
272
+ "pytest-raises-exception-group", # `pytest.RaisesGroup` checks the grouped exceptions,
273
+ # `pytest.raises(ExceptionGroup)` only the wrapper
268
274
 
269
275
  # flake8-pytest-style (1): `pytest.warns()` without a warning fails at runtime
270
276
  "pytest-warns-without-warning",
@@ -321,9 +327,9 @@ ignore = [
321
327
  # ANN401 (typing.Any disallowed) stays ON: with basedpyright dropped
322
328
  # in favor of ty, ANN401 is the gate's only coverage for explicit
323
329
  # 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",
330
+ # missing-return-type-special-method stays ON: ty types the target of
331
+ # `with X() as x` as Unknown when `__enter__` has no return type. ruff
332
+ # fixes `__init__ -> None` and `__str__ -> str` itself (safe fix).
327
333
  "missing-type-args", # missing type on *args — typically pass-through,
328
334
  # type depends on caller
329
335
  "missing-type-kwargs", # missing type on **kwargs — same reasoning as
@@ -339,18 +345,18 @@ ignore = [
339
345
  # than max_snr
340
346
 
341
347
  # ── Code style ──
342
- "lambda-assignment", # `transform = lambda x: x.strip()` is sometimes
343
- # clearer than a named function for one-off local
344
- # transforms
348
+ # lambda-assignment stays ON: a lambda cannot annotate its parameters,
349
+ # so ty types `strip = lambda x: x.strip()` as `(x) -> Unknown` and
350
+ # accepts `strip(1)`.
345
351
  "in-dict-keys", # use `key in dict` — `key in dict.keys()` is more
346
352
  # explicit about intent; Python Zen says explicit >
347
353
  # implicit
348
354
  "superfluous-else-return", # the else branch makes the two paths visually
349
355
  # symmetric and easier to read
350
356
  "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",
357
+ # super-call-with-parameters stays ON: zero-argument super() fails in a
358
+ # dataclass(slots=True) before Python 3.14, but ruff skips slots
359
+ # dataclasses for this rule (checked on ruff 0.17.3).
354
360
  "compare-to-empty-string", # `s == ""` is more explicit than `not s` (the
355
361
  # latter also matches None, 0, [])
356
362
 
@@ -391,11 +397,16 @@ ignore = [
391
397
  # callers, so its method count is not an
392
398
  # interface
393
399
  # missing return type — test functions always return None
394
- "missing-return-type-undocumented-public-function",
400
+ "missing-return-type-public-function",
395
401
  "import-private-name", # testing internal functions is legitimate
396
402
  "unused-async", # async test doubles mirror the awaited signature they replace
397
403
  "float-equality-comparison", # asserting an exactly representable result is correct
398
404
  "too-many-locals", # an integration test sets up many real collaborators
405
+ "missing-type-function-argument", # pytest fixtures: the fixture defines the type
406
+ "missing-return-type-private-function", # helpers and fakes local to a test module
407
+ "missing-return-type-special-method", # `__init__` / `__enter__` on test doubles
408
+ "import-outside-top-level", # imports inside a test patch or isolate the import
409
+ "private-member-access", # tests inspect internal state on purpose
399
410
  ]
400
411
  "test_*.py" = [
401
412
  "assert",
@@ -403,12 +414,17 @@ ignore = [
403
414
  "magic-value-comparison",
404
415
  "no-self-use",
405
416
  "too-many-public-methods",
406
- "missing-return-type-undocumented-public-function",
417
+ "missing-return-type-public-function",
407
418
  "implicit-namespace-package", # standalone test scripts
408
419
  "import-private-name",
409
420
  "unused-async",
410
421
  "float-equality-comparison",
411
422
  "too-many-locals",
423
+ "missing-type-function-argument",
424
+ "missing-return-type-private-function",
425
+ "missing-return-type-special-method",
426
+ "import-outside-top-level",
427
+ "private-member-access",
412
428
  ]
413
429
 
414
430
  # ── isort ───────────────────────────────────────────────────────────────────
@@ -2,7 +2,7 @@
2
2
  # ty Reference Configuration
3
3
  # ============================================================================
4
4
  #
5
- # Canonical rule table for new Python projects. Every rule ty 0.0.90 knows is
5
+ # Canonical rule table for new Python projects. Every rule ty 0.0.103 knows is
6
6
  # listed below, one line each, with no `all = "error"` base. Each line is
7
7
  # "error" or "ignore". A "warn" setting is never used, because a warning
8
8
  # does not fail a CI gate, so a green run hides it.
@@ -16,8 +16,8 @@
16
16
  # - into `pyproject.toml` under `[tool.ty.rules]` (rename the table header,
17
17
  # keep every line).
18
18
  #
19
- # Last triaged against: ty 0.0.90 (140 rules: 97 error-by-default,
20
- # 25 warn-by-default, 18 ignore-by-default).
19
+ # Last triaged against: ty 0.0.103 (152 rules: 107 error-by-default,
20
+ # 27 warn-by-default, 18 ignore-by-default).
21
21
  #
22
22
  # Re-check the copied table after every ty version bump:
23
23
  # python-linters check-config ty <path/to/ty.toml>
@@ -51,7 +51,7 @@ redundant-condition-strict = "ignore"
51
51
  # `re.Match.group()`) goes unflagged.
52
52
  # Re-triage on each ty bump: ty is 0.0.x and its inference changes.
53
53
  unsound-assignment = "ignore"
54
- unsound-return-statement = "ignore"
54
+ unsound-return = "ignore"
55
55
  unsound-yield = "ignore"
56
56
 
57
57
  # not applicable: checks classes built with `type()`, which the doctrine
@@ -80,66 +80,69 @@ truthiness-test-of-none-union = "error"
80
80
  # --- Enforced rules: on by default in ty, alphabetical ----------------------
81
81
 
82
82
  abstract-and-final-method = "error"
83
+ abstract-class-instantiation = "error"
84
+ abstract-method-call = "error"
83
85
  abstract-method-in-final-class = "error"
84
86
  ambiguous-protocol-member = "error"
85
87
  assert-type-unspellable-subtype = "error"
86
- call-abstract-method = "error"
87
- call-non-callable = "error"
88
- call-top-callable = "error"
88
+ call-to-function-with-unknown-signature = "error"
89
89
  conflicting-declarations = "error"
90
90
  conflicting-metaclass = "error"
91
91
  cyclic-class-definition = "error"
92
92
  cyclic-type-alias-definition = "error"
93
- dataclass-field-order = "error"
94
93
  deprecated = "error"
95
94
  duplicate-base = "error"
96
95
  duplicate-kw-only = "error"
97
- empty-body = "error"
98
96
  escape-character-in-forward-annotation = "error"
99
97
  experimental-syntax = "error"
100
98
  final-on-non-method = "error"
101
99
  final-without-value = "error"
102
100
  ignore-comment-unknown-rule = "error"
103
- implicit-concatenated-string-type-annotation = "error"
101
+ implicit-string-concatenation-in-annotation = "error"
102
+ import-not-found = "error"
103
+ incompatible-attribute-override = "error"
104
+ incompatible-method-override = "error"
105
+ incompatible-mutable-override = "error"
106
+ incompatible-property-override = "error"
104
107
  inconsistent-mro = "error"
108
+ inconsistent-overload-decorator = "error"
109
+ inconsistent-overload-implementation = "error"
105
110
  index-out-of-bounds = "error"
106
111
  ineffective-final = "error"
107
112
  instance-layout-conflict = "error"
108
113
  invalid-argument-type = "error"
109
114
  invalid-assignment = "error"
110
115
  invalid-attribute-access = "error"
111
- invalid-attribute-override = "error"
112
116
  invalid-await = "error"
113
117
  invalid-base = "error"
114
118
  invalid-context-manager = "error"
115
119
  invalid-dataclass = "error"
120
+ invalid-dataclass-field-order = "error"
116
121
  invalid-dataclass-override = "error"
117
122
  invalid-declaration = "error"
118
123
  invalid-enum-member-annotation = "error"
119
124
  invalid-exception-caught = "error"
120
125
  invalid-explicit-override = "error"
126
+ invalid-explicit-return = "error"
121
127
  invalid-frozen-dataclass-subclass = "error"
128
+ invalid-generator-return-type = "error"
122
129
  invalid-generic-class = "error"
123
130
  invalid-generic-enum = "error"
124
131
  invalid-ignore-comment = "error"
132
+ invalid-implicit-return = "error"
133
+ invalid-implicit-return-in-function-with-stub-body = "error"
125
134
  invalid-init-type-variable = "error"
126
- invalid-key = "error"
127
135
  invalid-legacy-type-variable = "error"
128
136
  invalid-match-pattern = "error"
129
137
  invalid-metaclass = "error"
130
- invalid-method-override = "error"
131
138
  invalid-module-getattr-call = "error"
132
- invalid-mutable-override = "error"
133
139
  invalid-named-tuple = "error"
134
140
  invalid-named-tuple-override = "error"
135
141
  invalid-newtype = "error"
136
- invalid-overload = "error"
137
142
  invalid-parameter-default = "error"
138
143
  invalid-paramspec = "error"
139
- invalid-property-type-override = "error"
140
144
  invalid-protocol = "error"
141
145
  invalid-raise = "error"
142
- invalid-return-type = "error"
143
146
  invalid-super-argument = "error"
144
147
  invalid-syntax-in-forward-annotation = "error"
145
148
  invalid-total-ordering = "error"
@@ -153,16 +156,24 @@ invalid-type-variable-constraints = "error"
153
156
  invalid-type-variable-default = "error"
154
157
  invalid-typed-dict-field = "error"
155
158
  invalid-typed-dict-header = "error"
159
+ invalid-typed-dict-key = "error"
156
160
  invalid-typed-dict-statement = "error"
157
161
  invalid-yield = "error"
158
162
  isinstance-against-protocol = "error"
159
163
  isinstance-against-typed-dict = "error"
164
+ lost-keyword-for-regular-parameter = "error"
160
165
  mismatched-type-name = "error"
166
+ misplaced-overload-decorator = "error"
161
167
  missing-argument = "error"
168
+ missing-attribute = "error"
169
+ missing-overload-implementation = "error"
162
170
  missing-slot = "error"
163
171
  missing-typed-dict-key = "error"
172
+ missing-union-attribute = "error"
164
173
  no-matching-overload = "error"
165
174
  non-callable-init-subclass = "error"
175
+ non-literal-typed-dict-key = "error"
176
+ not-callable = "error"
166
177
  not-iterable = "error"
167
178
  not-subscriptable = "error"
168
179
  override-of-final-method = "error"
@@ -176,7 +187,9 @@ raw-string-type-annotation = "error"
176
187
  redundant-cast = "error"
177
188
  redundant-condition = "error"
178
189
  redundant-final-classvar = "error"
190
+ renamed-rule-in-ignore-comment = "error"
179
191
  shadowed-type-variable = "error"
192
+ single-overload = "error"
180
193
  static-assert-error = "error"
181
194
  subclass-of-dataclass-with-order = "error"
182
195
  subclass-of-final-class = "error"
@@ -189,9 +202,8 @@ unavailable-implicit-super-arguments = "error"
189
202
  unbound-type-variable = "error"
190
203
  undefined-reveal = "error"
191
204
  unknown-argument = "error"
192
- unresolved-attribute = "error"
205
+ unknown-typed-dict-constructor-key = "error"
193
206
  unresolved-global = "error"
194
- unresolved-import = "error"
195
207
  unresolved-reference = "error"
196
208
  unsupported-base = "error"
197
209
  unsupported-bool-conversion = "error"