python-constricter 0.2.5__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. python_constricter-0.3.6/.gitignore +48 -0
  2. {python_constricter-0.2.5 → python_constricter-0.3.6}/PKG-INFO +61 -45
  3. {python_constricter-0.2.5 → python_constricter-0.3.6}/README.md +54 -37
  4. {python_constricter-0.2.5 → 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.6/constricter/cli/collecting.py +47 -0
  7. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/command.py +338 -284
  8. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/config.py +102 -28
  9. python_constricter-0.3.6/constricter/cli/edits.py +188 -0
  10. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/explain.py +2 -1
  11. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/hints.py +189 -157
  12. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/options.py +187 -10
  13. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/paths.py +31 -0
  14. python_constricter-0.3.6/constricter/cli/protocol.py +192 -0
  15. python_constricter-0.3.6/constricter/cli/remembered.py +52 -0
  16. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/report.py +11 -6
  17. python_constricter-0.3.6/constricter/cli/runs.py +90 -0
  18. python_constricter-0.3.6/constricter/cli/schedule.py +415 -0
  19. python_constricter-0.3.6/constricter/cli/traced.py +401 -0
  20. python_constricter-0.3.6/constricter/cli/workers.py +216 -0
  21. python_constricter-0.3.6/constricter/fix/core/__init__.py +2 -0
  22. python_constricter-0.3.6/constricter/fix/core/asked.py +56 -0
  23. python_constricter-0.3.6/constricter/fix/core/fixes.py +317 -0
  24. python_constricter-0.3.6/constricter/fix/core/imports.py +527 -0
  25. python_constricter-0.3.6/constricter/fix/core/inherited.py +310 -0
  26. python_constricter-0.3.6/constricter/fix/core/known.py +700 -0
  27. python_constricter-0.3.6/constricter/fix/core/signatures.py +125 -0
  28. python_constricter-0.3.6/constricter/fix/index/__init__.py +2 -0
  29. python_constricter-0.3.6/constricter/fix/index/atoms.py +425 -0
  30. python_constricter-0.3.6/constricter/fix/index/awaits.py +90 -0
  31. python_constricter-0.3.6/constricter/fix/index/beyond.py +148 -0
  32. python_constricter-0.3.6/constricter/fix/index/callers.py +154 -0
  33. python_constricter-0.3.6/constricter/fix/index/chained.py +69 -0
  34. python_constricter-0.3.6/constricter/fix/index/classnames.py +124 -0
  35. python_constricter-0.3.6/constricter/fix/index/declared.py +506 -0
  36. python_constricter-0.3.6/constricter/fix/index/decorated.py +121 -0
  37. python_constricter-0.3.6/constricter/fix/index/fixtures.py +176 -0
  38. python_constricter-0.3.6/constricter/fix/index/installed.py +348 -0
  39. python_constricter-0.3.6/constricter/fix/index/linked.py +142 -0
  40. python_constricter-0.3.6/constricter/fix/index/loose.py +98 -0
  41. python_constricter-0.3.6/constricter/fix/index/managed.py +63 -0
  42. python_constricter-0.3.6/constricter/fix/index/modules.py +849 -0
  43. python_constricter-0.3.6/constricter/fix/index/offers.py +138 -0
  44. python_constricter-0.3.6/constricter/fix/index/order.py +155 -0
  45. python_constricter-0.3.6/constricter/fix/index/own_methods.py +67 -0
  46. python_constricter-0.3.6/constricter/fix/index/own_overloads.py +70 -0
  47. python_constricter-0.3.6/constricter/fix/index/own_types.py +161 -0
  48. python_constricter-0.3.6/constricter/fix/index/plain.py +125 -0
  49. python_constricter-0.3.6/constricter/fix/index/project.py +980 -0
  50. python_constricter-0.3.6/constricter/fix/index/read.py +70 -0
  51. python_constricter-0.3.6/constricter/fix/index/sides.py +160 -0
  52. python_constricter-0.3.6/constricter/fix/index/strings.py +46 -0
  53. python_constricter-0.3.6/constricter/fix/index/stubbed.py +1000 -0
  54. python_constricter-0.3.6/constricter/fix/index/taken.py +45 -0
  55. python_constricter-0.3.6/constricter/fix/index/tuples.py +112 -0
  56. python_constricter-0.3.6/constricter/fix/libraries/__init__.py +2 -0
  57. python_constricter-0.3.6/constricter/fix/libraries/library.py +374 -0
  58. python_constricter-0.3.6/constricter/fix/libraries/opened.py +147 -0
  59. python_constricter-0.3.6/constricter/fix/libraries/overloads.py +968 -0
  60. python_constricter-0.3.6/constricter/fix/libraries/stdlib.py +740 -0
  61. python_constricter-0.3.6/constricter/fix/libraries/walks.py +49 -0
  62. python_constricter-0.3.6/constricter/fix/tables/aliases.json +147 -0
  63. python_constricter-0.3.6/constricter/fix/tables/attributes.json +700 -0
  64. python_constricter-0.3.6/constricter/fix/tables/awaited.json +69 -0
  65. python_constricter-0.3.6/constricter/fix/tables/awaited_overloads.json +14 -0
  66. python_constricter-0.3.6/constricter/fix/tables/bases.json +888 -0
  67. python_constricter-0.3.6/constricter/fix/tables/classes.json +1941 -0
  68. python_constricter-0.3.6/constricter/fix/tables/elements.json +156 -0
  69. python_constricter-0.3.6/constricter/fix/tables/functions.json +43 -0
  70. python_constricter-0.3.6/constricter/fix/tables/generic_attributes.json +254 -0
  71. python_constricter-0.3.6/constricter/fix/tables/method_overloads.json +461 -0
  72. python_constricter-0.3.6/constricter/fix/tables/method_signatures.json +896 -0
  73. python_constricter-0.3.6/constricter/fix/tables/methods.json +954 -0
  74. python_constricter-0.3.6/constricter/fix/tables/overloads.json +537 -0
  75. python_constricter-0.3.6/constricter/fix/tables/returns.json +1362 -0
  76. python_constricter-0.3.6/constricter/fix/tables/scalar_members.json +16 -0
  77. python_constricter-0.3.6/constricter/fix/tables/scalars.json +2707 -0
  78. python_constricter-0.3.6/constricter/fix/tables/source.json +26 -0
  79. python_constricter-0.3.6/constricter/fix/tables/subscriptable.json +77 -0
  80. python_constricter-0.3.6/constricter/fix/tables/type_parameters.json +178 -0
  81. python_constricter-0.3.6/constricter/fix/tables/variables.json +2959 -0
  82. python_constricter-0.3.6/constricter/fix/values/__init__.py +2 -0
  83. python_constricter-0.3.6/constricter/fix/values/aliased.py +195 -0
  84. python_constricter-0.3.6/constricter/fix/values/bodies.py +76 -0
  85. python_constricter-0.3.6/constricter/fix/values/callables.py +321 -0
  86. python_constricter-0.3.6/constricter/fix/values/called.py +106 -0
  87. python_constricter-0.3.6/constricter/fix/values/classvars.py +541 -0
  88. python_constricter-0.3.6/constricter/fix/values/decided.py +396 -0
  89. python_constricter-0.3.6/constricter/fix/values/displays.py +87 -0
  90. python_constricter-0.3.6/constricter/fix/values/doubts.py +673 -0
  91. python_constricter-0.3.6/constricter/fix/values/entered.py +331 -0
  92. python_constricter-0.3.6/constricter/fix/values/fills.py +435 -0
  93. python_constricter-0.3.6/constricter/fix/values/guesses.py +370 -0
  94. python_constricter-0.3.6/constricter/fix/values/hinted.py +645 -0
  95. python_constricter-0.3.6/constricter/fix/values/inference.py +996 -0
  96. python_constricter-0.3.6/constricter/fix/values/members.py +575 -0
  97. python_constricter-0.3.6/constricter/fix/values/narrowed.py +220 -0
  98. python_constricter-0.3.6/constricter/fix/values/operated.py +200 -0
  99. python_constricter-0.3.6/constricter/fix/values/ordered.py +74 -0
  100. python_constricter-0.3.6/constricter/fix/values/returned.py +967 -0
  101. {python_constricter-0.2.5/constricter/fix → python_constricter-0.3.6/constricter/fix/values}/returns.py +46 -2
  102. python_constricter-0.3.6/constricter/fix/values/shapes.py +579 -0
  103. python_constricter-0.3.6/constricter/fix/values/targets.py +383 -0
  104. python_constricter-0.3.6/constricter/fix/values/unchecked.py +278 -0
  105. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/notebook.py +1 -1
  106. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/offences.py +155 -10
  107. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/plugins/flake8.py +11 -1
  108. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/plugins/pylint.py +11 -0
  109. python_constricter-0.3.6/constricter/recording.py +583 -0
  110. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/annotations.py +376 -59
  111. python_constricter-0.3.6/constricter/rules/binding.py +647 -0
  112. python_constricter-0.3.6/constricter/rules/calls.py +459 -0
  113. python_constricter-0.3.6/constricter/rules/checker.py +950 -0
  114. python_constricter-0.3.6/constricter/rules/decorators.py +221 -0
  115. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/flow.py +20 -3
  116. python_constricter-0.3.6/constricter/rules/keys.py +79 -0
  117. python_constricter-0.3.6/constricter/rules/late.py +670 -0
  118. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/narrowing.py +48 -1
  119. python_constricter-0.3.6/constricter/rules/parsed.py +130 -0
  120. python_constricter-0.3.6/constricter/rules/quoted.py +141 -0
  121. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/rebinding.py +39 -12
  122. python_constricter-0.3.6/constricter/rules/recorded.py +167 -0
  123. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/redundant.py +2 -8
  124. python_constricter-0.3.6/constricter/rules/scope.py +995 -0
  125. python_constricter-0.3.6/constricter/rules/syntax.py +472 -0
  126. python_constricter-0.3.6/constricter/rules/tables.py +169 -0
  127. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/walked.py +59 -7
  128. python_constricter-0.3.6/constricter/rules/widened.py +322 -0
  129. python_constricter-0.3.6/constricter/trace.py +378 -0
  130. python_constricter-0.3.6/hatch_build.py +78 -0
  131. {python_constricter-0.2.5 → python_constricter-0.3.6}/pyproject.toml +103 -22
  132. python_constricter-0.3.6/stdlib_tables/__init__.py +16 -0
  133. python_constricter-0.3.6/stdlib_tables/__main__.py +8 -0
  134. python_constricter-0.3.6/stdlib_tables/elements.py +101 -0
  135. python_constricter-0.3.6/stdlib_tables/generate.py +996 -0
  136. python_constricter-0.3.6/stdlib_tables/overloads.py +999 -0
  137. python_constricter-0.3.6/stdlib_tables/partial.json +999 -0
  138. python_constricter-0.3.6/stdlib_tables/reading.py +847 -0
  139. python_constricter-0.3.6/stdlib_tables/stubs.py +532 -0
  140. python_constricter-0.3.6/stdlib_tables/templates.py +296 -0
  141. python_constricter-0.2.5/constricter/__main__.py +0 -9
  142. python_constricter-0.2.5/constricter/fix/doubts.py +0 -288
  143. python_constricter-0.2.5/constricter/fix/fills.py +0 -226
  144. python_constricter-0.2.5/constricter/fix/fixes.py +0 -192
  145. python_constricter-0.2.5/constricter/fix/guesses.py +0 -211
  146. python_constricter-0.2.5/constricter/fix/hinted.py +0 -242
  147. python_constricter-0.2.5/constricter/fix/imports.py +0 -174
  148. python_constricter-0.2.5/constricter/fix/inference.py +0 -748
  149. python_constricter-0.2.5/constricter/fix/known.py +0 -189
  150. python_constricter-0.2.5/constricter/fix/members.py +0 -197
  151. python_constricter-0.2.5/constricter/fix/opened.py +0 -74
  152. python_constricter-0.2.5/constricter/fix/project.py +0 -491
  153. python_constricter-0.2.5/constricter/fix/returned.py +0 -384
  154. python_constricter-0.2.5/constricter/fix/stdlib.json +0 -15351
  155. python_constricter-0.2.5/constricter/fix/stdlib.py +0 -186
  156. python_constricter-0.2.5/constricter/fix/targets.py +0 -184
  157. python_constricter-0.2.5/constricter/rules/checker.py +0 -808
  158. python_constricter-0.2.5/constricter/rules/parsed.py +0 -84
  159. python_constricter-0.2.5/constricter/rules/scope.py +0 -730
  160. python_constricter-0.2.5/constricter/rules/syntax.py +0 -239
  161. {python_constricter-0.2.5 → python_constricter-0.3.6}/LICENSE.md +0 -0
  162. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/__init__.py +0 -0
  163. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/baseline.py +0 -0
  164. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/cli/guard.py +0 -0
  165. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/fix/__init__.py +0 -0
  166. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/jsonc.py +0 -0
  167. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/noqa.py +0 -0
  168. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/plugins/__init__.py +0 -0
  169. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/py.typed +0 -0
  170. {python_constricter-0.2.5 → python_constricter-0.3.6}/constricter/rules/__init__.py +0 -0
@@ -0,0 +1,48 @@
1
+ # Everything generated locally (the dev venv, tool caches, coverage data, build output) lives under local/.
2
+ local/
3
+
4
+ # Written next to the sources by Python.
5
+ __pycache__/
6
+
7
+ # Generated by a build (hatch_build.py, or `python -m stdlib_tables`): the standard-library tables, and
8
+ # the tests' list of what only some platforms and Pythons have.
9
+ constricter/fix/tables/
10
+ stdlib_tables/partial.json
11
+
12
+ # npm installs of the pinned CI Markdown tools (.github/package.json)
13
+ .github/node_modules/
14
+
15
+ .hypothesis
16
+
17
+ # Claude Code's agent worktrees: other checkouts of this repository.
18
+ .claude/worktrees/
19
+
20
+ # Claude Code's sandbox bind-mounts /dev/null over these denied paths while a
21
+ # session runs; none is a file of this repo
22
+ /.bash_profile
23
+ /.bashrc
24
+ /.gitconfig
25
+ /.gitmodules
26
+ /.idea
27
+ /.mcp.json
28
+ /.profile
29
+ /.ripgreprc
30
+ /.zprofile
31
+ /.zshrc
32
+ /.claude/agents
33
+ /.claude/commands
34
+ /.claude/hooks
35
+ /.claude/launch.json
36
+ /.claude/loop.md
37
+ /.claude/output-styles
38
+ /.claude/routines
39
+ /.claude/scheduled_tasks.json
40
+ /.claude/settings.json
41
+ /.claude/settings.local.json
42
+ /.claude/skills
43
+ /.claude/workflows
44
+ /.claude/.cc-writes
45
+ /.vscode
46
+
47
+ # Backups merge tools leave next to a conflicted file.
48
+ *.orig
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-constricter
3
- Version: 0.2.5
3
+ Version: 0.3.6
4
+ Import-Name: constricter
4
5
  Summary: Lint rules: every local variable is typed where it's first bound. A flake8 plugin, a pylint plugin and a standalone CLI.
5
6
  Author: Ivy Duggan
6
- Requires-Python: >=3.11
7
- Description-Content-Type: text/markdown
8
7
  License-Expression: MIT
8
+ License-File: LICENSE.md
9
9
  Classifier: Framework :: Flake8
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Programming Language :: Python :: 3
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
18
18
  Classifier: Programming Language :: Python :: Implementation :: PyPy
19
19
  Classifier: Topic :: Software Development :: Quality Assurance
20
20
  Classifier: Typing :: Typed
21
- License-File: LICENSE.md
22
- Requires-Dist: flake8>=7 ; extra == "flake8"
23
- Requires-Dist: pylint>=3 ; extra == "pylint"
21
+ Requires-Python: >=3.11
24
22
  Provides-Extra: flake8
23
+ Requires-Dist: flake8>=7; extra == 'flake8'
25
24
  Provides-Extra: pylint
26
- Import-Name: constricter
25
+ Requires-Dist: pylint>=3; extra == 'pylint'
26
+ Description-Content-Type: text/markdown
27
27
 
28
28
  # python-con`strict`er
29
29
 
@@ -97,7 +97,7 @@ class bodies too. Statements are read in source order, and only a name's first b
97
97
  | `LVA002` | an untyped `for` target or `match` capture | `name: T` first (or a type comment) |
98
98
  | `LVA003` | a `for` target typed only by `# type: T` | `name: T` first |
99
99
  | `LVA004` | with `all-scopes`: the same as `LVA001`, in a module or class body | `name: T = ...` (`ClassVar[T]` in a dataclass) |
100
- | `LVA005` | an annotation with `Any`, `object` or a generic without its parameters | 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,32 +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
- | type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
177
- | all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
178
- | nesting | `--nesting N` | `nesting` | `--constricter-nesting` | `constricter-nesting` |
179
- | max length | `--max-length N` (LVA011) | `max-length` | `--constricter-max-length` | `constricter-max-length` |
180
- | type hierarchy | - | `narrower` (a table) | `--constricter-narrower` | `constricter-narrower` |
181
- | fix | `--fix` (`--unsafe-fixes` for guesses), `--diff` to preview | - | - | - |
182
- | infer with | `--infer-with CHECKERS` (`basedpyright`, `ty`, both: inferred types, as guesses) | `infer-with` | - | - |
183
- | show fixes | `--show-fixes` (each fix and how it was decided, text) | - | - | - |
184
- | 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` | - | - |
185
- | select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
186
- | 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` |
187
- | ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
188
- | exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
189
- | format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
190
- | statistics | `--statistics` (counts per code, text format) | - | - | - |
191
- | jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
192
- | baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
193
- | coverage | `--coverage`, `--fail-under PCT` | - | - | - |
194
- | per-path levels | - | `per-path-levels` | - | - |
195
- | per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
196
- | stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
197
- | exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
198
- | 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` |
199
209
 
200
210
  `constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
201
211
 
@@ -210,6 +220,8 @@ type-comments = false
210
220
  all-scopes = true
211
221
  nesting = 3
212
222
  max-length = 4
223
+ vague = -1 # the default: no `Any`; 0 lets `dict[str, Any]` through, 1 `Any` itself
224
+ min-python = "3.11" # the default: your `requires-python`'s lower bound
213
225
  jobs = 0
214
226
  baseline = "constricter-baseline.json" # the default; relative to this pyproject.toml
215
227
 
@@ -231,12 +243,17 @@ ignore = ["LVA003"]
231
243
 
232
244
  `--fix` adds the annotation where the value decides it: a literal (`count = 0` becomes
233
245
  `count: int = 0`), a container whose elements agree, a constructor or a function that declares its
234
- return type (in another checked file too), a copy, subscript, attribute or method call of a local
235
- whose type is known, and values computed from those; a loop's target or an unpacking's names get a
236
- declaration on the line before. `--unsafe-fixes` adds guesses, and `--show-fixes` lists each fix and
237
- how its value decided it. `--infer-with basedpyright` (or `ty`, or `basedpyright,ty`, installed
238
- alongside) asks those type checkers for what `--fix` can't type itself, as guesses. The full list is
239
- in [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
246
+ return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
247
+ method call of a local whose type is known, and values computed from those; a loop's target or an
248
+ unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses, `--likely`
249
+ only those measured to hold as often as a certain fix
250
+ ([docs/LIKELY.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/LIKELY.md)),
251
+ and `--show-fixes` lists each fix and how its value decided it, and after `--fix` each one made.
252
+ `--infer-with basedpyright` (or `ty`, or `pyrefly`, or several: `basedpyright,ty`; each installed
253
+ alongside) asks those type checkers for what `--fix` can't type itself, as guesses;
254
+ `--infer-from FILE` takes the types a run bound each name to, recorded by
255
+ `python -m constricter.trace -m pytest`, the same way. The full list is in
256
+ [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
240
257
 
241
258
  ### Installing and running
242
259
 
@@ -261,7 +278,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
261
278
 
262
279
  ```yaml
263
280
  - repo: https://github.com/ivylikethevine/python-constricter
264
- rev: v0.2.3
281
+ rev: v0.3.6
265
282
  hooks:
266
283
  - id: constricter
267
284
  ```
@@ -294,8 +311,8 @@ default one is used only if it exists.
294
311
  total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
295
312
  and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
296
313
  PCT, so CI can hold a codebase to a share. With `--format=json` it prints
297
- `{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON somewhere (a
298
- gist, a release asset) and point
314
+ `{"typed", "total", "widened", "percent", "files"}`, which a badge can read: publish that JSON
315
+ somewhere (a gist, a release asset) and point
299
316
  [shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
300
317
  `$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
301
318
 
@@ -375,4 +392,3 @@ I have used generative AI to write large parts of this code. All of the code her
375
392
  _responsibility_ regardless: AI is a tool, not an owner of a project. I have personally understood,
376
393
  reviewed, and approved all of the AI-generated code in this repository, and **mainline releases**
377
394
  carry the same accountability to me as anything I write and publish myself.
378
-
@@ -70,7 +70,7 @@ class bodies too. Statements are read in source order, and only a name's first b
70
70
  | `LVA002` | an untyped `for` target or `match` capture | `name: T` first (or a type comment) |
71
71
  | `LVA003` | a `for` target typed only by `# type: T` | `name: T` first |
72
72
  | `LVA004` | with `all-scopes`: the same as `LVA001`, in a module or class body | `name: T = ...` (`ClassVar[T]` in a dataclass) |
73
- | `LVA005` | an annotation with `Any`, `object` or a generic without its parameters | 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,32 +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
- | type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
150
- | all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
151
- | nesting | `--nesting N` | `nesting` | `--constricter-nesting` | `constricter-nesting` |
152
- | max length | `--max-length N` (LVA011) | `max-length` | `--constricter-max-length` | `constricter-max-length` |
153
- | type hierarchy | - | `narrower` (a table) | `--constricter-narrower` | `constricter-narrower` |
154
- | fix | `--fix` (`--unsafe-fixes` for guesses), `--diff` to preview | - | - | - |
155
- | infer with | `--infer-with CHECKERS` (`basedpyright`, `ty`, both: inferred types, as guesses) | `infer-with` | - | - |
156
- | show fixes | `--show-fixes` (each fix and how it was decided, text) | - | - | - |
157
- | 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` | - | - |
158
- | select | `--select CODES` (codes or prefixes) | `select` | flake8's own `select` | pylint's own `enable` |
159
- | 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` |
160
- | ignore | `--ignore CODES` | `ignore` | flake8's own `extend-ignore` | pylint's own `disable` |
161
- | exclude | `--exclude GLOB` (repeatable) | `exclude` | flake8's own `exclude` | pylint's own `ignore-paths` |
162
- | format | `--format`: `text`, `full` (with source), `json`, `github`, `sarif`, `gitlab`, `junit`, `rdjson` | - | - | - |
163
- | statistics | `--statistics` (counts per code, text format) | - | - | - |
164
- | jobs | `--jobs N` (`-j`; 0: one per CPU) | `jobs` | flake8's own `--jobs` | pylint's own `--jobs` |
165
- | baseline | `--baseline FILE`; `--write-baseline` records it | `baseline` | - | - |
166
- | coverage | `--coverage`, `--fail-under PCT` | - | - | - |
167
- | per-path levels | - | `per-path-levels` | - | - |
168
- | per-file ignores | - | `per-file-ignores` | flake8's own `per-file-ignores` | - |
169
- | stdin | `-` as the path, `--stdin-filename PATH` | - | flake8's own `-` | - |
170
- | exit status | `--exit-zero` | - | flake8's own `--exit-zero` | pylint's own `--exit-zero` |
171
- | 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` |
172
182
 
173
183
  `constricter --explain LVA002` prints a code's rationale, its fix, and the levels that report it.
174
184
 
@@ -183,6 +193,8 @@ type-comments = false
183
193
  all-scopes = true
184
194
  nesting = 3
185
195
  max-length = 4
196
+ vague = -1 # the default: no `Any`; 0 lets `dict[str, Any]` through, 1 `Any` itself
197
+ min-python = "3.11" # the default: your `requires-python`'s lower bound
186
198
  jobs = 0
187
199
  baseline = "constricter-baseline.json" # the default; relative to this pyproject.toml
188
200
 
@@ -204,12 +216,17 @@ ignore = ["LVA003"]
204
216
 
205
217
  `--fix` adds the annotation where the value decides it: a literal (`count = 0` becomes
206
218
  `count: int = 0`), a container whose elements agree, a constructor or a function that declares its
207
- return type (in another checked file too), a copy, subscript, attribute or method call of a local
208
- whose type is known, and values computed from those; a loop's target or an unpacking's names get a
209
- declaration on the line before. `--unsafe-fixes` adds guesses, and `--show-fixes` lists each fix and
210
- how its value decided it. `--infer-with basedpyright` (or `ty`, or `basedpyright,ty`, installed
211
- alongside) asks those type checkers for what `--fix` can't type itself, as guesses. The full list is
212
- in [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
219
+ return type or whose `return`s agree (in another checked file too), a copy, subscript, attribute or
220
+ method call of a local whose type is known, and values computed from those; a loop's target or an
221
+ unpacking's names get a declaration on the line before. `--unsafe-fixes` adds guesses, `--likely`
222
+ only those measured to hold as often as a certain fix
223
+ ([docs/LIKELY.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/LIKELY.md)),
224
+ and `--show-fixes` lists each fix and how its value decided it, and after `--fix` each one made.
225
+ `--infer-with basedpyright` (or `ty`, or `pyrefly`, or several: `basedpyright,ty`; each installed
226
+ alongside) asks those type checkers for what `--fix` can't type itself, as guesses;
227
+ `--infer-from FILE` takes the types a run bound each name to, recorded by
228
+ `python -m constricter.trace -m pytest`, the same way. The full list is in
229
+ [docs/FIXES.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/FIXES.md).
213
230
 
214
231
  ### Installing and running
215
232
 
@@ -234,7 +251,7 @@ pre-commit, after ruff's hooks (or `constricter-fix`, which runs `--fix` first):
234
251
 
235
252
  ```yaml
236
253
  - repo: https://github.com/ivylikethevine/python-constricter
237
- rev: v0.2.3
254
+ rev: v0.3.6
238
255
  hooks:
239
256
  - id: constricter
240
257
  ```
@@ -267,8 +284,8 @@ default one is used only if it exists.
267
284
  total; the bindings are the ones the rules cover (with `--all-scopes`, module and class bodies too),
268
285
  and `# noqa` doesn't make one typed. `--fail-under PCT` (which implies `--coverage`) exits 1 below
269
286
  PCT, so CI can hold a codebase to a share. With `--format=json` it prints
270
- `{"typed", "total", "percent", "files"}`, which a badge can read: publish that JSON somewhere (a
271
- gist, a release asset) and point
287
+ `{"typed", "total", "widened", "percent", "files"}`, which a badge can read: publish that JSON
288
+ somewhere (a gist, a release asset) and point
272
289
  [shields.io's dynamic JSON badge](https://shields.io/badges/dynamic-json-badge) at it with the query
273
290
  `$.percent`. This project keeps its own share at 100% in CI, so its badge is static.
274
291
 
@@ -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.2.5"
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()
@@ -0,0 +1,47 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """The garbage collector, as the command runs it: by hand, once per file checked.
3
+
4
+ A check keeps every file's tree (see `parsed`), and allocates a great deal for each file, little of it
5
+ cyclic. Left to itself, the collector looked through the kept trees again and again as the check
6
+ allocated: a third of the standard library's check (34s, 21s without). So while the command runs,
7
+ it's off: what indexing built is frozen (never looked through again), and after each file, only
8
+ what's been allocated since (the young generations) is collected, which bounds what a cycle can hold.
9
+ """
10
+
11
+ import contextlib
12
+ import gc
13
+ from collections.abc import Generator
14
+ from typing import Final
15
+
16
+ _YOUNG: Final = 1 # the generations collected after each file: 0 and 1, never the frozen or the old
17
+ _FREEZES: Final = hasattr(gc, "freeze") # PyPy's collector has no freezing
18
+
19
+
20
+ @contextlib.contextmanager
21
+ def by_hand() -> Generator[None]:
22
+ """Turn automatic collection off for the command's run, and back as it was after.
23
+
24
+ Yields:
25
+ Nothing: the run.
26
+
27
+ """
28
+ was: bool = gc.isenabled()
29
+ gc.disable()
30
+ try:
31
+ yield
32
+ finally:
33
+ if _FREEZES:
34
+ gc.unfreeze()
35
+ if was:
36
+ gc.enable()
37
+
38
+
39
+ def indexed() -> None:
40
+ """Freeze what indexing built (the kept trees, the index): it lives until the check takes it."""
41
+ if _FREEZES:
42
+ gc.freeze()
43
+
44
+
45
+ def sweep() -> None:
46
+ """Collect what the file just checked left behind (see the module docstring)."""
47
+ _ = gc.collect(_YOUNG)