python-constricter 0.3.3__tar.gz → 0.3.5__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 (160) hide show
  1. {python_constricter-0.3.3 → python_constricter-0.3.5}/PKG-INFO +42 -40
  2. {python_constricter-0.3.3 → python_constricter-0.3.5}/README.md +41 -39
  3. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/__init__.py +1 -1
  4. python_constricter-0.3.5/constricter/__main__.py +26 -0
  5. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/command.py +103 -55
  6. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/config.py +28 -1
  7. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/hints.py +18 -12
  8. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/options.py +29 -1
  9. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/protocol.py +25 -5
  10. python_constricter-0.3.5/constricter/cli/remembered.py +52 -0
  11. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/report.py +7 -5
  12. python_constricter-0.3.5/constricter/cli/runs.py +90 -0
  13. python_constricter-0.3.5/constricter/cli/schedule.py +415 -0
  14. python_constricter-0.3.5/constricter/cli/traced.py +401 -0
  15. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/workers.py +85 -23
  16. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/fixes.py +51 -7
  17. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/imports.py +100 -2
  18. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/inherited.py +117 -17
  19. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/known.py +177 -49
  20. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/signatures.py +22 -4
  21. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/atoms.py +18 -3
  22. python_constricter-0.3.5/constricter/fix/index/awaits.py +90 -0
  23. python_constricter-0.3.5/constricter/fix/index/beyond.py +148 -0
  24. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/callers.py +2 -1
  25. python_constricter-0.3.5/constricter/fix/index/chained.py +69 -0
  26. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/classnames.py +17 -0
  27. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/declared.py +68 -4
  28. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/decorated.py +3 -1
  29. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/installed.py +35 -3
  30. python_constricter-0.3.5/constricter/fix/index/linked.py +142 -0
  31. python_constricter-0.3.5/constricter/fix/index/loose.py +98 -0
  32. python_constricter-0.3.5/constricter/fix/index/managed.py +63 -0
  33. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/modules.py +331 -47
  34. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/offers.py +4 -1
  35. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/order.py +10 -3
  36. python_constricter-0.3.5/constricter/fix/index/own_methods.py +67 -0
  37. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/own_overloads.py +14 -1
  38. python_constricter-0.3.5/constricter/fix/index/own_types.py +161 -0
  39. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/plain.py +3 -1
  40. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/project.py +137 -153
  41. python_constricter-0.3.5/constricter/fix/index/sides.py +160 -0
  42. python_constricter-0.3.5/constricter/fix/index/strings.py +46 -0
  43. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/stubbed.py +51 -12
  44. python_constricter-0.3.5/constricter/fix/index/taken.py +45 -0
  45. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/tuples.py +54 -28
  46. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/libraries/library.py +95 -7
  47. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/libraries/opened.py +57 -16
  48. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/libraries/overloads.py +32 -6
  49. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/libraries/stdlib.py +64 -13
  50. python_constricter-0.3.5/constricter/fix/libraries/walks.py +49 -0
  51. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/attributes.json +2 -0
  52. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/classes.json +1 -0
  53. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/generic_attributes.json +2 -0
  54. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/method_overloads.json +14 -6
  55. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/method_signatures.json +50 -20
  56. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/methods.json +1 -0
  57. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/overloads.json +38 -37
  58. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/scalars.json +2 -0
  59. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/source.json +1 -1
  60. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/type_parameters.json +1 -0
  61. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/aliased.py +9 -1
  62. python_constricter-0.3.5/constricter/fix/values/bodies.py +76 -0
  63. python_constricter-0.3.5/constricter/fix/values/callables.py +321 -0
  64. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/called.py +10 -12
  65. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/classvars.py +124 -21
  66. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/decided.py +22 -2
  67. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/doubts.py +104 -27
  68. python_constricter-0.3.5/constricter/fix/values/entered.py +331 -0
  69. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/fills.py +160 -13
  70. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/guesses.py +51 -19
  71. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/inference.py +48 -54
  72. python_constricter-0.3.5/constricter/fix/values/members.py +564 -0
  73. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/narrowed.py +60 -4
  74. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/operated.py +7 -6
  75. python_constricter-0.3.5/constricter/fix/values/ordered.py +74 -0
  76. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/returned.py +319 -224
  77. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/returns.py +44 -1
  78. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/shapes.py +132 -32
  79. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/targets.py +42 -15
  80. python_constricter-0.3.5/constricter/fix/values/unchecked.py +162 -0
  81. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/offences.py +45 -6
  82. python_constricter-0.3.5/constricter/recording.py +571 -0
  83. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/annotations.py +30 -10
  84. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/binding.py +229 -37
  85. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/calls.py +58 -8
  86. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/checker.py +177 -70
  87. python_constricter-0.3.5/constricter/rules/keys.py +79 -0
  88. python_constricter-0.3.5/constricter/rules/late.py +663 -0
  89. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/quoted.py +32 -0
  90. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/rebinding.py +35 -10
  91. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/recorded.py +12 -8
  92. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/scope.py +241 -74
  93. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/syntax.py +20 -0
  94. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/tables.py +18 -4
  95. python_constricter-0.3.5/constricter/rules/widened.py +322 -0
  96. python_constricter-0.3.5/constricter/trace.py +378 -0
  97. {python_constricter-0.3.3 → python_constricter-0.3.5}/pyproject.toml +56 -8
  98. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/elements.py +24 -0
  99. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/generate.py +12 -4
  100. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/overloads.py +24 -19
  101. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/reading.py +15 -0
  102. python_constricter-0.3.3/constricter/__main__.py +0 -9
  103. python_constricter-0.3.3/constricter/cli/runs.py +0 -43
  104. python_constricter-0.3.3/constricter/cli/schedule.py +0 -262
  105. python_constricter-0.3.3/constricter/cli/traced.py +0 -289
  106. python_constricter-0.3.3/constricter/fix/index/beyond.py +0 -90
  107. python_constricter-0.3.3/constricter/fix/index/sides.py +0 -65
  108. python_constricter-0.3.3/constricter/fix/values/entered.py +0 -173
  109. python_constricter-0.3.3/constricter/fix/values/members.py +0 -325
  110. python_constricter-0.3.3/constricter/rules/late.py +0 -324
  111. python_constricter-0.3.3/constricter/trace.py +0 -385
  112. {python_constricter-0.3.3 → python_constricter-0.3.5}/.gitignore +0 -0
  113. {python_constricter-0.3.3 → python_constricter-0.3.5}/LICENSE.md +0 -0
  114. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/__init__.py +0 -0
  115. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/baseline.py +0 -0
  116. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/collecting.py +0 -0
  117. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/edits.py +0 -0
  118. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/explain.py +0 -0
  119. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/guard.py +0 -0
  120. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/cli/paths.py +0 -0
  121. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/__init__.py +0 -0
  122. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/__init__.py +0 -0
  123. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/core/asked.py +0 -0
  124. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/__init__.py +0 -0
  125. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/fixtures.py +0 -0
  126. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/index/read.py +0 -0
  127. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/libraries/__init__.py +0 -0
  128. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/aliases.json +0 -0
  129. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/awaited.json +0 -0
  130. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/awaited_overloads.json +0 -0
  131. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/bases.json +0 -0
  132. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/elements.json +0 -0
  133. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/functions.json +0 -0
  134. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/returns.json +0 -0
  135. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/scalar_members.json +0 -0
  136. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/subscriptable.json +0 -0
  137. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/tables/variables.json +0 -0
  138. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/__init__.py +0 -0
  139. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/displays.py +0 -0
  140. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/fix/values/hinted.py +0 -0
  141. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/jsonc.py +0 -0
  142. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/noqa.py +0 -0
  143. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/notebook.py +0 -0
  144. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/plugins/__init__.py +0 -0
  145. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/plugins/flake8.py +0 -0
  146. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/plugins/pylint.py +0 -0
  147. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/py.typed +0 -0
  148. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/__init__.py +0 -0
  149. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/decorators.py +0 -0
  150. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/flow.py +0 -0
  151. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/narrowing.py +0 -0
  152. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/parsed.py +0 -0
  153. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/redundant.py +0 -0
  154. {python_constricter-0.3.3 → python_constricter-0.3.5}/constricter/rules/walked.py +0 -0
  155. {python_constricter-0.3.3 → python_constricter-0.3.5}/hatch_build.py +0 -0
  156. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/__init__.py +0 -0
  157. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/__main__.py +0 -0
  158. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/partial.json +0 -0
  159. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/stubs.py +0 -0
  160. {python_constricter-0.3.3 → python_constricter-0.3.5}/stdlib_tables/templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-constricter
