"""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 """ # Note: though it might be more natural to name this module 'compiler', that # name is in the stdlib and name collisions with the stdlib tend to produce # weird problems (often with third-party tools). #----------------------------------------------------------------------------- # Copyright (C) 2010 The IPython Development Team. # # Distributed under the terms of the BSD License. # # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- from __future__ import print_function # Stdlib imports import codeop import hashlib import linecache import time #----------------------------------------------------------------------------- # Local utilities #----------------------------------------------------------------------------- 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.md5(code.encode("utf-8")).hexdigest() # Include the number and 12 characters of the hash in the name. It's # pretty much impossible that in a single session we'll have collisions # even with truncated hashes, and the full one makes tracebacks too long return ''.format(number, hash_digest[:12]) #----------------------------------------------------------------------------- # Classes and functions #----------------------------------------------------------------------------- class CachingCompiler(object): """A compiler that caches code compiled from interactive statements. """ def __init__(self): self._compiler = codeop.CommandCompiler() # This is ugly, but it must be done this way to allow multiple # simultaneous ipython instances to coexist. Since Python itself # directly accesses the data structures in the linecache module, and # the cache therein is global, we must work with that data structure. # We must hold a reference to the original checkcache routine and call # that in our own check_cache() below, but the special IPython cache # must also be shared by all IPython instances. If we were to hold # separate caches (one in each CachingCompiler instance), any call made # by Python itself to linecache.checkcache() would obliterate the # cached data from the other IPython instances. if not hasattr(linecache, '_ipython_cache'): linecache._ipython_cache = {} if not hasattr(linecache, '_checkcache_ori'): linecache._checkcache_ori = linecache.checkcache # Now, we must monkeypatch the linecache directly so that parts of the # stdlib that call it outside our control go through our codepath # (otherwise we'd lose our tracebacks). linecache.checkcache = self.check_cache @property def compiler_flags(self): """Flags currently active in the compilation process. """ return self._compiler.compiler.flags def __call__(self, code, symbol, number=0): """Compile some code while caching its contents such that the inspect module can find it later. Parameters ---------- code : str Source code to be compiled, one or more lines. symbol : str One of 'single', 'exec' or 'eval' (see the builtin ``compile`` documentation for further details on these fields). number : int, optional An integer argument identifying the code, useful for informational purposes in tracebacks (typically it will be the IPython prompt number). """ name = code_name(code, number) code_obj = self._compiler(code, name, symbol) entry = (len(code), time.time(), [line+'\n' for line in code.splitlines()], name) # Cache the info both in the linecache (a global cache used internally # by most of Python's inspect/traceback machinery), and in our cache linecache.cache[name] = entry linecache._ipython_cache[name] = entry return code_obj def check_cache(self, *args): """Call linecache.checkcache() safely protecting our cached values. """ # First call the orignal checkcache as intended linecache._checkcache_ori(*args) # Then, update back the cache with our data, so that tracebacks related # to our compiled codes can be produced. linecache.cache.update(linecache._ipython_cache)