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.
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__.cp314-win_amd64.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp314-win_amd64.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp314-win_amd64.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp314-win_amd64.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp314-win_amd64.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp314-win_amd64.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp314-win_amd64.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp314-win_amd64.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp314-win_amd64.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp314-win_amd64.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp314-win_amd64.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp314-win_amd64.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp314-win_amd64.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp314-win_amd64.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp314-win_amd64.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp314-win_amd64.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp314-win_amd64.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp314-win_amd64.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp314-win_amd64.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp314-win_amd64.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp314-win_amd64.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp314-win_amd64.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp314-win_amd64.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp314-win_amd64.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp314-win_amd64.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp314-win_amd64.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp314-win_amd64.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp314-win_amd64.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp314-win_amd64.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp314-win_amd64.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp314-win_amd64.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp314-win_amd64.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp314-win_amd64.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp314-win_amd64.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp314-win_amd64.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp314-win_amd64.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp314-win_amd64.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp314-win_amd64.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp314-win_amd64.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp314-win_amd64.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp314-win_amd64.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp314-win_amd64.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp314-win_amd64.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp314-win_amd64.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp314-win_amd64.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp314-win_amd64.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp314-win_amd64.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp314-win_amd64.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp314-win_amd64.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp314-win_amd64.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp314-win_amd64.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp314-win_amd64.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp314-win_amd64.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp314-win_amd64.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp314-win_amd64.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp314-win_amd64.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp314-win_amd64.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp314-win_amd64.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp314-win_amd64.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp314-win_amd64.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp314-win_amd64.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp314-win_amd64.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp314-win_amd64.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp314-win_amd64.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp314-win_amd64.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp314-win_amd64.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp314-win_amd64.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp314-win_amd64.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp314-win_amd64.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp314-win_amd64.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp314-win_amd64.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp314-win_amd64.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__.cp314-win_amd64.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp314-win_amd64.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp314-win_amd64.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp314-win_amd64.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp314-win_amd64.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp314-win_amd64.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp314-win_amd64.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp314-win_amd64.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp314-win_amd64.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp314-win_amd64.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp314-win_amd64.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp314-win_amd64.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp314-win_amd64.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp314-win_amd64.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp314-win_amd64.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp314-win_amd64.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp314-win_amd64.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp314-win_amd64.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp314-win_amd64.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp314-win_amd64.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp314-win_amd64.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp314-win_amd64.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp314-win_amd64.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp314-win_amd64.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp314-win_amd64.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,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())