Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make IPython Import What I Mean

I want to modify how IPython handles import errors by default. When I prototype something in the IPython shell, I usually forget to first import os, re or whatever I need. The first few statements often follow this pattern:

In [1]: os.path.exists("~/myfile.txt")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-0ffb6014a804> in <module>()
----> 1 os.path.exists("~/myfile.txt")

NameError: name 'os' is not defined

In [2]: import os

In [3]: os.path.exists("~/myfile.txt")
Out[3]: False

Sure, that's my fault for having bad habits and, sure, in a script or real program that makes sense, but in the shell I'd rather that IPython follow the DWIM principle, by at least trying to import what I am trying to use.

In [1]: os.path.exists("~/myfile.txt")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-0ffb6014a804> in <module>()
----> 1 os.path.exists("~/myfile.txt")

NameError: name 'os' is not defined

Catching this for you and trying to import "os" … success!
Retrying …
---------------------------------------------------------------------------
Out[1]: False

If this is not possible with a vanilla IPython, what would I have to do to make this work? Is a wrapper kernel the easiest way forward? Or should this be implemented directly in the core, with a magic command?

Note, this is different from those kind of question where someone wants to always load pre-defined modules. I don't. Cuz I don't know what I will be working on, and I don't want to load everything (nor do I want to keep the list of everything updated.

like image 598
mknecht Avatar asked Mar 20 '16 09:03

mknecht


1 Answers

NOTE: This is now being maintained on Github. Download the latest version of the script from there!

I developed a script that binds to IPython's exception handling through set_custom_exc. If there's a NameError, it uses a regex to find what module you tried to use and then attempt to import it. It then runs the function you tried to call again. Here is the code:

import sys, IPython, colorama # <-- colorama must be "pip install"-ed

colorama.init()

def custom_exc(shell, etype, evalue, tb, tb_offset=None):
    pre = colorama.Fore.CYAN + colorama.Style.BRIGHT + "AutoImport: " + colorama.Style.NORMAL + colorama.Fore.WHITE
    if etype == NameError:
        shell.showtraceback((etype, evalue, tb), tb_offset) # Show the normal traceback
        import re
        try:
            # Get the name of the module you tried to import
            results = re.match("name '(.*)' is not defined", str(evalue))
            name = results.group(1)

            try:
                __import__(name)
            except:
                print(pre + "{} isn't a module".format(name))
                return

            # Import the module
            IPython.get_ipython().run_code("import {}".format(name))
            print(pre + "Imported referenced module \"{}\", will retry".format(name))
        except Exception as e:
            print(pre + "Attempted to import \"{}\" but an exception occured".format(name))

        try:
            # Run the failed line again
            res = IPython.get_ipython().run_cell(list(get_ipython().history_manager.get_range())[-1][-1])
        except Exception as e:
            print(pre + "Another exception occured while retrying")
            shell.showtraceback((type(e), e, None), None)
    else:
        shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)

# Bind the function we created to IPython's exception handler
IPython.get_ipython().set_custom_exc((Exception,), custom_exc)

You can make this run automatically when you start an IPython prompt by saving it somewhere and then setting an environment variable called PYTHONSTARTUP to the path to this file. You set environment variables differently depending on your OS (remember to alter the paths):

  • Windows: setx PYTHONSTARTUP C:\startup.py in command prompt
  • Mac/Linux (bash): Put export PYTHONSTARTUP=$HOME/startup.py into your ~/.bashrc

Here's a demo of the script in action:

Demo

like image 100
Aaron Christiansen Avatar answered Oct 10 '22 04:10

Aaron Christiansen