Qix- / better-exceptions

Pretty and useful exceptions in Python, automatically.
MIT License
4.59k stars 203 forks source link

ipython support #10

Open kootenpv opened 7 years ago

kootenpv commented 7 years ago

Issuehunt badges

Does not appear to work in ipython (at least in emacs)


IssueHunt Summary ### Backers (Total: $70.00) - [issuehunt issuehunt](https://issuehunt.io/u/issuehunt) ($70.00) #### [Become a backer now!](https://issuehunt.io/r/Qix-/better-exceptions/issues/10) #### [Or submit a pull request to get the deposits!](https://issuehunt.io/r/Qix-/better-exceptions/issues/10) ### Tips - Checkout the [Issuehunt explorer](https://issuehunt.io/r/Qix-/better-exceptions/) to discover more funded issues. - Need some help from other developers? [Add your repositories](https://issuehunt.io/r/new) on IssueHunt to raise funds. --- IssueHunt has been backed by the following sponsors. [Become a sponsor](https://issuehunt.io/membership/members)
Qix- commented 7 years ago

Hi - can you post exact errors so I know Im reproducing correctly?

Thanks!

kootenpv commented 7 years ago

There's just no difference when I import it, it's not actually an error. Try running ipython and importing better_exceptions

Qix- commented 7 years ago

Ah, iPython uses a different means to hook into exceptions...

I'll have to do a bit of research about this. iPython doesn't seem to want to install for me at the moment.

lincolnfrias commented 7 years ago

+1 on this

SylvainDe commented 7 years ago

You may find some inspiration in the following projects working on exception hooks as well:

The trick is probably to call set_custom_exc(exc_tuple, handler).

(By the way, awesome project!)

Omnifarious commented 7 years ago

If you put this in $HOME/.ipython/profile_default/startup/00-better_exceptions.py this will enable better_exceptions to be used as the default exception formatter in ipython. Maybe this should go in a contrib folder or something like that.

from __future__ import print_function
import better_exceptions, sys

ip = get_ipython()
old_show = ip.showtraceback

def exception_thunk(exc_tuple=None, filename=None,
                    tb_offset=None, exception_only=False):
    print("Thunk: %r %r %r %r" % (exc_tuple, filename, tb_offset, exception_only),
          file=sys.stderr)
    notuple = False
    if exc_tuple is None:
        notuple = True
        exc_tuple = sys.exc_info()
    use_better = True
    use_better = use_better and (filename is None)
    use_better = use_better and (tb_offset is None)
    use_better = use_better and (not exception_only)
    use_better = use_better and (not isinstance(exc_tuple[0], SyntaxError))
    if use_better:
        return better_exceptions.excepthook(*exc_tuple)
    else:
        return old_show(None if notuple else exc_tuple,
                        filename, tb_offset, exception_only)

ip.showtraceback = exception_thunk
ramast commented 7 years ago

doesnt work with python3

Python 3.5.3 (default, Jan 19 2017, 14:11:04)
Type "copyright", "credits" or "license" for more information.

IPython 5.3.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: def hey(a):
   ...:     return a/a
   ...:

In [2]: hey(0)
Thunk: None None None False
Nosterx commented 6 years ago

example by @Omnifarious rewritten for IPython with python3 put in $HOME/.ipython/profile_default/startup/00-better_exceptions.py

import better_exceptions, sys 

ip = get_ipython()
old_show_tb = ip.showtraceback

def exception_thunk(exc_tuple=None, filename=None,
                    tb_offset=None, exception_only=False):
    new_exc_tuple = exc_tuple or sys.exc_info()
    if not isinstance(new_exc_tuple[0], SyntaxError):
        return print(better_exceptions.format_exception(*new_exc_tuple))
    else:
        return old_show_tb(exc_tuple, filename,
                           tb_offset, exception_only)

ip.showtraceback = exception_thunk

WARNING: as alternative you can use %xmode magic

ramast commented 6 years ago

Re-writing the example above to work with IPython 3.2.1 and also to handle the case where better_exceptions_module is not present in the system

import sys
try:
    import better_exceptions
