Source code for metakernel.magic

import traceback
import optparse
import inspect
import sys
import os
import shlex
from ast import literal_eval as safe_eval

try:
    _maxsize = sys.maxint
except:
    # python3
    _maxsize = sys.maxsize

PY3 = sys.version_info[0] == 3

class MagicOptionParser(optparse.OptionParser):
    def error(self, msg):
        raise Exception('Magic Parse error: "%s"' % msg)

    def exit(self, status=0, msg=None):
        if msg:
            sys.stderr.write(msg)
        raise Exception(msg)

    ## FIXME: override help to also stop processing
    ## currently --help gives syntax error


[docs]class Magic(object): """ Base class to define magics for MetaKernel based kernels. Users can redefine the default magics provided by Metakernel by creating a module with the exact same name as the Metakernel magic. For example, you can override %matplotlib in your kernel by writing a new magic inside magics/matplotlib_magic.py """ def __init__(self, kernel): self.kernel = kernel self.evaluate = True self.code = '' def get_args(self, mtype, name, code, args) : self.code = code old_args = args mtype = mtype.replace('sticky', 'cell') func = getattr(self, mtype + '_' + name) try: args, kwargs = _parse_args(func, args, usage=self.get_help(mtype, name)) except Exception as e: self.kernel.Error(str(e)) return self arg_spec = inspect.getfullargspec(func) if PY3 \ else inspect.getargspec(func) fargs = arg_spec.args if fargs[0] == 'self': fargs = fargs[1:] fargs = [f for f in fargs if not f in kwargs.keys()] if len(args) > len(fargs) and not arg_spec.varargs: extra = ' '.join(str(s) for s in (args[len(fargs) - 1:])) args = args[:len(fargs) - 1] + [extra] return (args, kwargs, old_args) def call_magic(self, mtype, name, code, args): self.code = code old_args = args mtype = mtype.replace('sticky', 'cell') func = getattr(self, mtype + '_' + name) try: args, kwargs = _parse_args(func, args, usage=self.get_help(mtype, name)) except Exception as e: self.kernel.Error(str(e)) return self arg_spec = inspect.getfullargspec(func) if PY3 \ else inspect.getargspec(func) fargs = arg_spec.args if fargs[0] == 'self': fargs = fargs[1:] fargs = [f for f in fargs if not f in kwargs.keys()] if len(args) > len(fargs) and not arg_spec.varargs: extra = ' '.join(str(s) for s in (args[len(fargs) - 1:])) args = args[:len(fargs) - 1] + [extra] try: try: func(*args, **kwargs) except TypeError: func(old_args) except Exception as exc: msg = "Error in calling magic '%s' on %s:\n %s\n args: %s\n kwargs: %s" % ( name, mtype, str(exc), args, kwargs) self.kernel.Error(msg) self.kernel.Error(traceback.format_exc()) self.kernel.Error(self.get_help(mtype, name)) # return dummy magic to end processing: return Magic(self.kernel) return self def get_help(self, mtype, name, level=0): if hasattr(self, mtype + '_' + name): func = getattr(self, mtype + '_' + name) if level == 0: if func.__doc__: return _trim(func.__doc__) else: return "No help available for magic '%s' for %ss." % (name, mtype) else: filename = inspect.getfile(func) if filename and os.path.exists(filename): with open(filename) as f: return f.read() else: return "No help available for magic '%s' for %ss." % (name, mtype) else: return "No such magic '%s' for %ss." % (name, mtype) def get_help_on(self, info, level=0): return "Sorry, no help is available on '%s'." % info['code']
[docs] def get_completions(self, info): """ Get completions based on info dict from magic. """ return []
def get_magics(self, mtype): magics = [] for name in dir(self): if name.startswith(mtype + '_'): magics.append(name.replace(mtype + '_', '')) return magics def get_code(self): return self.code def post_process(self, retval): return retval
[docs]def option(*args, **kwargs): """Return decorator that adds a magic option to a function. """ def decorator(func): help_text = "" if not getattr(func, 'has_options', False): func.has_options = True func.options = [] help_text += 'Options:\n-------\n' try: option = optparse.Option(*args, **kwargs) except optparse.OptionError: help_text += args[0] + "\n" else: help_text += _format_option(option) + "\n" func.options.append(option) if func.__doc__: func.__doc__ += _indent(func.__doc__, help_text) else: func.__doc__ = help_text return func return decorator
def _parse_args(func, args, usage=None): """Parse the arguments given to a magic function""" if isinstance(args, list): args = ' '.join(args) args = _split_args(args) kwargs = dict() if getattr(func, 'has_options', False): parser = MagicOptionParser(usage=usage, conflict_handler="resolve") parser.add_options(func.options) left = [] value = None if '--' in args: left = args[:args.index('--')] value, args = parser.parse_args(args[args.index('--') + 1:]) else: while args: try: value, args = parser.parse_args(args) except Exception: left.append(args.pop(0)) else: break args = left + args if value: kwargs = value.__dict__ new_args = [] for arg in args: try: new_args.append(safe_eval(arg)) except: new_args.append(arg) for (key, value) in kwargs.items(): try: kwargs[key] = safe_eval(value) except: pass return new_args, kwargs def _split_args(args): try: # do not use posix mode, to avoid eating quote characters args = shlex.split(args, posix=False) except: # parse error; let's pass args along rather than crashing args = args.split() new_args = [] temp = '' for arg in args: if arg.startswith('-'): new_args.append(arg) elif temp: arg = temp + ' ' + arg try: safe_eval(arg) except: temp = arg else: new_args.append(arg) temp = '' elif arg.startswith(('(', '[', '{')) or '(' in arg: try: safe_eval(arg) except: temp = arg else: new_args.append(arg) else: new_args.append(arg) if temp: new_args.append(temp) return new_args def _format_option(option): output = '' if option._short_opts: output = option._short_opts[0] + ' ' output += option.get_opt_string() + ' ' output += ' ' * (15 - len(output)) output += option.help + ' ' if not option.default == ('NO', 'DEFAULT'): output += '[default: %s]' % option.default return output def _trim(docstring, return_lines=False): """ Trim of unnecessary leading indentations. """ # from: http://legacy.python.org/dev/peps/pep-0257/ if not docstring: return '' # Convert tabs to spaces (following the normal Python rules) # and split into a list of lines: lines = docstring.expandtabs().splitlines() indent = _min_indent(lines) # Remove indentation (first line is special): trimmed = [lines[0].strip()] if indent < _maxsize: for line in lines[1:]: trimmed.append(line[indent:].rstrip()) # Strip off trailing and leading blank lines: while trimmed and not trimmed[-1]: trimmed.pop() while trimmed and not trimmed[0]: trimmed.pop(0) if return_lines: return trimmed else: # Return a single string: return '\n'.join(trimmed) def _min_indent(lines): """ Determine minimum indentation (first line doesn't count): """ indent = _maxsize for line in lines[1:]: stripped = line.lstrip() if stripped: indent = min(indent, len(line) - len(stripped)) return indent def _indent(docstring, text): """ Returns text indented at appropriate indententation level. """ if not docstring: return text lines = docstring.expandtabs().splitlines() indent = _min_indent(lines) if indent < _maxsize: newlines = _trim(text, return_lines=True) return "\n" + ("\n".join([(" " * indent) + line for line in newlines])) else: return "\n" + text