Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/core/compilerop.py: 48%

48 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1"""Compiler tools with improved interactive support. 

2 

3Provides compilation machinery similar to codeop, but with caching support so 

4we can provide interactive tracebacks. 

5 

6Authors 

7------- 

8* Robert Kern 

9* Fernando Perez 

10* Thomas Kluyver 

11""" 

12 

13# Note: though it might be more natural to name this module 'compiler', that 

14# name is in the stdlib and name collisions with the stdlib tend to produce 

15# weird problems (often with third-party tools). 

16 

17#----------------------------------------------------------------------------- 

18# Copyright (C) 2010-2011 The IPython Development Team. 

19# 

20# Distributed under the terms of the BSD License. 

21# 

22# The full license is in the file COPYING.txt, distributed with this software. 

23#----------------------------------------------------------------------------- 

24 

25#----------------------------------------------------------------------------- 

26# Imports 

27#----------------------------------------------------------------------------- 

28 

29# Stdlib imports 

30import __future__ 

31from ast import PyCF_ONLY_AST 

32import codeop 

33import functools 

34import hashlib 

35import linecache 

36import operator 

37import time 

38from contextlib import contextmanager 

39 

40#----------------------------------------------------------------------------- 

41# Constants 

42#----------------------------------------------------------------------------- 

43 

44# Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, 

45# this is used as a bitmask to extract future-related code flags. 

46PyCF_MASK = functools.reduce(operator.or_, 

47 (getattr(__future__, fname).compiler_flag 

48 for fname in __future__.all_feature_names)) 

49 

50#----------------------------------------------------------------------------- 

51# Local utilities 

52#----------------------------------------------------------------------------- 

53 

54def code_name(code, number=0): 

55 """ Compute a (probably) unique name for code for caching. 

56 

57 This now expects code to be unicode. 

58 """ 

59 hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() 

60 # Include the number and 12 characters of the hash in the name. It's 

61 # pretty much impossible that in a single session we'll have collisions 

62 # even with truncated hashes, and the full one makes tracebacks too long 

63 return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12]) 

64 

65#----------------------------------------------------------------------------- 

66# Classes and functions 

67#----------------------------------------------------------------------------- 

68 

69class CachingCompiler(codeop.Compile): 

70 """A compiler that caches code compiled from interactive statements. 

71 """ 

72 

73 def __init__(self): 

74 codeop.Compile.__init__(self) 

75 

76 # Caching a dictionary { filename: execution_count } for nicely 

77 # rendered tracebacks. The filename corresponds to the filename 

78 # argument used for the builtins.compile function. 

79 self._filename_map = {} 

80 

81 def ast_parse(self, source, filename='<unknown>', symbol='exec'): 

82 """Parse code to an AST with the current compiler flags active. 

83 

84 Arguments are exactly the same as ast.parse (in the standard library), 

85 and are passed to the built-in compile function.""" 

86 return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) 

87 

88 def reset_compiler_flags(self): 

89 """Reset compiler flags to default state.""" 

90 # This value is copied from codeop.Compile.__init__, so if that ever 

91 # changes, it will need to be updated. 

92 self.flags = codeop.PyCF_DONT_IMPLY_DEDENT 

93 

94 @property 

95 def compiler_flags(self): 

96 """Flags currently active in the compilation process. 

97 """ 

98 return self.flags 

99 

100 def get_code_name(self, raw_code, transformed_code, number): 

101 """Compute filename given the code, and the cell number. 

102 

103 Parameters 

104 ---------- 

105 raw_code : str 

106 The raw cell code. 

107 transformed_code : str 

108 The executable Python source code to cache and compile. 

109 number : int 

110 A number which forms part of the code's name. Used for the execution 

111 counter. 

112 

113 Returns 

114 ------- 

115 The computed filename. 

116 """ 

117 return code_name(transformed_code, number) 

118 

119 def format_code_name(self, name): 

120 """Return a user-friendly label and name for a code block. 

121 

122 Parameters 

123 ---------- 

124 name : str 

125 The name for the code block returned from get_code_name 

126 

127 Returns 

128 ------- 

129 A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used. 

130 """ 

131 if name in self._filename_map: 

132 return "Cell", "In[%s]" % self._filename_map[name] 

133 

134 def cache(self, transformed_code, number=0, raw_code=None): 

135 """Make a name for a block of code, and cache the code. 

136 

137 Parameters 

138 ---------- 

139 transformed_code : str 

140 The executable Python source code to cache and compile. 

141 number : int 

142 A number which forms part of the code's name. Used for the execution 

143 counter. 

144 raw_code : str 

145 The raw code before transformation, if None, set to `transformed_code`. 

146 

147 Returns 

148 ------- 

149 The name of the cached code (as a string). Pass this as the filename 

150 argument to compilation, so that tracebacks are correctly hooked up. 

151 """ 

152 if raw_code is None: 

153 raw_code = transformed_code 

154 

155 name = self.get_code_name(raw_code, transformed_code, number) 

156 

157 # Save the execution count 

158 self._filename_map[name] = number 

159 

160 # Since Python 2.5, setting mtime to `None` means the lines will 

161 # never be removed by `linecache.checkcache`. This means all the 

162 # monkeypatching has *never* been necessary, since this code was 

163 # only added in 2010, at which point IPython had already stopped 

164 # supporting Python 2.4. 

165 # 

166 # Note that `linecache.clearcache` and `linecache.updatecache` may 

167 # still remove our code from the cache, but those show explicit 

168 # intent, and we should not try to interfere. Normally the former 

169 # is never called except when out of memory, and the latter is only 

170 # called for lines *not* in the cache. 

171 entry = ( 

172 len(transformed_code), 

173 None, 

174 [line + "\n" for line in transformed_code.splitlines()], 

175 name, 

176 ) 

177 linecache.cache[name] = entry 

178 return name 

179 

180 @contextmanager 

181 def extra_flags(self, flags): 

182 ## bits that we'll set to 1 

183 turn_on_bits = ~self.flags & flags 

184 

185 

186 self.flags = self.flags | flags 

187 try: 

188 yield 

189 finally: 

190 # turn off only the bits we turned on so that something like 

191 # __future__ that set flags stays. 

192 self.flags &= ~turn_on_bits 

193 

194 

195def check_linecache_ipython(*args): 

196 """Deprecated since IPython 8.6. Call linecache.checkcache() directly. 

197 

198 It was already not necessary to call this function directly. If no 

199 CachingCompiler had been created, this function would fail badly. If 

200 an instance had been created, this function would've been monkeypatched 

201 into place. 

202 

203 As of IPython 8.6, the monkeypatching has gone away entirely. But there 

204 were still internal callers of this function, so maybe external callers 

205 also existed? 

206 """ 

207 import warnings 

208 

209 warnings.warn( 

210 "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.", 

211 DeprecationWarning, 

212 stacklevel=2, 

213 ) 

214 linecache.checkcache()