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,203 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# ty Reference Configuration
|
|
3
|
+
# ============================================================================
|
|
4
|
+
#
|
|
5
|
+
# Canonical rule table for new Python projects. Every rule ty 0.0.90 knows is
|
|
6
|
+
# listed below, one line each, with no `all = "error"` base. Each line is
|
|
7
|
+
# "error" or "ignore". A "warn" setting is never used, because a warning
|
|
8
|
+
# does not fail a CI gate, so a green run hides it.
|
|
9
|
+
#
|
|
10
|
+
# Three sections: the ignored rules, each under a comment giving its triage
|
|
11
|
+
# reason. Then the rules ty turns off by default that this file turns on.
|
|
12
|
+
# Then the rules ty already enables, alphabetical, without comments.
|
|
13
|
+
#
|
|
14
|
+
# Copy this table as-is:
|
|
15
|
+
# - into a project's own `ty.toml` (as the top-level `[rules]` table), or
|
|
16
|
+
# - into `pyproject.toml` under `[tool.ty.rules]` (rename the table header,
|
|
17
|
+
# keep every line).
|
|
18
|
+
#
|
|
19
|
+
# Last triaged against: ty 0.0.90 (140 rules: 97 error-by-default,
|
|
20
|
+
# 25 warn-by-default, 18 ignore-by-default).
|
|
21
|
+
#
|
|
22
|
+
# Re-check the copied table after every ty version bump:
|
|
23
|
+
# python-linters check-config ty <path/to/ty.toml>
|
|
24
|
+
# It reports rules ty knows that the table omits (untriaged) and rules the
|
|
25
|
+
# table names that ty no longer knows (stale), and exits non-zero on either.
|
|
26
|
+
# ============================================================================
|
|
27
|
+
|
|
28
|
+
[rules]
|
|
29
|
+
|
|
30
|
+
# --- Ignored rules: each carries its triage reason -------------------------
|
|
31
|
+
|
|
32
|
+
# not applicable: checks PEP 484's legacy `__`-prefixed positional-only
|
|
33
|
+
# parameters, obsolete since PEP 570's `/` (Python 3.8).
|
|
34
|
+
invalid-legacy-positional-parameter = "ignore"
|
|
35
|
+
|
|
36
|
+
# not applicable: a no-op unless `TY_UV=1` is set and uv >= 0.12 supplies
|
|
37
|
+
# dependency metadata. Re-triage when a project opts in.
|
|
38
|
+
missing-direct-dependency = "ignore"
|
|
39
|
+
|
|
40
|
+
# false positives: ty's flow narrowing ignores a concurrent task mutating an
|
|
41
|
+
# instance attribute across an `await`. So it reports a check on that
|
|
42
|
+
# attribute after the `await` as always true or always false.
|
|
43
|
+
redundant-condition-strict = "ignore"
|
|
44
|
+
|
|
45
|
+
# false positives: ty infers an empty literal (`[]`, `{}`, `set()`) as
|
|
46
|
+
# `list[Unknown]` etc. and rejects it against `Sequence`, `Mapping`,
|
|
47
|
+
# `AbstractSet`. The only per-site fix is code shaped for the checker
|
|
48
|
+
# (`list[Path]()` instead of `[]`), and code never changes only because ty
|
|
49
|
+
# cannot infer a type.
|
|
50
|
+
# cost: an `Any` reaching a typed name (argparse attributes,
|
|
51
|
+
# `re.Match.group()`) goes unflagged.
|
|
52
|
+
# Re-triage on each ty bump: ty is 0.0.x and its inference changes.
|
|
53
|
+
unsound-assignment = "ignore"
|
|
54
|
+
unsound-return-statement = "ignore"
|
|
55
|
+
unsound-yield = "ignore"
|
|
56
|
+
|
|
57
|
+
# not applicable: checks classes built with `type()`, which the doctrine
|
|
58
|
+
# does not use. Astral calls the rule noisy on dynamic `type()` code.
|
|
59
|
+
unsupported-dynamic-base = "ignore"
|
|
60
|
+
|
|
61
|
+
# --- Enabled although off by default in ty --------------------------------
|
|
62
|
+
|
|
63
|
+
# off by default for false-positive volume. On here until a triage shows
|
|
64
|
+
# that volume, then they move to the ignored section with the evidence.
|
|
65
|
+
division-by-zero = "error"
|
|
66
|
+
possibly-missing-attribute = "error"
|
|
67
|
+
possibly-missing-import = "error"
|
|
68
|
+
possibly-unresolved-reference = "error"
|
|
69
|
+
|
|
70
|
+
# opt-in strictness: astral keeps these off as stricter than most projects
|
|
71
|
+
# want. This file wants that strictness.
|
|
72
|
+
blanket-ignore-comment = "error"
|
|
73
|
+
disjoint-cast = "error"
|
|
74
|
+
dynamic-function-decorator-return = "error"
|
|
75
|
+
mismatched-sentinel-name = "error"
|
|
76
|
+
missing-override-decorator = "error"
|
|
77
|
+
missing-type-argument = "error"
|
|
78
|
+
truthiness-test-of-none-union = "error"
|
|
79
|
+
|
|
80
|
+
# --- Enforced rules: on by default in ty, alphabetical ----------------------
|
|
81
|
+
|
|
82
|
+
abstract-and-final-method = "error"
|
|
83
|
+
abstract-method-in-final-class = "error"
|
|
84
|
+
ambiguous-protocol-member = "error"
|
|
85
|
+
assert-type-unspellable-subtype = "error"
|
|
86
|
+
call-abstract-method = "error"
|
|
87
|
+
call-non-callable = "error"
|
|
88
|
+
call-top-callable = "error"
|
|
89
|
+
conflicting-declarations = "error"
|
|
90
|
+
conflicting-metaclass = "error"
|
|
91
|
+
cyclic-class-definition = "error"
|
|
92
|
+
cyclic-type-alias-definition = "error"
|
|
93
|
+
dataclass-field-order = "error"
|
|
94
|
+
deprecated = "error"
|
|
95
|
+
duplicate-base = "error"
|
|
96
|
+
duplicate-kw-only = "error"
|
|
97
|
+
empty-body = "error"
|
|
98
|
+
escape-character-in-forward-annotation = "error"
|
|
99
|
+
experimental-syntax = "error"
|
|
100
|
+
final-on-non-method = "error"
|
|
101
|
+
final-without-value = "error"
|
|
102
|
+
ignore-comment-unknown-rule = "error"
|
|
103
|
+
implicit-concatenated-string-type-annotation = "error"
|
|
104
|
+
inconsistent-mro = "error"
|
|
105
|
+
index-out-of-bounds = "error"
|
|
106
|
+
ineffective-final = "error"
|
|
107
|
+
instance-layout-conflict = "error"
|
|
108
|
+
invalid-argument-type = "error"
|
|
109
|
+
invalid-assignment = "error"
|
|
110
|
+
invalid-attribute-access = "error"
|
|
111
|
+
invalid-attribute-override = "error"
|
|
112
|
+
invalid-await = "error"
|
|
113
|
+
invalid-base = "error"
|
|
114
|
+
invalid-context-manager = "error"
|
|
115
|
+
invalid-dataclass = "error"
|
|
116
|
+
invalid-dataclass-override = "error"
|
|
117
|
+
invalid-declaration = "error"
|
|
118
|
+
invalid-enum-member-annotation = "error"
|
|
119
|
+
invalid-exception-caught = "error"
|
|
120
|
+
invalid-explicit-override = "error"
|
|
121
|
+
invalid-frozen-dataclass-subclass = "error"
|
|
122
|
+
invalid-generic-class = "error"
|
|
123
|
+
invalid-generic-enum = "error"
|
|
124
|
+
invalid-ignore-comment = "error"
|
|
125
|
+
invalid-init-type-variable = "error"
|
|
126
|
+
invalid-key = "error"
|
|
127
|
+
invalid-legacy-type-variable = "error"
|
|
128
|
+
invalid-match-pattern = "error"
|
|
129
|
+
invalid-metaclass = "error"
|
|
130
|
+
invalid-method-override = "error"
|
|
131
|
+
invalid-module-getattr-call = "error"
|
|
132
|
+
invalid-mutable-override = "error"
|
|
133
|
+
invalid-named-tuple = "error"
|
|
134
|
+
invalid-named-tuple-override = "error"
|
|
135
|
+
invalid-newtype = "error"
|
|
136
|
+
invalid-overload = "error"
|
|
137
|
+
invalid-parameter-default = "error"
|
|
138
|
+
invalid-paramspec = "error"
|
|
139
|
+
invalid-property-type-override = "error"
|
|
140
|
+
invalid-protocol = "error"
|
|
141
|
+
invalid-raise = "error"
|
|
142
|
+
invalid-return-type = "error"
|
|
143
|
+
invalid-super-argument = "error"
|
|
144
|
+
invalid-syntax-in-forward-annotation = "error"
|
|
145
|
+
invalid-total-ordering = "error"
|
|
146
|
+
invalid-type-alias-type = "error"
|
|
147
|
+
invalid-type-arguments = "error"
|
|
148
|
+
invalid-type-checking-constant = "error"
|
|
149
|
+
invalid-type-form = "error"
|
|
150
|
+
invalid-type-guard-definition = "error"
|
|
151
|
+
invalid-type-variable-bound = "error"
|
|
152
|
+
invalid-type-variable-constraints = "error"
|
|
153
|
+
invalid-type-variable-default = "error"
|
|
154
|
+
invalid-typed-dict-field = "error"
|
|
155
|
+
invalid-typed-dict-header = "error"
|
|
156
|
+
invalid-typed-dict-statement = "error"
|
|
157
|
+
invalid-yield = "error"
|
|
158
|
+
isinstance-against-protocol = "error"
|
|
159
|
+
isinstance-against-typed-dict = "error"
|
|
160
|
+
mismatched-type-name = "error"
|
|
161
|
+
missing-argument = "error"
|
|
162
|
+
missing-slot = "error"
|
|
163
|
+
missing-typed-dict-key = "error"
|
|
164
|
+
no-matching-overload = "error"
|
|
165
|
+
non-callable-init-subclass = "error"
|
|
166
|
+
not-iterable = "error"
|
|
167
|
+
not-subscriptable = "error"
|
|
168
|
+
override-of-final-method = "error"
|
|
169
|
+
override-of-final-variable = "error"
|
|
170
|
+
parameter-already-assigned = "error"
|
|
171
|
+
positional-only-parameter-as-kwarg = "error"
|
|
172
|
+
possibly-missing-implicit-call = "error"
|
|
173
|
+
possibly-missing-submodule = "error"
|
|
174
|
+
pydantic-discarded-extra-argument = "error"
|
|
175
|
+
raw-string-type-annotation = "error"
|
|
176
|
+
redundant-cast = "error"
|
|
177
|
+
redundant-condition = "error"
|
|
178
|
+
redundant-final-classvar = "error"
|
|
179
|
+
shadowed-type-variable = "error"
|
|
180
|
+
static-assert-error = "error"
|
|
181
|
+
subclass-of-dataclass-with-order = "error"
|
|
182
|
+
subclass-of-final-class = "error"
|
|
183
|
+
super-call-in-named-tuple-method = "error"
|
|
184
|
+
too-many-positional-arguments = "error"
|
|
185
|
+
truthiness-test-of-callable = "error"
|
|
186
|
+
truthiness-test-of-iterable = "error"
|
|
187
|
+
type-assertion-failure = "error"
|
|
188
|
+
unavailable-implicit-super-arguments = "error"
|
|
189
|
+
unbound-type-variable = "error"
|
|
190
|
+
undefined-reveal = "error"
|
|
191
|
+
unknown-argument = "error"
|
|
192
|
+
unresolved-attribute = "error"
|
|
193
|
+
unresolved-global = "error"
|
|
194
|
+
unresolved-import = "error"
|
|
195
|
+
unresolved-reference = "error"
|
|
196
|
+
unsupported-base = "error"
|
|
197
|
+
unsupported-bool-conversion = "error"
|
|
198
|
+
unsupported-operator = "error"
|
|
199
|
+
unused-awaitable = "error"
|
|
200
|
+
unused-ignore-comment = "error"
|
|
201
|
+
unused-type-ignore-comment = "error"
|
|
202
|
+
useless-overload-body = "error"
|
|
203
|
+
zero-stepsize-in-slice = "error"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Shared subprocess-failure handling for every external tool this package runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToolFailedError(Exception):
|
|
13
|
+
"""An external tool exited with a code outside its accepted set.
|
|
14
|
+
|
|
15
|
+
Raised at the boundary where the subprocess result is read, so the
|
|
16
|
+
interior (grouping, config comparison, ...) never has to distinguish
|
|
17
|
+
"0 findings" from "the tool crashed and printed nothing".
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, tool: str, stderr: str) -> None:
|
|
21
|
+
self.tool = tool
|
|
22
|
+
self.stderr = stderr
|
|
23
|
+
super().__init__(f"{tool} failed:\n{stderr}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_checked(
|
|
27
|
+
cmd: list[str],
|
|
28
|
+
cwd: Path,
|
|
29
|
+
tool: str,
|
|
30
|
+
ok_codes: tuple[int, ...] = (0,),
|
|
31
|
+
env: dict[str, str] | None = None,
|
|
32
|
+
) -> subprocess.CompletedProcess[str]:
|
|
33
|
+
"""Run cmd, raising ToolFailedError unless its exit code is in ok_codes.
|
|
34
|
+
|
|
35
|
+
env=None (the default) inherits this process's environment, same as
|
|
36
|
+
subprocess.run's own default. Callers pass an explicit env when the
|
|
37
|
+
inherited one would steer the tool wrong (see run_ty's VIRTUAL_ENV fix).
|
|
38
|
+
"""
|
|
39
|
+
result = subprocess.run( # ruff: ignore[subprocess-without-shell-equals-true] # cmd is a fixed argv list built by this package, never shell-interpolated or from untrusted input
|
|
40
|
+
cmd, cwd=cwd, capture_output=True, text=True, check=False, env=env
|
|
41
|
+
)
|
|
42
|
+
if result.returncode not in ok_codes:
|
|
43
|
+
raise ToolFailedError(tool, result.stderr)
|
|
44
|
+
return result
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-linters
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Run ruff, ty and pymodularity on a Python project and print one JSON report grouped by rule.
|
|
5
|
+
Author: Tilo Himmelsbach
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Dist: ruff==0.17.3
|
|
8
|
+
Requires-Dist: ty==0.0.90
|
|
9
|
+
Requires-Dist: pymodularity==0.5.0
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# python-linters
|
|
14
|
+
|
|
15
|
+
Run ruff, ty and pymodularity on a Python project and print one JSON report
|
|
16
|
+
grouped by rule, for agents and CI to read.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
As a dev dependency of the project you want to lint:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
uv add --dev python-linters
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
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`.
|
|
31
|
+
- `uv run python-linters reference ruff|ty` — prints this package's shipped
|
|
32
|
+
reference config for the given tool to stdout. The package never writes
|
|
33
|
+
it into your project — copying it into `ruff.toml`/`ty.toml` (or
|
|
34
|
+
`pyproject.toml`) stays with you.
|
|
35
|
+
- `uv run python-linters check-config ruff|ty <path/to/config>` — compares
|
|
36
|
+
your copy against the pinned tool's current rule set: rules the tool
|
|
37
|
+
knows that your config omits (untriaged), and rules your config names
|
|
38
|
+
that the tool no longer knows (stale).
|
|
39
|
+
|
|
40
|
+
## Exit codes
|
|
41
|
+
|
|
42
|
+
- `lint`: 0 clean, 1 findings, 2 any tool failure (named on stderr).
|
|
43
|
+
- `check-config`: 0 up to date, 1 untriaged or stale rules found, 2 tool
|
|
44
|
+
failure.
|
|
@@ -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=Tj92OWNaVcQ81SXiN13pMfbvFctI1iJnzF0sK5JqU6s,14401
|
|
5
|
+
python_linters/lint_report.py,sha256=GfEowFIJx4BOQtSnjya2k31w_wVumscix-NB4yAN0qs,10340
|
|
6
|
+
python_linters/reference_files.py,sha256=pD0fGW-PJiV09LFIy9-hZKD7EVmHIUjkE3SfD-NeiI4,764
|
|
7
|
+
python_linters/references/ruff.reference.toml,sha256=1nzYkfphwmJdUPBo6WIg3DbuVfCvvVDxf17jcX6GIAY,28330
|
|
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.0.dist-info/WHEEL,sha256=3NO1oYsF7mANFUWVdyEQLM-zmecczWWEmmF5rEbXrWk,80
|
|
11
|
+
python_linters-0.2.0.dist-info/entry_points.txt,sha256=2qemc7DiLCP7wVNw-8KENf-v5L62Jx40tHgVXC0s_FM,65
|
|
12
|
+
python_linters-0.2.0.dist-info/METADATA,sha256=AocHZGXEE-8jYTT1bwXE9gJJCw--ZwJQ5_xQ2rzNqig,1497
|
|
13
|
+
python_linters-0.2.0.dist-info/RECORD,,
|