except ImportError:
    better_exceptions = None

def exception_thunk(exc_tuple=None, filename=None,
                    tb_offset=None, exception_only=False, **kwargs):
    new_exc_tuple = exc_tuple or sys.exc_info()
    if not isinstance(new_exc_tuple[0], SyntaxError):
        return print(better_exceptions.format_exception(*new_exc_tuple))
    else:
        return old_show_tb(exc_tuple, filename,
                           tb_offset, exception_only)
if better_exceptions:
    ip = get_ipython()
    old_show_tb = ip.showtraceback
    ip.showtraceback = exception_thunk

I've added **kwargs because IPython gave that error TypeError: exception_thunk() got an unexpected keyword argument 'running_compiled_code'

kootenpv commented 6 years ago

And now to make it work with pdb mode ;)

Qix- commented 6 years ago

@kootenpv It already does.

def foo(a):
    import pdb; pdb.set_trace()
    assert a == 10

foo(12)
image

EDIT: ah I see, it doesn't with stepping.

Incanus3 commented 5 years ago

Hi, I just tried the startup script from @ramast and it works fine, but the better_exceptions output isn't colorized. To make colors work, you can add better_exceptions.SUPPORTS_COLOR = True to the final if body (not sure what it will do if the terminal doesn't support colors, probably just print the color codes).

Macr0phag3 commented 5 years ago

Hi~

if you don't want to see ipython's exception in the Traceback, you can use this one:

import sys
try:
    import better_exceptions
except ImportError:
    better_exceptions = None

def exception_thunk(exc_tuple=None, filename=None,
                    tb_offset=None, exception_only=False, **kwargs):
    new_exc_tuple = exc_tuple or sys.exc_info()
    if not isinstance(new_exc_tuple[0], SyntaxError):
        # return print(better_exceptions.format_exception(*new_exc_tuple))
        error = better_exceptions.format_exception(*new_exc_tuple).split('\n')
        return print("Traceback (most recent call last):\n"+'\n'.join(error[6:]), end='')
    else:
        return old_show_tb(exc_tuple, filename,
                           tb_offset, exception_only)
if better_exceptions:
    ip = get_ipython()
    old_show_tb = ip.showtraceback
    ip.showtraceback = exception_thunk

example:

before:

In [1]: def foo(a):
   ...:     assert a > 1
   ...:

In [2]: foo(0)
Traceback (most recent call last):
  File "/xxxx/xxxx/.pyenv/versions/3.6.5/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3267, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
         │         │                    └ <IPython.terminal.interactiveshell.TerminalInteractiveShell object at 0x10433f438>
         │         └ <IPython.terminal.interactiveshell.TerminalInteractiveShell object at 0x10433f438>
         └ <code object <module> at 0x1047096f0, file "<ipython-input-2-e3ad81521767>", line 1>
  File "<ipython-input-2-e3ad81521767>", line 1, in <module>
    foo(0)
    └ <function foo at 0x104706f28>
  File "<ipython-input-1-41263cb52715>", line 2, in foo
    assert a > 1
           └ 0
AssertionError: assert a > 1

after:

In [5]: def foo(a):
   ...:     assert a > 1
   ...:

In [6]: foo(0)
Traceback (most recent call last):
  File "<ipython-input-6-7d8d25071659>", line 1, in <module>
    foo(0)
    └ <function foo at 0x111562950>
  File "<ipython-input-5-46c24d49c44a>", line 2, in foo
    assert a > 1
           └ 0
AssertionError: assert a > 1

i don't know how to pop the exception in the Traceback...so i just delete the line 1 to 6

frostming commented 5 years ago

To make it work with IPython debug, use this improved version:

from __future__ import print_function
import better_exceptions, sys, types
ip = get_ipython()
old_show = ip.showtraceback
def exception_thunk(self, exc_tuple=None, filename=None,
                    tb_offset=None, exception_only=False, **kwargs):

    notuple = False
    if exc_tuple is None:
        notuple = True
        exc_tuple = sys.exc_info()
    etype, value, tb = self._get_exc_info(exc_tuple)
    use_better = not any ([filename, tb_offset, exception_only, issubclass(etype, SyntaxError)])
    if use_better:
        return better_exceptions.excepthook(etype, value, tb)
    else:
        return old_show(None if notuple else exc_tuple,
                        filename, tb_offset, exception_only, **kwargs)

