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.
- 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__.cp313-win32.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp313-win32.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp313-win32.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp313-win32.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp313-win32.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp313-win32.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp313-win32.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp313-win32.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp313-win32.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp313-win32.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp313-win32.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp313-win32.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp313-win32.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp313-win32.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp313-win32.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp313-win32.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp313-win32.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp313-win32.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp313-win32.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp313-win32.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp313-win32.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp313-win32.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp313-win32.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp313-win32.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp313-win32.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp313-win32.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp313-win32.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp313-win32.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp313-win32.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp313-win32.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp313-win32.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp313-win32.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp313-win32.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp313-win32.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp313-win32.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp313-win32.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp313-win32.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp313-win32.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp313-win32.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp313-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__.cp313-win32.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp313-win32.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp313-win32.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp313-win32.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp313-win32.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp313-win32.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp313-win32.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp313-win32.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp313-win32.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp313-win32.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp313-win32.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp313-win32.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp313-win32.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp313-win32.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp313-win32.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp313-win32.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp313-win32.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp313-win32.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp313-win32.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp313-win32.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp313-win32.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp313-win32.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp313-win32.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp313-win32.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp313-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__.cp313-win32.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp313-win32.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp313-win32.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp313-win32.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp313-win32.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp313-win32.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp313-win32.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp313-win32.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp313-win32.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp313-win32.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp313-win32.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp313-win32.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp313-win32.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp313-win32.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp313-win32.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp313-win32.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp313-win32.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp313-win32.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp313-win32.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp313-win32.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp313-win32.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp313-win32.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp313-win32.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp313-win32.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp313-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
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Checking files in `order.plan`'s order: each after the modules whose unannotated functions it calls.
|
|
3
|
+
|
|
4
|
+
A component (files calling each other's functions) is checked once all it comes after have settled,
|
|
5
|
+
knowing what their functions return; then, while their `return`s change what its files import, its
|
|
6
|
+
files are checked again, up to `CYCLE_ROUNDS` times, before it settles. With worker processes, a
|
|
7
|
+
component starts as soon as it can, rather than in lockstep with the others.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pickle
|
|
11
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, Final, NamedTuple, cast
|
|
15
|
+
|
|
16
|
+
from constricter.cli import collecting
|
|
17
|
+
from constricter.cli.runs import CoverageRun, FileRun
|
|
18
|
+
from constricter.cli.workers import Again, Answering, Asked, File, Told, Workers, check_share, first_done
|
|
19
|
+
from constricter.empty import Empty
|
|
20
|
+
from constricter.fix.core.known import Guarded, Hints, Outside, Passed, Returns
|
|
21
|
+
from constricter.fix.index import (
|
|
22
|
+
awaits,
|
|
23
|
+
beyond,
|
|
24
|
+
callers,
|
|
25
|
+
crossed,
|
|
26
|
+
decorated,
|
|
27
|
+
fixtures,
|
|
28
|
+
linked,
|
|
29
|
+
loose,
|
|
30
|
+
managed,
|
|
31
|
+
offers,
|
|
32
|
+
order,
|
|
33
|
+
own_methods,
|
|
34
|
+
own_types,
|
|
35
|
+
plain,
|
|
36
|
+
project,
|
|
37
|
+
sides,
|
|
38
|
+
taken,
|
|
39
|
+
tuples,
|
|
40
|
+
)
|
|
41
|
+
from constricter.fix.index.modules import Memo
|
|
42
|
+
from constricter.fix.index.stubs import stubbed
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from constricter.fix.core.signatures import ReadSignature
|
|
46
|
+
|
|
47
|
+
CYCLE_ROUNDS: Final = 3 # how many times to check again files calling each other's functions
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def outside(modules: project.Index, path: Path, hinted: Mapping[Path, tuple[Hints, ...]]) -> Outside:
|
|
51
|
+
"""Find what's known of a file from outside it: what it imports from the others, and its hints.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
It.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
seeded: dict[str, Guarded] = {}
|
|
58
|
+
seeds: dict[str, Passed] = fixtures.visible(modules, path, seeded)
|
|
59
|
+
imported: project.Imported = crossed.imported(
|
|
60
|
+
modules,
|
|
61
|
+
path,
|
|
62
|
+
(seeded, fixtures.attributes(seeds)),
|
|
63
|
+
)
|
|
64
|
+
side_calls: dict[str, str]
|
|
65
|
+
guarded: dict[str, Guarded]
|
|
66
|
+
side_calls, guarded = sides.calls(modules, path, imported.guarded)
|
|
67
|
+
methods: stubbed.Methods = stubbed.methods(modules, path, guarded)
|
|
68
|
+
hints: tuple[Hints, ...] = offers.vetted(modules, path, hinted.get(path, ()))
|
|
69
|
+
# What only a hint can name: the classes the file imports for type checking alone.
|
|
70
|
+
own: offers.Own = offers.own(modules, path) if hints else offers.Own()
|
|
71
|
+
overloaded: dict[str, tuple[ReadSignature, ...]] = {
|
|
72
|
+
**stubbed.overloaded(modules, path, guarded),
|
|
73
|
+
**methods.signatures,
|
|
74
|
+
**own_methods.overloaded(modules, path, guarded),
|
|
75
|
+
}
|
|
76
|
+
return Outside(
|
|
77
|
+
calls={**imported.calls, **side_calls, **decorated.own(modules, path)},
|
|
78
|
+
# With those of the classes the overloaded methods' returns import.
|
|
79
|
+
classes=crossed.guarded_classes(modules, path, imported, guarded),
|
|
80
|
+
hints=hints,
|
|
81
|
+
type_vars=project.type_vars(modules, path),
|
|
82
|
+
returned=awaits.returned(
|
|
83
|
+
modules,
|
|
84
|
+
path,
|
|
85
|
+
guarded,
|
|
86
|
+
loose.returned(modules, path, guarded, imported.returned),
|
|
87
|
+
),
|
|
88
|
+
guarded=guarded,
|
|
89
|
+
generics=imported.generics | own.generics,
|
|
90
|
+
callees=callers.callees(modules, path),
|
|
91
|
+
parameters=callers.own_parameters(modules, path),
|
|
92
|
+
overloaded=overloaded,
|
|
93
|
+
installed_classes=stubbed.classes(modules, path),
|
|
94
|
+
installed_parameters=methods.parameters,
|
|
95
|
+
installed_lineage=own_types.lineages(modules, path, guarded, methods.lineage),
|
|
96
|
+
installed_aliases=methods.aliases,
|
|
97
|
+
checking=own.guarded,
|
|
98
|
+
plain=plain.classes(modules, path),
|
|
99
|
+
members={**imported.members, **plain.members(modules, path)},
|
|
100
|
+
same=linked.same(modules, path, {**offers.imported(path, hints), **guarded}),
|
|
101
|
+
partial=imported.partial,
|
|
102
|
+
tuples=tuples.fields(modules, path, guarded),
|
|
103
|
+
fixtures=seeds,
|
|
104
|
+
beyond=beyond.library_bases(modules, path),
|
|
105
|
+
awaits=awaits.calls(modules, path, guarded),
|
|
106
|
+
values=linked.values(modules, path),
|
|
107
|
+
untyped=linked.untyped(modules, path),
|
|
108
|
+
unions=tuples.unions(modules, path, guarded),
|
|
109
|
+
managers=managed.calls(modules, path, guarded),
|
|
110
|
+
vouched=decorated.vouched(modules, path),
|
|
111
|
+
takers=taken.calls(modules, path, guarded),
|
|
112
|
+
emptied=beyond.emptied(modules, path),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Rounds(NamedTuple):
|
|
117
|
+
"""How a check stands to the files' earlier ones.
|
|
118
|
+
|
|
119
|
+
`merge` joins a file's two rounds' runs (`None`: the later one's stands). `kept`: the last runs
|
|
120
|
+
of those whose text is as it was then, by index: each is checked only if it knows anything else
|
|
121
|
+
from outside now, else that run is its own.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
merge: Callable[[FileRun, FileRun], FileRun] | None = None
|
|
125
|
+
kept: Mapping[int, FileRun] = Empty()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class _Planned:
|
|
129
|
+
"""What checking the files in order has found so far, and the index as it grows."""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
paths: Sequence[Path],
|
|
134
|
+
modules: project.Index,
|
|
135
|
+
hinted: Mapping[Path, tuple[Hints, ...]],
|
|
136
|
+
rounds: Rounds,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Plan checking `paths` with `modules` and their hints, after `rounds`."""
|
|
139
|
+
self.paths: Sequence[Path] = paths
|
|
140
|
+
self.modules: project.Index = modules
|
|
141
|
+
self.hinted: Mapping[Path, tuple[Hints, ...]] = hinted
|
|
142
|
+
self.plan: order.Plan = order.plan(modules, paths)
|
|
143
|
+
self.runs: dict[int, FileRun] = {}
|
|
144
|
+
self.earlier: Rounds = rounds
|
|
145
|
+
self.given: dict[int, Outside] = {} # what each file was last checked knowing
|
|
146
|
+
self.rounds: list[int] = [0] * len(self.plan.components)
|
|
147
|
+
# Each file's component.
|
|
148
|
+
self.of: dict[int, int] = {
|
|
149
|
+
at: part for part, files in enumerate(self.plan.components) for at in files
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def start(self, component: int) -> dict[int, Outside]:
|
|
153
|
+
"""Find what the component's files are to be checked knowing, now that it can start.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Each file's; not a kept one's (see `checked`) that knows what it was last checked knowing.
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
given: dict[int, Outside] = {
|
|
160
|
+
at: outside(self.modules, self.paths[at], self.hinted) for at in self.plan.components[component]
|
|
161
|
+
}
|
|
162
|
+
self.given.update(given)
|
|
163
|
+
return {
|
|
164
|
+
at: known
|
|
165
|
+
for at, known in given.items()
|
|
166
|
+
if at not in self.earlier.kept or _WORKER.given.get(self.paths[at]) != known
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
def record(self, component: int, found: Mapping[int, FileRun]) -> dict[str, Returns]:
|
|
170
|
+
"""Record what a round of the component's files found, and what its functions return.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
What each checked module's functions return now, by its name.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
merge: Callable[[FileRun, FileRun], FileRun] | None = self.earlier.merge
|
|
177
|
+
at: int
|
|
178
|
+
run: FileRun
|
|
179
|
+
for at, run in found.items():
|
|
180
|
+
self.runs[at] = merge(self.runs[at], run) if merge and at in self.runs else run
|
|
181
|
+
returned: dict[str, Returns] = {
|
|
182
|
+
self.plan.names[at]: run.returned
|
|
183
|
+
for at, run in found.items()
|
|
184
|
+
if at in self.plan.names and not isinstance(run, CoverageRun)
|
|
185
|
+
}
|
|
186
|
+
self.modules = linked.with_returned(self.modules, returned)
|
|
187
|
+
self.rounds[component] += 1
|
|
188
|
+
return returned
|
|
189
|
+
|
|
190
|
+
def settled(self, component: int) -> bool:
|
|
191
|
+
"""Check whether the component has no more rounds to run: one file, or `CYCLE_ROUNDS` done.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Whether it is.
|
|
195
|
+
|
|
196
|
+
"""
|
|
197
|
+
return len(self.plan.components[component]) == 1 or self.rounds[component] > CYCLE_ROUNDS
|
|
198
|
+
|
|
199
|
+
def settle(self, component: int, found: Mapping[int, FileRun]) -> dict[int, Outside]:
|
|
200
|
+
"""Record a round (see `record`), and find what to check again, in this process.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Those of its files to check again, each with what it's to know now; none once it's settled.
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
_ = self.record(component, found)
|
|
207
|
+
if self.settled(component):
|
|
208
|
+
return {}
|
|
209
|
+
again: dict[int, Outside] = {
|
|
210
|
+
at: outside(self.modules, self.paths[at], self.hinted) for at in self.plan.components[component]
|
|
211
|
+
}
|
|
212
|
+
again = {at: known for at, known in again.items() if known.returned != self.given[at].returned}
|
|
213
|
+
self.given.update(again)
|
|
214
|
+
return again
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def checked(
|
|
218
|
+
paths: Sequence[Path],
|
|
219
|
+
check: Callable[[Path, Outside], FileRun],
|
|
220
|
+
pool: Workers | None,
|
|
221
|
+
known: tuple[project.Index, Mapping[Path, tuple[Hints, ...]]],
|
|
222
|
+
rounds: Rounds,
|
|
223
|
+
) -> tuple[list[FileRun], project.Index]:
|
|
224
|
+
"""Check `paths` in order (see `order.plan`), in `pool`'s workers or this process.
|
|
225
|
+
|
|
226
|
+
`known`: the index and each file's hints; `rounds`: how this check stands to earlier ones.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
What each file found, and the index, with what their functions return.
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
planned: _Planned = _Planned(paths, known[0], known[1], rounds)
|
|
233
|
+
if pool is None:
|
|
234
|
+
component: int
|
|
235
|
+
for component in range(len(planned.plan.components)):
|
|
236
|
+
_in_process(planned, check, component, planned.start(component))
|
|
237
|
+
else:
|
|
238
|
+
_Pooled(planned, check, pool).run()
|
|
239
|
+
runs: list[FileRun] = [planned.runs.get(at) or planned.earlier.kept[at] for at in range(len(paths))]
|
|
240
|
+
return runs, planned.modules
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _in_process(
|
|
244
|
+
planned: _Planned,
|
|
245
|
+
check: Callable[[Path, Outside], FileRun],
|
|
246
|
+
component: int,
|
|
247
|
+
todo: Mapping[int, Outside],
|
|
248
|
+
) -> None:
|
|
249
|
+
"""Check a component's files here (`todo`: each with what it's to know), round by round."""
|
|
250
|
+
if todo:
|
|
251
|
+
runs: list[FileRun] = check_share(check, [(planned.paths[at], given) for at, given in todo.items()])
|
|
252
|
+
_WORKER.given.update((planned.paths[at], given) for at, given in todo.items())
|
|
253
|
+
_in_process(planned, check, component, planned.settle(component, dict(zip(todo, runs, strict=True))))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@dataclass
|
|
257
|
+
class _Worker:
|
|
258
|
+
"""What a worker process knows: the index it was sent, and what each of its files last knew.
|
|
259
|
+
|
|
260
|
+
A run checking alone keeps the latter here too: its files' checks, round after round.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
modules: project.Index = field(default_factory=lambda: project.Index({}, []))
|
|
264
|
+
given: dict[Path, Outside] = field(default_factory=dict[Path, Outside])
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass
|
|
268
|
+
class _News:
|
|
269
|
+
"""What each recorded round's modules return, and how much of it each worker has been told."""
|
|
270
|
+
|
|
271
|
+
returned: list[dict[str, Returns]]
|
|
272
|
+
told: list[int]
|
|
273
|
+
|
|
274
|
+
def since(self, worker: int) -> dict[str, Returns]:
|
|
275
|
+
"""Gather what a worker hasn't been told yet, which it's told now.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Each such module's returns, the latest of each.
|
|
279
|
+
|
|
280
|
+
"""
|
|
281
|
+
news: dict[str, Returns] = {}
|
|
282
|
+
last: int = self.told[worker]
|
|
283
|
+
each: dict[str, Returns]
|
|
284
|
+
for each in self.returned[last:]:
|
|
285
|
+
news.update(each)
|
|
286
|
+
self.told[worker] = len(self.returned)
|
|
287
|
+
return news
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
_WORKER: Final = _Worker()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def sent(modules: project.Index) -> bytes:
|
|
294
|
+
"""Pickle the index for the workers: once, for them all, each then reading it at the same time.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
It, without what it keeps worked out, which each worker works out for itself.
|
|
298
|
+
|
|
299
|
+
"""
|
|
300
|
+
return pickle.dumps(modules._replace(memo=None), pickle.HIGHEST_PROTOCOL)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def know(index: Told) -> None:
|
|
304
|
+
"""Take the index (see `sent`), in a worker process: what it works its files' `Outside` out from."""
|
|
305
|
+
_WORKER.modules = cast("project.Index", pickle.loads(index))._replace(memo=Memo())
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def forget() -> None:
|
|
309
|
+
"""Forget what each file was last checked knowing, as a run starts: its workers are new."""
|
|
310
|
+
_WORKER.given.clear()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def check_asked(check: Callable[[Path, Outside], FileRun], asked: Asked) -> list[FileRun | None]:
|
|
314
|
+
"""Check a worker's files, each knowing what the worker's index says of it now.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
What each found; `None` for one left unchecked, knowing what it last did (see `Again`).
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
files: Sequence[File]
|
|
321
|
+
returned: Mapping[str, Returns]
|
|
322
|
+
again: Again
|
|
323
|
+
files, returned, again = asked
|
|
324
|
+
_WORKER.modules = linked.with_returned(_WORKER.modules, returned)
|
|
325
|
+
found: list[FileRun | None] = []
|
|
326
|
+
path: Path
|
|
327
|
+
hints: tuple[Hints, ...]
|
|
328
|
+
for path, hints in files:
|
|
329
|
+
known: Outside = outside(_WORKER.modules, path, {path: hints} if hints else {})
|
|
330
|
+
last: Outside | None = _WORKER.given.get(path)
|
|
331
|
+
if last is not None and (
|
|
332
|
+
(again is Again.RETURNED and known.returned == last.returned)
|
|
333
|
+
or (again is Again.OUTSIDE and known == last)
|
|
334
|
+
):
|
|
335
|
+
found.append(None)
|
|
336
|
+
continue
|
|
337
|
+
_WORKER.given[path] = known
|
|
338
|
+
found.append(check(path, known))
|
|
339
|
+
collecting.sweep()
|
|
340
|
+
return found
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class _Pooled:
|
|
344
|
+
"""Checking files in the workers, in `order.plan`'s order: each component once all it follows settle.
|
|
345
|
+
|
|
346
|
+
Each worker has the index, and works out what its files know from outside as it reaches them:
|
|
347
|
+
it's told what the checked modules' functions return since it was last asked (`told`).
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
def __init__(self, planned: _Planned, check: Callable[[Path, Outside], FileRun], pool: Workers) -> None:
|
|
351
|
+
"""Plan the order: what each component waits for, and what follows it."""
|
|
352
|
+
self.planned: _Planned = planned
|
|
353
|
+
self.check: Callable[[Path, Outside], FileRun] = check
|
|
354
|
+
self.pool: Workers = pool
|
|
355
|
+
self.waiting: list[int] = [len(after) for after in planned.plan.after] # components still to settle
|
|
356
|
+
self.followers: list[list[int]] = [[] for _ in planned.plan.components]
|
|
357
|
+
component: int
|
|
358
|
+
after: frozenset[int]
|
|
359
|
+
for component, after in enumerate(planned.plan.after):
|
|
360
|
+
callee: int
|
|
361
|
+
for callee in after:
|
|
362
|
+
self.followers[callee].append(component)
|
|
363
|
+
self.left: list[int] = [0] * len(planned.plan.components) # each component's files being checked
|
|
364
|
+
self.found: dict[int, dict[int, FileRun]] = {} # what they've found so far
|
|
365
|
+
self.active: dict[Answering, list[int]] = {} # what each worker is checking
|
|
366
|
+
self.news: _News = _News([], [0] * len(pool.shares))
|
|
367
|
+
|
|
368
|
+
def run(self) -> None:
|
|
369
|
+
"""Check every file: start what waits for nothing, then whatever each answer lets start."""
|
|
370
|
+
self.pool.tell(know, sent(self.planned.modules))
|
|
371
|
+
self.submit(
|
|
372
|
+
[
|
|
373
|
+
at
|
|
374
|
+
for component, count in enumerate(self.waiting)
|
|
375
|
+
if not count
|
|
376
|
+
for at in self.planned.plan.components[component]
|
|
377
|
+
],
|
|
378
|
+
again=False,
|
|
379
|
+
)
|
|
380
|
+
done: set[Answering]
|
|
381
|
+
for done in iter(self._done, None):
|
|
382
|
+
first: list[int] = []
|
|
383
|
+
again: list[int] = []
|
|
384
|
+
future: Answering
|
|
385
|
+
for future in done:
|
|
386
|
+
at: int
|
|
387
|
+
run: FileRun | None
|
|
388
|
+
for at, run in zip(self.active.pop(future), future.result(), strict=True):
|
|
389
|
+
todo: tuple[list[int], bool] = self._answered(at, run)
|
|
390
|
+
(again if todo[1] else first).extend(todo[0])
|
|
391
|
+
self.submit(first, again=False)
|
|
392
|
+
self.submit(again, again=True)
|
|
393
|
+
|
|
394
|
+
def _done(self) -> set[Answering] | None:
|
|
395
|
+
"""Wait for a worker's answer.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Those that have answered; `None` once none is checking anything.
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
return first_done(self.active) if self.active else None
|
|
402
|
+
|
|
403
|
+
def submit(self, files: Sequence[int], *, again: bool) -> None:
|
|
404
|
+
"""Start checking `files` (by index), each by its worker.
|
|
405
|
+
|
|
406
|
+
`again`: only those whose imports return something else now. Otherwise a kept file (see
|
|
407
|
+
`checked`) only if it knows anything else from outside now.
|
|
408
|
+
"""
|
|
409
|
+
if not files:
|
|
410
|
+
return
|
|
411
|
+
hinted: Mapping[Path, tuple[Hints, ...]] = self.planned.hinted
|
|
412
|
+
paths: Sequence[Path] = self.planned.paths
|
|
413
|
+
kept: Mapping[int, FileRun] = self.planned.earlier.kept
|
|
414
|
+
wanted: frozenset[int] = frozenset(files)
|
|
415
|
+
worker: int
|
|
416
|
+
share: list[int]
|
|
417
|
+
for worker, share in enumerate(self.pool.shares):
|
|
418
|
+
mine: list[int] = [at for at in share if at in wanted]
|
|
419
|
+
each: list[int]
|
|
420
|
+
how: Again
|
|
421
|
+
for each, how in (
|
|
422
|
+
[(mine, Again.RETURNED)]
|
|
423
|
+
if again
|
|
424
|
+
else [
|
|
425
|
+
([at for at in mine if at not in kept], Again.NO),
|
|
426
|
+
([at for at in mine if at in kept], Again.OUTSIDE),
|
|
427
|
+
]
|
|
428
|
+
):
|
|
429
|
+
if each:
|
|
430
|
+
given: list[File] = [(paths[at], hinted.get(paths[at], ())) for at in each]
|
|
431
|
+
asked: Asked = (given, self.news.since(worker), how)
|
|
432
|
+
self.active[self.pool.ask(worker, check_asked, self.check, asked)] = each
|
|
433
|
+
at: int
|
|
434
|
+
for at in files:
|
|
435
|
+
self.left[self.planned.of[at]] += 1
|
|
436
|
+
|
|
437
|
+
def _answered(self, at: int, run: FileRun | None) -> tuple[list[int], bool]:
|
|
438
|
+
"""Record what file `at` found (`None`: left unchecked); its component's round done, settle it.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
The files to check next, and whether again (the component's, where their imports return
|
|
442
|
+
anew) or for the first time (those of the components it let start).
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
component: int = self.planned.of[at]
|
|
446
|
+
if run is not None:
|
|
447
|
+
self.found.setdefault(component, {})[at] = run
|
|
448
|
+
self.left[component] -= 1
|
|
449
|
+
if self.left[component]:
|
|
450
|
+
return [], False
|
|
451
|
+
found: dict[int, FileRun]
|
|
452
|
+
# None found: every file's imports return what they did, and the component's settled.
|
|
453
|
+
if found := self.found.pop(component, {}):
|
|
454
|
+
self.news.returned.append(self.planned.record(component, found))
|
|
455
|
+
if not self.planned.settled(component):
|
|
456
|
+
return list(self.planned.plan.components[component]), True
|
|
457
|
+
started: list[int] = []
|
|
458
|
+
follower: int
|
|
459
|
+
for follower in self.followers[component]:
|
|
460
|
+
self.waiting[follower] -= 1
|
|
461
|
+
if not self.waiting[follower]:
|
|
462
|
+
started.extend(self.planned.plan.components[follower])
|
|
463
|
+
return started, False
|