python-constricter 0.3.1__tar.gz → 0.3.6__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.
- {python_constricter-0.3.1 → python_constricter-0.3.6}/.gitignore +27 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/PKG-INFO +52 -38
- {python_constricter-0.3.1 → python_constricter-0.3.6}/README.md +51 -37
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/__init__.py +1 -1
- python_constricter-0.3.6/constricter/__main__.py +26 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/command.py +222 -99
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/config.py +94 -13
- python_constricter-0.3.6/constricter/cli/edits.py +188 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/explain.py +2 -1
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/hints.py +139 -40
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/options.py +151 -8
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/protocol.py +30 -5
- python_constricter-0.3.6/constricter/cli/remembered.py +52 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/report.py +11 -6
- python_constricter-0.3.6/constricter/cli/runs.py +90 -0
- python_constricter-0.3.6/constricter/cli/schedule.py +415 -0
- python_constricter-0.3.6/constricter/cli/traced.py +401 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/workers.py +86 -24
- python_constricter-0.3.6/constricter/fix/core/__init__.py +2 -0
- python_constricter-0.3.6/constricter/fix/core/asked.py +56 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/core}/fixes.py +115 -30
- python_constricter-0.3.6/constricter/fix/core/imports.py +527 -0
- python_constricter-0.3.6/constricter/fix/core/inherited.py +310 -0
- python_constricter-0.3.6/constricter/fix/core/known.py +700 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/core}/signatures.py +36 -13
- python_constricter-0.3.6/constricter/fix/index/__init__.py +2 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/atoms.py +29 -11
- python_constricter-0.3.6/constricter/fix/index/awaits.py +90 -0
- python_constricter-0.3.6/constricter/fix/index/beyond.py +148 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/callers.py +6 -5
- python_constricter-0.3.6/constricter/fix/index/chained.py +69 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/classnames.py +22 -5
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/declared.py +84 -4
- python_constricter-0.3.6/constricter/fix/index/decorated.py +121 -0
- python_constricter-0.3.6/constricter/fix/index/fixtures.py +176 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/installed.py +69 -16
- python_constricter-0.3.6/constricter/fix/index/linked.py +142 -0
- python_constricter-0.3.6/constricter/fix/index/loose.py +98 -0
- python_constricter-0.3.6/constricter/fix/index/managed.py +63 -0
- python_constricter-0.3.6/constricter/fix/index/modules.py +849 -0
- python_constricter-0.3.6/constricter/fix/index/offers.py +138 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/order.py +14 -4
- python_constricter-0.3.6/constricter/fix/index/own_methods.py +67 -0
- python_constricter-0.3.6/constricter/fix/index/own_overloads.py +70 -0
- python_constricter-0.3.6/constricter/fix/index/own_types.py +161 -0
- python_constricter-0.3.6/constricter/fix/index/plain.py +125 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/project.py +389 -164
- python_constricter-0.3.6/constricter/fix/index/read.py +70 -0
- python_constricter-0.3.6/constricter/fix/index/sides.py +160 -0
- python_constricter-0.3.6/constricter/fix/index/strings.py +46 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/stubbed.py +105 -95
- python_constricter-0.3.6/constricter/fix/index/taken.py +45 -0
- python_constricter-0.3.6/constricter/fix/index/tuples.py +112 -0
- python_constricter-0.3.6/constricter/fix/libraries/__init__.py +2 -0
- python_constricter-0.3.6/constricter/fix/libraries/library.py +374 -0
- python_constricter-0.3.6/constricter/fix/libraries/opened.py +147 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/libraries}/overloads.py +157 -16
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/libraries}/stdlib.py +354 -18
- python_constricter-0.3.6/constricter/fix/libraries/walks.py +49 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/attributes.json +25 -0
- python_constricter-0.3.6/constricter/fix/tables/awaited.json +69 -0
- python_constricter-0.3.6/constricter/fix/tables/awaited_overloads.json +14 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/classes.json +3 -1
- python_constricter-0.3.6/constricter/fix/tables/elements.json +156 -0
- python_constricter-0.3.6/constricter/fix/tables/functions.json +43 -0
- python_constricter-0.3.6/constricter/fix/tables/generic_attributes.json +254 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/method_overloads.json +177 -69
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/method_signatures.json +280 -39
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/methods.json +185 -88
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/overloads.json +106 -68
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/returns.json +34 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/scalars.json +5 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/source.json +5 -1
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/subscriptable.json +1 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/type_parameters.json +3 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/variables.json +21 -0
- python_constricter-0.3.6/constricter/fix/values/__init__.py +2 -0
- python_constricter-0.3.6/constricter/fix/values/aliased.py +195 -0
- python_constricter-0.3.6/constricter/fix/values/bodies.py +76 -0
- python_constricter-0.3.6/constricter/fix/values/callables.py +321 -0
- python_constricter-0.3.6/constricter/fix/values/called.py +106 -0
- python_constricter-0.3.6/constricter/fix/values/classvars.py +541 -0
- python_constricter-0.3.6/constricter/fix/values/decided.py +396 -0
- python_constricter-0.3.6/constricter/fix/values/displays.py +87 -0
- python_constricter-0.3.6/constricter/fix/values/doubts.py +673 -0
- python_constricter-0.3.6/constricter/fix/values/entered.py +331 -0
- python_constricter-0.3.6/constricter/fix/values/fills.py +435 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/guesses.py +145 -53
- python_constricter-0.3.6/constricter/fix/values/hinted.py +645 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/inference.py +404 -211
- python_constricter-0.3.6/constricter/fix/values/members.py +575 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/narrowed.py +60 -4
- python_constricter-0.3.6/constricter/fix/values/operated.py +200 -0
- python_constricter-0.3.6/constricter/fix/values/ordered.py +74 -0
- python_constricter-0.3.6/constricter/fix/values/returned.py +967 -0
- {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/returns.py +46 -2
- python_constricter-0.3.6/constricter/fix/values/shapes.py +579 -0
- python_constricter-0.3.6/constricter/fix/values/targets.py +383 -0
- python_constricter-0.3.6/constricter/fix/values/unchecked.py +278 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/notebook.py +1 -1
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/offences.py +148 -9
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/plugins/flake8.py +11 -1
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/plugins/pylint.py +11 -0
- python_constricter-0.3.6/constricter/recording.py +583 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/annotations.py +347 -84
- python_constricter-0.3.6/constricter/rules/binding.py +647 -0
- python_constricter-0.3.6/constricter/rules/calls.py +459 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/checker.py +368 -163
- python_constricter-0.3.6/constricter/rules/decorators.py +221 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/flow.py +18 -3
- python_constricter-0.3.6/constricter/rules/keys.py +79 -0
- python_constricter-0.3.6/constricter/rules/late.py +670 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/parsed.py +44 -5
- python_constricter-0.3.6/constricter/rules/quoted.py +141 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/rebinding.py +39 -12
- python_constricter-0.3.6/constricter/rules/recorded.py +167 -0
- python_constricter-0.3.6/constricter/rules/scope.py +995 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/syntax.py +143 -5
- python_constricter-0.3.6/constricter/rules/tables.py +169 -0
- python_constricter-0.3.6/constricter/rules/widened.py +322 -0
- python_constricter-0.3.6/constricter/trace.py +378 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/pyproject.toml +61 -11
- python_constricter-0.3.6/stdlib_tables/elements.py +101 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/generate.py +249 -30
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/overloads.py +146 -36
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/partial.json +25 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/reading.py +93 -10
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/stubs.py +11 -4
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/templates.py +42 -7
- python_constricter-0.3.1/constricter/__main__.py +0 -9
- python_constricter-0.3.1/constricter/cli/runs.py +0 -43
- python_constricter-0.3.1/constricter/cli/schedule.py +0 -230
- python_constricter-0.3.1/constricter/fix/doubts.py +0 -390
- python_constricter-0.3.1/constricter/fix/fills.py +0 -228
- python_constricter-0.3.1/constricter/fix/hinted.py +0 -242
- python_constricter-0.3.1/constricter/fix/imports.py +0 -238
- python_constricter-0.3.1/constricter/fix/known.py +0 -328
- python_constricter-0.3.1/constricter/fix/library.py +0 -164
- python_constricter-0.3.1/constricter/fix/members.py +0 -214
- python_constricter-0.3.1/constricter/fix/modules.py +0 -375
- python_constricter-0.3.1/constricter/fix/opened.py +0 -74
- python_constricter-0.3.1/constricter/fix/returned.py +0 -703
- python_constricter-0.3.1/constricter/fix/tables/generic_attributes.json +0 -55
- python_constricter-0.3.1/constricter/fix/targets.py +0 -184
- python_constricter-0.3.1/constricter/rules/binding.py +0 -251
- python_constricter-0.3.1/constricter/rules/calls.py +0 -211
- python_constricter-0.3.1/constricter/rules/late.py +0 -289
- python_constricter-0.3.1/constricter/rules/scope.py +0 -638
- {python_constricter-0.3.1 → python_constricter-0.3.6}/LICENSE.md +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/__init__.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/baseline.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/collecting.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/guard.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/paths.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/__init__.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/aliases.json +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/bases.json +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/scalar_members.json +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/jsonc.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/noqa.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/plugins/__init__.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/py.typed +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/__init__.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/narrowing.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/redundant.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/walked.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/hatch_build.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/__init__.py +0 -0
- {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/__main__.py +0 -0
|
@@ -17,5 +17,32 @@ stdlib_tables/partial.json
|
|
|
17
17
|
# Claude Code's agent worktrees: other checkouts of this repository.
|
|
18
18
|
.claude/worktrees/
|
|
19
19
|
|
|
20
|
+
# Claude Code's sandbox bind-mounts /dev/null over these denied paths while a
|
|
21
|
+
# session runs; none is a file of this repo
|
|
22
|
+
/.bash_profile
|
|
23
|
+
/.bashrc
|
|
24
|
+
/.gitconfig
|
|
25
|
+
/.gitmodules
|
|
26
|
+
/.idea
|
|
27
|
+
/.mcp.json
|
|
28
|
+
/.profile
|
|
29
|
+
/.ripgreprc
|
|
30
|
+
/.zprofile
|
|
31
|
+
/.zshrc
|
|
32
|
+
/.claude/agents
|
|
33
|
+
/.claude/commands
|
|
34
|
+
/.claude/hooks
|
|
35
|
+
/.claude/launch.json
|
|
36
|
+
/.claude/loop.md
|
|
37
|
+
/.claude/output-styles
|
|
38
|
+
/.claude/routines
|
|
39
|
+
/.claude/scheduled_tasks.json
|
|
40
|
+
/.claude/settings.json
|
|
41
|
+
/.claude/settings.local.json
|
|
42
|
+
/.claude/skills
|
|
43
|
+
/.claude/workflows
|
|
44
|
+
/.claude/.cc-writes
|
|
45
|
+
/.vscode
|
|
46
|
+
|
|
20
47
|
# Backups merge tools leave next to a conflicted file.
|
|
21
48
|
*.orig
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-constricter
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Import-Name: constricter
|
|
5
5
|
Summary: Lint rules: every local variable is typed where it's first bound. A flake8 plugin, a pylint plugin and a standalone CLI.
|
|
6
6
|
Author: Ivy Duggan
|
|
@@ -97,7 +97,7 @@ class bodies too. Statements are read in source order, and only a name's first b
|
|
|
97
97
|
| `LVA002` | an untyped `for` target or `match` capture | `name: T` first (or a type comment) |
|
|
98
98
|
| `LVA003` | a `for` target typed only by `# type: T` | `name: T` first |
|
|
99
99
|
| `LVA004` | with `all-scopes`: the same as `LVA001`, in a module or class body | `name: T = ...` (`ClassVar[T]` in a dataclass) |
|
|
100
|
-
| `LVA005` | an annotation with `Any`, `object` or a generic without its parameters
|
|
100
|
+
| `LVA005` | an annotation with `Any`, `object` or a generic without its parameters, past `vague` | name the real type |
|
|
101
101
|
| `LVA006` | an annotation nested `nesting` deep (3 by default) | a `type` alias for a part of it |
|
|
102
102
|
| `LVA007` | a name annotated again with the type it already has, in the same block | drop the second annotation |
|
|
103
103
|
| `LVA008` | with every value the name ever holds known: an annotation that could narrow to them | narrow it (`total: int`) |
|
|
@@ -109,7 +109,10 @@ class bodies too. Statements are read in source order, and only a name's first b
|
|
|
109
109
|
Exempt: comprehensions, `except ... as`, imports, `def`/`class`, `type` aliases, parameters,
|
|
110
110
|
`global`/`nonlocal`, and `_`; in module and class bodies, dunder names (`__all__`, `__slots__`) and
|
|
111
111
|
enum members (a base imported from `enum`, however it's aliased, or else whose name ends in `Enum`
|
|
112
|
-
or `Flag`).
|
|
112
|
+
or `Flag`). And what makes a type, not a value, which an annotation would turn into a variable to a
|
|
113
|
+
type checker: `T = TypeVar("T")`, a `ParamSpec`, a `TypeVarTuple`, a `NewType`, and a functional
|
|
114
|
+
`NamedTuple`, `TypedDict`, `Enum` or `collections.namedtuple`, however `typing`,
|
|
115
|
+
`typing_extensions`, `enum` or `collections` is imported.
|
|
113
116
|
|
|
114
117
|
A `# type:` comment (`x = 1 # type: int`, `with f() as x: # type: T`) counts as an annotation with
|
|
115
118
|
`type-comments`, or automatically in a module written to run on Python 2: one that imports
|
|
@@ -170,34 +173,39 @@ enabled).
|
|
|
170
173
|
|
|
171
174
|
Options:
|
|
172
175
|
|
|
173
|
-
| Option | CLI
|
|
174
|
-
| ---------------- |
|
|
175
|
-
| level | `--level`
|
|
176
|
-
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012`
|
|
177
|
-
| every fix | `--max-fix`: `--max`, `--fix --unsafe-fixes`, and `--infer-with` each of basedpyright and
|
|
178
|
-
| type comments | `--type-comments`
|
|
179
|
-
| all scopes | `--all-scopes`
|
|
180
|
-
| nesting | `--nesting N`
|
|
181
|
-
| max length | `--max-length N` (LVA011)
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
| per
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
176
|
+
| Option | CLI | `[tool.constricter]` | flake8 (CLI or config) | pylint |
|
|
177
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------- |
|
|
178
|
+
| level | `--level` | `level` | `--constricter-level` | `constricter-level` |
|
|
179
|
+
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012` | — | — | — |
|
|
180
|
+
| every fix | `--max-fix`: `--max`, `--fix --unsafe-fixes`, and `--infer-with` each of basedpyright, ty and pyrefly that's installed and runs | — | — | — |
|
|
181
|
+
| type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
|
|
182
|
+
| all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
|
|
183
|
+
| nesting | `--nesting N` | `nesting` | `--constricter-nesting` | `constricter-nesting` |
|
|
184
|
+
| max length | `--max-length N` (LVA011) | `max-length` | `--constricter-max-length` | `constricter-max-length` |
|
|
185
|
+
| vague | `--vague LEVEL` (LVA005, and the fixes offered): -1 none (the default), 0 one `Any` inside a type that says the rest (`tuple[str, Any]`), N from 1 N + 1, or one alone | `vague` | `--constricter-vague` | `constricter-vague` |
|
|
186
|
+
| min python | `--min-python VERSION` (the oldest Python the code runs on, whose syntax `--fix` writes; default: `requires-python`'s) | `min-python` | - | - |
|
|
187
|
+
| type hierarchy | - | `narrower` (a table) | `--constricter-narrower` | `constricter-narrower` |
|
|
188
|
+
| fix | `--fix` (`--unsafe-fixes` for guesses, `--likely` for those measured to hold), `--diff` to preview | - | - | - |
|
|
189
|
+
| infer with | `--infer-with CHECKERS` (`basedpyright`, `ty`, `pyrefly`, several: inferred types, as guesses) | `infer-with` | - | - |
|
|
190
|
+
| infer from | `--infer-from FILE` (the types `python -m constricter.trace -m pytest` recorded from a run, as guesses) | `infer-from` | - | - |
|
|
191
|
+
| show fixes | `--show-fixes` (each fix and how it was decided; with `--fix`, those made too) | - | - | - |
|
|
192
|
+
| fix levels | `--fix-select`, `--fix-ignore`, `--unsafe-fix-select` (mechanisms: [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md#fix-levels)) | `fix-select`, `fix-ignore`, `unsafe-fix-select` | - | - |
|
|
193
|
+
| fix plain bases | `--fix-plain-bases BASES` (frameworks' classes whose subclasses' bodies `--fix` annotates; django's are built in) | `fix-plain-bases` | - | - |
|
|
194
|
+
| fix widen | `--fix-widen KINDS` (wider types `--fix` may write, each marked: `untyped-parameters`, `empty-containers`, `mixed-containers`, `unions`, `vague`, `untyped-calls`, `unknown-calls`; `all`: every one; [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md#wider-types-fix-widen)) | `fix-widen` | - | - |
|
|
195
|
+
| select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
|
|
196
|
+
| extend select | `--extend-select CODES` (also report these; an opt-in code by its full code) | `extend-select` | flake8's own `extend-select` | pylint's own `enable` |
|
|
197
|
+
| ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
|
|
198
|
+
| exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
|
|
199
|
+
| format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
|
|
200
|
+
| statistics | `--statistics` (counts per code, text format) | - | - | - |
|
|
201
|
+
| jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
|
|
202
|
+
| baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
|
|
203
|
+
| coverage | `--coverage`, `--fail-under PCT` | - | - | - |
|
|
204
|
+
| per-path levels | - | `per-path-levels` | - | - |
|
|
205
|
+
| per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
|
|
206
|
+
| stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
|
|
207
|
+
| exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
|
|
208
|
+
| output file | `--output-file FILE` | - | flake8's own `--output-file` | pylint's own `--output` |
|
|
201
209
|
|
|
202
210
|
`constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
|
|
203
211
|
|
|
@@ -212,6 +220,8 @@ type-comments = false
|
|
|
212
220
|
all-scopes = true
|
|
213
221
|
nesting = 3
|
|
214
222
|
max-length = 4
|
|
223
|
+
vague = -1 # the default: no `Any`; 0 lets `dict[str, Any]` through, 1 `Any` itself
|
|
224
|
+
min-python = "3.11" # the default: your `requires-python`'s lower bound
|
|
215
225
|
jobs = 0
|
|
216
226
|
baseline = "constricter-baseline.json" # the default; relative to this pyproject.toml
|
|
217
227
|
|
|
@@ -235,10 +245,14 @@ ignore = ["LVA003"]
|
|
|
235
245
|
`count: int = 0`), a container whose elements agree, a constructor or a function that declares its
|
|
236
246
|
return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
|
|
237
247
|
method call of a local whose type is known, and values computed from those; a loop's target or an
|
|
238
|
-
unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses, `--likely`
|
|
249
|
+
only those measured to hold as often as a certain fix
|
|
250
|
+
([docs/LIKELY.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/LIKELY.md)),
|
|
251
|
+
and `--show-fixes` lists each fix and how its value decided it, and after `--fix` each one made.
|
|
252
|
+
`--infer-with basedpyright` (or `ty`, or `pyrefly`, or several: `basedpyright,ty`; each installed
|
|
253
|
+
alongside) asks those type checkers for what `--fix` can't type itself, as guesses;
|
|
254
|
+
`--infer-from FILE` takes the types a run bound each name to, recorded by
|
|
255
|
+
`python -m constricter.trace -m pytest`, the same way. The full list is in
|
|
242
256
|
[docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
|
|
243
257
|
|
|
244
258
|
### Installing and running
|
|
@@ -264,7 +278,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
|
|
|
264
278
|
|
|
265
279
|
```yaml
|
|
266
280
|
- repo: https://github.com/ivylikethevine/python-constricter
|
|
267
|
-
rev: v0.3.
|
|
281
|
+
rev: v0.3.6
|
|
268
282
|
hooks:
|
|
269
283
|
- id: constricter
|
|
270
284
|
```
|
|
@@ -297,8 +311,8 @@ default one is used only if it exists.
|
|
|
297
311
|
total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
|
|
298
312
|
and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
|
|
299
313
|
PCT, so CI can hold a codebase to a share. With `--format=json` it prints
|
|
300
|
-
`{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON
|
|
301
|
-
gist, a release asset) and point
|
|
314
|
+
`{"typed", "total", "widened", "percent", "files"}`, which a badge can read: publish that JSON
|
|
315
|
+
somewhere (a gist, a release asset) and point
|
|
302
316
|
[shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
|
|
303
317
|
`$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
|
|
304
318
|
|
|
@@ -70,7 +70,7 @@ class bodies too. Statements are read in source order, and only a name's first b
|
|
|
70
70
|
| `LVA002` | an untyped `for` target or `match` capture | `name: T` first (or a type comment) |
|
|
71
71
|
| `LVA003` | a `for` target typed only by `# type: T` | `name: T` first |
|
|
72
72
|
| `LVA004` | with `all-scopes`: the same as `LVA001`, in a module or class body | `name: T = ...` (`ClassVar[T]` in a dataclass) |
|
|
73
|
-
| `LVA005` | an annotation with `Any`, `object` or a generic without its parameters
|
|
73
|
+
| `LVA005` | an annotation with `Any`, `object` or a generic without its parameters, past `vague` | name the real type |
|
|
74
74
|
| `LVA006` | an annotation nested `nesting` deep (3 by default) | a `type` alias for a part of it |
|
|
75
75
|
| `LVA007` | a name annotated again with the type it already has, in the same block | drop the second annotation |
|
|
76
76
|
| `LVA008` | with every value the name ever holds known: an annotation that could narrow to them | narrow it (`total: int`) |
|
|
@@ -82,7 +82,10 @@ class bodies too. Statements are read in source order, and only a name's first b
|
|
|
82
82
|
Exempt: comprehensions, `except ... as`, imports, `def`/`class`, `type` aliases, parameters,
|
|
83
83
|
`global`/`nonlocal`, and `_`; in module and class bodies, dunder names (`__all__`, `__slots__`) and
|
|
84
84
|
enum members (a base imported from `enum`, however it's aliased, or else whose name ends in `Enum`
|
|
85
|
-
or `Flag`).
|
|
85
|
+
or `Flag`). And what makes a type, not a value, which an annotation would turn into a variable to a
|
|
86
|
+
type checker: `T = TypeVar("T")`, a `ParamSpec`, a `TypeVarTuple`, a `NewType`, and a functional
|
|
87
|
+
`NamedTuple`, `TypedDict`, `Enum` or `collections.namedtuple`, however `typing`,
|
|
88
|
+
`typing_extensions`, `enum` or `collections` is imported.
|
|
86
89
|
|
|
87
90
|
A `# type:` comment (`x = 1 # type: int`, `with f() as x: # type: T`) counts as an annotation with
|
|
88
91
|
`type-comments`, or automatically in a module written to run on Python 2: one that imports
|
|
@@ -143,34 +146,39 @@ enabled).
|
|
|
143
146
|
|
|
144
147
|
Options:
|
|
145
148
|
|
|
146
|
-
| Option | CLI
|
|
147
|
-
| ---------------- |
|
|
148
|
-
| level | `--level`
|
|
149
|
-
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012`
|
|
150
|
-
| every fix | `--max-fix`: `--max`, `--fix --unsafe-fixes`, and `--infer-with` each of basedpyright and
|
|
151
|
-
| type comments | `--type-comments`
|
|
152
|
-
| all scopes | `--all-scopes`
|
|
153
|
-
| nesting | `--nesting N`
|
|
154
|
-
| max length | `--max-length N` (LVA011)
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
| per
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
|
149
|
+
| Option | CLI | `[tool.constricter]` | flake8 (CLI or config) | pylint |
|
|
150
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------- |
|
|
151
|
+
| level | `--level` | `level` | `--constricter-level` | `constricter-level` |
|
|
152
|
+
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012` | — | — | — |
|
|
153
|
+
| every fix | `--max-fix`: `--max`, `--fix --unsafe-fixes`, and `--infer-with` each of basedpyright, ty and pyrefly that's installed and runs | — | — | — |
|
|
154
|
+
| type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
|
|
155
|
+
| all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
|
|
156
|
+
| nesting | `--nesting N` | `nesting` | `--constricter-nesting` | `constricter-nesting` |
|
|
157
|
+
| max length | `--max-length N` (LVA011) | `max-length` | `--constricter-max-length` | `constricter-max-length` |
|
|
158
|
+
| vague | `--vague LEVEL` (LVA005, and the fixes offered): -1 none (the default), 0 one `Any` inside a type that says the rest (`tuple[str, Any]`), N from 1 N + 1, or one alone | `vague` | `--constricter-vague` | `constricter-vague` |
|
|
159
|
+
| min python | `--min-python VERSION` (the oldest Python the code runs on, whose syntax `--fix` writes; default: `requires-python`'s) | `min-python` | - | - |
|
|
160
|
+
| type hierarchy | - | `narrower` (a table) | `--constricter-narrower` | `constricter-narrower` |
|
|
161
|
+
| fix | `--fix` (`--unsafe-fixes` for guesses, `--likely` for those measured to hold), `--diff` to preview | - | - | - |
|
|
162
|
+
| infer with | `--infer-with CHECKERS` (`basedpyright`, `ty`, `pyrefly`, several: inferred types, as guesses) | `infer-with` | - | - |
|
|
163
|
+
| infer from | `--infer-from FILE` (the types `python -m constricter.trace -m pytest` recorded from a run, as guesses) | `infer-from` | - | - |
|
|
164
|
+
| show fixes | `--show-fixes` (each fix and how it was decided; with `--fix`, those made too) | - | - | - |
|
|
165
|
+
| fix levels | `--fix-select`, `--fix-ignore`, `--unsafe-fix-select` (mechanisms: [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md#fix-levels)) | `fix-select`, `fix-ignore`, `unsafe-fix-select` | - | - |
|
|
166
|
+
| fix plain bases | `--fix-plain-bases BASES` (frameworks' classes whose subclasses' bodies `--fix` annotates; django's are built in) | `fix-plain-bases` | - | - |
|
|
167
|
+
| fix widen | `--fix-widen KINDS` (wider types `--fix` may write, each marked: `untyped-parameters`, `empty-containers`, `mixed-containers`, `unions`, `vague`, `untyped-calls`, `unknown-calls`; `all`: every one; [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md#wider-types-fix-widen)) | `fix-widen` | - | - |
|
|
168
|
+
| select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
|
|
169
|
+
| extend select | `--extend-select CODES` (also report these; an opt-in code by its full code) | `extend-select` | flake8's own `extend-select` | pylint's own `enable` |
|
|
170
|
+
| ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
|
|
171
|
+
| exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
|
|
172
|
+
| format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
|
|
173
|
+
| statistics | `--statistics` (counts per code, text format) | - | - | - |
|
|
174
|
+
| jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
|
|
175
|
+
| baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
|
|
176
|
+
| coverage | `--coverage`, `--fail-under PCT` | - | - | - |
|
|
177
|
+
| per-path levels | - | `per-path-levels` | - | - |
|
|
178
|
+
| per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
|
|
179
|
+
| stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
|
|
180
|
+
| exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
|
|
181
|
+
| output file | `--output-file FILE` | - | flake8's own `--output-file` | pylint's own `--output` |
|
|
174
182
|
|
|
175
183
|
`constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
|
|
176
184
|
|
|
@@ -185,6 +193,8 @@ type-comments = false
|
|
|
185
193
|
all-scopes = true
|
|
186
194
|
nesting = 3
|
|
187
195
|
max-length = 4
|
|
196
|
+
vague = -1 # the default: no `Any`; 0 lets `dict[str, Any]` through, 1 `Any` itself
|
|
197
|
+
min-python = "3.11" # the default: your `requires-python`'s lower bound
|
|
188
198
|
jobs = 0
|
|
189
199
|
baseline = "constricter-baseline.json" # the default; relative to this pyproject.toml
|
|
190
200
|
|
|
@@ -208,10 +218,14 @@ ignore = ["LVA003"]
|
|
|
208
218
|
`count: int = 0`), a container whose elements agree, a constructor or a function that declares its
|
|
209
219
|
return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
|
|
210
220
|
method call of a local whose type is known, and values computed from those; a loop's target or an
|
|
211
|
-
unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses, `--likely`
|
|
222
|
+
only those measured to hold as often as a certain fix
|
|
223
|
+
([docs/LIKELY.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/LIKELY.md)),
|
|
224
|
+
and `--show-fixes` lists each fix and how its value decided it, and after `--fix` each one made.
|
|
225
|
+
`--infer-with basedpyright` (or `ty`, or `pyrefly`, or several: `basedpyright,ty`; each installed
|
|
226
|
+
alongside) asks those type checkers for what `--fix` can't type itself, as guesses;
|
|
227
|
+
`--infer-from FILE` takes the types a run bound each name to, recorded by
|
|
228
|
+
`python -m constricter.trace -m pytest`, the same way. The full list is in
|
|
215
229
|
[docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
|
|
216
230
|
|
|
217
231
|
### Installing and running
|
|
@@ -237,7 +251,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
|
|
|
237
251
|
|
|
238
252
|
```yaml
|
|
239
253
|
- repo: https://github.com/ivylikethevine/python-constricter
|
|
240
|
-
rev: v0.3.
|
|
254
|
+
rev: v0.3.6
|
|
241
255
|
hooks:
|
|
242
256
|
- id: constricter
|
|
243
257
|
```
|
|
@@ -270,8 +284,8 @@ default one is used only if it exists.
|
|
|
270
284
|
total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
|
|
271
285
|
and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
|
|
272
286
|
PCT, so CI can hold a codebase to a share. With `--format=json` it prints
|
|
273
|
-
`{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON
|
|
274
|
-
gist, a release asset) and point
|
|
287
|
+
`{"typed", "total", "widened", "percent", "files"}`, which a badge can read: publish that JSON
|
|
288
|
+
somewhere (a gist, a release asset) and point
|
|
275
289
|
[shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
|
|
276
290
|
`$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
|
|
277
291
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""`python -m constricter`, and the `constricter` script."""
|
|
3
|
+
|
|
4
|
+
import contextlib
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from constricter.cli.command import main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run() -> None:
|
|
12
|
+
"""Run the command, and leave with its status at once.
|
|
13
|
+
|
|
14
|
+
Not by the interpreter's own exit, which frees every parsed file and the index an object at a
|
|
15
|
+
time: a fifth of a check of a thousand files. What the command wrote is flushed first; nothing
|
|
16
|
+
else waits on the exit (its workers have ended, its output file is closed).
|
|
17
|
+
"""
|
|
18
|
+
status: int = main()
|
|
19
|
+
with contextlib.suppress(OSError, ValueError): # a reader that left, a stream closed already
|
|
20
|
+
_ = sys.stdout.flush()
|
|
21
|
+
_ = sys.stderr.flush()
|
|
22
|
+
os._exit(status)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__": # not when `--jobs` workers import it
|
|
26
|
+
run()
|