diff --git a/jupyter_kernel/magic.py b/jupyter_kernel/magic.py index 56337bb7..452ba931 100644 --- a/jupyter_kernel/magic.py +++ b/jupyter_kernel/magic.py @@ -25,16 +25,17 @@ def call_magic(self, mtype, name, code, args): args = [] try: - func(*args, **kwargs) - except TypeError: try: + func(*args, **kwargs) + except TypeError: func(old_args) - except Exception: - msg = "Error in calling magic '%s' on %s" % (name, mtype) - self.kernel.Error(msg) - self.kernel.Error(self.get_help(mtype, name)) - # return dummy magic to end processing: - return Magic(self.kernel) + 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(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): diff --git a/jupyter_kernel/magickernel.py b/jupyter_kernel/magickernel.py index 676398e6..33bbe771 100644 --- a/jupyter_kernel/magickernel.py +++ b/jupyter_kernel/magickernel.py @@ -12,6 +12,7 @@ import imp import re import inspect +import logging class MagicKernel(Kernel): @@ -22,6 +23,12 @@ class MagicKernel(Kernel): def __init__(self, *args, **kwargs): super(MagicKernel, self).__init__(*args, **kwargs) + if self.log is None: + # This occurs if we call as a stand-alone kernel + # (eg, not as a process) + # FIXME: take care of input/output, eg StringIO + # make work without a session + self.log = logging.Logger(".magickernel") self.sticky_magics = {} self._i = None self._ii = None @@ -43,6 +50,15 @@ def __init__(self, *args, **kwargs): jupyter_kernel.JUPYTER_INSTANCE = self self.set_variable("get_jupyter", jupyter_kernel.get_jupyter) + @classmethod + def subkernel(cls, kernel): + """ + FIXME: monkeypatch to Make this kernel class be a subkernel to another. + """ + cls.log = kernel.log + cls.iopub_socket = kernel.iopub_socket + cls._parent_header = kernel._parent_header + ##################################### # Methods which provide kernel - specific behavior diff --git a/jupyter_kernel/magics/kernel_magic.py b/jupyter_kernel/magics/kernel_magic.py index a1e84489..75eacd7e 100644 --- a/jupyter_kernel/magics/kernel_magic.py +++ b/jupyter_kernel/magics/kernel_magic.py @@ -22,9 +22,9 @@ def line_kernel(self, module_name, class_name, kernel_name="default"): """ module = __import__(module_name) class_ = getattr(module, class_name) - class_.log = logging.Logger(".kernel") - class_.iopub_socket = self.kernel.iopub_socket - class_._parent_header = self.kernel._parent_header + # FIXME: monkeypatch to replace methods of class + # with methods of instance + class_.subkernel(self.kernel) self.kernels[kernel_name] = class_() self.retval = self.kernels[kernel_name] diff --git a/jupyter_kernel/magics/parallel_magic.py b/jupyter_kernel/magics/parallel_magic.py index 1d123314..75cfeda4 100644 --- a/jupyter_kernel/magics/parallel_magic.py +++ b/jupyter_kernel/magics/parallel_magic.py @@ -5,22 +5,40 @@ from jupyter_kernel import Magic, option import logging +class Slice(object): + """Utility class for making slice ranges.""" + def __getitem__(self, item): + return item + +slicer = Slice() ## instance to parse slices + class ParallelMagic(Magic): client = None view = None module_name = None class_name = None kernel_name = None + ids = None retval = None + @option( + '-k', '--kernel_name', action='store', default="default", + help='arbitrary name given to reference kernel' + ) + @option( + '-i', '--ids', action='store', default=None, + help='the machine ids to use from the cluster' + + ) def line_parallel(self, module_name, class_name, kernel_name="default", ids=None): """ - %parallel MODULE CLASS NAME ids[...] - construct an interface to the cluster. + %parallel MODULE CLASS [-k NAME] [-i [...]] - construct an interface to the cluster. Example: %parallel bash_kernel BashKernel - %parallel bash_kernel BashKernel bash [0, 2, 4] + %parallel bash_kernel BashKernel -k bash + %parallel bash_kernel BashKernel --i [0,2:5,9,...] Use %px or %%px to send code to the cluster. """ @@ -28,40 +46,71 @@ def line_parallel(self, module_name, class_name, kernel_name="default", ids=None self.client = Client() if ids is None: self.view = self.client[:] - # ids[:] = slice(None, None, None) - # ids[1:3] = slice(1, 3, None) - # ids[1:3:1] = slice(1, 3, 1) - # ids[1, 2, ...] = [1, 2, Ellipsis] - # ids[1, 2:4, ...] = [1, slice(2, 4, None), Ellipsis] + else: + # ids[:] = slice(None, None, None) + # ids[1:3] = slice(1, 3, None) + # ids[1:3:1] = slice(1, 3, 1) + # ids[1, 2, ...] = [1, 2, Ellipsis] + # ids[1, 2:4, ...] = [1, slice(2, 4, None), Ellipsis] + try: + ids_slice = eval("slicer%s" % ids) # slicer[0,...,7] + except: + ids_slice = slicer[:] + if isinstance(ids_slice, (slice, int)): + self.view = self.client[ids_slice] + else: # tuple of indexes/slices + # TEST: can we do this? + # FIXME: if so, handle Ellipsis + view = [] + for item in ids_slice: + view.append(self.client[item]) + self.view = view self.module_name = module_name self.class_name = class_name self.kernel_name = kernel_name self.view.execute(""" +try: + kernels +except: + kernels = {} from %(module_name)s import %(class_name)s -import logging -%(class_name)s.log = logging.Logger(".kernel") -kernel = %(class_name)s() +kernels['%(kernel_name)s'] = %(class_name)s() """ % {"module_name": module_name, - "class_name": class_name}, block=True) + "class_name": class_name, + "kernel_name": kernel_name}, + block=True) self.retval = None - ## px --kernel NAME - def line_px(self, expression): + @option( + '-k', '--kernel_name', action='store', default=None, + help='kernel name given to use for execution' + ) + def line_px(self, expression, kernel_name=None): """ %px EXPRESSION - send EXPRESSION to the cluster. Example: %px sys.version - %px (define x 42) + %px -k scheme (define x 42) %px x Use %parallel to initialize the cluster. """ - self.retval = self.view["kernel.do_execute_direct(\"%s\")" % expression.replace('"', '\\"')] + if kernel_name is None: + kernel_name = self.kernel_name + self.retval = self.view["kernels['%s'].do_execute_direct(\"%s\")" % ( + kernel_name, self._clean_code(expression))] + + def _clean_code(self, expr): + return expr.strip().replace('"', '\\"').replace("\n", "\\n") ## px --kernel NAME - def cell_px(self): + @option( + '-k', '--kernel_name', action='store', default=None, + help='kernel name given to use for execution' + ) + def cell_px(self, kernel_name=None): """ %%px - send cell to the cluster. @@ -72,7 +121,10 @@ def cell_px(self): Use %parallel to initialize the cluster. """ - self.retval = self.view["kernel.do_execute_direct(\"%s\")" % self.code.replace('"', '\\"')] + if kernel_name is None: + kernel_name = self.kernel_name + self.retval = self.view["kernels['%s'].do_execute_direct(\"%s\")" % ( + kernel_name, self._clean_code(self.code))] self.evaluate = False def post_process(self, retval):