python-constricter 0.4.1__cp313-cp313-win32.whl

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 (261) hide show
  1. constricter/__init__.py +55 -0
  2. constricter/__main__.py +26 -0
  3. constricter/_version.py +4 -0
  4. constricter/cli/__init__.py +2 -0
  5. constricter/cli/baseline.py +107 -0
  6. constricter/cli/collecting.py +54 -0
  7. constricter/cli/command.py +547 -0
  8. constricter/cli/config.py +322 -0
  9. constricter/cli/explain.py +104 -0
  10. constricter/cli/lsp/__init__.py +2 -0
  11. constricter/cli/lsp/connection.py +475 -0
  12. constricter/cli/lsp/edits.py +188 -0
  13. constricter/cli/lsp/guard.py +133 -0
  14. constricter/cli/lsp/hints.py +391 -0
  15. constricter/cli/lsp/protocol.py +192 -0
  16. constricter/cli/lsp/remembered.py +81 -0
  17. constricter/cli/lsp/stored.py +200 -0
  18. constricter/cli/options.py +799 -0
  19. constricter/cli/paths.py +151 -0
  20. constricter/cli/reading.py +36 -0
  21. constricter/cli/report.py +397 -0
  22. constricter/cli/rounds.py +328 -0
  23. constricter/cli/runs.py +90 -0
  24. constricter/cli/schedule.py +463 -0
  25. constricter/cli/traced.py +401 -0
  26. constricter/cli/workers.py +286 -0
  27. constricter/empty.py +66 -0
  28. constricter/fix/__init__.cp313-win32.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp313-win32.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp313-win32.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp313-win32.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp313-win32.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp313-win32.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp313-win32.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp313-win32.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp313-win32.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp313-win32.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp313-win32.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp313-win32.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp313-win32.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp313-win32.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp313-win32.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp313-win32.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp313-win32.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp313-win32.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp313-win32.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp313-win32.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp313-win32.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp313-win32.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp313-win32.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp313-win32.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp313-win32.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp313-win32.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp313-win32.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp313-win32.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp313-win32.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp313-win32.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp313-win32.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp313-win32.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp313-win32.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp313-win32.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp313-win32.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp313-win32.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp313-win32.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp313-win32.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp313-win32.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp313-win32.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp313-win32.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp313-win32.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp313-win32.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp313-win32.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp313-win32.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp313-win32.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp313-win32.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp313-win32.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp313-win32.pyd +0 -0
  125. constricter/fix/libraries/walks.py +49 -0
  126. constricter/fix/tables/aliases.json +147 -0
  127. constricter/fix/tables/attributes.json +700 -0
  128. constricter/fix/tables/awaited.json +69 -0
  129. constricter/fix/tables/awaited_overloads.json +14 -0
  130. constricter/fix/tables/bases.json +888 -0
  131. constricter/fix/tables/classes.json +1941 -0
  132. constricter/fix/tables/elements.json +156 -0
  133. constricter/fix/tables/functions.json +58 -0
  134. constricter/fix/tables/generic_attributes.json +254 -0
  135. constricter/fix/tables/method_overloads.json +461 -0
  136. constricter/fix/tables/method_signatures.json +954 -0
  137. constricter/fix/tables/methods.json +954 -0
  138. constricter/fix/tables/overloads.json +540 -0
  139. constricter/fix/tables/platform.json +37 -0
  140. constricter/fix/tables/returns.json +1362 -0
  141. constricter/fix/tables/scalar_members.json +16 -0
  142. constricter/fix/tables/scalars.json +2707 -0
  143. constricter/fix/tables/source.json +28 -0
  144. constricter/fix/tables/subscriptable.json +77 -0
  145. constricter/fix/tables/type_parameters.json +178 -0
  146. constricter/fix/tables/variables.json +2962 -0
  147. constricter/fix/values/__init__.cp313-win32.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp313-win32.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp313-win32.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp313-win32.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp313-win32.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp313-win32.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp313-win32.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp313-win32.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp313-win32.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp313-win32.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp313-win32.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp313-win32.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp313-win32.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp313-win32.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp313-win32.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp313-win32.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp313-win32.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp313-win32.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp313-win32.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp313-win32.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp313-win32.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp313-win32.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp313-win32.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp313-win32.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp313-win32.pyd +0 -0
  196. constricter/fix/values/unchecked.py +278 -0
  197. constricter/jsonc.py +50 -0
  198. constricter/noqa.py +47 -0
  199. constricter/notebook.py +115 -0
  200. constricter/offences.py +436 -0
  201. constricter/overrides.py +24 -0
  202. constricter/plugins/__init__.py +2 -0
  203. constricter/plugins/flake8.py +133 -0
  204. constricter/plugins/pylint.py +186 -0
  205. constricter/py.typed +0 -0
  206. constricter/recording.py +660 -0
  207. constricter/rules/__init__.cp313-win32.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp313-win32.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp313-win32.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp313-win32.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp313-win32.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp313-win32.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp313-win32.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp313-win32.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp313-win32.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp313-win32.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp313-win32.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp313-win32.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp313-win32.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp313-win32.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp313-win32.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp313-win32.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp313-win32.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp313-win32.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp313-win32.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp313-win32.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp313-win32.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp313-win32.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp313-win32.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp313-win32.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp313-win32.pyd +0 -0
  257. python_constricter-0.4.1.dist-info/METADATA +400 -0
  258. python_constricter-0.4.1.dist-info/RECORD +261 -0
  259. python_constricter-0.4.1.dist-info/WHEEL +4 -0
  260. python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
  261. python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,55 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Every local variable typed where it's first bound."""