ip.showtraceback = types.MethodType(exception_thunk, ip)
issuehunt-oss[bot] commented 5 years ago

@issuehunt has funded $70.00 to this issue.


techdragon commented 5 years ago

There are a number of code snippets posted in this issue. It seems like this may already work, but not out of the box. Is there a canonical workaround for ipython's different way of handling tracebacks

Ideally one that doesn't require messing with creating a personal ipython startup script. My personal preference would be registering an ipython magic like many other ipython related libraries do. Something like %%better_exceptions so it can be explicitly turned on/off per cell... But anything is usually an improvement on nothing. So it would be good to just have a clear workaround at all. 😄

alexmojaki commented 5 years ago

Someone mentioned %xmode in passing and I think it deserves more attention. For those who don't know, %xmode Verbose will change the builtin exception handler to display variables. It's trying to solve the same problem as better-exceptions. Wouldn't the best solution be to identify what better-exceptions has to offer that xmode doesn't and then integrate those features directly into IPython?

Going a little off-track, it's possible to do better than either in some cases. In GUI environments like Jupyter notebooks and Jupyterlab, one can create an interactive traceback that only shows variables when requested. Django puts them in a collapsible panel:

django

Flask/werkzeug lets you open a console in any frame:

flask

I don't know if anyone does this, but I think it might be awesome if hovering over a variable name in a traceback displays it.

There's an open issue about this: https://github.com/jupyter/notebook/issues/1247

alexmojaki commented 5 years ago

OK, somewhat coincidentally, an alternative possibility has come up here: https://github.com/ipython/ipython/issues/11827#issuecomment-513001601

In particular:

Ultratb is a real mess and really old. Any refactor to it is welcomed; I've been willing from some time to separate the traceback information extracting from the formatting, to potentially make a nice HTML repr... If we want/can extract these into common packages I'm all for it. There is also https://github.com/qix-/better-exceptions that looks nice; though showing variable values can be a security issue s can't be enabled by default.

Essentially it would be good if there was a package which had a lower level API that returned information about a traceback which a user could easily format in different ways. IPython would be interested in such a package, as would I, and potentially other packages such as Django and Flask.

@Qix- are you interested in this package being refactored to offer such an API? It would then power the existing higher level API which does the formatting, hooks, REPL, etc. The higher level API could also still be useful to other packages, e.g. @Carreau was probably mainly interested in the variable formatting.

I would be happy to work on the coding, and I'm guessing others would too. But I'm concerned by the PRs that are starting to queue up with little/no response. @Qix-, do you still have the time and energy to maintain this repository and review contributions? Would you be able to handle the increased pressure if this became a dependency of IPython? If not, would you be willing to hand the responsibility over to others such as @Delgan? Otherwise I am tempted to write such a package myself from scratch and borrowing bits of code from here.

Qix- commented 5 years ago

Hey @alexmojaki, I'm still around - moved around the world about 8 months ago and have just lately been getting back into OSS.

I appreciate the heads up about the ipython issue - I'd be very interested in a more comprehensive package.

As for the neglect, I will be rectifying that in the coming week.

Carreau commented 5 years ago

Thanks that is great ! Hope you enjoyed your trip around the world !

Some notes: IPython also support showing variables values in stacktraces; it is disabled by default because we had issues where this was leaking secrets in tracebacks during talks, or to notebooks published on github. Though the way you show it is way nicer than ours!

There are also 2 things that we do, which I believe this does not do yet is 1) show more than 1 line context, and 2) elide when there is a recursion error with N frames repeated X times.

A more comprehensive package and single-stop-shop is always good. Even better if we can at some point justify movin some of the functionality to core Python :-)

alexmojaki commented 5 years ago

@Carreau I've opened #92 to discuss showing multiple lines in a frame.