Repository: ipython/ipython

Issue:
running ipython on python files adds an extra traceback frame
If you make a file `test.py` that throws a trivial error, and then run it with `ipython test.py`, you get an extra frame in the traceback:

```
root@8e0bcc11822a:~# ipython test.py
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
/usr/lib/python2.7/dist-packages/IPython/utils/py3compat.pyc in execfile(fname, *where)
    173             else:
    174                 filename = fname
--> 175             __builtin__.execfile(filename, *where)

/home/coderpad/test.py in <module>()
      2 while x < 10:
      3   x += 1
----> 4   10 / (5 - x)
      5
      6 print x

ZeroDivisionError: integer division or modulo by zero
root@8e0bcc11822a:~# ipython --version
0.12.1
```

Is there some way to configure the traceback formatted for file runs to drop the topmost frame? I know that the console does this in places, but I have no idea how to achieve that for script runs.



Discussion:
Yes, I'm sure there's some way to configure that, because we have to do it when user code runs, otherwise you'd see half a dozen extra stack frames.

Can you check that it's reproducible in a recent IPython, though? 0.12 is old hat now.

Sure thing, had a super old apt-get install. On 1.1.0, same issue:

```
root@70560302794b:~# ipython test.py
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
/usr/local/lib/python2.7/dist-packages/IPython/utils/py3compat.pyc in execfile(fname, *where)
    202             else:
    203                 filename = fname
--> 204             __builtin__.execfile(filename, *where)

/home/coderpad/test.py in <module>()
      2 while x < 10:
      3   x += 1
----> 4   10 / (5 - x)
      5
      6 print x

ZeroDivisionError: integer division or modulo by zero
root@70560302794b:~# ipython --version
1.1.0
```

Thanks, I've dug into it a bit. `IPython.core.shellapp.InteractiveShellApp._run_cmd_line_code` is what runs the file in question, but it's `IPython.core.interactiveshell.InteractiveShell.safe_execfile` that calls the method to show the traceback. There's a `tb_offset` parameter to drop frames from the top of the traceback, but I'll need to work out what's the right place to set it.

Thanks for looking into it. In the meantime, is there some way I can bandaid over the issue in user-land?

@takluyver any luck on this one? It's something I'd really like to be able to at least monkey patch on my end real soon.


Resolving patch:
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -2533,7 +2533,8 @@ def safe_execfile(self, fname, *where, **kw):
             except:
                 if kw['raise_exceptions']:
                     raise
-                self.showtraceback()
+                # tb offset is 2 because we wrap execfile
+                self.showtraceback(tb_offset=2)
 
     def safe_execfile_ipy(self, fname):
         """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
diff --git a/IPython/utils/py3compat.py b/IPython/utils/py3compat.py
--- a/IPython/utils/py3compat.py
+++ b/IPython/utils/py3compat.py
@@ -181,9 +181,6 @@ def itervalues(d): return d.itervalues()
     def MethodType(func, instance):
         return types.MethodType(func, instance, type(instance))
     
-    # don't override system execfile on 2.x:
-    execfile = execfile
-    
     def doctest_refactor_print(func_or_str):
         return func_or_str
 


Test patch:
diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py
--- a/IPython/core/tests/test_run.py
+++ b/IPython/core/tests/test_run.py
@@ -28,6 +28,7 @@
 from IPython.testing import decorators as dec
 from IPython.testing import tools as tt
 from IPython.utils import py3compat
+from IPython.utils.io import capture_output
 from IPython.utils.tempdir import TemporaryDirectory
 from IPython.core import debugger
 
@@ -474,3 +475,40 @@ def test_run__name__():
         
         _ip.magic('run -n {}'.format(path))
         nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
+
+def test_run_tb():
+    """Test traceback offset in %run"""
+    with TemporaryDirectory() as td:
+        path = pjoin(td, 'foo.py')
+        with open(path, 'w') as f:
+            f.write('\n'.join([
+                "def foo():",
+                "    return bar()",
+                "def bar():",
+                "    raise RuntimeError('hello!')",
+                "foo()",
+            ]))
+        with capture_output() as io:
+            _ip.magic('run {}'.format(path))
+        out = io.stdout
+        nt.assert_not_in("execfile", out)
+        nt.assert_in("RuntimeError", out)
+        nt.assert_equal(out.count("---->"), 3)
+
+def test_script_tb():
+    """Test traceback offset in `ipython script.py`"""
+    with TemporaryDirectory() as td:
+        path = pjoin(td, 'foo.py')
+        with open(path, 'w') as f:
+            f.write('\n'.join([
+                "def foo():",
+                "    return bar()",
+                "def bar():",
+                "    raise RuntimeError('hello!')",
+                "foo()",
+            ]))
+        out, err = tt.ipexec(path)
+        nt.assert_not_in("execfile", out)
+        nt.assert_in("RuntimeError", out)
+        nt.assert_equal(out.count("---->"), 3)
+