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

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 

4 

5"""Astroid hooks for understanding functools library module.""" 

6 

7from __future__ import annotations 

8 

9from collections.abc import Iterator 

10from functools import partial 

11from itertools import chain 

12 

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 

23 

24LRU_CACHE = "functools.lru_cache" 

25 

26 

27class LruWrappedModel(objectmodel.FunctionModel): 

28 """Special attribute model for functions decorated with functools.lru_cache. 

29 

30 The said decorators patches at decoration time some functions onto 

31 the decorated function. 

32 """ 

33 

34 @property 

35 def attr___wrapped__(self): 

36 return self._instance 

37 

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 ) 

46 

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 

56 

57 return CacheInfoBoundMethod(proxy=self._instance, bound=self._instance) 

58 

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()) 

63 

64 

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) 

73 

74 

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 ) 

86 

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") 

96 

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") 

112 

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,)) 

130 

131 

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 

142 

143 

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 

155 

156 

157_looks_like_partial = partial(_looks_like_functools_member, member="partial") 

158 

159 

160AstroidManager().register_transform( 

161 FunctionDef, _transform_lru_cache, _looks_like_lru_cache 

162) 

163 

164 

165AstroidManager().register_transform( 

166 Call, 

167 inference_tip(_functools_partial_inference), 

168 _looks_like_partial, 

169)