3
- Version: 0.3.3
3
+ Version: 0.3.5
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
@@ -173,38 +173,39 @@ enabled).
173
173
 
174
174
  Options:
175
175
 
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), `--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, text) | - | - | - |
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
- | select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
195
- | 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` |
196
- | ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
197
- | exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
198
- | format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
199
- | statistics | `--statistics` (counts per code, text format) | - | - | - |
200
- | jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
201
- | baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
202
- | coverage | `--coverage`, `--fail-under PCT` | - | - | - |
203
- | per-path levels | - | `per-path-levels` | - | - |
204
- | per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
205
- | stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
206
- | exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
207
- | 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), `--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` |
208
209
 
209
210
  `constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
210
211
 
@@ -245,10 +246,11 @@ ignore = ["LVA003"]
245
246
  return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
246
247
  method call of a local whose type is known, and values computed from those; a loop's target or an
247
248
  unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses, and
248
- `--show-fixes` lists each fix and how its value decided it. `--infer-with basedpyright` (or `ty`, or
249
- `pyrefly`, or several: `basedpyright,ty`; each installed alongside) asks those type checkers for
250
- what `--fix` can't type itself, as guesses; `--infer-from FILE` takes the types a run bound each
251
- name to, recorded by `python -m constricter.trace -m pytest`, the same way. The full list is in
249
+ `--show-fixes` lists each fix and how its value decided it, and after `--fix` each one made.
250
+ `--infer-with basedpyright` (or `ty`, or `pyrefly`, or several: `basedpyright,ty`; each installed
251
+ alongside) asks those type checkers for what `--fix` can't type itself, as guesses;
252
+ `--infer-from FILE` takes the types a run bound each name to, recorded by
253
+ `python -m constricter.trace -m pytest`, the same way. The full list is in
252
254
  [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
253
255
 
254
256
  ### Installing and running
@@ -274,7 +276,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
274
276
 
275
277
  ```yaml
