0

I have defined a custom iPython magic like this:

%%writefile pymagic.py

from IPython.core import magic_arguments

@magic_arguments.magic_arguments()
@magic_arguments.argument('--include', '-i', nargs='+', help='Pass file patterns to include')
@magic_arguments.argument('--omit', '-o', nargs='+', help='Pass file patterns to omit')
def pymagic(line, cell):
    args = magic_arguments.parse_argstring(pymagic, line)
    ip = get_ipython()
    out = ip.run_cell(cell)
    return args, 'test'

def load_ipython_extension(ipython):
    ipython.register_magic_function(pymagic, magic_kind='cell')

I can use it like this:

%load_ext pymagic

%%pymagic --include /home/test/a* --omit /home/test/b*
a = [1,2,3]
# Output: (Namespace(include=['/home/test/a*'], omit=['/home/test/b*']), 'test')

Now, can I do this without using the @magic_arguments decorators?

The reason why I want this is - I have to avoid global imports of IPython module, since they're causing some other unexpected issues. Which means I have to move the 'from IPython.core import magic_arguments' import away from the global scope, preferably inside the load_python_extension() function.

I'm thinking of achieving something like this, which would allow me to get rid of the global import of Ipython.core: (The below snippet is just a rough description, and it's not syntactically correct)

def pymagic(line, cell):
    args = magic_arguments.parse_argstring(pymagic, line)
    ip = get_ipython()
    out = ip.run_cell(cell)
    return args, 'test'

def load_ipython_extension(ipython):
    from IPython.core import magic_arguments
    magic_arguments.add_magic_arg(func=pymagic, arg_attrs=('--include', '-i', nargs='+', help='Pass file patterns to include'))
    magic_arguments.add_magic_arg(func=pymagic, arg_attrs=('--omit', '-o', nargs='+', help='Pass file patterns to omit'))
    ipython.register_magic_function(pymagic, magic_kind='cell')

Is this possible?

1 Answers1

0

Sure, you can do what you're asking. Applying decorators, like this:

@foo
@bar
def baz():
    pass

Is (almost) equivalent to doing it manually like this:

def baz()
    pass

baz = foo(bar(baz))

The only difference is that with @ syntax the undecorated function is never assigned to the namespace.

Notice that the decorators apply from the bottom up (bar gets called first, and its results get passed to foo)!

In your specific case, you can do:

def load_ipython_extension(ipython):
    from IPython.core import magic_arguments
    global pymagic
    pymagic = magic_arguments.add_magic_arg(func=pymagic, arg_attrs=('--omit', '-o', nargs='+', help='Pass file patterns to omit'))
    pymagic = magic_arguments.add_magic_arg(func=pymagic, arg_attrs=('--include', '-i', nargs='+', help='Pass file patterns to include'))
    pymagic = magic_arguments(pymagic)
    ipython.register_magic_function(pymagic, magic_kind='cell')

I made several assignments only because the lines were long, you could do it all in one series of nested calls, if you really wanted to. It also might not be necessary to overwrite the global name, you could do all the decorating in a local variable that you register, and leave the undecorated function alone in the module namespace. I also doubt it matters which order you apply the .add_arguments decorators, but I kept the same order as the @decorator syntax version just in case.

Blckknght
  • 100,903
  • 11
  • 120
  • 169