python-constricter 0.4.1__cp312-cp312-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.
- constricter/__init__.py +55 -0
- constricter/__main__.py +26 -0
- constricter/_version.py +4 -0
- constricter/cli/__init__.py +2 -0
- constricter/cli/baseline.py +107 -0
- constricter/cli/collecting.py +54 -0
- constricter/cli/command.py +547 -0
- constricter/cli/config.py +322 -0
- constricter/cli/explain.py +104 -0
- constricter/cli/lsp/__init__.py +2 -0
- constricter/cli/lsp/connection.py +475 -0
- constricter/cli/lsp/edits.py +188 -0
- constricter/cli/lsp/guard.py +133 -0
- constricter/cli/lsp/hints.py +391 -0
- constricter/cli/lsp/protocol.py +192 -0
- constricter/cli/lsp/remembered.py +81 -0
- constricter/cli/lsp/stored.py +200 -0
- constricter/cli/options.py +799 -0
- constricter/cli/paths.py +151 -0
- constricter/cli/reading.py +36 -0
- constricter/cli/report.py +397 -0
- constricter/cli/rounds.py +328 -0
- constricter/cli/runs.py +90 -0
- constricter/cli/schedule.py +463 -0
- constricter/cli/traced.py +401 -0
- constricter/cli/workers.py +286 -0
- constricter/empty.py +66 -0
- constricter/fix/__init__.cp312-win32.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp312-win32.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp312-win32.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp312-win32.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp312-win32.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp312-win32.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp312-win32.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp312-win32.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp312-win32.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp312-win32.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp312-win32.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp312-win32.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp312-win32.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp312-win32.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp312-win32.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp312-win32.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp312-win32.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp312-win32.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp312-win32.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp312-win32.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp312-win32.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp312-win32.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp312-win32.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp312-win32.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp312-win32.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp312-win32.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp312-win32.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp312-win32.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp312-win32.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp312-win32.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp312-win32.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp312-win32.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp312-win32.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp312-win32.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp312-win32.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp312-win32.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp312-win32.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp312-win32.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp312-win32.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp312-win32.pyd +0 -0
- constricter/fix/libraries/walks.py +49 -0
- constricter/fix/tables/aliases.json +147 -0
- constricter/fix/tables/attributes.json +700 -0
- constricter/fix/tables/awaited.json +69 -0
- constricter/fix/tables/awaited_overloads.json +14 -0
- constricter/fix/tables/bases.json +888 -0
- constricter/fix/tables/classes.json +1941 -0
- constricter/fix/tables/elements.json +156 -0
- constricter/fix/tables/functions.json +58 -0
- constricter/fix/tables/generic_attributes.json +254 -0
- constricter/fix/tables/method_overloads.json +461 -0
- constricter/fix/tables/method_signatures.json +954 -0
- constricter/fix/tables/methods.json +954 -0
- constricter/fix/tables/overloads.json +540 -0
- constricter/fix/tables/platform.json +37 -0
- constricter/fix/tables/returns.json +1362 -0
- constricter/fix/tables/scalar_members.json +16 -0
- constricter/fix/tables/scalars.json +2707 -0
- constricter/fix/tables/source.json +28 -0
- constricter/fix/tables/subscriptable.json +77 -0
- constricter/fix/tables/type_parameters.json +178 -0
- constricter/fix/tables/variables.json +2962 -0
- constricter/fix/values/__init__.cp312-win32.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp312-win32.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp312-win32.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp312-win32.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp312-win32.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp312-win32.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp312-win32.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp312-win32.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp312-win32.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp312-win32.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp312-win32.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp312-win32.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp312-win32.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp312-win32.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp312-win32.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp312-win32.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp312-win32.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp312-win32.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp312-win32.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp312-win32.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp312-win32.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp312-win32.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp312-win32.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp312-win32.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp312-win32.pyd +0 -0
- constricter/fix/values/unchecked.py +278 -0
- constricter/jsonc.py +50 -0
- constricter/noqa.py +47 -0
- constricter/notebook.py +115 -0
- constricter/offences.py +436 -0
- constricter/overrides.py +24 -0
- constricter/plugins/__init__.py +2 -0
- constricter/plugins/flake8.py +133 -0
- constricter/plugins/pylint.py +186 -0
- constricter/py.typed +0 -0
- constricter/recording.py +660 -0
- constricter/rules/__init__.cp312-win32.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp312-win32.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp312-win32.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp312-win32.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp312-win32.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp312-win32.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp312-win32.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp312-win32.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp312-win32.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp312-win32.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp312-win32.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp312-win32.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp312-win32.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp312-win32.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp312-win32.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp312-win32.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp312-win32.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp312-win32.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp312-win32.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp312-win32.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp312-win32.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp312-win32.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp312-win32.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp312-win32.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp312-win32.pyd +0 -0
- python_constricter-0.4.1.dist-info/METADATA +400 -0
- python_constricter-0.4.1.dist-info/RECORD +261 -0
- python_constricter-0.4.1.dist-info/WHEEL +4 -0
- python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
- python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
constricter/__init__.py
ADDED
|
@@ -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
|
+
]
|
constricter/__main__.py
ADDED
|
@@ -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()
|
constricter/_version.py
ADDED
|
@@ -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)
|