diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 56aea6d..2f8c69c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2565,10 +2565,16 @@ class InteractiveShell(SingletonConfigurable): silenced for zero status, as it is so common). raise_exceptions : bool (False) If True raise exceptions everywhere. Meant for testing. + shell_futures : bool (False) + 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. """ kw.setdefault('exit_ignore', False) kw.setdefault('raise_exceptions', False) + kw.setdefault('shell_futures', False) fname = os.path.abspath(os.path.expanduser(fname)) @@ -2587,7 +2593,10 @@ class InteractiveShell(SingletonConfigurable): with prepended_to_syspath(dname): try: - py3compat.execfile(fname,*where) + glob, loc = (where + (None, ))[:2] + py3compat.execfile( + fname, glob, loc, + self.compile if kw['shell_futures'] else None) except SystemExit as status: # If the call was made with 0 or None exit status (sys.exit(0) # or sys.exit() ), don't bother showing a traceback, as both of @@ -2608,7 +2617,7 @@ class InteractiveShell(SingletonConfigurable): # tb offset is 2 because we wrap execfile self.showtraceback(tb_offset=2) - def safe_execfile_ipy(self, fname): + def safe_execfile_ipy(self, fname, shell_futures=False): """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax. Parameters @@ -2616,6 +2625,11 @@ class InteractiveShell(SingletonConfigurable): fname : str The name of the file to execute. The filename must have a .ipy or .ipynb extension. + shell_futures : bool (False) + 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. """ fname = os.path.abspath(os.path.expanduser(fname)) @@ -2654,7 +2668,7 @@ class InteractiveShell(SingletonConfigurable): # raised in user code. It would be nice if there were # versions of run_cell that did raise, so # we could catch the errors. - self.run_cell(cell, silent=True, shell_futures=False) + self.run_cell(cell, silent=True, shell_futures=shell_futures) except: self.showtraceback() warn('Unknown failure executing file: <%s>' % fname) diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index e7b57b1..aa3d6ec 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -324,7 +324,7 @@ class InteractiveShellApp(Configurable): self.log.warn("Unknown error in handling IPythonApp.exec_lines:") self.shell.showtraceback() - def _exec_file(self, fname): + def _exec_file(self, fname, shell_futures=False): try: full_filename = filefind(fname, [u'.', self.ipython_dir]) except IOError as e: @@ -346,11 +346,13 @@ class InteractiveShellApp(Configurable): with preserve_keys(self.shell.user_ns, '__file__'): self.shell.user_ns['__file__'] = fname if full_filename.endswith('.ipy'): - self.shell.safe_execfile_ipy(full_filename) + self.shell.safe_execfile_ipy(full_filename, + shell_futures=shell_futures) else: # default to python, even without extension self.shell.safe_execfile(full_filename, - self.shell.user_ns) + self.shell.user_ns, + shell_futures=shell_futures) finally: sys.argv = save_argv @@ -418,7 +420,7 @@ class InteractiveShellApp(Configurable): elif self.file_to_run: fname = self.file_to_run try: - self._exec_file(fname) + self._exec_file(fname, shell_futures=True) except: self.log.warn("Error in executing file in user namespace: %s" % fname) diff --git a/IPython/core/tests/test_shellapp.py b/IPython/core/tests/test_shellapp.py index 5ca5252..61502da 100644 --- a/IPython/core/tests/test_shellapp.py +++ b/IPython/core/tests/test_shellapp.py @@ -19,6 +19,11 @@ import unittest from IPython.testing import decorators as dec from IPython.testing import tools as tt +from IPython.utils.py3compat import PY3 + +sqlite_err_maybe = dec.module_not_available('sqlite3') +SQLITE_NOT_AVAILABLE_ERROR = ('WARNING: IPython History requires SQLite,' + ' your history will not be saved\n') class TestFileToRun(unittest.TestCase, tt.TempFileMixin): """Test the behavior of the file_to_run parameter.""" @@ -28,10 +33,7 @@ class TestFileToRun(unittest.TestCase, tt.TempFileMixin): src = "print(__file__)\n" self.mktmp(src) - if dec.module_not_available('sqlite3'): - err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' - else: - err = None + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None tt.ipexec_validate(self.fname, self.fname, err) def test_ipy_script_file_attribute(self): @@ -39,11 +41,24 @@ class TestFileToRun(unittest.TestCase, tt.TempFileMixin): src = "print(__file__)\n" self.mktmp(src, ext='.ipy') - if dec.module_not_available('sqlite3'): - err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' - else: - err = None + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None tt.ipexec_validate(self.fname, self.fname, err) - # Ideally we would also test that `__file__` is not set in the - # interactive namespace after running `ipython -i `. + def test_py_script_file_attribute_interactively(self): + """Test that `__file__` is not set after `ipython -i file.py`""" + src = "True\n" + self.mktmp(src) + + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None + tt.ipexec_validate(self.fname, 'False', err, options=['-i'], + commands=['"__file__" in globals()', 'exit()']) + + @dec.skipif(PY3) + def test_py_script_file_compiler_directive(self): + """Test `__future__` compiler directives with `ipython -i file.py`""" + src = "from __future__ import division\n" + self.mktmp(src) + + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None + tt.ipexec_validate(self.fname, 'float', err, options=['-i'], + commands=['type(1/2)', 'exit()']) diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 5970775..8a5c1c7 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -177,7 +177,7 @@ def get_ipython_cmd(as_string=False): return ipython_cmd -def ipexec(fname, options=None): +def ipexec(fname, options=None, commands=()): """Utility to call 'ipython filename'. Starts IPython with a minimal and safe configuration to make startup as fast @@ -193,6 +193,9 @@ def ipexec(fname, options=None): options : optional, list Extra command-line flags to be passed to IPython. + commands : optional, list + Commands to send in on stdin + Returns ------- (stdout, stderr) of ipython subprocess. @@ -215,8 +218,8 @@ def ipexec(fname, options=None): full_cmd = ipython_cmd + cmdargs + [full_fname] env = os.environ.copy() env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr - p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, env=env) - out, err = p.communicate() + p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env) + out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None) out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err) # `import readline` causes 'ESC[?1034h' to be output sometimes, # so strip that out before doing comparisons @@ -226,7 +229,7 @@ def ipexec(fname, options=None): def ipexec_validate(fname, expected_out, expected_err='', - options=None): + options=None, commands=()): """Utility to call 'ipython filename' and validate output/error. This function raises an AssertionError if the validation fails. @@ -254,7 +257,7 @@ def ipexec_validate(fname, expected_out, expected_err='', import nose.tools as nt - out, err = ipexec(fname, options) + out, err = ipexec(fname, options, commands) #print 'OUT', out # dbg #print 'ERR', err # dbg # If there are any errors, we must check those befor stdout, as they may be diff --git a/IPython/utils/py3compat.py b/IPython/utils/py3compat.py index 47d1d12..08c3ad9 100644 --- a/IPython/utils/py3compat.py +++ b/IPython/utils/py3compat.py @@ -101,11 +101,12 @@ if sys.version_info[0] >= 3: getcwd = os.getcwd MethodType = types.MethodType - - def execfile(fname, glob, loc=None): + + def execfile(fname, glob, loc=None, compiler=None): loc = loc if (loc is not None) else glob with open(fname, 'rb') as f: - exec(compile(f.read(), fname, 'exec'), glob, loc) + compiler = compiler or compile + exec(compiler(f.read(), fname, 'exec'), glob, loc) # Refactor print statements in doctests. _print_statement_re = re.compile(r"\bprint (?P.*)$", re.MULTILINE) @@ -185,7 +186,7 @@ else: return s.format(u='u') if sys.platform == 'win32': - def execfile(fname, glob=None, loc=None): + def execfile(fname, glob=None, loc=None, compiler=None): loc = loc if (loc is not None) else glob # The rstrip() is necessary b/c trailing whitespace in files will # cause an IndentationError in Python 2.6 (this was fixed in 2.7, @@ -197,14 +198,21 @@ else: filename = unicode_to_str(fname) else: filename = fname - exec(compile(scripttext, filename, 'exec'), glob, loc) + compiler = compiler or compile + exec(compiler(scripttext, filename, 'exec'), glob, loc) + else: - def execfile(fname, *where): + def execfile(fname, glob=None, loc=None, compiler=None): if isinstance(fname, unicode): filename = fname.encode(sys.getfilesystemencoding()) else: filename = fname - builtin_mod.execfile(filename, *where) + where = [ns for ns in [glob, loc] if ns is not None] + if compiler is None: + builtin_mod.execfile(filename, *where) + else: + scripttext = builtin_mod.open(fname).read().rstrip() + '\n' + exec(compiler(scripttext, filename, 'exec'), glob, loc) def annotate(**kwargs):