compilerop.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. """Compiler tools with improved interactive support.
  2. Provides compilation machinery similar to codeop, but with caching support so
  3. we can provide interactive tracebacks.
  4. Authors
  5. -------
  6. * Robert Kern
  7. * Fernando Perez
  8. * Thomas Kluyver
  9. """
  10. # Note: though it might be more natural to name this module 'compiler', that
  11. # name is in the stdlib and name collisions with the stdlib tend to produce
  12. # weird problems (often with third-party tools).
  13. #-----------------------------------------------------------------------------
  14. # Copyright (C) 2010-2011 The IPython Development Team.
  15. #
  16. # Distributed under the terms of the BSD License.
  17. #
  18. # The full license is in the file COPYING.txt, distributed with this software.
  19. #-----------------------------------------------------------------------------
  20. #-----------------------------------------------------------------------------
  21. # Imports
  22. #-----------------------------------------------------------------------------
  23. # Stdlib imports
  24. import __future__
  25. from ast import PyCF_ONLY_AST
  26. import codeop
  27. import functools
  28. import hashlib
  29. import linecache
  30. import operator
  31. import time
  32. from contextlib import contextmanager
  33. #-----------------------------------------------------------------------------
  34. # Constants
  35. #-----------------------------------------------------------------------------
  36. # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
  37. # this is used as a bitmask to extract future-related code flags.
  38. PyCF_MASK = functools.reduce(operator.or_,
  39. (getattr(__future__, fname).compiler_flag
  40. for fname in __future__.all_feature_names))
  41. #-----------------------------------------------------------------------------
  42. # Local utilities
  43. #-----------------------------------------------------------------------------
  44. def code_name(code, number=0):
  45. """ Compute a (probably) unique name for code for caching.
  46. This now expects code to be unicode.
  47. """
  48. hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
  49. # Include the number and 12 characters of the hash in the name. It's
  50. # pretty much impossible that in a single session we'll have collisions
  51. # even with truncated hashes, and the full one makes tracebacks too long
  52. return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
  53. #-----------------------------------------------------------------------------
  54. # Classes and functions
  55. #-----------------------------------------------------------------------------
  56. class CachingCompiler(codeop.Compile):
  57. """A compiler that caches code compiled from interactive statements.
  58. """
  59. def __init__(self):
  60. codeop.Compile.__init__(self)
  61. # Caching a dictionary { filename: execution_count } for nicely
  62. # rendered tracebacks. The filename corresponds to the filename
  63. # argument used for the builtins.compile function.
  64. self._filename_map = {}
  65. def ast_parse(self, source, filename='<unknown>', symbol='exec'):
  66. """Parse code to an AST with the current compiler flags active.
  67. Arguments are exactly the same as ast.parse (in the standard library),
  68. and are passed to the built-in compile function."""
  69. return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
  70. def reset_compiler_flags(self):
  71. """Reset compiler flags to default state."""
  72. # This value is copied from codeop.Compile.__init__, so if that ever
  73. # changes, it will need to be updated.
  74. self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
  75. @property
  76. def compiler_flags(self):
  77. """Flags currently active in the compilation process.
  78. """
  79. return self.flags
  80. def get_code_name(self, raw_code, transformed_code, number):
  81. """Compute filename given the code, and the cell number.
  82. Parameters
  83. ----------
  84. raw_code : str
  85. The raw cell code.
  86. transformed_code : str
  87. The executable Python source code to cache and compile.
  88. number : int
  89. A number which forms part of the code's name. Used for the execution
  90. counter.
  91. Returns
  92. -------
  93. The computed filename.
  94. """
  95. return code_name(transformed_code, number)
  96. def format_code_name(self, name):
  97. """Return a user-friendly label and name for a code block.
  98. Parameters
  99. ----------
  100. name : str
  101. The name for the code block returned from get_code_name
  102. Returns
  103. -------
  104. A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
  105. """
  106. if name in self._filename_map:
  107. return "Cell", "In[%s]" % self._filename_map[name]
  108. def cache(self, transformed_code, number=0, raw_code=None):
  109. """Make a name for a block of code, and cache the code.
  110. Parameters
  111. ----------
  112. transformed_code : str
  113. The executable Python source code to cache and compile.
  114. number : int
  115. A number which forms part of the code's name. Used for the execution
  116. counter.
  117. raw_code : str
  118. The raw code before transformation, if None, set to `transformed_code`.
  119. Returns
  120. -------
  121. The name of the cached code (as a string). Pass this as the filename
  122. argument to compilation, so that tracebacks are correctly hooked up.
  123. """
  124. if raw_code is None:
  125. raw_code = transformed_code
  126. name = self.get_code_name(raw_code, transformed_code, number)
  127. # Save the execution count
  128. self._filename_map[name] = number
  129. # Since Python 2.5, setting mtime to `None` means the lines will
  130. # never be removed by `linecache.checkcache`. This means all the
  131. # monkeypatching has *never* been necessary, since this code was
  132. # only added in 2010, at which point IPython had already stopped
  133. # supporting Python 2.4.
  134. #
  135. # Note that `linecache.clearcache` and `linecache.updatecache` may
  136. # still remove our code from the cache, but those show explicit
  137. # intent, and we should not try to interfere. Normally the former
  138. # is never called except when out of memory, and the latter is only
  139. # called for lines *not* in the cache.
  140. entry = (
  141. len(transformed_code),
  142. None,
  143. [line + "\n" for line in transformed_code.splitlines()],
  144. name,
  145. )
  146. linecache.cache[name] = entry
  147. return name
  148. @contextmanager
  149. def extra_flags(self, flags):
  150. ## bits that we'll set to 1
  151. turn_on_bits = ~self.flags & flags
  152. self.flags = self.flags | flags
  153. try:
  154. yield
  155. finally:
  156. # turn off only the bits we turned on so that something like
  157. # __future__ that set flags stays.
  158. self.flags &= ~turn_on_bits
  159. def check_linecache_ipython(*args):
  160. """Deprecated since IPython 8.6. Call linecache.checkcache() directly.
  161. It was already not necessary to call this function directly. If no
  162. CachingCompiler had been created, this function would fail badly. If
  163. an instance had been created, this function would've been monkeypatched
  164. into place.
  165. As of IPython 8.6, the monkeypatching has gone away entirely. But there
  166. were still internal callers of this function, so maybe external callers
  167. also existed?
  168. """
  169. import warnings
  170. warnings.warn(
  171. "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
  172. DeprecationWarning,
  173. stacklevel=2,
  174. )
  175. linecache.checkcache()