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.
Files changed (169) hide show
  1. {python_constricter-0.3.1 → python_constricter-0.3.6}/.gitignore +27 -0
  2. {python_constricter-0.3.1 → python_constricter-0.3.6}/PKG-INFO +52 -38
  3. {python_constricter-0.3.1 → python_constricter-0.3.6}/README.md +51 -37
  4. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/__init__.py +1 -1
  5. python_constricter-0.3.6/constricter/__main__.py +26 -0
  6. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/command.py +222 -99
  7. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/config.py +94 -13
  8. python_constricter-0.3.6/constricter/cli/edits.py +188 -0
  9. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/explain.py +2 -1
  10. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/hints.py +139 -40
  11. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/options.py +151 -8
  12. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/protocol.py +30 -5
  13. python_constricter-0.3.6/constricter/cli/remembered.py +52 -0
  14. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/report.py +11 -6
  15. python_constricter-0.3.6/constricter/cli/runs.py +90 -0
  16. python_constricter-0.3.6/constricter/cli/schedule.py +415 -0
  17. python_constricter-0.3.6/constricter/cli/traced.py +401 -0
  18. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/workers.py +86 -24
  19. python_constricter-0.3.6/constricter/fix/core/__init__.py +2 -0
  20. python_constricter-0.3.6/constricter/fix/core/asked.py +56 -0
  21. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/core}/fixes.py +115 -30
  22. python_constricter-0.3.6/constricter/fix/core/imports.py +527 -0
  23. python_constricter-0.3.6/constricter/fix/core/inherited.py +310 -0
  24. python_constricter-0.3.6/constricter/fix/core/known.py +700 -0
  25. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/core}/signatures.py +36 -13
  26. python_constricter-0.3.6/constricter/fix/index/__init__.py +2 -0
  27. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/atoms.py +29 -11
  28. python_constricter-0.3.6/constricter/fix/index/awaits.py +90 -0
  29. python_constricter-0.3.6/constricter/fix/index/beyond.py +148 -0
  30. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/callers.py +6 -5
  31. python_constricter-0.3.6/constricter/fix/index/chained.py +69 -0
  32. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/classnames.py +22 -5
  33. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/declared.py +84 -4
  34. python_constricter-0.3.6/constricter/fix/index/decorated.py +121 -0
  35. python_constricter-0.3.6/constricter/fix/index/fixtures.py +176 -0
  36. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/installed.py +69 -16
  37. python_constricter-0.3.6/constricter/fix/index/linked.py +142 -0
  38. python_constricter-0.3.6/constricter/fix/index/loose.py +98 -0
  39. python_constricter-0.3.6/constricter/fix/index/managed.py +63 -0
  40. python_constricter-0.3.6/constricter/fix/index/modules.py +849 -0
  41. python_constricter-0.3.6/constricter/fix/index/offers.py +138 -0
  42. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/order.py +14 -4
  43. python_constricter-0.3.6/constricter/fix/index/own_methods.py +67 -0
  44. python_constricter-0.3.6/constricter/fix/index/own_overloads.py +70 -0
  45. python_constricter-0.3.6/constricter/fix/index/own_types.py +161 -0
  46. python_constricter-0.3.6/constricter/fix/index/plain.py +125 -0
  47. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/project.py +389 -164
  48. python_constricter-0.3.6/constricter/fix/index/read.py +70 -0
  49. python_constricter-0.3.6/constricter/fix/index/sides.py +160 -0
  50. python_constricter-0.3.6/constricter/fix/index/strings.py +46 -0
  51. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/index}/stubbed.py +105 -95
  52. python_constricter-0.3.6/constricter/fix/index/taken.py +45 -0
  53. python_constricter-0.3.6/constricter/fix/index/tuples.py +112 -0
  54. python_constricter-0.3.6/constricter/fix/libraries/__init__.py +2 -0
  55. python_constricter-0.3.6/constricter/fix/libraries/library.py +374 -0
  56. python_constricter-0.3.6/constricter/fix/libraries/opened.py +147 -0
  57. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/libraries}/overloads.py +157 -16
  58. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/libraries}/stdlib.py +354 -18
  59. python_constricter-0.3.6/constricter/fix/libraries/walks.py +49 -0
  60. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/attributes.json +25 -0
  61. python_constricter-0.3.6/constricter/fix/tables/awaited.json +69 -0
  62. python_constricter-0.3.6/constricter/fix/tables/awaited_overloads.json +14 -0
  63. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/classes.json +3 -1
  64. python_constricter-0.3.6/constricter/fix/tables/elements.json +156 -0
  65. python_constricter-0.3.6/constricter/fix/tables/functions.json +43 -0
  66. python_constricter-0.3.6/constricter/fix/tables/generic_attributes.json +254 -0
  67. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/method_overloads.json +177 -69
  68. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/method_signatures.json +280 -39
  69. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/methods.json +185 -88
  70. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/overloads.json +106 -68
  71. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/returns.json +34 -0
  72. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/scalars.json +5 -0
  73. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/source.json +5 -1
  74. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/subscriptable.json +1 -0
  75. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/type_parameters.json +3 -0
  76. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/variables.json +21 -0
  77. python_constricter-0.3.6/constricter/fix/values/__init__.py +2 -0
  78. python_constricter-0.3.6/constricter/fix/values/aliased.py +195 -0
  79. python_constricter-0.3.6/constricter/fix/values/bodies.py +76 -0
  80. python_constricter-0.3.6/constricter/fix/values/callables.py +321 -0
  81. python_constricter-0.3.6/constricter/fix/values/called.py +106 -0
  82. python_constricter-0.3.6/constricter/fix/values/classvars.py +541 -0
  83. python_constricter-0.3.6/constricter/fix/values/decided.py +396 -0
  84. python_constricter-0.3.6/constricter/fix/values/displays.py +87 -0
  85. python_constricter-0.3.6/constricter/fix/values/doubts.py +673 -0
  86. python_constricter-0.3.6/constricter/fix/values/entered.py +331 -0
  87. python_constricter-0.3.6/constricter/fix/values/fills.py +435 -0
  88. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/guesses.py +145 -53
  89. python_constricter-0.3.6/constricter/fix/values/hinted.py +645 -0
  90. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/inference.py +404 -211
  91. python_constricter-0.3.6/constricter/fix/values/members.py +575 -0
  92. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/narrowed.py +60 -4
  93. python_constricter-0.3.6/constricter/fix/values/operated.py +200 -0
  94. python_constricter-0.3.6/constricter/fix/values/ordered.py +74 -0
  95. python_constricter-0.3.6/constricter/fix/values/returned.py +967 -0
  96. {python_constricter-0.3.1/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/returns.py +46 -2
  97. python_constricter-0.3.6/constricter/fix/values/shapes.py +579 -0
  98. python_constricter-0.3.6/constricter/fix/values/targets.py +383 -0
  99. python_constricter-0.3.6/constricter/fix/values/unchecked.py +278 -0
  100. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/notebook.py +1 -1
  101. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/offences.py +148 -9
  102. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/plugins/flake8.py +11 -1
  103. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/plugins/pylint.py +11 -0
  104. python_constricter-0.3.6/constricter/recording.py +583 -0
  105. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/annotations.py +347 -84
  106. python_constricter-0.3.6/constricter/rules/binding.py +647 -0
  107. python_constricter-0.3.6/constricter/rules/calls.py +459 -0
  108. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/checker.py +368 -163
  109. python_constricter-0.3.6/constricter/rules/decorators.py +221 -0
  110. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/flow.py +18 -3
  111. python_constricter-0.3.6/constricter/rules/keys.py +79 -0
  112. python_constricter-0.3.6/constricter/rules/late.py +670 -0
  113. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/parsed.py +44 -5
  114. python_constricter-0.3.6/constricter/rules/quoted.py +141 -0
  115. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/rebinding.py +39 -12
  116. python_constricter-0.3.6/constricter/rules/recorded.py +167 -0
  117. python_constricter-0.3.6/constricter/rules/scope.py +995 -0
  118. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/syntax.py +143 -5
  119. python_constricter-0.3.6/constricter/rules/tables.py +169 -0
  120. python_constricter-0.3.6/constricter/rules/widened.py +322 -0
  121. python_constricter-0.3.6/constricter/trace.py +378 -0
  122. {python_constricter-0.3.1 → python_constricter-0.3.6}/pyproject.toml +61 -11
  123. python_constricter-0.3.6/stdlib_tables/elements.py +101 -0
  124. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/generate.py +249 -30
  125. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/overloads.py +146 -36
  126. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/partial.json +25 -0
  127. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/reading.py +93 -10
  128. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/stubs.py +11 -4
  129. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/templates.py +42 -7
  130. python_constricter-0.3.1/constricter/__main__.py +0 -9
  131. python_constricter-0.3.1/constricter/cli/runs.py +0 -43
  132. python_constricter-0.3.1/constricter/cli/schedule.py +0 -230
  133. python_constricter-0.3.1/constricter/fix/doubts.py +0 -390
  134. python_constricter-0.3.1/constricter/fix/fills.py +0 -228
  135. python_constricter-0.3.1/constricter/fix/hinted.py +0 -242
  136. python_constricter-0.3.1/constricter/fix/imports.py +0 -238
  137. python_constricter-0.3.1/constricter/fix/known.py +0 -328
  138. python_constricter-0.3.1/constricter/fix/library.py +0 -164
  139. python_constricter-0.3.1/constricter/fix/members.py +0 -214
  140. python_constricter-0.3.1/constricter/fix/modules.py +0 -375
  141. python_constricter-0.3.1/constricter/fix/opened.py +0 -74
  142. python_constricter-0.3.1/constricter/fix/returned.py +0 -703
  143. python_constricter-0.3.1/constricter/fix/tables/generic_attributes.json +0 -55
  144. python_constricter-0.3.1/constricter/fix/targets.py +0 -184
  145. python_constricter-0.3.1/constricter/rules/binding.py +0 -251
  146. python_constricter-0.3.1/constricter/rules/calls.py +0 -211
  147. python_constricter-0.3.1/constricter/rules/late.py +0 -289
  148. python_constricter-0.3.1/constricter/rules/scope.py +0 -638
  149. {python_constricter-0.3.1 → python_constricter-0.3.6}/LICENSE.md +0 -0
  150. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/__init__.py +0 -0
  151. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/baseline.py +0 -0
  152. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/collecting.py +0 -0
  153. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/guard.py +0 -0
  154. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/cli/paths.py +0 -0
  155. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/__init__.py +0 -0
  156. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/aliases.json +0 -0
  157. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/bases.json +0 -0
  158. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/fix/tables/scalar_members.json +0 -0
  159. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/jsonc.py +0 -0
  160. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/noqa.py +0 -0
  161. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/plugins/__init__.py +0 -0
  162. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/py.typed +0 -0
  163. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/__init__.py +0 -0
  164. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/narrowing.py +0 -0
  165. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/redundant.py +0 -0
  166. {python_constricter-0.3.1 → python_constricter-0.3.6}/constricter/rules/walked.py +0 -0
  167. {python_constricter-0.3.1 → python_constricter-0.3.6}/hatch_build.py +0 -0
  168. {python_constricter-0.3.1 → python_constricter-0.3.6}/stdlib_tables/__init__.py +0 -0
  169. {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.1
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 | name the real type |
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 | `[tool.constricter]` | flake8 (CLI or config) | pylint |
174
- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------- |
175
- | level | `--level` | `level` | `--constricter-level` | `constricter-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 ty that's installed and runs | — | — | — |
178
- | type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
179
- | all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
180
- | nesting | `--nesting N` | `nesting` | `--constricter-nesting` | `constricter-nesting` |
181
- | max length | `--max-length N` (LVA011) | `max-length` | `--constricter-max-length` | `constricter-max-length` |
182
- | type hierarchy | - | `narrower` (a table) | `--constricter-narrower` | `constricter-narrower` |
183
- | fix | `--fix` (`--unsafe-fixes` for guesses), `--diff` to preview | - | - | - |
184
- | infer with | `--infer-with CHECKERS` (`basedpyright`, `ty`, both: inferred types, as guesses) | `infer-with` | - | - |
185
- | show fixes | `--show-fixes` (each fix and how it was decided, text) | - | - | - |
186
- | 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` | - | - |
187
- | select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
188
- | 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` |
189
- | ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
190
- | exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
191
- | format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
192
- | statistics | `--statistics` (counts per code, text format) | - | - | - |
193
- | jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
194
- | baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
195
- | coverage | `--coverage`, `--fail-under PCT` | - | - | - |
196
- | per-path levels | - | `per-path-levels` | - | - |
197
- | per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
198
- | stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
199
- | exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
200
- | output file | `--output-file FILE` | - | flake8's own `--output-file` | pylint's own `--output` |
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, and
239
- `--show-fixes` lists each fix and how its value decided it. `--infer-with basedpyright` (or `ty`, or
240
- `basedpyright,ty`, installed alongside) asks those type checkers for what `--fix` can't type itself,
241
- as guesses. The full list is in
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.1
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 somewhere (a
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 | name the real type |
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 | `[tool.constricter]` | flake8 (CLI or config) | pylint |
147
- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------- |
148
- | level | `--level` | `level` | `--constricter-level` | `constricter-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 ty that's installed and runs | — | — | — |
151
- | type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
152
- | all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
153
- | nesting | `--nesting N` | `nesting` | `--constricter-nesting` | `constricter-nesting` |
154
- | max length | `--max-length N` (LVA011) | `max-length` | `--constricter-max-length` | `constricter-max-length` |
155
- | type hierarchy | - | `narrower` (a table) | `--constricter-narrower` | `constricter-narrower` |
156
- | fix | `--fix` (`--unsafe-fixes` for guesses), `--diff` to preview | - | - | - |
157
- | infer with | `--infer-with CHECKERS` (`basedpyright`, `ty`, both: inferred types, as guesses) | `infer-with` | - | - |
158
- | show fixes | `--show-fixes` (each fix and how it was decided, text) | - | - | - |
159
- | 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` | - | - |
160
- | select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
161
- | 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` |
162
- | ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
163
- | exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
164
- | format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
165
- | statistics | `--statistics` (counts per code, text format) | - | - | - |
166
- | jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
167
- | baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
168
- | coverage | `--coverage`, `--fail-under PCT` | - | - | - |
169
- | per-path levels | - | `per-path-levels` | - | - |
170
- | per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
171
- | stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
172
- | exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
173
- | output file | `--output-file FILE` | - | flake8's own `--output-file` | pylint's own `--output` |
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, and
212
- `--show-fixes` lists each fix and how its value decided it. `--infer-with basedpyright` (or `ty`, or
213
- `basedpyright,ty`, installed alongside) asks those type checkers for what `--fix` can't type itself,
214
- as guesses. The full list is in
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.1
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 somewhere (a
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
 
@@ -25,7 +25,7 @@ from constricter.offences import (
25
25
  )
26
26
  from constricter.rules.checker import Coverage, annotation_coverage, check_source, check_tree
27
27
 
28
- __version__ = "0.3.1"
28
+ __version__ = "0.3.6"
29
29
  __all__ = [
30
30
  "COMMENT_TYPED_TARGET",
31
31
  "DEFAULT_CHECKS",
@@ -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()