3
+
4
+ from constricter._version import __version__ # written by the build, from the git tag
5
+ from constricter.offences import (
6
+ COMMENT_TYPED_TARGET,
7
+ DEFAULT_CHECKS,
8
+ FIX_KINDS,
9
+ LEVELS,
10
+ LONG_TUPLE,
11
+ MAX_LENGTH,
12
+ MISMATCHED_TYPE,
13
+ NARROWABLE_TYPE,
14
+ NESTED_TYPE,
15
+ NESTING,
16
+ REDUNDANT_TYPE,
17
+ UNANNOTATED,
18
+ UNANNOTATED_MEMBER,
19
+ UNTYPED_TARGET,
20
+ UNUSED_UNION_MEMBER,
21
+ VAGUE_TYPE,
22
+ Checks,
23
+ FixPolicy,
24
+ Level,
25
+ Offence,
26
+ )
27
+ from constricter.rules.checker import Coverage, annotation_coverage, check_source, check_tree
28
+
29
+ __all__ = [
30
+ "COMMENT_TYPED_TARGET",
31
+ "DEFAULT_CHECKS",
32
+ "FIX_KINDS",
33
+ "LEVELS",
34
+ "LONG_TUPLE",
35
+ "MAX_LENGTH",
36
+ "MISMATCHED_TYPE",
37
+ "NARROWABLE_TYPE",
38
+ "NESTED_TYPE",
39
+ "NESTING",
40
+ "REDUNDANT_TYPE",
41
+ "UNANNOTATED",
42
+ "UNANNOTATED_MEMBER",
43
+ "UNTYPED_TARGET",
44
+ "UNUSED_UNION_MEMBER",
45
+ "VAGUE_TYPE",
46
+ "Checks",
47
+ "Coverage",
48
+ "FixPolicy",
49
+ "Level",
50
+ "Offence",
51
+ "__version__",
52
+ "annotation_coverage",
53
+ "check_source",
54
+ "check_tree",
55
+ ]
@@ -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,4 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """The version this was built as, from its git tag (written by the build, not tracked)."""
3
+
4
+ __version__ = "0.4.1"
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """The `constricter` command (`command.main`): its options, the files it checks, and how it reports."""
@@ -0,0 +1,107 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Baseline files: the offences a codebase already has, so only new ones are reported.
3
+
4
+ An entry is a file, a code and a variable name, with how many times it occurs; line numbers aren't
5
+ kept, so a baseline survives code moving around. Paths are relative to the baseline file.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from collections import Counter
11
+ from collections.abc import Mapping, Sequence
12
+ from pathlib import Path
13
+ from typing import Final, TypeAlias, cast
14
+
15
+ from constricter import jsonc
16
+ from constricter.offences import Offence
17
+
18
+ VERSION: Final = 1
19
+ Entries: TypeAlias = dict[str, dict[str, int]] # file -> "CODE name" -> count
20
+ _Json: TypeAlias = "str | int | float | bool | list[_Json] | dict[str, _Json] | None"
21
+
22
+
23
+ def key(path: Path, baseline: Path) -> str:
24
+ """Make `path` relative to the baseline at `baseline`.
25
+
26
+ Returns:
27
+ `path` as the baseline records it: relative to it, with `/`.
28
+
29
+ """
30
+ return Path(os.path.relpath(path.resolve(), baseline.resolve().parent)).as_posix()
31
+
32
+
33
+ def _entry(offence: Offence) -> str:
34
+ return f"{offence.code} {offence.name}"
35
+
36
+
37
+ def read(baseline: Path) -> Entries:
38
+ """Return the entries in the baseline file `baseline`.
39
+
40
+ Returns:
41
+ Each file's count of each `CODE name` entry.
42
+
43
+ Raises:
44
+ ValueError: It can't be read, or isn't a baseline.
45
+
46
+ """
47
+ message: str
48
+ document: _Json
49
+ try:
50
+ document = cast("_Json", jsonc.loads(baseline.read_bytes()))
51
+ except (OSError, ValueError) as error:
52
+ message = f"{baseline}: can't read the baseline ({error}); create it with --write-baseline"
53
+ raise ValueError(message) from error
54
+ files: dict[str, _Json]
55
+ entries: Entries = {}
56
+ match document:
57
+ case {"version": 1, "offences": dict() as files}:
58
+ path: str
59
+ counts: _Json
60
+ for path, counts in files.items():
61
+ if not isinstance(counts, dict) or not all(jsonc.is_int(n) for n in counts.values()):
62
+ break
63
+ entries[path] = {entry: n for entry, n in counts.items() if isinstance(n, int)}
64
+ else:
65
+ return entries
66
+ case _:
67
+ pass
68
+ message = f"{baseline}: not a constricter baseline (version {VERSION})"
69
+ raise ValueError(message)
70
+
71
+
72
+ def write(baseline: Path, found: Mapping[str, Sequence[Offence]]) -> int:
73
+ """Write every offence in `found` (keyed as `key` makes them) to `baseline`.
74
+
75
+ Returns:
76
+ How many it wrote.
77
+
78
+ """
79
+ files: Entries = {
80
+ path: dict(sorted(Counter(_entry(o) for o in offences).items()))
81
+ for path, offences in sorted(found.items())
82
+ if offences
83
+ }
84
+ _ = baseline.write_text(
85
+ json.dumps({"version": VERSION, "offences": files}, indent=2) + "\n",
86
+ encoding="utf-8",
87
+ newline="\n",
88
+ )
89
+ return sum(len(offences) for offences in found.values())
90
+
91
+
92
+ def remaining(offences: Sequence[Offence], counts: Mapping[str, int]) -> tuple[list[Offence], int]:
93
+ """Match one file's offences against the baseline's `counts` for it.
94
+
95
+ Returns:
96
+ The offences it doesn't cover, and how many it does.
97
+
98
+ """
99
+ left: Counter[str] = Counter(counts)
100
+ kept: list[Offence] = []
101
+ o: Offence
102
+ for o in offences:
103
+ if left[_entry(o)] > 0:
104
+ left[_entry(o)] -= 1
105
+ else:
106
+ kept.append(o)
107
+ return kept, len(offences) - len(kept)
@@ -0,0 +1,54 @@
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
+ Not on a free-threaded build, whose collector has no generations: each collection there looks through
10
+ everything, and one after every file made a check several times as long. Its cycles wait for the run's
11
+ end (a check of pandas held 2.9 GB so, to 2.7 GB).
12
+ """
13
+
14
+ import contextlib
15
+ import gc
16
+ import sysconfig
17
+ from collections.abc import Generator
18
+ from typing import Final
19
+
20
+ _YOUNG: Final = 1 # the generations collected after each file: 0 and 1, never the frozen or the old
21
+ _FREEZES: Final = hasattr(gc, "freeze") # PyPy's collector has no freezing
22
+ # Whether the collector has generations: a free-threaded build's has none, its GIL on or off.
23
+ _YOUNGER: Final = not sysconfig.get_config_var("Py_GIL_DISABLED")
24
+
25
+
26
+ @contextlib.contextmanager
27
+ def by_hand() -> Generator[None]:
28
+ """Turn automatic collection off for the command's run, and back as it was after.
29
+
30
+ Yields:
31
+ Nothing: the run.
32
+
33
+ """
34
+ was: bool = gc.isenabled()
35
+ gc.disable()
36
+ try:
37
+ yield
38
+ finally:
39
+ if _FREEZES:
40
+ gc.unfreeze()
41
+ if was:
42
+ gc.enable()
43
+
44
+
45
+ def indexed() -> None:
46
+ """Freeze what indexing built (the kept trees, the index): it lives until the check takes it."""
47
+ if _FREEZES:
48
+ gc.freeze()
49
+
50
+
51
+ def sweep() -> None:
52
+ """Collect what the file just checked left behind (see the module docstring)."""
53
+ if _YOUNGER:
54
+ _ = gc.collect(_YOUNG)