276
278
  - repo: https://github.com/ivylikethevine/python-constricter
277
- rev: v0.3.3
279
+ rev: v0.3.5
278
280
  hooks:
279
281
  - id: constricter
280
282
  ```
@@ -307,8 +309,8 @@ default one is used only if it exists.
307
309
  total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
308
310
  and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
309
311
  PCT, so CI can hold a codebase to a share. With `--format=json` it prints
310
- `{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON somewhere (a
311
- gist, a release asset) and point
312
+ `{"typed", "total", "widened", "percent", "files"}`, which a badge can read: publish that JSON
313
+ somewhere (a gist, a release asset) and point
312
314
  [shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
313
315
  `$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
314
316
 
@@ -146,38 +146,39 @@ enabled).
146
146
 
147
147
  Options:
148
148
 
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), `--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, text) | - | - | - |
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
- | select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
168
- | 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` |
169
- | ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
170
- | exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
171
- | format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
172
- | statistics | `--statistics` (counts per code, text format) | - | - | - |
173
- | jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
174
- | baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
175
- | coverage | `--coverage`, `--fail-under PCT` | - | - | - |
176
- | per-path levels | - | `per-path-levels` | - | - |
177
- | per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
178
- | stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
179
- | exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
180
- | 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), `--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` |
181
182
 
182
183
  `constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
183
184
 
@@ -218,10 +219,11 @@ ignore = ["LVA003"]
218
219
  return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
219
220
  method call of a local whose type is known, and values computed from those; a loop's target or an
220
221
  unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses, and
221
- `--show-fixes` lists each fix and how its value decided it. `--infer-with basedpyright` (or `ty`, or
222
- `pyrefly`, or several: `basedpyright,ty`; each installed alongside) asks those type checkers for
223
- what `--fix` can't type itself, as guesses; `--infer-from FILE` takes the types a run bound each
224
- name to, recorded by `python -m constricter.trace -m pytest`, the same way. The full list is in
222
+ `--show-fixes` lists each fix and how its value decided it, and after `--fix` each one made.
223
+ `--infer-with basedpyright` (or `ty`, or `pyrefly`, or several: `basedpyright,ty`; each installed
224
+ alongside) asks those type checkers for what `--fix` can't type itself, as guesses;
225
+ `--infer-from FILE` takes the types a run bound each name to, recorded by
226
+ `python -m constricter.trace -m pytest`, the same way. The full list is in
225
227
  [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
226
228
 
227
229
  ### Installing and running
@@ -247,7 +249,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
247
249
 
248
250
  ```yaml
249
251
  - repo: https://github.com/ivylikethevine/python-constricter
250
- rev: v0.3.3
252
+ rev: v0.3.5
251
253
  hooks:
252
254
  - id: constricter
253
255
  ```
@@ -280,8 +282,8 @@ default one is used only if it exists.
280
282
  total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
281
283
  and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
282
284
  PCT, so CI can hold a codebase to a share. With `--format=json` it prints
283
- `{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON somewhere (a
284
- gist, a release asset) and point
285
+ `{"typed", "total", "widened", "percent", "files"}`, which a badge can read: publish that JSON
286
+ somewhere (a gist, a release asset) and point
285
287
  [shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
286
288
  `$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
287
289
 
@@ -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.3"
28
+ __version__ = "0.3.5"
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()
@@ -21,11 +21,11 @@ from constricter.cli.options import Mode, Options, Output
21
21
  from constricter.cli.paths import STDIN, python_files
22
22
  from constricter.cli.protocol import HintError
23
23
  from constricter.cli.report import Format, Result, fix_reasons, render, statistics
24
- from constricter.cli.runs import BaselineRun, CheckRun, CoverageRun, FileRun
25
- from constricter.cli.workers import Workers
24
+ from constricter.cli.runs import Added, BaselineRun, CheckRun, CoverageRun, FileRun, merged
25
+ from constricter.cli.workers import Workers, kept, started
26
26
  from constricter.fix.core import fixes
27
27
  from constricter.fix.core.known import Callee, Hints, Outside, Returns
28
- from constricter.fix.index import callers, decorated, installed, plain, project
28
+ from constricter.fix.index import callers, decorated, installed, loose, plain, project
29
29
  from constricter.noqa import lines, unsuppressed
30
30
  from constricter.offences import (
31
31
  DEFAULT_CHECKS,
@@ -127,6 +127,7 @@ def _placed(offence: Offence, where: list[notebook.Line]) -> Offence:
127
127
 
128
128
 
129
129
  # One changed part of a fixed file (the file, or a notebook's cell): its label, old and new lines.
130
+ _Hinted: TypeAlias = dict[Path, tuple[Hints, ...]] # each file's hints, a checker's each
130
131
  _Change: TypeAlias = tuple[str, list[str], list[str]]
131
132
  # One JSON value a coverage report holds, and one file's row of them.
132
133
  _Scalar: TypeAlias = str | int | float
@@ -308,7 +309,29 @@ def _handled(path: Path, text: tuple[str, Path], offences: list[Offence], option
308
309
  return CheckRun(results, baselined, error=f"{name}: error: {message}")
309
310
  if fixed:
310
311
  _ = parsed.take(raw) # the tree of the text it had: no check will ask for it again
311
- return CheckRun(left, baselined, fixed)
312
+ return CheckRun(
313
+ left,
314
+ baselined,
315
+ fixed,
316
+ made=[r._replace(replacements=(), fixed=True) for r in results if r.offence in fixing],
317
+ rounds=(_added(raw, name, fixing),),
318
+ )
319
+
320
+
321
+ def _added(raw: str, name: Path, fixing: Sequence[Offence]) -> Added:
322
+ """Find the lines fixing `fixing` adds to `raw`, the text of `name` (see `fixes.inserted`).
323
+
324
+ Returns:
325
+ Each one's cell (`None` outside a notebook) and the line it goes before; a notebook's cells
326
+ get declarations alone.
327
+
328
+ """
329
+ if name.suffix != notebook.SUFFIX:
330
+ return tuple((None, line) for line in fixes.inserted(lines(raw), fixing))
331
+ declared: list[tuple[int, int]] = [
332
+ (o.cell or 0, o.edit.span[0]) for o in fixing if o.edit is not None and o.edit.edit is Edit.DECLARE
333
+ ]
334
+ return tuple(sorted(declared))
312
335
 
313
336
 
314
337
  def _baseline_path(path: Path, outside: Outside, options: Options) -> BaselineRun:
@@ -340,6 +363,17 @@ def _cover_path(path: Path, _outside: Outside, options: Options) -> CoverageRun:
340
363
 
341
364
 
342
365
  def _check_all(options: Options) -> tuple[list[Path], list[FileRun]]:
366
+ """Check every file (see `_check_rounds`), its rounds by one set of worker processes.
367
+
368
+ Returns:
369
+ The names, and what each file found.
370
+
371
+ """
372
+ with kept():
373
+ return _check_rounds(options)
374
+
375
+
376
+ def _check_rounds(options: Options) -> tuple[list[Path], list[FileRun]]:
343
377
  """Check every file (`--jobs` at a time), in order.
344
378
 
345
379
  With `--infer-with`, the type checker's server runs throughout: it's asked for every file's
@@ -401,7 +435,7 @@ def _check_all(options: Options) -> tuple[list[Path], list[FileRun]]:
401
435
  index: int
402
436
  run: FileRun
403
437
  for index, run in zip(again, redone, strict=True):
404
- runs[index] = _merged(runs[index], run)
438
+ runs[index] = merged(runs[index], run)
405
439
  again = [index for index, run in zip(again, redone, strict=True) if cast("CheckRun", run).fixed]
406
440
  checker: str
407
441
  path: Path
@@ -447,7 +481,7 @@ def _called_again(
447
481
  calling = [
448
482
  at
449
483
  for at in calling
450
- if project.returned(modules, paths[at], {}) != project.returned(before, paths[at], {})
484
+ if loose.returned(modules, paths[at], {}) != loose.returned(before, paths[at], {})
451
485
  ]
452
486
  return runs, _checked_more(paths, runs, calling, (check, options, session), modules)
453
487
 
@@ -473,7 +507,7 @@ def _checked_more(
473
507
  at: int
474
508
  run: FileRun
475
509
  for at, run in zip(which, redone, strict=True):
476
- runs[at] = _merged(runs[at], run) if how[1].mode is Mode.FIX else run
510
+ runs[at] = merged(runs[at], run) if how[1].mode is Mode.FIX else run
477
511
  return modules
478
512
 
479
513
 
@@ -509,44 +543,29 @@ def _checked_all(
509
543
  What each file found, and the index of every file's module.
510
544
 
511
545
  """
512
- hinted: dict[Path, tuple[Hints, ...]] = {} if session is None else session.hints(_texts(paths))
546
+ # Before a worker starts: one forked while the servers' threads run could wait on a lock one holds.
547
+ hinted: _Hinted = {} if session is None else session.hints(_texts(paths))
513
548
  if options.input.trace is not None:
514
549
  hinted = traced.merged(hinted, traced.load(options.input.trace), paths)
515
550
  coverage: bool = options.mode is Mode.COVERAGE # needs nothing from the other files
516
- stack: contextlib.ExitStack
517
- with contextlib.ExitStack() as stack:
518
- pool: Workers | None = None
519
- if options.jobs != 1 and len(paths) > 1:
520
- pool = stack.enter_context(Workers(paths, options.jobs))
521
- if modules is None and pool:
522
- modules = project.Index({}, []) if coverage else pool.index()
523
- elif modules is None:
524
- modules = project.Index({}, []) if coverage else project.index(paths)
525
- collecting.indexed()
526
- if not coverage:
527
- modules = plain.settled(
528
- decorated.passed(installed.with_installed(modules, installed.search_path())),
529
- options.checks.plain_bases,
530
- )
531
- return schedule.checked(
532
- paths,
533
- check,
534
- pool,
535
- (modules, hinted),
536
- _merged if options.mode is Mode.FIX else None,
551
+ pool: Workers | None = started(paths, options.jobs) if options.jobs != 1 and len(paths) > 1 else None
552
+ if modules is None and pool:
553
+ modules = project.Index({}, []) if coverage else pool.index()
554
+ elif modules is None:
555
+ modules = project.Index({}, []) if coverage else project.index(paths)
556
+ collecting.indexed()
557
+ if not coverage:
558
+ modules = plain.settled(
559
+ decorated.passed(installed.with_installed(modules, installed.search_path())),
560
+ options.checks.plain_bases,
537
561
  )
538
-
539
-
540
- def _merged(before: FileRun, after: FileRun) -> FileRun:
541
- """Join a file's two `--fix` rounds' `CheckRun`s: what's left is the later's, what's fixed is both's.
542
-
543
- Returns:
544
- The joined run.
545
-
546
- """
547
- first: CheckRun = cast("CheckRun", before)
548
- later: CheckRun = cast("CheckRun", after)
549
- return replace(later, fixed=first.fixed + later.fixed)
562
+ return schedule.checked(
563
+ paths,
564
+ check,
565
+ pool,
566
+ (modules, hinted),
567
+ merged if options.mode is Mode.FIX else None,
568
+ )
550
569
 
551
570
 
552
571
  def _texts(paths: Sequence[Path]) -> dict[Path, str]:
@@ -579,10 +598,16 @@ def _report(options: Options, runs: Sequence[FileRun], files: int) -> int:
579
598
  results: list[Result] = [result for run in checked for result in run.results]
580
599
  output: Output = options.output
581
600
  text: bool = output.fmt in {Format.TEXT, Format.FULL}
601
+ # With `--show-fixes`, the fixes `--fix` made too: after the rest, as text or JSON.
602
+ shown: list[Result] = [*results, *(made for run in checked if output.show_fixes for made in run.made)]
582
603
  line: str
583
- for line in statistics(results) if text and output.statistics else render(output.fmt, results):
604
+ for line in (
605
+ statistics(results)
606
+ if text and output.statistics
607
+ else render(output.fmt, shown if output.fmt is Format.JSON else results)
608
+ ):
584
609
  _ = sys.stdout.write(f"{line}\n")
585
- for line in fix_reasons(results) if text and output.show_fixes else ():
610
+ for line in fix_reasons(shown) if text and output.show_fixes else ():
586
611
  _ = sys.stdout.write(f"{line}\n")
587
612
  errors: int = sum(r.offence.is_error(r.level) for r in results)
588
613
  if text and not output.quiet:
@@ -611,30 +636,53 @@ def _coverage(options: Options, paths: Sequence[Path], runs: Sequence[FileRun])
611
636
  counted: list[tuple[Path, Coverage]] = [
612
637
  (path, run.coverage) for path, run in zip(paths, covered, strict=True) if run.coverage is not None
613
638
  ]
614
- total: Coverage = Coverage(sum(c.typed for _, c in counted), sum(c.total for _, c in counted))
639
+ total: Coverage = Coverage(
640
+ sum(c.typed for _, c in counted),
641
+ sum(c.total for _, c in counted),
642
+ sum(c.widened for _, c in counted),
643
+ )
615
644
  if options.output.fmt is Format.JSON:
616
645
  report: dict[str, _Scalar | list[_Row]] = {
617
- "typed": total.typed,
618
- "total": total.total,
619
- "percent": round(total.percent, 1),
620
- "files": [
621
- {"path": str(path), "typed": c.typed, "total": c.total, "percent": round(c.percent, 1)}
622
- for path, c in counted
623
- ],
646
+ **_counts(total),
647
+ "files": [{"path": str(path), **_counts(c)} for path, c in counted],
624
648
  }
625
649
  _ = sys.stdout.write(json.dumps(report, indent=2) + "\n")
626
650
  else:
627
651
  path: Path
628
652
  c: Coverage
629
653
  for path, c in counted:
630
- _ = sys.stdout.write(f"{path}: {c.typed}/{c.total} typed ({c.percent:.1f}%)\n")
631
- _ = sys.stdout.write(
632
- f"Total: {total.typed}/{total.total} typed ({total.percent:.1f}%) in {len(counted)} file(s).\n",
633
- )
654
+ _ = sys.stdout.write(f"{path}: {_typed(c)}\n")
655
+ _ = sys.stdout.write(f"Total: {_typed(total)} in {len(counted)} file(s).\n")
634
656
  threshold: float | None = options.output.fail_under
635
657
  return EXIT_FOUND if threshold is not None and total.percent < threshold else EXIT_CLEAN
636
658
 
637
659
 
660
+ def _counts(found: Coverage) -> _Row:
661
+ """Name a coverage's counts, for `--format=json`.
662
+
663
+ Returns:
664
+ Them, by name.
665
+
666
+ """
667
+ return {
668
+ "typed": found.typed,
669
+ "total": found.total,
670
+ "widened": found.widened,
671
+ "percent": round(found.percent, 1),
672
+ }
673
+
674
+
675
+ def _typed(found: Coverage) -> str:
676
+ """Write a coverage as text: `3/4 typed (75.0%)`, then what a widening annotated, if anything.
677
+
678
+ Returns:
679
+ It.
680
+
681
+ """
682
+ wider: str = f", {found.widened} widened" if found.widened else ""
683
+ return f"{found.typed}/{found.total} typed ({found.percent:.1f}%){wider}"
684
+
685
+
638
686
  def _run(options: Options) -> int:
639
687
  """Check, fix, diff, count or baseline, printing what that finds.
640
688