|
"""Compiler tools with improved interactive support. |
|
|
|
Provides compilation machinery similar to codeop, but with caching support so |
|
we can provide interactive tracebacks. |
|
|
|
Authors |
|
------- |
|
* Robert Kern |
|
* Fernando Perez |
|
* Thomas Kluyver |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import __future__ |
|
from ast import PyCF_ONLY_AST |
|
import codeop |
|
import functools |
|
import hashlib |
|
import linecache |
|
import operator |
|
import time |
|
from contextlib import contextmanager |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PyCF_MASK = functools.reduce(operator.or_, |
|
(getattr(__future__, fname).compiler_flag |
|
for fname in __future__.all_feature_names)) |
|
|
|
|
|
|
|
|
|
|
|
def code_name(code, number=0): |
|
""" Compute a (probably) unique name for code for caching. |
|
|
|
This now expects code to be unicode. |
|
""" |
|
hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() |
|
|
|
|
|
|
|
return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12]) |
|
|
|
|
|
|
|
|
|
|
|
class CachingCompiler(codeop.Compile): |
|
"""A compiler that caches code compiled from interactive statements. |
|
""" |
|
|
|
def __init__(self): |
|
codeop.Compile.__init__(self) |
|
|
|
|
|
|
|
|
|
self._filename_map = {} |
|
|
|
def ast_parse(self, source, filename='<unknown>', symbol='exec'): |
|
"""Parse code to an AST with the current compiler flags active. |
|
|
|
Arguments are exactly the same as ast.parse (in the standard library), |
|
and are passed to the built-in compile function.""" |
|
return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) |
|
|
|
def reset_compiler_flags(self): |
|
"""Reset compiler flags to default state.""" |
|
|
|
|
|
self.flags = codeop.PyCF_DONT_IMPLY_DEDENT |
|
|
|
@property |
|
def compiler_flags(self): |
|
"""Flags currently active in the compilation process. |
|
""" |
|
return self.flags |
|
|
|
def get_code_name(self, raw_code, transformed_code, number): |
|
"""Compute filename given the code, and the cell number. |
|
|
|
Parameters |
|
---------- |
|
raw_code : str |
|
The raw cell code. |
|
transformed_code : str |
|
The executable Python source code to cache and compile. |
|
number : int |
|
A number which forms part of the code's name. Used for the execution |
|
counter. |
|
|
|
Returns |
|
------- |
|
The computed filename. |
|
""" |
|
return code_name(transformed_code, number) |
|
|
|
def format_code_name(self, name): |
|
"""Return a user-friendly label and name for a code block. |
|
|
|
Parameters |
|
---------- |
|
name : str |
|
The name for the code block returned from get_code_name |
|
|
|
Returns |
|
------- |
|
A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used. |
|
""" |
|
if name in self._filename_map: |
|
return "Cell", "In[%s]" % self._filename_map[name] |
|
|
|
def cache(self, transformed_code, number=0, raw_code=None): |
|
"""Make a name for a block of code, and cache the code. |
|
|
|
Parameters |
|
---------- |
|
transformed_code : str |
|
The executable Python source code to cache and compile. |
|
number : int |
|
A number which forms part of the code's name. Used for the execution |
|
counter. |
|
raw_code : str |
|
The raw code before transformation, if None, set to `transformed_code`. |
|
|
|
Returns |
|
------- |
|
The name of the cached code (as a string). Pass this as the filename |
|
argument to compilation, so that tracebacks are correctly hooked up. |
|
""" |
|
if raw_code is None: |
|
raw_code = transformed_code |
|
|
|
name = self.get_code_name(raw_code, transformed_code, number) |
|
|
|
|
|
self._filename_map[name] = number |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entry = ( |
|
len(transformed_code), |
|
None, |
|
[line + "\n" for line in transformed_code.splitlines()], |
|
name, |
|
) |
|
linecache.cache[name] = entry |
|
return name |
|
|
|
@contextmanager |
|
def extra_flags(self, flags): |
|
|
|
turn_on_bits = ~self.flags & flags |
|
|
|
|
|
self.flags = self.flags | flags |
|
try: |
|
yield |
|
finally: |
|
|
|
|
|
self.flags &= ~turn_on_bits |
|
|
|
|
|
def check_linecache_ipython(*args): |
|
"""Deprecated since IPython 8.6. Call linecache.checkcache() directly. |
|
|
|
It was already not necessary to call this function directly. If no |
|
CachingCompiler had been created, this function would fail badly. If |
|
an instance had been created, this function would've been monkeypatched |
|
into place. |
|
|
|
As of IPython 8.6, the monkeypatching has gone away entirely. But there |
|
were still internal callers of this function, so maybe external callers |
|
also existed? |
|
""" |
|
import warnings |
|
|
|
warnings.warn( |
|
"Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.", |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
linecache.checkcache() |
|
|