Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

__future__ environments #1671

Merged
merged 5 commits into from
Jan 18, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 9 additions & 9 deletions IPython/core/compilerop.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def __init__(self):
# 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
linecache.checkcache = check_linecache_ipython
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made this a standalone function, so that linecache isn't holding a reference to the last CachingCompiler() we created. It didn't need to be a method in the first place.


def ast_parse(self, source, filename='<unknown>', symbol='exec'):
"""Parse code to an AST with the current compiler flags active.
Expand Down Expand Up @@ -134,11 +134,11 @@ def cache(self, code, number=0):
linecache._ipython_cache[name] = entry
return name

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)
def check_linecache_ipython(*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)
34 changes: 23 additions & 11 deletions IPython/core/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# Imports
#-----------------------------------------------------------------------------

from __future__ import with_statement
from __future__ import absolute_import
from __future__ import print_function

Expand Down Expand Up @@ -42,7 +41,7 @@
from IPython.core.alias import AliasManager, AliasError
from IPython.core.autocall import ExitAutocall
from IPython.core.builtin_trap import BuiltinTrap
from IPython.core.compilerop import CachingCompiler
from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
from IPython.core.display_trap import DisplayTrap
from IPython.core.displayhook import DisplayHook
from IPython.core.displaypub import DisplayPublisher
Expand Down Expand Up @@ -1533,7 +1532,7 @@ def init_traceback_handlers(self, custom_exceptions):
self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
color_scheme='NoColor',
tb_offset = 1,
check_cache=self.compile.check_cache)
check_cache=check_linecache_ipython)

# The instance will store a pointer to the system-wide exception hook,
# so that runtime code (such as magics) can access it. This is because
Expand Down Expand Up @@ -2514,7 +2513,7 @@ def safe_execfile_ipy(self, fname):
# raised in user code. It would be nice if there were
# versions of runlines, execfile that did raise, so
# we could catch the errors.
self.run_cell(thefile.read(), store_history=False)
self.run_cell(thefile.read(), store_history=False, shell_futures=False)
except:
self.showtraceback()
warn('Unknown failure executing file: <%s>' % fname)
Expand Down Expand Up @@ -2548,7 +2547,7 @@ def _run_cached_cell_magic(self, magic_name, line):
self._current_cell_magic_body = None
return self.run_cell_magic(magic_name, line, cell)

def run_cell(self, raw_cell, store_history=False, silent=False):
def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True):
"""Run a complete IPython cell.

Parameters
Expand All @@ -2562,6 +2561,11 @@ def run_cell(self, raw_cell, store_history=False, silent=False):
silent : bool
If True, avoid side-effects, such as implicit displayhooks and
and logging. silent=True forces store_history=False.
shell_futures : bool
If True, the code will share future statements with the interactive
shell. It will both be affected by previous __future__ imports, and
any __future__ imports in the code will affect the shell. If False,
__future__ imports are not shared in either direction.
"""
if (not raw_cell) or raw_cell.isspace():
return
Expand All @@ -2579,6 +2583,11 @@ def run_cell(self, raw_cell, store_history=False, silent=False):
self._current_cell_magic_body = \
''.join(self.input_splitter.cell_magic_parts)
cell = self.input_splitter.source_reset()

# Our own compiler remembers the __future__ environment. If we want to
# run code with a separate __future__ environment, use the default
# compiler
compiler = self.compile if shell_futures else CachingCompiler()

with self.builtin_trap:
prefilter_failed = False
Expand Down Expand Up @@ -2608,8 +2617,7 @@ def run_cell(self, raw_cell, store_history=False, silent=False):

with self.display_trap:
try:
code_ast = self.compile.ast_parse(cell,
filename=cell_name)
code_ast = compiler.ast_parse(cell, filename=cell_name)
except IndentationError:
self.showindentationerror()
if store_history:
Expand All @@ -2626,7 +2634,7 @@ def run_cell(self, raw_cell, store_history=False, silent=False):

interactivity = "none" if silent else self.ast_node_interactivity
self.run_ast_nodes(code_ast.body, cell_name,
interactivity=interactivity)
interactivity=interactivity, compiler=compiler)

# Execute any registered post-execution functions.
# unless we are silent
Expand Down Expand Up @@ -2682,7 +2690,8 @@ def transform_ast(self, node):
return ast.fix_missing_locations(node)


def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr'):
def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr',
compiler=compile):
"""Run a sequence of AST nodes. The execution mode depends on the
interactivity parameter.

Expand All @@ -2699,6 +2708,9 @@ def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr'):
will run the last node interactively only if it is an expression (i.e.
expressions in loops or other blocks are not displayed. Other values
for this parameter will raise a ValueError.
compiler : callable
A function with the same interface as the built-in compile(), to turn
the AST nodes into code objects. Default is the built-in compile().
"""
if not nodelist:
return
Expand All @@ -2723,13 +2735,13 @@ def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr'):
try:
for i, node in enumerate(to_run_exec):
mod = ast.Module([node])
code = self.compile(mod, cell_name, "exec")
code = compiler(mod, cell_name, "exec")
if self.run_code(code):
return True

for i, node in enumerate(to_run_interactive):
mod = ast.Interactive([node])
code = self.compile(mod, cell_name, "single")
code = compiler(mod, cell_name, "single")
if self.run_code(code):
return True

Expand Down
2 changes: 1 addition & 1 deletion IPython/core/tests/test_compilerop.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_compiler_check_cache():
cp = compilerop.CachingCompiler()
cp.cache('x=1', 99)
# Ensure now that after clearing the cache, our entries survive
cp.check_cache()
linecache.checkcache()
for k in linecache.cache:
if k.startswith('<ipython-input-99'):
break
Expand Down
16 changes: 16 additions & 0 deletions IPython/core/tests/test_interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,22 @@ def my_handler(shell, etype, value, tb, tb_offset=None):
finally:
# Reset the custom exception hook
ip.set_custom_exc((), None)

@skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3")
def test_future_environment(self):
"Can we run code with & without the shell's __future__ imports?"
ip.run_cell("from __future__ import division")
ip.run_cell("a = 1/2", shell_futures=True)
self.assertEqual(ip.user_ns['a'], 0.5)
ip.run_cell("b = 1/2", shell_futures=False)
self.assertEqual(ip.user_ns['b'], 0)

ip.compile.reset_compiler_flags()
# This shouldn't leak to the shell's compiler
ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False)
self.assertEqual(ip.user_ns['c'], 0.5)
ip.run_cell("d = 1/2", shell_futures=True)
self.assertEqual(ip.user_ns['d'], 0)


class TestSafeExecfileNonAsciiPath(unittest.TestCase):
Expand Down
2 changes: 1 addition & 1 deletion IPython/core/tests/test_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ def test_edit_interactive():
except code.InteractivelyDefined as e:
nt.assert_equal(e.index, n)
else:
nt.fail("Should have raised InteractivelyDefined")
raise AssertionError("Should have raised InteractivelyDefined")


def test_edit_cell():
Expand Down