Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/brain/brain_functools.py: 47%
78 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
5"""Astroid hooks for understanding functools library module."""
7from __future__ import annotations
9from collections.abc import Iterator
10from functools import partial
11from itertools import chain
13from astroid import BoundMethod, arguments, extract_node, helpers, nodes, objects
14from astroid.context import InferenceContext
15from astroid.exceptions import InferenceError, UseInferenceDefault
16from astroid.inference_tip import inference_tip
17from astroid.interpreter import objectmodel
18from astroid.manager import AstroidManager
19from astroid.nodes.node_classes import AssignName, Attribute, Call, Name
20from astroid.nodes.scoped_nodes import FunctionDef
21from astroid.typing import InferenceResult, SuccessfulInferenceResult
22from astroid.util import UninferableBase
24LRU_CACHE = "functools.lru_cache"
27class LruWrappedModel(objectmodel.FunctionModel):
28 """Special attribute model for functions decorated with functools.lru_cache.
30 The said decorators patches at decoration time some functions onto
31 the decorated function.
32 """
34 @property
35 def attr___wrapped__(self):
36 return self._instance
38 @property
39 def attr_cache_info(self):
40 cache_info = extract_node(
41 """
42 from functools import _CacheInfo
43 _CacheInfo(0, 0, 0, 0)
44 """
45 )
47 class CacheInfoBoundMethod(BoundMethod):
48 def infer_call_result(
49 self,
50 caller: SuccessfulInferenceResult | None,
51 context: InferenceContext | None = None,
52 ) -> Iterator[InferenceResult]:
53 res = helpers.safe_infer(cache_info)
54 assert res is not None
55 yield res
57 return CacheInfoBoundMethod(proxy=self._instance, bound=self._instance)
59 @property
60 def attr_cache_clear(self):
61 node = extract_node("""def cache_clear(self): pass""")
62 return BoundMethod(proxy=node, bound=self._instance.parent.scope())
65def _transform_lru_cache(node, context: InferenceContext | None = None) -> None:
66 # TODO: this is not ideal, since the node should be immutable,
67 # but due to https://github.com/pylint-dev/astroid/issues/354,
68 # there's not much we can do now.
69 # Replacing the node would work partially, because,
70 # in pylint, the old node would still be available, leading
71 # to spurious false positives.
72 node.special_attributes = LruWrappedModel()(node)
75def _functools_partial_inference(
76 node: nodes.Call, context: InferenceContext | None = None
77) -> Iterator[objects.PartialFunction]:
78 call = arguments.CallSite.from_call(node, context=context)
79 number_of_positional = len(call.positional_arguments)
80 if number_of_positional < 1:
81 raise UseInferenceDefault("functools.partial takes at least one argument")
82 if number_of_positional == 1 and not call.keyword_arguments:
83 raise UseInferenceDefault(
84 "functools.partial needs at least to have some filled arguments"
85 )
87 partial_function = call.positional_arguments[0]
88 try:
89 inferred_wrapped_function = next(partial_function.infer(context=context))
90 except (InferenceError, StopIteration) as exc:
91 raise UseInferenceDefault from exc
92 if isinstance(inferred_wrapped_function, UninferableBase):
93 raise UseInferenceDefault("Cannot infer the wrapped function")
94 if not isinstance(inferred_wrapped_function, FunctionDef):
95 raise UseInferenceDefault("The wrapped function is not a function")
97 # Determine if the passed keywords into the callsite are supported
98 # by the wrapped function.
99 if not inferred_wrapped_function.args:
100 function_parameters = []
101 else:
102 function_parameters = chain(
103 inferred_wrapped_function.args.args or (),
104 inferred_wrapped_function.args.posonlyargs or (),
105 inferred_wrapped_function.args.kwonlyargs or (),
106 )
107 parameter_names = {
108 param.name for param in function_parameters if isinstance(param, AssignName)
109 }
110 if set(call.keyword_arguments) - parameter_names:
111 raise UseInferenceDefault("wrapped function received unknown parameters")
113 partial_function = objects.PartialFunction(
114 call,
115 name=inferred_wrapped_function.name,
116 lineno=inferred_wrapped_function.lineno,
117 col_offset=inferred_wrapped_function.col_offset,
118 parent=node.parent,
119 )
120 partial_function.postinit(
121 args=inferred_wrapped_function.args,
122 body=inferred_wrapped_function.body,
123 decorators=inferred_wrapped_function.decorators,
124 returns=inferred_wrapped_function.returns,
125 type_comment_returns=inferred_wrapped_function.type_comment_returns,
126 type_comment_args=inferred_wrapped_function.type_comment_args,
127 doc_node=inferred_wrapped_function.doc_node,
128 )
129 return iter((partial_function,))
132def _looks_like_lru_cache(node) -> bool:
133 """Check if the given function node is decorated with lru_cache."""
134 if not node.decorators:
135 return False
136 for decorator in node.decorators.nodes:
137 if not isinstance(decorator, Call):
138 continue
139 if _looks_like_functools_member(decorator, "lru_cache"):
140 return True
141 return False
144def _looks_like_functools_member(node, member) -> bool:
145 """Check if the given Call node is a functools.partial call."""
146 if isinstance(node.func, Name):
147 return node.func.name == member
148 if isinstance(node.func, Attribute):
149 return (
150 node.func.attrname == member
151 and isinstance(node.func.expr, Name)
152 and node.func.expr.name == "functools"
153 )
154 return False
157_looks_like_partial = partial(_looks_like_functools_member, member="partial")
160AstroidManager().register_transform(
161 FunctionDef, _transform_lru_cache, _looks_like_lru_cache
162)
165AstroidManager().register_transform(
166 Call,
167 inference_tip(_functools_partial_inference),
168 _looks_like_partial,
169)