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.
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.6
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ python-linters = python_linters.__main__:main
3
+