Todo

Subcommands in IPython and Jupyter.

There’s a ton that could be done here. Well I guess sub-apps would be a better name for it but whatever.

Mostly here to note that I think the ‘editor’ hooks doesn’t work the way it’s supposed to if you want to configure it.

If you run %edit in the shell it doesn’t provide any arguments, but it’ll complain that it needs a function that accepts 4 positional parameters.

I’m pretty confident it isn’t just me calling the function wrong too. Try this.:

from IPython import get_ipython
ip = get_ipython()
ip.hooks['editor'].chain
# inspect your hooks look normal
from IPython.core.hooks import fix_error_editor
ip.hooks['editor'].add(fix_error_editor)
%edit # --> complains that it needs 3 more positional parameters

Before I noticed this, I wrote a function that only accepts 1 positional arg, which btw…it was the filename duh. I don’t know or care what column number I start at??? and takes optional keyword arguments.

Said the function was getting called wrong as it only accepted 1 positional and was being called with 4. sigh.

IPython history

history

Here’s some seemingly inconsistent behavior.

$: ipython history
No subcommand specified. Must specify one of: dict_keys(['trim', 'clear'])
Manage the IPython history database.
Subcommands
-----------
Subcommands are launched as `ipython-history cmd [args]`. For information
on using subcommand 'cmd', do: `ipython-history cmd -h`.

So there we have instructions to use the invocation ipython-history.

$: ipython-history -h
ipython-history: command not found

Erhm. That’s confusing. So how does the history command work?

IPython History API

Interact with the IPython sqlite3 database.

trim

Trim the IPython history database to the last 1000 entries.

clear

Clear the IPython history database, deleting all entries.

Also the original implementation only defines 2 options for the subcommand.

But it would be nice to have options like backup and grep or something. Though to be fair the :class:`~IPython.utils.text.SList` class has a ‘grep’ method.

There are a handful of nice to have but ultimately pointless functions in IPython.utils so why not take advantage?

Writing Magics For Our Users

In the documentation, it specifies the requirements for a magic. And I quote the custommagics document.:

There are two main ways to define your own magic functions: from standalone functions and by inheriting from a base class provided by IPython, Magics.

It then gives an example.

from IPython.core.magic import (Magics, magics_class,
                                line_magic,cell_magic, line_cell_magic)

# The class MUST call this class decorator at creation time
@magics_class
class MyMagics(Magics):

    @line_magic
    def lmagic(self, line):
        "my line magic"
        print("Full access to the main IPython object:", self.shell)
        print("Variables in the user namespace:", list(self.shell.user_ns.keys()))
        return line

    @cell_magic
    def cmagic(self, line, cell):
        "my cell magic"
        return line, cell

    @line_cell_magic
    def lcmagic(self, line, cell=None):
        "Magic that works both as %lmagic and as %%cmagic"
        if cell is None:
            print("Called as line magic")
            return line
        else:
            print("Called as cell magic")
            return line, cell
# In order to actually use these magics, you must register them with a
# running IPython instance.
def load_ipython_extension(ipython):
    """
    Any module file that define a function named `load_ipython_extension`
    can be loaded via `%load_ext module.path` or be configured to be
    autoloaded by IPython at startup time.
    """
    # You can register the class itself without instantiating it.  IPython will
    # call the default constructor on it.
    ipython.register_magics(MyMagics)

How can we rewrite the magic implementation so that the decorator magics_class isn’t required anymore?

Like if they pass us a string can we not just feed it to our own home-brewed wrapper function? Off the top of my head I’m guessing something like this.:

arg, _ = sys.argv[1:]
if not hasattr(arg, 'load_ext'):   # or whatever interface is expected

   @magics_class
   @functools.wraps
   def wrapped(*args, **kwargs):
       return *args, **kwargs

shell.register_magic('MyMagic')   # <---- incorrectly passed as a str

# But in the register_magic method we would do:

class InteractiveShell:

   ...
   def register_magic(self, *args, **kwargs):
       # Run that interface check with
       if not hasattr(arg, 'load_ext'):   # or whatever interface is expected
           # and then call the wrapped function with the args that were passed to us

           ...
           # the usual stuff

I’m sure that I poorly executed that here; however, after some deliberation, would it be that hard to do?

%alias_magic

Create an alias for an already definedmagic.

%alias_magic is really convenient and makes it possible to create really short monikers for rather complicated mixes of shell scripts and object-oriented python. But it doesn’t copy over the __doc__ from the old magic.

There’s a ton of good information that gets lost going from %edit to %ed and %history to %hist. Anything we can do about that?