python-constricter 0.2.5__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.6/.gitignore +48 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/PKG-INFO +61 -45
- {python_constricter-0.2.5 → python_constricter-0.3.6}/README.md +54 -37
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/__init__.py +1 -1
- python_constricter-0.3.6/constricter/__main__.py +26 -0
- python_constricter-0.3.6/constricter/cli/collecting.py +47 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/command.py +338 -284
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/config.py +102 -28
- python_constricter-0.3.6/constricter/cli/edits.py +188 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/explain.py +2 -1
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/hints.py +189 -157
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/options.py +187 -10
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/paths.py +31 -0
- python_constricter-0.3.6/constricter/cli/protocol.py +192 -0
- python_constricter-0.3.6/constricter/cli/remembered.py +52 -0
- {python_constricter-0.2.5 → 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.6/constricter/cli/workers.py +216 -0
- 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.6/constricter/fix/core/fixes.py +317 -0
- 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.6/constricter/fix/core/signatures.py +125 -0
- python_constricter-0.3.6/constricter/fix/index/__init__.py +2 -0
- python_constricter-0.3.6/constricter/fix/index/atoms.py +425 -0
- 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.6/constricter/fix/index/callers.py +154 -0
- python_constricter-0.3.6/constricter/fix/index/chained.py +69 -0
- python_constricter-0.3.6/constricter/fix/index/classnames.py +124 -0
- python_constricter-0.3.6/constricter/fix/index/declared.py +506 -0
- 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.6/constricter/fix/index/installed.py +348 -0
- 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.6/constricter/fix/index/order.py +155 -0
- 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.6/constricter/fix/index/project.py +980 -0
- 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.6/constricter/fix/index/stubbed.py +1000 -0
- 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.6/constricter/fix/libraries/overloads.py +968 -0
- python_constricter-0.3.6/constricter/fix/libraries/stdlib.py +740 -0
- python_constricter-0.3.6/constricter/fix/libraries/walks.py +49 -0
- python_constricter-0.3.6/constricter/fix/tables/aliases.json +147 -0
- python_constricter-0.3.6/constricter/fix/tables/attributes.json +700 -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.6/constricter/fix/tables/bases.json +888 -0
- python_constricter-0.3.6/constricter/fix/tables/classes.json +1941 -0
- 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.6/constricter/fix/tables/method_overloads.json +461 -0
- python_constricter-0.3.6/constricter/fix/tables/method_signatures.json +896 -0
- python_constricter-0.3.6/constricter/fix/tables/methods.json +954 -0
- python_constricter-0.3.6/constricter/fix/tables/overloads.json +537 -0
- python_constricter-0.3.6/constricter/fix/tables/returns.json +1362 -0
- python_constricter-0.3.6/constricter/fix/tables/scalar_members.json +16 -0
- python_constricter-0.3.6/constricter/fix/tables/scalars.json +2707 -0
- python_constricter-0.3.6/constricter/fix/tables/source.json +26 -0
- python_constricter-0.3.6/constricter/fix/tables/subscriptable.json +77 -0
- python_constricter-0.3.6/constricter/fix/tables/type_parameters.json +178 -0
- python_constricter-0.3.6/constricter/fix/tables/variables.json +2959 -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.6/constricter/fix/values/guesses.py +370 -0
- python_constricter-0.3.6/constricter/fix/values/hinted.py +645 -0
- python_constricter-0.3.6/constricter/fix/values/inference.py +996 -0
- python_constricter-0.3.6/constricter/fix/values/members.py +575 -0
- python_constricter-0.3.6/constricter/fix/values/narrowed.py +220 -0
- 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.2.5/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.2.5 → python_constricter-0.3.6}/constricter/notebook.py +1 -1
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/offences.py +155 -10
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/plugins/flake8.py +11 -1
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/plugins/pylint.py +11 -0
- python_constricter-0.3.6/constricter/recording.py +583 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/annotations.py +376 -59
- 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.6/constricter/rules/checker.py +950 -0
- python_constricter-0.3.6/constricter/rules/decorators.py +221 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/flow.py +20 -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.2.5 → python_constricter-0.3.6}/constricter/rules/narrowing.py +48 -1
- python_constricter-0.3.6/constricter/rules/parsed.py +130 -0
- python_constricter-0.3.6/constricter/rules/quoted.py +141 -0
- {python_constricter-0.2.5 → 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.2.5 → python_constricter-0.3.6}/constricter/rules/redundant.py +2 -8
- python_constricter-0.3.6/constricter/rules/scope.py +995 -0
- python_constricter-0.3.6/constricter/rules/syntax.py +472 -0
- python_constricter-0.3.6/constricter/rules/tables.py +169 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/walked.py +59 -7
- 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.6/hatch_build.py +78 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/pyproject.toml +103 -22
- python_constricter-0.3.6/stdlib_tables/__init__.py +16 -0
- python_constricter-0.3.6/stdlib_tables/__main__.py +8 -0
- python_constricter-0.3.6/stdlib_tables/elements.py +101 -0
- python_constricter-0.3.6/stdlib_tables/generate.py +996 -0
- python_constricter-0.3.6/stdlib_tables/overloads.py +999 -0
- python_constricter-0.3.6/stdlib_tables/partial.json +999 -0
- python_constricter-0.3.6/stdlib_tables/reading.py +847 -0
- python_constricter-0.3.6/stdlib_tables/stubs.py +532 -0
- python_constricter-0.3.6/stdlib_tables/templates.py +296 -0
- python_constricter-0.2.5/constricter/__main__.py +0 -9
- python_constricter-0.2.5/constricter/fix/doubts.py +0 -288
- python_constricter-0.2.5/constricter/fix/fills.py +0 -226
- python_constricter-0.2.5/constricter/fix/fixes.py +0 -192
- python_constricter-0.2.5/constricter/fix/guesses.py +0 -211
- python_constricter-0.2.5/constricter/fix/hinted.py +0 -242
- python_constricter-0.2.5/constricter/fix/imports.py +0 -174
- python_constricter-0.2.5/constricter/fix/inference.py +0 -748
- python_constricter-0.2.5/constricter/fix/known.py +0 -189
- python_constricter-0.2.5/constricter/fix/members.py +0 -197
- python_constricter-0.2.5/constricter/fix/opened.py +0 -74
- python_constricter-0.2.5/constricter/fix/project.py +0 -491
- python_constricter-0.2.5/constricter/fix/returned.py +0 -384
- python_constricter-0.2.5/constricter/fix/stdlib.json +0 -15351
- python_constricter-0.2.5/constricter/fix/stdlib.py +0 -186
- python_constricter-0.2.5/constricter/fix/targets.py +0 -184
- python_constricter-0.2.5/constricter/rules/checker.py +0 -808
- python_constricter-0.2.5/constricter/rules/parsed.py +0 -84
- python_constricter-0.2.5/constricter/rules/scope.py +0 -730
- python_constricter-0.2.5/constricter/rules/syntax.py +0 -239
- {python_constricter-0.2.5 → python_constricter-0.3.6}/LICENSE.md +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/__init__.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/baseline.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/guard.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/fix/__init__.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/jsonc.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/noqa.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/plugins/__init__.py +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/py.typed +0 -0
- {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/__init__.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Everything generated locally (the dev venv, tool caches, coverage data, build output) lives under local/.
|
|
2
|
+
local/
|
|
3
|
+
|
|
4
|
+
# Written next to the sources by Python.
|
|
5
|
+
__pycache__/
|
|
6
|
+
|
|
7
|
+
# Generated by a build (hatch_build.py, or `python -m stdlib_tables`): the standard-library tables, and
|
|
8
|
+
# the tests' list of what only some platforms and Pythons have.
|
|
9
|
+
constricter/fix/tables/
|
|
10
|
+
stdlib_tables/partial.json
|
|
11
|
+
|
|
12
|
+
# npm installs of the pinned CI Markdown tools (.github/package.json)
|
|
13
|
+
.github/node_modules/
|
|
14
|
+
|
|
15
|
+
.hypothesis
|
|
16
|
+
|
|
17
|
+
# Claude Code's agent worktrees: other checkouts of this repository.
|
|
18
|
+
.claude/worktrees/
|
|
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
|
+
|
|
47
|
+
# Backups merge tools leave next to a conflicted file.
|
|
48
|
+
*.orig
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-constricter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.6
|
|
4
|
+
Import-Name: constricter
|
|
4
5
|
Summary: Lint rules: every local variable is typed where it's first bound. A flake8 plugin, a pylint plugin and a standalone CLI.
|
|
5
6
|
Author: Ivy Duggan
|
|
6
|
-
Requires-Python: >=3.11
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
7
|
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE.md
|
|
9
9
|
Classifier: Framework :: Flake8
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
19
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
|
-
|
|
22
|
-
Requires-Dist: flake8>=7 ; extra == "flake8"
|
|
23
|
-
Requires-Dist: pylint>=3 ; extra == "pylint"
|
|
21
|
+
Requires-Python: >=3.11
|
|
24
22
|
Provides-Extra: flake8
|
|
23
|
+
Requires-Dist: flake8>=7; extra == 'flake8'
|
|
25
24
|
Provides-Extra: pylint
|
|
26
|
-
|
|
25
|
+
Requires-Dist: pylint>=3; extra == 'pylint'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
28
28
|
# python-con`strict`er
|
|
29
29
|
|
|
@@ -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,32 +173,39 @@ enabled).
|
|
|
170
173
|
|
|
171
174
|
Options:
|
|
172
175
|
|
|
173
|
-
| Option | CLI
|
|
174
|
-
| ---------------- |
|
|
175
|
-
| level | `--level`
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
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` |
|
|
199
209
|
|
|
200
210
|
`constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
|
|
201
211
|
|
|
@@ -210,6 +220,8 @@ type-comments = false
|
|
|
210
220
|
all-scopes = true
|
|
211
221
|
nesting = 3
|
|
212
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
|
|
213
225
|
jobs = 0
|
|
214
226
|
baseline = "constricter-baseline.json" # the default; relative to this pyproject.toml
|
|
215
227
|
|
|
@@ -231,12 +243,17 @@ ignore = ["LVA003"]
|
|
|
231
243
|
|
|
232
244
|
`--fix` adds the annotation where the value decides it: a literal (`count = 0` becomes
|
|
233
245
|
`count: int = 0`), a container whose elements agree, a constructor or a function that declares its
|
|
234
|
-
return type (in another checked file too), a copy, subscript, attribute or
|
|
235
|
-
whose type is known, and values computed from those; a loop's target or an
|
|
236
|
-
declaration on the line before. `--unsafe-fixes` adds guesses,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
|
|
247
|
+
method call of a local whose type is known, and values computed from those; a loop's target or an
|
|
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
|
|
256
|
+
[docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
|
|
240
257
|
|
|
241
258
|
### Installing and running
|
|
242
259
|
|
|
@@ -261,7 +278,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
|
|
|
261
278
|
|
|
262
279
|
```yaml
|
|
263
280
|
- repo: https://github.com/ivylikethevine/python-constricter
|
|
264
|
-
rev: v0.
|
|
281
|
+
rev: v0.3.6
|
|
265
282
|
hooks:
|
|
266
283
|
- id: constricter
|
|
267
284
|
```
|
|
@@ -294,8 +311,8 @@ default one is used only if it exists.
|
|
|
294
311
|
total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
|
|
295
312
|
and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
|
|
296
313
|
PCT, so CI can hold a codebase to a share. With `--format=json` it prints
|
|
297
|
-
`{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON
|
|
298
|
-
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
|
|
299
316
|
[shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
|
|
300
317
|
`$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
|
|
301
318
|
|
|
@@ -375,4 +392,3 @@ I have used generative AI to write large parts of this code. All of the code her
|
|
|
375
392
|
_responsibility_ regardless: AI is a tool, not an owner of a project. I have personally understood,
|
|
376
393
|
reviewed, and approved all of the AI-generated code in this repository, and **mainline releases**
|
|
377
394
|
carry the same accountability to me as anything I write and publish myself.
|
|
378
|
-
|
|
@@ -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,32 +146,39 @@ enabled).
|
|
|
143
146
|
|
|
144
147
|
Options:
|
|
145
148
|
|
|
146
|
-
| Option | CLI
|
|
147
|
-
| ---------------- |
|
|
148
|
-
| level | `--level`
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
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` |
|
|
172
182
|
|
|
173
183
|
`constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
|
|
174
184
|
|
|
@@ -183,6 +193,8 @@ type-comments = false
|
|
|
183
193
|
all-scopes = true
|
|
184
194
|
nesting = 3
|
|
185
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
|
|
186
198
|
jobs = 0
|
|
187
199
|
baseline = "constricter-baseline.json" # the default; relative to this pyproject.toml
|
|
188
200
|
|
|
@@ -204,12 +216,17 @@ ignore = ["LVA003"]
|
|
|
204
216
|
|
|
205
217
|
`--fix` adds the annotation where the value decides it: a literal (`count = 0` becomes
|
|
206
218
|
`count: int = 0`), a container whose elements agree, a constructor or a function that declares its
|
|
207
|
-
return type (in another checked file too), a copy, subscript, attribute or
|
|
208
|
-
whose type is known, and values computed from those; a loop's target or an
|
|
209
|
-
declaration on the line before. `--unsafe-fixes` adds guesses,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
|
|
220
|
+
method call of a local whose type is known, and values computed from those; a loop's target or an
|
|
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
|
|
229
|
+
[docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
|
|
213
230
|
|
|
214
231
|
### Installing and running
|
|
215
232
|
|
|
@@ -234,7 +251,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
|
|
|
234
251
|
|
|
235
252
|
```yaml
|
|
236
253
|
- repo: https://github.com/ivylikethevine/python-constricter
|
|
237
|
-
rev: v0.
|
|
254
|
+
rev: v0.3.6
|
|
238
255
|
hooks:
|
|
239
256
|
- id: constricter
|
|
240
257
|
```
|
|
@@ -267,8 +284,8 @@ default one is used only if it exists.
|
|
|
267
284
|
total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
|
|
268
285
|
and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
|
|
269
286
|
PCT, so CI can hold a codebase to a share. With `--format=json` it prints
|
|
270
|
-
`{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON
|
|
271
|
-
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
|
|
272
289
|
[shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
|
|
273
290
|
`$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
|
|
274
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()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""The garbage collector, as the command runs it: by hand, once per file checked.
|
|
3
|
+
|
|
4
|
+
A check keeps every file's tree (see `parsed`), and allocates a great deal for each file, little of it
|
|
5
|
+
cyclic. Left to itself, the collector looked through the kept trees again and again as the check
|
|
6
|
+
allocated: a third of the standard library's check (34s, 21s without). So while the command runs,
|
|
7
|
+
it's off: what indexing built is frozen (never looked through again), and after each file, only
|
|
8
|
+
what's been allocated since (the young generations) is collected, which bounds what a cycle can hold.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import gc
|
|
13
|
+
from collections.abc import Generator
|
|
14
|
+
from typing import Final
|
|
15
|
+
|
|
16
|
+
_YOUNG: Final = 1 # the generations collected after each file: 0 and 1, never the frozen or the old
|
|
17
|
+
_FREEZES: Final = hasattr(gc, "freeze") # PyPy's collector has no freezing
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@contextlib.contextmanager
|
|
21
|
+
def by_hand() -> Generator[None]:
|
|
22
|
+
"""Turn automatic collection off for the command's run, and back as it was after.
|
|
23
|
+
|
|
24
|
+
Yields:
|
|
25
|
+
Nothing: the run.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
was: bool = gc.isenabled()
|
|
29
|
+
gc.disable()
|
|
30
|
+
try:
|
|
31
|
+
yield
|
|
32
|
+
finally:
|
|
33
|
+
if _FREEZES:
|
|
34
|
+
gc.unfreeze()
|
|
35
|
+
if was:
|
|
36
|
+
gc.enable()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def indexed() -> None:
|
|
40
|
+
"""Freeze what indexing built (the kept trees, the index): it lives until the check takes it."""
|
|
41
|
+
if _FREEZES:
|
|
42
|
+
gc.freeze()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def sweep() -> None:
|
|
46
|
+
"""Collect what the file just checked left behind (see the module docstring)."""
|
|
47
|
+
_ = gc.collect(_YOUNG)
|