python-constricter 0.4.1__cp314-cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp314-win_amd64.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp314-win_amd64.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp314-win_amd64.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp314-win_amd64.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp314-win_amd64.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp314-win_amd64.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp314-win_amd64.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp314-win_amd64.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp314-win_amd64.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp314-win_amd64.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp314-win_amd64.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp314-win_amd64.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp314-win_amd64.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp314-win_amd64.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp314-win_amd64.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp314-win_amd64.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp314-win_amd64.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp314-win_amd64.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp314-win_amd64.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp314-win_amd64.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp314-win_amd64.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp314-win_amd64.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp314-win_amd64.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp314-win_amd64.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp314-win_amd64.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp314-win_amd64.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp314-win_amd64.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp314-win_amd64.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,332 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""`--fix` for `with manager as name:`: what the context manager's `__enter__` returns.
|
|
3
|
+
|
|
4
|
+
`name` is typed as a call to `manager.__enter__()` would be: by the standard-library tables
|
|
5
|
+
(`zipfile.ZipFile(path)` gives itself, `tempfile.TemporaryDirectory()` a `str`), a project class's
|
|
6
|
+
declared `__enter__`, or an installed package's. A call to one of the module's own functions made a
|
|
7
|
+
context manager by `@contextmanager` gives what it declares it yields (`Iterator[T]`'s `T`), as does
|
|
8
|
+
one to such a method of its classes, on a receiver whose type is known (`self.defs.entry(key)`).
|
|
9
|
+
One declaring no return gives what its `yield`s do (see `constricter.rules.recorded`), and another
|
|
10
|
+
checked file's function either (see `constricter.fix.index.managed`). `open(...)`'s is
|
|
11
|
+
`constricter.fix.libraries.opened`'s, by its mode.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import ast
|
|
15
|
+
from collections import Counter
|
|
16
|
+
from collections.abc import Mapping, Sequence
|
|
17
|
+
from typing import Final, TypeAlias, cast
|
|
18
|
+
|
|
19
|
+
from constricter.fix.core.known import ImportPlan, Inference, Known, Passed
|
|
20
|
+
from constricter.fix.libraries import stdlib
|
|
21
|
+
from constricter.fix.values.inference import inference
|
|
22
|
+
from constricter.rules.annotations import dotted, is_vague, node_name
|
|
23
|
+
from constricter.rules.declarations import defined_type_vars
|
|
24
|
+
from constricter.rules.syntax import Start
|
|
25
|
+
from constricter.rules.walked import classes, of_type
|
|
26
|
+
|
|
27
|
+
ENTER: Final = "__enter__"
|
|
28
|
+
_AENTER: Final = "__aenter__"
|
|
29
|
+
_NONE: Final = "None"
|
|
30
|
+
_MANAGER: Final = ["contextmanager"] # the one decorator that makes a generator function a manager
|
|
31
|
+
# What such a function declares it returns: its first argument is what it yields.
|
|
32
|
+
_YIELDING: Final = frozenset({"Iterator", "Generator", "Iterable"})
|
|
33
|
+
_CALL: Final = "call" # the fix kind of a function's declared return
|
|
34
|
+
_METHOD: Final = "method" # and of a method's
|
|
35
|
+
_SELF: Final = "Self" # in a method's declared type: its receiver's class, which a base's isn't
|
|
36
|
+
_STDLIB: Final = "stdlib" # the fix kind of what typeshed declares
|
|
37
|
+
# `unittest.mock`'s patchers that make a mock, each with how many arguments come before its `new`.
|
|
38
|
+
_PATCHES: Final = {"unittest.mock.patch": 1, "unittest.mock.patch.object": 2}
|
|
39
|
+
_REPLACEMENTS: Final = frozenset({"new", "new_callable"}) # the keywords that give the mock's replacement
|
|
40
|
+
_MOCKS: Final = ("unittest.mock.MagicMock", "unittest.mock.AsyncMock")
|
|
41
|
+
# Before a function's name in `Returned`'s tables: what `with` gives of one `@contextmanager` makes
|
|
42
|
+
# a manager that declares no return, by its `yield`s.
|
|
43
|
+
ENTERED: Final = "with "
|
|
44
|
+
_RETURNED: Final = "returned" # the fix kind of what `yield`s alone decide
|
|
45
|
+
# What a `with` statement binds: its inference, the values it rests on, and what else a guess does.
|
|
46
|
+
Entered: TypeAlias = tuple[Inference, list[ast.expr], frozenset[str]]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_manager(func: ast.AST) -> bool:
|
|
50
|
+
"""Check whether a function is one `@contextmanager` alone decorates, however it's imported.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Whether it is: a plain `def`.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
return (
|
|
57
|
+
isinstance(func, ast.FunctionDef)
|
|
58
|
+
and [node_name(decorator) for decorator in func.decorator_list] == _MANAGER
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def undeclared(tree: ast.Module) -> frozenset[str]:
|
|
63
|
+
"""Name the module's top-level functions `@contextmanager` makes managers that declare no return.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Them: each typed by its `yield`s, if at all.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
return frozenset(
|
|
70
|
+
stmt.name
|
|
71
|
+
for stmt in tree.body
|
|
72
|
+
if isinstance(stmt, ast.FunctionDef) and is_manager(stmt) and stmt.returns is None
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def managers(tree: ast.Module) -> dict[str, str]:
|
|
77
|
+
"""Map each function `@contextmanager` makes a context manager to what `with` gives of it.
|
|
78
|
+
|
|
79
|
+
A plain `def`, defined once, decorated by `contextmanager` alone (however it's imported), that
|
|
80
|
+
declares an `Iterator[T]`, `Generator[T, ...]` or `Iterable[T]`: `T`, unless it's `None`, vague
|
|
81
|
+
or names one of the module's type variables. At the module's top level, or (as `Class.method`)
|
|
82
|
+
directly in a class it defines once: there, not a `T` naming `Self`.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Each function's name, and `T` as source text.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
type_vars: frozenset[str] = defined_type_vars(tree)
|
|
89
|
+
found: dict[str, str] = _managers(tree.body, "", type_vars)
|
|
90
|
+
nodes: Sequence[ast.ClassDef] = classes(tree)
|
|
91
|
+
owners: Counter[str] = Counter(node.name for node in nodes)
|
|
92
|
+
node: ast.ClassDef
|
|
93
|
+
for node in nodes:
|
|
94
|
+
if owners[node.name] == 1:
|
|
95
|
+
found.update(_managers(node.body, f"{node.name}.", type_vars | {_SELF}))
|
|
96
|
+
return found
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _managers(body: Sequence[ast.stmt], prefix: str, unusable: frozenset[str]) -> dict[str, str]:
|
|
100
|
+
"""Find the functions directly in `body` that `@contextmanager` makes context managers (see `managers`).
|
|
101
|
+
|
|
102
|
+
`unusable`: the names a yielded type can't be written with.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Each one's name after `prefix`, and what it yields as source text.
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
counts: Counter[str] = Counter(
|
|
109
|
+
stmt.name for stmt in body if isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
110
|
+
)
|
|
111
|
+
found: dict[str, str] = {}
|
|
112
|
+
stmt: ast.stmt
|
|
113
|
+
for stmt in body:
|
|
114
|
+
yielded: ast.expr | None
|
|
115
|
+
if (
|
|
116
|
+
isinstance(stmt, ast.FunctionDef)
|
|
117
|
+
and counts[stmt.name] == 1
|
|
118
|
+
and is_manager(stmt)
|
|
119
|
+
and (yielded := _yielded(stmt.returns)) is not None
|
|
120
|
+
and not unusable & {node_name(node) for node in ast.walk(yielded)}
|
|
121
|
+
):
|
|
122
|
+
found[f"{prefix}{stmt.name}"] = ast.unparse(yielded)
|
|
123
|
+
return found
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _yielded(returns: ast.expr | None) -> ast.expr | None:
|
|
127
|
+
"""Read what a generator function's declared return says it yields.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
`Iterator[T]`'s `T`, or `None` for any other annotation, a `None`, a string or a vague `T`.
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
index: ast.expr
|
|
134
|
+
match returns:
|
|
135
|
+
case ast.Subscript(slice=index) if node_name(returns.value) in _YIELDING:
|
|
136
|
+
first: ast.expr = index.elts[0] if isinstance(index, ast.Tuple) and index.elts else index
|
|
137
|
+
return None if isinstance(first, ast.Constant | ast.Tuple) or is_vague(first) else first
|
|
138
|
+
case _:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _patched(manager: ast.expr, known: Known) -> Inference | None:
|
|
143
|
+
"""Infer what `with mock.patch(target) as name:` binds: the mock it makes, with no `new` given.
|
|
144
|
+
|
|
145
|
+
`unittest.mock.patch` and `patch.object`, however the module names them: a `MagicMock`, or an
|
|
146
|
+
`AsyncMock` for an `async def`, as typeshed declares it.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The inference, or `None` for any other manager, one given its replacement (`new`,
|
|
150
|
+
`new_callable`, or by position), or a module that can't name both classes.
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
func: ast.expr
|
|
154
|
+
args: list[ast.expr]
|
|
155
|
+
keywords: list[ast.keyword]
|
|
156
|
+
match manager:
|
|
157
|
+
case ast.Call(func=func, args=args, keywords=keywords):
|
|
158
|
+
before: int | None = _PATCHES.get(stdlib.resolved(func, known.names.stdlib) or "")
|
|
159
|
+
given: bool = any(isinstance(arg, ast.Starred) for arg in args) or any(
|
|
160
|
+
keyword.arg is None or keyword.arg in _REPLACEMENTS for keyword in keywords
|
|
161
|
+
)
|
|
162
|
+
plan: ImportPlan | None = known.names.plan
|
|
163
|
+
if before is None or len(args) > before or given or plan is None:
|
|
164
|
+
return None
|
|
165
|
+
spelled: list[str | None] = [plan.spell(mock) for mock in _MOCKS]
|
|
166
|
+
return (
|
|
167
|
+
None
|
|
168
|
+
if None in spelled
|
|
169
|
+
else Inference(
|
|
170
|
+
" | ".join(name for name in spelled if name is not None),
|
|
171
|
+
"`unittest.mock.patch`'s mock, given no other",
|
|
172
|
+
frozenset({_STDLIB}),
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
case _:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def targets(module: ast.Module) -> list[Start]:
|
|
180
|
+
"""Find the `with` statements' managers that bind a target: each one's `__enter__` is called.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Where each such manager starts (its line and column).
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
return [
|
|
187
|
+
(item.context_expr.lineno, item.context_expr.col_offset)
|
|
188
|
+
for node in cast("list[ast.With]", of_type(module, ast.With))
|
|
189
|
+
for item in node.items
|
|
190
|
+
if item.optional_vars is not None
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def entering(manager: ast.expr) -> ast.Call:
|
|
195
|
+
"""Write the call a `with` statement makes of its context manager: `manager.__enter__()`.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
It, placed where `manager` is.
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
method: ast.Attribute = ast.copy_location(ast.Attribute(manager, ENTER, ast.Load()), manager)
|
|
202
|
+
return ast.copy_location(ast.Call(method, [], []), manager)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def entered_async(
|
|
206
|
+
manager: ast.expr,
|
|
207
|
+
known: Known,
|
|
208
|
+
declared: Mapping[str, str],
|
|
209
|
+
) -> Entered | None:
|
|
210
|
+
"""Infer what `async with manager as name:` binds `name` to: a standard-library manager's `__aenter__`'s.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
The inference, and the value it rests on; or `None`.
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
own: Inference | None = inference(manager, known, declared)
|
|
217
|
+
found: Inference | None = None if own is None else stdlib.awaited_member(own.annotation, _AENTER, known)
|
|
218
|
+
if own is None or found is None:
|
|
219
|
+
return None
|
|
220
|
+
return found._replace(kinds=found.kinds | own.kinds), [manager], frozenset()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def entered(
|
|
224
|
+
manager: ast.expr,
|
|
225
|
+
known: Known,
|
|
226
|
+
declared: Mapping[str, str],
|
|
227
|
+
functions: Mapping[str, Passed],
|
|
228
|
+
) -> Entered | None:
|
|
229
|
+
"""Infer what `with manager as name:` binds `name` to.
|
|
230
|
+
|
|
231
|
+
`declared`: the scope's typed names; `functions`: the module's `managers`, and those it imports,
|
|
232
|
+
each with what it rests on if it's a guess.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The inference, the values it rests on (a guess about one makes it a guess) and what else it
|
|
236
|
+
does as a guess; or `None`.
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
name: str
|
|
240
|
+
receiver: ast.expr
|
|
241
|
+
func: ast.expr
|
|
242
|
+
made: Entered | None
|
|
243
|
+
match manager:
|
|
244
|
+
case ast.Call(func=ast.Name() | ast.Attribute() as func) if (
|
|
245
|
+
made := _function(dotted(func) or "", known, declared, functions)
|
|
246
|
+
) is not None:
|
|
247
|
+
return made
|
|
248
|
+
case ast.Call(func=ast.Attribute(value=receiver, attr=name)) if (
|
|
249
|
+
made := _method(receiver, name, (known, declared), functions)
|
|
250
|
+
) is not None:
|
|
251
|
+
return made
|
|
252
|
+
case _:
|
|
253
|
+
pass
|
|
254
|
+
patched: Inference | None
|
|
255
|
+
if (patched := _patched(manager, known)) is not None:
|
|
256
|
+
return patched, [], frozenset()
|
|
257
|
+
# A standard-library manager that returns itself gives its own type, its type arguments included.
|
|
258
|
+
own: Inference | None = inference(manager, known, declared)
|
|
259
|
+
found: Inference | None = (
|
|
260
|
+
own._replace(reason=f"{own.reason}, its own context manager")
|
|
261
|
+
if own is not None and stdlib.enters_itself(own.annotation, known)
|
|
262
|
+
else inference(entering(manager), known, declared)
|
|
263
|
+
)
|
|
264
|
+
# What enters as `None` (a `catch_warnings()` that records nothing) binds nothing worth declaring.
|
|
265
|
+
return None if found is None or found.annotation == _NONE else (found, [manager], frozenset())
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _method(
|
|
269
|
+
receiver: ast.expr,
|
|
270
|
+
name: str,
|
|
271
|
+
typed: tuple[Known, Mapping[str, str]],
|
|
272
|
+
functions: Mapping[str, Passed],
|
|
273
|
+
) -> Entered | None:
|
|
274
|
+
"""Infer what `with receiver.name(...) as target:` binds, for a method `@contextmanager` makes a manager.
|
|
275
|
+
|
|
276
|
+
Of the module's classes, on a receiver whose type is known (`typed`: the module's declarations
|
|
277
|
+
and the scope's names): what it declares it yields, or else what its `yield`s give, a guess as
|
|
278
|
+
a method's `return`s are.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
What `entered` does, or `None`.
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
known: Known
|
|
285
|
+
known, _ = typed
|
|
286
|
+
loose: str = f"{ENTERED}{name}"
|
|
287
|
+
if not any(key.endswith(f".{name}") for key in functions) and not any(
|
|
288
|
+
loose in methods for methods in known.returned.methods.values()
|
|
289
|
+
):
|
|
290
|
+
return None # most calls: no receiver to type
|
|
291
|
+
owner: Inference | None = inference(receiver, *typed)
|
|
292
|
+
definer: str | None = None if owner is None else known.class_side.lineage.definer(owner.annotation, name)
|
|
293
|
+
if owner is None or definer is None:
|
|
294
|
+
return None
|
|
295
|
+
reason: str = f"what `{definer}.{name}`'s context manager yields"
|
|
296
|
+
kinds: frozenset[str] = owner.kinds - {"copy"} | {_METHOD}
|
|
297
|
+
if f"{definer}.{name}" in functions:
|
|
298
|
+
return Inference(functions[f"{definer}.{name}"][0], reason, kinds), [receiver], frozenset()
|
|
299
|
+
yielded: str | None
|
|
300
|
+
if (yielded := known.returned.methods.get(definer, {}).get(loose)) is None:
|
|
301
|
+
return None
|
|
302
|
+
rests: frozenset[str] = known.returned.guesses.get(f"{definer}.{loose}", frozenset()) | {_RETURNED}
|
|
303
|
+
return Inference(yielded, reason, kinds | {_RETURNED}), [receiver], rests
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _function(
|
|
307
|
+
callee: str,
|
|
308
|
+
known: Known,
|
|
309
|
+
declared: Mapping[str, str],
|
|
310
|
+
functions: Mapping[str, Passed],
|
|
311
|
+
) -> Entered | None:
|
|
312
|
+
"""Infer what `with callee(...) as name:` binds, for a function `@contextmanager` makes a manager.
|
|
313
|
+
|
|
314
|
+
One of `functions`, by what it declares it yields; or one of the module's own that declares
|
|
315
|
+
nothing, by its `yield`s (see `ENTERED`). Not through a name the scope types: a local.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
What `entered` does, or `None`.
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
if not callee or callee.partition(".")[0] in declared:
|
|
322
|
+
return None
|
|
323
|
+
reason: str = f"what `{callee}`'s context manager yields"
|
|
324
|
+
if callee in functions:
|
|
325
|
+
rests: frozenset[str] = functions[callee][1]
|
|
326
|
+
kinds: frozenset[str] = frozenset({_RETURNED if rests else _CALL})
|
|
327
|
+
return Inference(functions[callee][0], reason, kinds), [], rests
|
|
328
|
+
key: str
|
|
329
|
+
if (key := f"{ENTERED}{callee}") not in known.returned.calls:
|
|
330
|
+
return None
|
|
331
|
+
yielded: Inference = Inference(known.returned.calls[key], reason, frozenset({_RETURNED}))
|
|
332
|
+
return yielded, [], known.returned.guesses.get(key, frozenset())
|
|
Binary file
|