compilerop.py
119 lines
| 4.8 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r3175 | """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. | ||||
""" | ||||
hash_digest = hashlib.md5(code).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 '<ipython-input-{0}-{1}>'.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) | ||||