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