python-linters 0.2.1__py3-none-any.whl → 0.2.2__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.
@@ -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:
@@ -120,7 +132,7 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
120
132
  # Resolution: the python-linters environment's own pinned ruff, run as
121
133
  # `sys.executable -m ruff` so it never falls through to PATH. In
122
134
  # dev-dependency mode that environment is the linted project's venv, so
123
- # 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
124
136
  # actually runs — never a PATH ruff of another version, which lints
125
137
  # with other rules and may refuse the reference config.
126
138
  #
@@ -161,6 +173,53 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
161
173
  return _group_findings(findings())
162
174
 
163
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
+
164
223
  def run_ty(project_path: Path) -> dict[str, FindingGroup]:
165
224
  # Resolution: `sys.executable -m ty` from the python-linters
166
225
  # environment, never a bare `ty` entry point (that one falls through to
@@ -282,6 +341,7 @@ def build_report(project_path: Path) -> tuple[dict[str, object], int]:
282
341
 
283
342
  for tool, run in (
284
343
  ("ruff", run_ruff),
344
+ ("ruff-format", run_ruff_format),
285
345
  ("ty", run_ty),
286
346
  ("pymodularity", run_pymodularity),
287
347
  ):
@@ -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",
@@ -391,7 +397,7 @@ 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
@@ -408,7 +414,7 @@ ignore = [
408
414
  "magic-value-comparison",
409
415
  "no-self-use",
410
416
  "too-many-public-methods",
411
- "missing-return-type-undocumented-public-function",
417
+ "missing-return-type-public-function",
412
418
  "implicit-namespace-package", # standalone test scripts
413
419
  "import-private-name",
414
420
  "unused-async",
@@ -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"
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-linters
3
- Version: 0.2.1
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
@@ -0,0 +1,13 @@
1
+ python_linters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ python_linters/__main__.py,sha256=Xkx-N0S-OWOvqBBgDA0HgCwrW324UrwBEQbR8PUayJI,158
3
+ python_linters/cli_commands.py,sha256=tVneEJJd6bgBrzunto3UJoEJHupwc5wNjMfTGwFCmN0,3122
4
+ python_linters/config_check.py,sha256=gFsI8KVBRkY_x7cREdRE3G51se_sfQAMAacTWR7pdxk,14687
5
+ python_linters/lint_report.py,sha256=TgJEsKCDulBtC8Mx-40emK_ewgGJPkaJNGwgEl-EVBw,13066
6
+ python_linters/reference_files.py,sha256=pD0fGW-PJiV09LFIy9-hZKD7EVmHIUjkE3SfD-NeiI4,764
7
+ python_linters/references/ruff.reference.toml,sha256=Q6kKaeekpeiGcVQoqPDdFfWA-jUij66s8q33EZWWZQ0,29668
8
+ python_linters/references/ty.reference.toml,sha256=AKwhaBhs75QwbtrgqhuyoAYFTQX9Y7ns7TtwF8reQ4E,8244
9
+ python_linters/tool_errors.py,sha256=Fw3byFD0kk8QKJQg85weKB11_CRrnu2h_N2XQId6Abc,1572
10
+ python_linters-0.2.2.dist-info/WHEEL,sha256=3OmCxFA0ffBIsX4x0o8CdfSvZAOvO3mzEG8U_z5TvV4,80
11
+ python_linters-0.2.2.dist-info/entry_points.txt,sha256=2qemc7DiLCP7wVNw-8KENf-v5L62Jx40tHgVXC0s_FM,65
12
+ python_linters-0.2.2.dist-info/METADATA,sha256=UEZArPlda0mJSwVW2ySXc91uCD8tKW9tsA8_nWjNvU8,1521
13
+ python_linters-0.2.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.13.6
2
+ Generator: uv 0.13.8
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,13 +0,0 @@
1
- python_linters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- python_linters/__main__.py,sha256=Xkx-N0S-OWOvqBBgDA0HgCwrW324UrwBEQbR8PUayJI,158
3
- python_linters/cli_commands.py,sha256=tVneEJJd6bgBrzunto3UJoEJHupwc5wNjMfTGwFCmN0,3122
4
- python_linters/config_check.py,sha256=gFsI8KVBRkY_x7cREdRE3G51se_sfQAMAacTWR7pdxk,14687
5
- python_linters/lint_report.py,sha256=5eCrWtZVEKi7Hulbw0VrkKbTnwDav2MuqEo8RmKCKSo,10623
6
- python_linters/reference_files.py,sha256=pD0fGW-PJiV09LFIy9-hZKD7EVmHIUjkE3SfD-NeiI4,764
7
- python_linters/references/ruff.reference.toml,sha256=J7uUpDhi6STS3yoUlyO1ZT2dgQLb9gJmFpFScKzupng,29058
8
- python_linters/references/ty.reference.toml,sha256=674u63jOImBHrY92lAa27ZcilevZj6GSqxWywp6kQTw,7677
9
- python_linters/tool_errors.py,sha256=Fw3byFD0kk8QKJQg85weKB11_CRrnu2h_N2XQId6Abc,1572
10
- python_linters-0.2.1.dist-info/WHEEL,sha256=3NO1oYsF7mANFUWVdyEQLM-zmecczWWEmmF5rEbXrWk,80
11
- python_linters-0.2.1.dist-info/entry_points.txt,sha256=2qemc7DiLCP7wVNw-8KENf-v5L62Jx40tHgVXC0s_FM,65
12
- python_linters-0.2.1.dist-info/METADATA,sha256=SwxLKHe6IGzYTOX3xETKwesEehJ8DUwiUI470ynXJR4,1497
13
- python_linters-0.2.1.dist-info/RECORD,,