Beispiel #1
0
def comms(kernel):
    """Get the comms"""
    commA = dummyComm()
    commB = dummyComm()
    commA.other = commB
    commB.other = commA

    frontend_comm = FrontendComm(kernel)
    kernel_comm = KernelComm()

    class DummyKernelClient():
        comm_channel = None

    kernel_comm.kernel_client = DummyKernelClient()

    kernel_comm._register_comm(commA)

    # Bypass the target system as this is not what is being tested
    frontend_comm._comm_open(commB,
                             {'content': {
                                 'data': {
                                     'pickle_protocol': 2
                                 }
                             }})

    return (kernel_comm, frontend_comm)
Beispiel #2
0
def comms(kernel):
    """Get the comms"""
    commA = dummyComm()
    commB = dummyComm()
    commA.other = commB
    commB.other = commA

    frontend_comm = FrontendComm(kernel)
    kernel_comm = KernelComm()

    def dummy_set_comm_port(port):
        """There is no port to set."""
        pass

    kernel_comm.register_call_handler('_set_comm_port', dummy_set_comm_port)

    class DummyKernelClient():
        comm_channel = 0
        shell_channel = 0

    kernel_comm.kernel_client = DummyKernelClient()

    kernel_comm._register_comm(commA)

    # Bypass the target system as this is not what is being tested
    frontend_comm._comm_open(commB,
                             {'content': {'data': {'pickle_protocol': 2}}})

    return (kernel_comm, frontend_comm)
Beispiel #3
0
    def __init__(self, *args, **kwargs):
        super(SpyderKernel, self).__init__(*args, **kwargs)

        self.frontend_comm = FrontendComm(self)

        # All functions that can be called through the comm
        handlers = {
            'set_breakpoints': self.set_spyder_breakpoints,
            'set_pdb_ignore_lib': self.set_pdb_ignore_lib,
            'set_pdb_execute_events': self.set_pdb_execute_events,
            'set_pdb_use_exclamation_mark': self.set_pdb_use_exclamation_mark,
            'get_value': self.get_value,
            'load_data': self.load_data,
            'save_namespace': self.save_namespace,
            'is_defined': self.is_defined,
            'get_doc': self.get_doc,
            'get_source': self.get_source,
            'set_value': self.set_value,
            'remove_value': self.remove_value,
            'copy_value': self.copy_value,
            'set_cwd': self.set_cwd,
            'get_cwd': self.get_cwd,
            'get_syspath': self.get_syspath,
            'get_env': self.get_env,
            'close_all_mpl_figures': self.close_all_mpl_figures,
            'show_mpl_backend_errors': self.show_mpl_backend_errors,
            'get_namespace_view': self.get_namespace_view,
            'set_namespace_view_settings': self.set_namespace_view_settings,
            'get_var_properties': self.get_var_properties,
            'set_sympy_forecolor': self.set_sympy_forecolor,
            'update_syspath': self.update_syspath,
            'is_special_kernel_valid': self.is_special_kernel_valid,
            'get_matplotlib_backend': self.get_matplotlib_backend,
            'set_matplotlib_backend': self.set_matplotlib_backend,
            'set_mpl_inline_figure_format': self.set_mpl_inline_figure_format,
            'set_mpl_inline_resolution': self.set_mpl_inline_resolution,
            'set_mpl_inline_figure_size': self.set_mpl_inline_figure_size,
            'set_mpl_inline_bbox_inches': self.set_mpl_inline_bbox_inches,
            'set_jedi_completer': self.set_jedi_completer,
            'set_greedy_completer': self.set_greedy_completer,
            'set_autocall': self.set_autocall,
            'pdb_input_reply': self.pdb_input_reply,
            '_interrupt_eventloop': self._interrupt_eventloop,
        }
        for call_id in handlers:
            self.frontend_comm.register_call_handler(call_id,
                                                     handlers[call_id])

        self.namespace_view_settings = {}

        self._pdb_obj = None
        self._pdb_step = None
        self._do_publish_pdb_state = True
        self._mpl_backend_error = None
        self._running_namespace = None
        self._pdb_input_line = None
        self.shell.get_local_scope = self.get_local_scope
Beispiel #4
0
    def __init__(self, *args, **kwargs):
        super(SpyderKernel, self).__init__(*args, **kwargs)

        self.comm_manager.get_comm = self._get_comm
        self.frontend_comm = FrontendComm(self)

        # All functions that can be called through the comm
        handlers = {
            'set_breakpoints': self.set_spyder_breakpoints,
            'set_pdb_ignore_lib': self.set_pdb_ignore_lib,
            'set_pdb_execute_events': self.set_pdb_execute_events,
            'set_pdb_use_exclamation_mark': self.set_pdb_use_exclamation_mark,
            'get_value': self.get_value,
            'load_data': self.load_data,
            'save_namespace': self.save_namespace,
            'is_defined': self.is_defined,
            'get_doc': self.get_doc,
            'get_source': self.get_source,
            'set_value': self.set_value,
            'remove_value': self.remove_value,
            'copy_value': self.copy_value,
            'set_cwd': self.set_cwd,
            'get_cwd': self.get_cwd,
            'get_syspath': self.get_syspath,
            'get_env': self.get_env,
            'close_all_mpl_figures': self.close_all_mpl_figures,
            'show_mpl_backend_errors': self.show_mpl_backend_errors,
            'get_namespace_view': self.get_namespace_view,
            'set_namespace_view_settings': self.set_namespace_view_settings,
            'get_var_properties': self.get_var_properties,
            'set_sympy_forecolor': self.set_sympy_forecolor,
            'update_syspath': self.update_syspath,
            'is_special_kernel_valid': self.is_special_kernel_valid,
            'get_matplotlib_backend': self.get_matplotlib_backend,
            'get_mpl_interactive_backend': self.get_mpl_interactive_backend,
            'pdb_input_reply': self.pdb_input_reply,
            '_interrupt_eventloop': self._interrupt_eventloop,
            'enable_faulthandler': self.enable_faulthandler,
            "flush_std": self.flush_std,
            'get_current_frames': self.get_current_frames,
        }
        for call_id in handlers:
            self.frontend_comm.register_call_handler(call_id,
                                                     handlers[call_id])

        self.namespace_view_settings = {}
        self._mpl_backend_error = None
        self._running_namespace = None
        self.faulthandler_handle = None

        self.control_handlers['comm_msg'] = self.control_comm_msg
        self.control_handlers['complete_request'] = self.shell_handlers[
            'complete_request']
Beispiel #5
0
    def __init__(self, *args, **kwargs):
        super(SpyderKernel, self).__init__(*args, **kwargs)

        self.frontend_comm = FrontendComm(self)

        # All functions that can be called through the comm
        handlers = {
            'set_breakpoints': self.set_spyder_breakpoints,
            'set_pdb_ignore_lib': self.set_pdb_ignore_lib,
            'set_pdb_execute_events': self.set_pdb_execute_events,
            'get_value': self.get_value,
            'load_data': self.load_data,
            'save_namespace': self.save_namespace,
            'is_defined': self.is_defined,
            'get_doc': self.get_doc,
            'get_source': self.get_source,
            'set_value': self.set_value,
            'remove_value': self.remove_value,
            'copy_value': self.copy_value,
            'set_cwd': self.set_cwd,
            'get_cwd': self.get_cwd,
            'get_syspath': self.get_syspath,
            'get_env': self.get_env,
            'close_all_mpl_figures': self.close_all_mpl_figures,
            'show_mpl_backend_errors': self.show_mpl_backend_errors,
            'get_namespace_view': self.get_namespace_view,
            'set_namespace_view_settings': self.set_namespace_view_settings,
            'get_var_properties': self.get_var_properties,
            'set_sympy_forecolor': self.set_sympy_forecolor,
            'set_pdb_echo_code': self.set_pdb_echo_code,
            'update_syspath': self.update_syspath,
            'is_special_kernel_valid': self.is_special_kernel_valid
            }
        for call_id in handlers:
            self.frontend_comm.register_call_handler(
                call_id, handlers[call_id])

        self.namespace_view_settings = {}

        self._pdb_obj = None
        self._pdb_step = None
        self._pdb_print_code = True
        self._do_publish_pdb_state = True
        self._mpl_backend_error = None
        self._running_namespace = None
        self.shell.get_local_scope = self.get_local_scope
Beispiel #6
0
class SpyderKernel(IPythonKernel):
    """Spyder kernel for Jupyter."""
    def __init__(self, *args, **kwargs):
        super(SpyderKernel, self).__init__(*args, **kwargs)

        self.frontend_comm = FrontendComm(self)

        # All functions that can be called through the comm
        handlers = {
            'set_breakpoints': self.set_spyder_breakpoints,
            'set_pdb_ignore_lib': self.set_pdb_ignore_lib,
            'set_pdb_execute_events': self.set_pdb_execute_events,
            'set_pdb_use_exclamation_mark': self.set_pdb_use_exclamation_mark,
            'get_value': self.get_value,
            'load_data': self.load_data,
            'save_namespace': self.save_namespace,
            'is_defined': self.is_defined,
            'get_doc': self.get_doc,
            'get_source': self.get_source,
            'set_value': self.set_value,
            'remove_value': self.remove_value,
            'copy_value': self.copy_value,
            'set_cwd': self.set_cwd,
            'get_cwd': self.get_cwd,
            'get_syspath': self.get_syspath,
            'get_env': self.get_env,
            'close_all_mpl_figures': self.close_all_mpl_figures,
            'show_mpl_backend_errors': self.show_mpl_backend_errors,
            'get_namespace_view': self.get_namespace_view,
            'set_namespace_view_settings': self.set_namespace_view_settings,
            'get_var_properties': self.get_var_properties,
            'set_sympy_forecolor': self.set_sympy_forecolor,
            'update_syspath': self.update_syspath,
            'is_special_kernel_valid': self.is_special_kernel_valid,
            'pdb_input_reply': self.pdb_input_reply,
            '_interrupt_eventloop': self._interrupt_eventloop,
        }
        for call_id in handlers:
            self.frontend_comm.register_call_handler(call_id,
                                                     handlers[call_id])

        self.namespace_view_settings = {}

        self._pdb_obj = None
        self._pdb_step = None
        self._do_publish_pdb_state = True
        self._mpl_backend_error = None
        self._running_namespace = None
        self._pdb_input_line = None
        self.shell.get_local_scope = self.get_local_scope

    def get_local_scope(self, stack_depth):
        """Get local scope at given frame depth."""
        frame = sys._getframe(stack_depth + 1)
        if self._pdb_frame is frame:
            # we also give the globals because they might not be in
            # self.shell.user_ns
            namespace = frame.f_globals.copy()
            namespace.update(self._pdb_locals)
            return namespace
        else:
            return frame.f_locals

    # -- Public API -----------------------------------------------------------
    def frontend_call(self,
                      blocking=False,
                      broadcast=True,
                      timeout=None,
                      callback=None):
        """Call the frontend."""
        # If not broadcast, send only to the calling comm
        if broadcast:
            comm_id = None
        else:
            comm_id = self.frontend_comm.calling_comm_id

        return self.frontend_comm.remote_call(blocking=blocking,
                                              comm_id=comm_id,
                                              callback=callback,
                                              timeout=timeout)

    # --- For the Variable Explorer
    def set_namespace_view_settings(self, settings):
        """Set namespace_view_settings."""
        self.namespace_view_settings = settings

    def get_namespace_view(self):
        """
        Return the namespace view

        This is a dictionary with the following structure

        {'a': {'color': '#800000', 'size': 1, 'type': 'str', 'view': '1'}}

        Here:
        * 'a' is the variable name
        * 'color' is the color used to show it
        * 'size' and 'type' are self-evident
        * and'view' is its value or the text shown in the last column
        """
        from spyder_kernels.utils.nsview import make_remote_view

        settings = self.namespace_view_settings
        if settings:
            ns = self._get_current_namespace()
            view = make_remote_view(ns, settings, EXCLUDED_NAMES)
            return view
        else:
            return None

    def get_var_properties(self):
        """
        Get some properties of the variables in the current
        namespace
        """
        from spyder_kernels.utils.nsview import get_remote_data

        settings = self.namespace_view_settings
        if settings:
            ns = self._get_current_namespace()
            data = get_remote_data(ns,
                                   settings,
                                   mode='editable',
                                   more_excluded_names=EXCLUDED_NAMES)

            properties = {}
            for name, value in list(data.items()):
                properties[name] = {
                    'is_list': isinstance(value, (tuple, list)),
                    'is_dict': isinstance(value, dict),
                    'is_set': isinstance(value, set),
                    'len': self._get_len(value),
                    'is_array': self._is_array(value),
                    'is_image': self._is_image(value),
                    'is_data_frame': self._is_data_frame(value),
                    'is_series': self._is_series(value),
                    'array_shape': self._get_array_shape(value),
                    'array_ndim': self._get_array_ndim(value)
                }

            return properties
        else:
            return None

    def get_value(self, name):
        """Get the value of a variable"""
        ns = self._get_current_namespace()
        self._do_publish_pdb_state = False
        return ns[name]

    def set_value(self, name, value):
        """Set the value of a variable"""
        ns = self._get_reference_namespace(name)
        ns[name] = value
        self.log.debug(ns)

    def remove_value(self, name):
        """Remove a variable"""
        ns = self._get_reference_namespace(name)
        ns.pop(name)

    def copy_value(self, orig_name, new_name):
        """Copy a variable"""
        ns = self._get_reference_namespace(orig_name)
        ns[new_name] = ns[orig_name]

    def load_data(self, filename, ext, overwrite=False):
        """
        Load data from filename.

        Use 'overwrite' to determine if conflicts between variable names need
        to be handle or not.

        For example, if a loaded variable is call 'var'
        and there is already a variable 'var' in the namespace, having
        'overwrite=True' will cause 'var' to be updated.
        In the other hand, with 'overwrite=False', a new variable will be
        created with a sufix starting with 000 i.e 'var000' (default behavior).
        """
        from spyder_kernels.utils.iofuncs import iofunctions
        from spyder_kernels.utils.misc import fix_reference_name

        glbs = self._mglobals()

        load_func = iofunctions.load_funcs[ext]
        data, error_message = load_func(filename)

        if error_message:
            return error_message

        if not overwrite:
            # We convert to list since we mutate this dictionary
            for key in list(data.keys()):
                new_key = fix_reference_name(key, blacklist=list(glbs.keys()))
                if new_key != key:
                    data[new_key] = data.pop(key)

        try:
            glbs.update(data)
        except Exception as error:
            return str(error)

        return None

    def save_namespace(self, filename):
        """Save namespace into filename"""
        from spyder_kernels.utils.nsview import get_remote_data
        from spyder_kernels.utils.iofuncs import iofunctions

        ns = self._get_current_namespace()
        settings = self.namespace_view_settings
        data = get_remote_data(ns,
                               settings,
                               mode='picklable',
                               more_excluded_names=EXCLUDED_NAMES).copy()
        return iofunctions.save(data, filename)

    # --- For Pdb
    def is_debugging(self):
        """
        Check if we are currently debugging.
        """
        return bool(self._pdb_frame)

    def _do_complete(self, code, cursor_pos):
        """Call parent class do_complete"""
        return super(SpyderKernel, self).do_complete(code, cursor_pos)

    def do_complete(self, code, cursor_pos):
        """
        Call PdB complete if we are debugging.

        Public method of ipykernel overwritten for debugging.
        """
        if self.is_debugging():
            return self._pdb_obj.do_complete(code, cursor_pos)
        return self._do_complete(code, cursor_pos)

    def publish_pdb_state(self):
        """
        Publish Variable Explorer state and Pdb step through
        send_spyder_msg.
        """
        if self._pdb_obj and self._do_publish_pdb_state:
            state = dict(namespace_view=self.get_namespace_view(),
                         var_properties=self.get_var_properties(),
                         step=self._pdb_step)
            self.frontend_call(blocking=False).pdb_state(state)
        self._do_publish_pdb_state = True

    def set_spyder_breakpoints(self, breakpoints):
        """
        Handle a message from the frontend
        """
        if self._pdb_obj:
            self._pdb_obj.set_spyder_breakpoints(breakpoints)

    def set_pdb_ignore_lib(self, state):
        """
        Change the "Ignore libraries while stepping" debugger setting.
        """
        if self._pdb_obj:
            self._pdb_obj.pdb_ignore_lib = state

    def set_pdb_execute_events(self, state):
        """
        Handle a message from the frontend
        """
        if self._pdb_obj:
            self._pdb_obj.pdb_execute_events = state

    def set_pdb_use_exclamation_mark(self, state):
        """
        Set an option on the current debugging session to decide wether
        the Pdb commands needs to be prefixed by '!'
        """
        if self._pdb_obj:
            self._pdb_obj.pdb_use_exclamation_mark = state

    def pdb_input_reply(self, line, echo_stack_entry=True):
        """Get a pdb command from the frontend."""
        if self._pdb_obj:
            self._pdb_obj._disable_next_stack_entry = not echo_stack_entry
        self._pdb_input_line = line
        if self.eventloop:
            # Interrupting the eventloop is only implemented when a message is
            # received on the shell channel, but this message is queued and
            # won't be processed because an `execute` message is being
            # processed. Therefore we process the message here (comm channel)
            # and request a dummy message to be sent on the shell channel to
            # stop the eventloop. This will call back `_interrupt_eventloop`.
            self.frontend_call().request_interrupt_eventloop()

    def cmd_input(self, prompt=''):
        """
        Special input function for commands.
        Runs the eventloop while debugging.
        """
        # Only works if the comm is open and this is a pdb prompt.
        if not self.frontend_comm.is_open() or not self._pdb_frame:
            return input(prompt)

        # Flush output before making the request.
        sys.stderr.flush()
        sys.stdout.flush()

        # Send the input request.
        self._pdb_input_line = None
        self.frontend_call().pdb_input(prompt)

        # Allow GUI event loop to update
        if PY3:
            is_main_thread = (threading.current_thread() is
                              threading.main_thread())
        else:
            is_main_thread = isinstance(threading.current_thread(),
                                        threading._MainThread)

        if is_main_thread and self.eventloop:
            while self._pdb_input_line is None:
                self.eventloop(self)
        else:
            self.frontend_comm.wait_until(
                lambda: self._pdb_input_line is not None)
        return self._pdb_input_line

    def _interrupt_eventloop(self):
        """Interrupts the eventloop."""
        # Receiving the request is enough to stop the eventloop.
        pass

    # --- For the Help plugin
    def is_defined(self, obj, force_import=False):
        """Return True if object is defined in current namespace"""
        from spyder_kernels.utils.dochelpers import isdefined

        ns = self._get_current_namespace(with_magics=True)
        return isdefined(obj, force_import=force_import, namespace=ns)

    def get_doc(self, objtxt):
        """Get object documentation dictionary"""
        try:
            import matplotlib
            matplotlib.rcParams['docstring.hardcopy'] = True
        except:
            pass
        from spyder_kernels.utils.dochelpers import getdoc

        obj, valid = self._eval(objtxt)
        if valid:
            return getdoc(obj)

    def get_source(self, objtxt):
        """Get object source"""
        from spyder_kernels.utils.dochelpers import getsource

        obj, valid = self._eval(objtxt)
        if valid:
            return getsource(obj)

    # --- Additional methods
    def set_cwd(self, dirname):
        """Set current working directory."""
        os.chdir(dirname)

    def get_cwd(self):
        """Get current working directory."""
        return os.getcwd()

    def get_syspath(self):
        """Return sys.path contents."""
        return sys.path[:]

    def get_env(self):
        """Get environment variables."""
        return os.environ.copy()

    def close_all_mpl_figures(self):
        """Close all Matplotlib figures."""
        try:
            import matplotlib.pyplot as plt
            plt.close('all')
            del plt
        except:
            pass

    def is_special_kernel_valid(self):
        """
        Check if optional dependencies are available for special consoles.
        """
        try:
            if os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True':
                import matplotlib
            elif os.environ.get('SPY_SYMPY_O') == 'True':
                import sympy
            elif os.environ.get('SPY_RUN_CYTHON') == 'True':
                import cython
        except Exception:
            # Use Exception instead of ImportError here because modules can
            # fail to be imported due to a lot of issues.
            if os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True':
                return u'matplotlib'
            elif os.environ.get('SPY_SYMPY_O') == 'True':
                return u'sympy'
            elif os.environ.get('SPY_RUN_CYTHON') == 'True':
                return u'cython'
        return None

    def update_syspath(self, path_dict, new_path_dict):
        """
        Update the PYTHONPATH of the kernel.

        `path_dict` and `new_path_dict` have the paths as keys and the state
        as values. The state is `True` for active and `False` for inactive.

        `path_dict` corresponds to the previous state of the PYTHONPATH.
        `new_path_dict` corresponds to the new state of the PYTHONPATH.
        """
        # Remove old paths
        for path in path_dict:
            while path in sys.path:
                sys.path.remove(path)

        # Add new paths
        # We do this in reverse order as we use `sys.path.insert(1, path)`.
        # This ensures the end result has the correct path order.
        for path, active in reversed(new_path_dict.items()):
            if active:
                sys.path.insert(1, path)

    # -- Private API ---------------------------------------------------
    # --- For the Variable Explorer
    def _get_current_namespace(self, with_magics=False):
        """
        Return current namespace

        This is globals() if not debugging, or a dictionary containing
        both locals() and globals() for current frame when debugging
        """
        ns = {}
        if self._running_namespace is None:
            ns.update(self._mglobals())
        else:
            running_globals, running_locals = self._running_namespace
            ns.update(running_globals)
            if running_locals is not None:
                ns.update(running_locals)

        if self._pdb_frame is not None:
            ns.update(self._pdb_locals)

        # Add magics to ns so we can show help about them on the Help
        # plugin
        if with_magics:
            line_magics = self.shell.magics_manager.magics['line']
            cell_magics = self.shell.magics_manager.magics['cell']
            ns.update(line_magics)
            ns.update(cell_magics)

        return ns

    def _get_reference_namespace(self, name):
        """
        Return namespace where reference name is defined

        It returns the globals() if reference has not yet been defined
        """
        glbs = self._mglobals()
        if self._pdb_frame is None:
            return glbs
        else:
            lcls = self._pdb_locals
            if name in lcls:
                return lcls
            else:
                return glbs

    def _mglobals(self):
        """Return current globals -- handles Pdb frames"""
        if self._pdb_frame is not None:
            return self._pdb_frame.f_globals
        else:
            return self.shell.user_ns

    def _get_len(self, var):
        """Return sequence length"""
        try:
            return len(var)
        except:
            return None

    def _is_array(self, var):
        """Return True if variable is a NumPy array"""
        try:
            import numpy
            return isinstance(var, numpy.ndarray)
        except:
            return False

    def _is_image(self, var):
        """Return True if variable is a PIL.Image image"""
        try:
            from PIL import Image
            return isinstance(var, Image.Image)
        except:
            return False

    def _is_data_frame(self, var):
        """Return True if variable is a DataFrame"""
        try:
            from pandas import DataFrame
            return isinstance(var, DataFrame)
        except:
            return False

    def _is_series(self, var):
        """Return True if variable is a Series"""
        try:
            from pandas import Series
            return isinstance(var, Series)
        except:
            return False

    def _get_array_shape(self, var):
        """Return array's shape"""
        try:
            if self._is_array(var):
                return var.shape
            else:
                return None
        except:
            return None

    def _get_array_ndim(self, var):
        """Return array's ndim"""
        try:
            if self._is_array(var):
                return var.ndim
            else:
                return None
        except:
            return None

    # --- For Pdb
    def _register_pdb_session(self, pdb_obj):
        """Register Pdb session to use it later"""
        self._pdb_obj = pdb_obj

    @property
    def _pdb_frame(self):
        """Return current Pdb frame if there is any"""
        if self._pdb_obj is not None and self._pdb_obj.curframe is not None:
            return self._pdb_obj.curframe

    @property
    def _pdb_locals(self):
        """
        Return current Pdb frame locals if available. Otherwise
        return an empty dictionary
        """
        if self._pdb_frame:
            return self._pdb_obj.curframe_locals
        else:
            return {}

    # --- For the Help plugin
    def _eval(self, text):
        """
        Evaluate text and return (obj, valid)
        where *obj* is the object represented by *text*
        and *valid* is True if object evaluation did not raise any exception
        """
        from spyder_kernels.py3compat import is_text_string

        assert is_text_string(text)
        ns = self._get_current_namespace(with_magics=True)
        try:
            return eval(text, ns), True
        except:
            return None, False

    # --- For Matplotlib
    def _set_mpl_backend(self, backend, pylab=False):
        """
        Set a backend for Matplotlib.

        backend: A parameter that can be passed to %matplotlib
                 (e.g. 'inline' or 'tk').
        """
        import traceback
        from IPython.core.getipython import get_ipython

        generic_error = ("\n" + "=" * 73 + "\n"
                         "NOTE: The following error appeared when setting "
                         "your Matplotlib backend!!\n" + "=" * 73 + "\n\n"
                         "{0}")

        magic = 'pylab' if pylab else 'matplotlib'

        error = None
        try:
            get_ipython().run_line_magic(magic, backend)
        except RuntimeError as err:
            # This catches errors generated by ipykernel when
            # trying to set a backend. See issue 5541
            if "GUI eventloops" in str(err):
                import matplotlib
                previous_backend = matplotlib.get_backend()
                if not backend in previous_backend.lower():
                    # Only inform about an error if the user selected backend
                    # and the one set by Matplotlib are different. Else this
                    # message is very confusing.
                    error = (
                        "\n"
                        "NOTE: Spyder *can't* set your selected Matplotlib "
                        "backend because there is a previous backend already "
                        "in use.\n\n"
                        "Your backend will be {0}".format(previous_backend))
                del matplotlib
            # This covers other RuntimeError's
            else:
                error = generic_error.format(traceback.format_exc())
        except Exception:
            error = generic_error.format(traceback.format_exc())

        self._mpl_backend_error = error

    def show_mpl_backend_errors(self):
        """Show Matplotlib backend errors after the prompt is ready."""
        if self._mpl_backend_error is not None:
            print(self._mpl_backend_error)  # spyder: test-skip

    def set_sympy_forecolor(self, background_color='dark'):
        """Set SymPy forecolor depending on console background."""
        if os.environ.get('SPY_SYMPY_O') == 'True':
            try:
                from sympy import init_printing
                from IPython.core.getipython import get_ipython
                if background_color == 'dark':
                    init_printing(forecolor='White', ip=get_ipython())
                elif background_color == 'light':
                    init_printing(forecolor='Black', ip=get_ipython())
            except Exception:
                pass

    # --- Others
    def _load_autoreload_magic(self):
        """Load %autoreload magic."""
        from IPython.core.getipython import get_ipython
        try:
            get_ipython().run_line_magic('reload_ext', 'autoreload')
            get_ipython().run_line_magic('autoreload', '2')
        except Exception:
            pass

    def _load_wurlitzer(self):
        """Load wurlitzer extension."""
        # Wurlitzer has no effect on Windows
        if not os.name == 'nt':
            from IPython.core.getipython import get_ipython
            # Enclose this in a try/except because if it fails the
            # console will be totally unusable.
            # Fixes spyder-ide/spyder#8668
            try:
                get_ipython().run_line_magic('reload_ext', 'wurlitzer')
            except Exception:
                pass
Beispiel #7
0
class SpyderKernel(IPythonKernel):
    """Spyder kernel for Jupyter."""

    shell_class = SpyderShell

    def __init__(self, *args, **kwargs):
        super(SpyderKernel, self).__init__(*args, **kwargs)

        self.comm_manager.get_comm = self._get_comm
        self.frontend_comm = FrontendComm(self)

        # All functions that can be called through the comm
        handlers = {
            'set_breakpoints': self.set_spyder_breakpoints,
            'set_pdb_ignore_lib': self.set_pdb_ignore_lib,
            'set_pdb_execute_events': self.set_pdb_execute_events,
            'set_pdb_use_exclamation_mark': self.set_pdb_use_exclamation_mark,
            'get_value': self.get_value,
            'load_data': self.load_data,
            'save_namespace': self.save_namespace,
            'is_defined': self.is_defined,
            'get_doc': self.get_doc,
            'get_source': self.get_source,
            'set_value': self.set_value,
            'remove_value': self.remove_value,
            'copy_value': self.copy_value,
            'set_cwd': self.set_cwd,
            'get_cwd': self.get_cwd,
            'get_syspath': self.get_syspath,
            'get_env': self.get_env,
            'close_all_mpl_figures': self.close_all_mpl_figures,
            'show_mpl_backend_errors': self.show_mpl_backend_errors,
            'get_namespace_view': self.get_namespace_view,
            'set_namespace_view_settings': self.set_namespace_view_settings,
            'get_var_properties': self.get_var_properties,
            'set_sympy_forecolor': self.set_sympy_forecolor,
            'update_syspath': self.update_syspath,
            'is_special_kernel_valid': self.is_special_kernel_valid,
            'get_matplotlib_backend': self.get_matplotlib_backend,
            'pdb_input_reply': self.pdb_input_reply,
            '_interrupt_eventloop': self._interrupt_eventloop,
            'enable_faulthandler': self.enable_faulthandler,
            "flush_std": self.flush_std,
        }
        for call_id in handlers:
            self.frontend_comm.register_call_handler(call_id,
                                                     handlers[call_id])

        self.namespace_view_settings = {}
        self._pdb_step = None
        self._mpl_backend_error = None
        self._running_namespace = None
        self._pdb_input_line = None
        self.faulthandler_handle = None

    # -- Public API -----------------------------------------------------------
    def do_shutdown(self, restart):
        """Disable faulthandler if enabled before proceeding."""
        self.disable_faulthandler()
        super(SpyderKernel, self).do_shutdown(restart)

    def frontend_call(self,
                      blocking=False,
                      broadcast=True,
                      timeout=None,
                      callback=None):
        """Call the frontend."""
        # If not broadcast, send only to the calling comm
        if broadcast:
            comm_id = None
        else:
            comm_id = self.frontend_comm.calling_comm_id

        return self.frontend_comm.remote_call(blocking=blocking,
                                              comm_id=comm_id,
                                              callback=callback,
                                              timeout=timeout)

    def flush_std(self):
        """Flush C standard streams."""
        sys.__stderr__.flush()
        sys.__stdout__.flush()

    def enable_faulthandler(self, fn):
        """
        Open a file to save the faulthandling and identifiers for
        internal threads.
        """
        if not PY3:
            # Not implemented
            return
        self.disable_faulthandler()
        f = open(fn, 'w')
        self.faulthandler_handle = f
        f.write("Main thread id:\n")
        f.write(hex(threading.main_thread().ident))
        f.write('\nSystem threads ids:\n')
        f.write(" ".join([
            hex(thread.ident) for thread in threading.enumerate()
            if thread is not threading.main_thread()
        ]))
        f.write('\n')
        faulthandler.enable(f)

    def disable_faulthandler(self):
        """
        Cancel the faulthandling, close the file handle and remove the file.
        """
        if not PY3:
            # Not implemented
            return
        if self.faulthandler_handle:
            faulthandler.disable()
            self.faulthandler_handle.close()
            self.faulthandler_handle = None

    # --- For the Variable Explorer
    def set_namespace_view_settings(self, settings):
        """Set namespace_view_settings."""
        self.namespace_view_settings = settings

    def get_namespace_view(self):
        """
        Return the namespace view

        This is a dictionary with the following structure

        {'a':
            {
                'type': 'str',
                'size': 1,
                'view': '1',
                'python_type': 'int',
                'numpy_type': 'Unknown'
            }
        }

        Here:
        * 'a' is the variable name.
        * 'type' and 'size' are self-evident.
        * 'view' is its value or its repr computed with
          `value_to_display`.
        * 'python_type' is its Python type computed with
          `get_type_string`.
        * 'numpy_type' is its Numpy type (if any) computed with
          `get_numpy_type_string`.
        """

        settings = self.namespace_view_settings
        if settings:
            ns = self._get_current_namespace()
            view = make_remote_view(ns, settings, EXCLUDED_NAMES)
            return view
        else:
            return None

    def get_var_properties(self):
        """
        Get some properties of the variables in the current
        namespace
        """
        settings = self.namespace_view_settings
        if settings:
            ns = self._get_current_namespace()
            data = get_remote_data(ns,
                                   settings,
                                   mode='editable',
                                   more_excluded_names=EXCLUDED_NAMES)

            properties = {}
            for name, value in list(data.items()):
                properties[name] = {
                    'is_list': isinstance(value, (tuple, list)),
                    'is_dict': isinstance(value, dict),
                    'is_set': isinstance(value, set),
                    'len': self._get_len(value),
                    'is_array': self._is_array(value),
                    'is_image': self._is_image(value),
                    'is_data_frame': self._is_data_frame(value),
                    'is_series': self._is_series(value),
                    'array_shape': self._get_array_shape(value),
                    'array_ndim': self._get_array_ndim(value)
                }

            return properties
        else:
            return None

    def get_value(self, name):
        """Get the value of a variable"""
        ns = self._get_current_namespace()
        return ns[name]

    def set_value(self, name, value):
        """Set the value of a variable"""
        ns = self._get_reference_namespace(name)
        ns[name] = value
        self.log.debug(ns)

    def remove_value(self, name):
        """Remove a variable"""
        ns = self._get_reference_namespace(name)
        ns.pop(name)

    def copy_value(self, orig_name, new_name):
        """Copy a variable"""
        ns = self._get_reference_namespace(orig_name)
        ns[new_name] = ns[orig_name]

    def load_data(self, filename, ext, overwrite=False):
        """
        Load data from filename.

        Use 'overwrite' to determine if conflicts between variable names need
        to be handle or not.

        For example, if a loaded variable is call 'var'
        and there is already a variable 'var' in the namespace, having
        'overwrite=True' will cause 'var' to be updated.
        In the other hand, with 'overwrite=False', a new variable will be
        created with a sufix starting with 000 i.e 'var000' (default behavior).
        """
        from spyder_kernels.utils.misc import fix_reference_name

        glbs = self.shell.user_ns
        load_func = iofunctions.load_funcs[ext]
        data, error_message = load_func(filename)

        if error_message:
            return error_message

        if not overwrite:
            # We convert to list since we mutate this dictionary
            for key in list(data.keys()):
                new_key = fix_reference_name(key, blacklist=list(glbs.keys()))
                if new_key != key:
                    data[new_key] = data.pop(key)

        try:
            glbs.update(data)
        except Exception as error:
            return str(error)

        return None

    def save_namespace(self, filename):
        """Save namespace into filename"""
        ns = self._get_current_namespace()
        settings = self.namespace_view_settings
        data = get_remote_data(ns,
                               settings,
                               mode='picklable',
                               more_excluded_names=EXCLUDED_NAMES).copy()
        return iofunctions.save(data, filename)

    # --- For Pdb
    def _do_complete(self, code, cursor_pos):
        """Call parent class do_complete"""
        return super(SpyderKernel, self).do_complete(code, cursor_pos)

    def do_complete(self, code, cursor_pos):
        """
        Call PdB complete if we are debugging.

        Public method of ipykernel overwritten for debugging.
        """
        if self.shell.is_debugging():
            return self.shell.pdb_session.do_complete(code, cursor_pos)
        return self._do_complete(code, cursor_pos)

    def publish_pdb_state(self):
        """Publish Pdb state."""
        if self.shell.pdb_session:
            state = dict(namespace_view=self.get_namespace_view(),
                         var_properties=self.get_var_properties(),
                         step=self._pdb_step)
            self.frontend_call(blocking=False).pdb_state(state)

    def set_spyder_breakpoints(self, breakpoints):
        """
        Handle a message from the frontend
        """
        if self.shell.pdb_session:
            self.shell.pdb_session.set_spyder_breakpoints(breakpoints)

    def set_pdb_ignore_lib(self, state):
        """
        Change the "Ignore libraries while stepping" debugger setting.
        """
        if self.shell.pdb_session:
            self.shell.pdb_session.pdb_ignore_lib = state

    def set_pdb_execute_events(self, state):
        """
        Handle a message from the frontend
        """
        if self.shell.pdb_session:
            self.shell.pdb_session.pdb_execute_events = state

    def set_pdb_use_exclamation_mark(self, state):
        """
        Set an option on the current debugging session to decide wether
        the Pdb commands needs to be prefixed by '!'
        """
        if self.shell.pdb_session:
            self.shell.pdb_session.pdb_use_exclamation_mark = state

    def pdb_input_reply(self, line, echo_stack_entry=True):
        """Get a pdb command from the frontend."""
        if self.shell.pdb_session:
            self.shell.pdb_session._disable_next_stack_entry = (
                not echo_stack_entry)
        self._pdb_input_line = line
        if self.eventloop:
            # Interrupting the eventloop is only implemented when a message is
            # received on the shell channel, but this message is queued and
            # won't be processed because an `execute` message is being
            # processed. Therefore we process the message here (comm channel)
            # and request a dummy message to be sent on the shell channel to
            # stop the eventloop. This will call back `_interrupt_eventloop`.
            self.frontend_call().request_interrupt_eventloop()

    def cmd_input(self, prompt=''):
        """
        Special input function for commands.
        Runs the eventloop while debugging.
        """
        # Only works if the comm is open and this is a pdb prompt.
        if not self.frontend_comm.is_open() or not self.shell.is_debugging():
            return input(prompt)

        # Flush output before making the request.
        sys.stderr.flush()
        sys.stdout.flush()

        # Send the input request.
        self._pdb_input_line = None
        self.frontend_call().pdb_input(prompt)

        # Allow GUI event loop to update
        if PY3:
            is_main_thread = (threading.current_thread() is
                              threading.main_thread())
        else:
            is_main_thread = isinstance(threading.current_thread(),
                                        threading._MainThread)

        # Get input by running eventloop
        if is_main_thread and self.eventloop:
            while self._pdb_input_line is None:
                eventloop = self.eventloop
                if eventloop:
                    eventloop(self)
                else:
                    break

        # Get input by blocking
        if self._pdb_input_line is None:
            self.frontend_comm.wait_until(
                lambda: self._pdb_input_line is not None)

        return self._pdb_input_line

    def _interrupt_eventloop(self):
        """Interrupts the eventloop."""
        # Receiving the request is enough to stop the eventloop.
        pass

    # --- For the Help plugin
    def is_defined(self, obj, force_import=False):
        """Return True if object is defined in current namespace"""
        from spyder_kernels.utils.dochelpers import isdefined

        ns = self._get_current_namespace(with_magics=True)
        return isdefined(obj, force_import=force_import, namespace=ns)

    def get_doc(self, objtxt):
        """Get object documentation dictionary"""
        try:
            import matplotlib
            matplotlib.rcParams['docstring.hardcopy'] = True
        except:
            pass
        from spyder_kernels.utils.dochelpers import getdoc

        obj, valid = self._eval(objtxt)
        if valid:
            return getdoc(obj)

    def get_source(self, objtxt):
        """Get object source"""
        from spyder_kernels.utils.dochelpers import getsource

        obj, valid = self._eval(objtxt)
        if valid:
            return getsource(obj)

    # -- For Matplolib
    def get_matplotlib_backend(self):
        """Get current matplotlib backend."""
        try:
            import matplotlib
            return MPL_BACKENDS_TO_SPYDER[matplotlib.get_backend()]
        except Exception:
            return None

    def set_matplotlib_backend(self, backend, pylab=False):
        """Set matplotlib backend given a Spyder backend option."""
        mpl_backend = MPL_BACKENDS_FROM_SPYDER[to_text_string(backend)]
        self._set_mpl_backend(mpl_backend, pylab=pylab)

    def set_mpl_inline_figure_format(self, figure_format):
        """Set the inline figure format to use with matplotlib."""
        mpl_figure_format = INLINE_FIGURE_FORMATS[figure_format]
        self._set_config_option('InlineBackend.figure_format',
                                mpl_figure_format)

    def set_mpl_inline_resolution(self, resolution):
        """Set inline figure resolution."""
        if LooseVersion(ipykernel.__version__) < LooseVersion('4.5'):
            option = 'savefig.dpi'
        else:
            option = 'figure.dpi'
        self._set_mpl_inline_rc_config(option, resolution)

    def set_mpl_inline_figure_size(self, width, height):
        """Set inline figure size."""
        value = (width, height)
        self._set_mpl_inline_rc_config('figure.figsize', value)

    def set_mpl_inline_bbox_inches(self, bbox_inches):
        """
        Set inline print figure bbox inches.

        The change is done by updating the 'print_figure_kwargs' config dict.
        """
        from IPython.core.getipython import get_ipython
        config = get_ipython().kernel.config
        inline_config = (config['InlineBackend']
                         if 'InlineBackend' in config else {})
        print_figure_kwargs = (inline_config['print_figure_kwargs'] if
                               'print_figure_kwargs' in inline_config else {})
        bbox_inches_dict = {'bbox_inches': 'tight' if bbox_inches else None}
        print_figure_kwargs.update(bbox_inches_dict)

        # This seems to be necessary for newer versions of Traitlets because
        # print_figure_kwargs doesn't return a dict.
        if isinstance(print_figure_kwargs, LazyConfigValue):
            figure_kwargs_dict = print_figure_kwargs.to_dict().get('update')
            if figure_kwargs_dict:
                print_figure_kwargs = figure_kwargs_dict

        self._set_config_option('InlineBackend.print_figure_kwargs',
                                print_figure_kwargs)

    # -- For completions
    def set_jedi_completer(self, use_jedi):
        """Enable/Disable jedi as the completer for the kernel."""
        self._set_config_option('IPCompleter.use_jedi', use_jedi)

    def set_greedy_completer(self, use_greedy):
        """Enable/Disable greedy completer for the kernel."""
        self._set_config_option('IPCompleter.greedy', use_greedy)

    def set_autocall(self, autocall):
        """Enable/Disable autocall funtionality."""
        self._set_config_option('ZMQInteractiveShell.autocall', autocall)

    # --- Additional methods
    def set_cwd(self, dirname):
        """Set current working directory."""
        os.chdir(dirname)

    def get_cwd(self):
        """Get current working directory."""
        try:
            return os.getcwd()
        except (IOError, OSError):
            pass

    def get_syspath(self):
        """Return sys.path contents."""
        return sys.path[:]

    def get_env(self):
        """Get environment variables."""
        return os.environ.copy()

    def close_all_mpl_figures(self):
        """Close all Matplotlib figures."""
        try:
            import matplotlib.pyplot as plt
            plt.close('all')
            del plt
        except:
            pass

    def is_special_kernel_valid(self):
        """
        Check if optional dependencies are available for special consoles.
        """
        try:
            if os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True':
                import matplotlib
            elif os.environ.get('SPY_SYMPY_O') == 'True':
                import sympy
            elif os.environ.get('SPY_RUN_CYTHON') == 'True':
                import cython
        except Exception:
            # Use Exception instead of ImportError here because modules can
            # fail to be imported due to a lot of issues.
            if os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True':
                return u'matplotlib'
            elif os.environ.get('SPY_SYMPY_O') == 'True':
                return u'sympy'
            elif os.environ.get('SPY_RUN_CYTHON') == 'True':
                return u'cython'
        return None

    def update_syspath(self, path_dict, new_path_dict):
        """
        Update the PYTHONPATH of the kernel.

        `path_dict` and `new_path_dict` have the paths as keys and the state
        as values. The state is `True` for active and `False` for inactive.

        `path_dict` corresponds to the previous state of the PYTHONPATH.
        `new_path_dict` corresponds to the new state of the PYTHONPATH.
        """
        # Remove old paths
        for path in path_dict:
            while path in sys.path:
                sys.path.remove(path)

        # Add new paths
        # We do this in reverse order as we use `sys.path.insert(1, path)`.
        # This ensures the end result has the correct path order.
        for path, active in reversed(new_path_dict.items()):
            if active:
                sys.path.insert(1, path)

    # -- Private API ---------------------------------------------------
    # --- For the Variable Explorer
    def _get_current_namespace(self, with_magics=False):
        """
        Return current namespace

        This is globals() if not debugging, or a dictionary containing
        both locals() and globals() for current frame when debugging
        """
        ns = {}
        if self._running_namespace is None:
            ns.update(self.shell.user_ns)
        else:
            # This is true when a file is executing.
            running_globals, running_locals = self._running_namespace
            ns.update(running_globals)
            if running_locals is not None:
                ns.update(running_locals)

        # Add debugging locals
        ns.update(self.shell._pdb_locals)
        # Add magics to ns so we can show help about them on the Help
        # plugin
        if with_magics:
            line_magics = self.shell.magics_manager.magics['line']
            cell_magics = self.shell.magics_manager.magics['cell']
            ns.update(line_magics)
            ns.update(cell_magics)
        return ns

    def _get_reference_namespace(self, name):
        """
        Return namespace where reference name is defined

        It returns the globals() if reference has not yet been defined
        """
        lcls = self.shell._pdb_locals
        if name in lcls:
            return lcls
        return self.shell.user_ns

    def _get_len(self, var):
        """Return sequence length"""
        try:
            return len(var)
        except:
            return None

    def _is_array(self, var):
        """Return True if variable is a NumPy array"""
        try:
            import numpy
            return isinstance(var, numpy.ndarray)
        except:
            return False

    def _is_image(self, var):
        """Return True if variable is a PIL.Image image"""
        try:
            from PIL import Image
            return isinstance(var, Image.Image)
        except:
            return False

    def _is_data_frame(self, var):
        """Return True if variable is a DataFrame"""
        try:
            from pandas import DataFrame
            return isinstance(var, DataFrame)
        except:
            return False

    def _is_series(self, var):
        """Return True if variable is a Series"""
        try:
            from pandas import Series
            return isinstance(var, Series)
        except:
            return False

    def _get_array_shape(self, var):
        """Return array's shape"""
        try:
            if self._is_array(var):
                return var.shape
            else:
                return None
        except:
            return None

    def _get_array_ndim(self, var):
        """Return array's ndim"""
        try:
            if self._is_array(var):
                return var.ndim
            else:
                return None
        except:
            return None

    # --- For the Help plugin
    def _eval(self, text):
        """
        Evaluate text and return (obj, valid)
        where *obj* is the object represented by *text*
        and *valid* is True if object evaluation did not raise any exception
        """
        from spyder_kernels.py3compat import is_text_string

        assert is_text_string(text)
        ns = self._get_current_namespace(with_magics=True)
        try:
            return eval(text, ns), True
        except:
            return None, False

    # --- For Matplotlib
    def _set_mpl_backend(self, backend, pylab=False):
        """
        Set a backend for Matplotlib.

        backend: A parameter that can be passed to %matplotlib
                 (e.g. 'inline' or 'tk').
        pylab: Is the pylab magic should be used in order to populate the
               namespace from numpy and matplotlib
        """
        import traceback
        from IPython.core.getipython import get_ipython
        import matplotlib

        generic_error = ("\n" + "=" * 73 + "\n"
                         "NOTE: The following error appeared when setting "
                         "your Matplotlib backend!!\n" + "=" * 73 + "\n\n"
                         "{0}")

        magic = 'pylab' if pylab else 'matplotlib'

        error = None
        try:
            # This prevents Matplotlib to automatically set the backend, which
            # overrides our own mechanism.
            matplotlib.rcParams['backend'] = 'Agg'

            # Set the backend
            get_ipython().run_line_magic(magic, backend)
        except RuntimeError as err:
            # This catches errors generated by ipykernel when
            # trying to set a backend. See issue 5541
            if "GUI eventloops" in str(err):
                previous_backend = matplotlib.get_backend()
                if not backend in previous_backend.lower():
                    # Only inform about an error if the user selected backend
                    # and the one set by Matplotlib are different. Else this
                    # message is very confusing.
                    error = (
                        "\n"
                        "NOTE: Spyder *can't* set your selected Matplotlib "
                        "backend because there is a previous backend already "
                        "in use.\n\n"
                        "Your backend will be {0}".format(previous_backend))
            # This covers other RuntimeError's
            else:
                error = generic_error.format(traceback.format_exc())
        except ImportError as err:
            additional_info = (
                "This is most likely caused by missing packages in the Python "
                "environment\n"
                "or installation whose interpreter is located at:\n\n"
                "    {0}").format(sys.executable)

            error = generic_error.format(err) + '\n\n' + additional_info
        except Exception:
            error = generic_error.format(traceback.format_exc())

        self._mpl_backend_error = error

    def _set_config_option(self, option, value):
        """
        Set config options using the %config magic.

        As parameters:
            option: config option, for example 'InlineBackend.figure_format'.
            value: value of the option, for example 'SVG', 'Retina', etc.
        """
        from IPython.core.getipython import get_ipython
        try:
            base_config = "{option} = "
            value_line = ("'{value}'"
                          if isinstance(value, TEXT_TYPES) else "{value}")
            config_line = base_config + value_line
            get_ipython().run_line_magic(
                'config', config_line.format(option=option, value=value))
        except Exception:
            pass

    def _set_mpl_inline_rc_config(self, option, value):
        """
        Update any of the Matplolib rcParams given an option and value.
        """
        try:
            from matplotlib import rcParams
            rcParams[option] = value
        except Exception:
            # Needed in case matplolib isn't installed
            pass

    def show_mpl_backend_errors(self):
        """Show Matplotlib backend errors after the prompt is ready."""
        if self._mpl_backend_error is not None:
            print(self._mpl_backend_error)  # spyder: test-skip

    def set_sympy_forecolor(self, background_color='dark'):
        """Set SymPy forecolor depending on console background."""
        if os.environ.get('SPY_SYMPY_O') == 'True':
            try:
                from sympy import init_printing
                from IPython.core.getipython import get_ipython
                if background_color == 'dark':
                    init_printing(forecolor='White', ip=get_ipython())
                elif background_color == 'light':
                    init_printing(forecolor='Black', ip=get_ipython())
            except Exception:
                pass

    # --- Others
    def _load_autoreload_magic(self):
        """Load %autoreload magic."""
        from IPython.core.getipython import get_ipython
        try:
            get_ipython().run_line_magic('reload_ext', 'autoreload')
            get_ipython().run_line_magic('autoreload', '2')
        except Exception:
            pass

    def _load_wurlitzer(self):
        """Load wurlitzer extension."""
        # Wurlitzer has no effect on Windows
        if not os.name == 'nt':
            from IPython.core.getipython import get_ipython
            # Enclose this in a try/except because if it fails the
            # console will be totally unusable.
            # Fixes spyder-ide/spyder#8668
            try:
                get_ipython().run_line_magic('reload_ext', 'wurlitzer')
            except Exception:
                pass

    def _get_comm(self, comm_id):
        """
        We need to redefine this method from ipykernel.comm_manager to
        avoid showing a warning when the comm corresponding to comm_id
        is not present.

        Fixes spyder-ide/spyder#15498
        """
        try:
            return self.comm_manager.comms[comm_id]
        except KeyError:
            pass
Beispiel #8
0
class SpyderKernel(IPythonKernel):
    """Spyder kernel for Jupyter"""
    def __init__(self, *args, **kwargs):
        super(SpyderKernel, self).__init__(*args, **kwargs)

        self.frontend_comm = FrontendComm(self)

        # All functions that can be called through the comm
        handlers = {
            'set_breakpoints': self.set_spyder_breakpoints,
            'get_value': self.get_value,
            'load_data': self.load_data,
            'save_namespace': self.save_namespace,
            'is_defined': self.is_defined,
            'get_doc': self.get_doc,
            'get_source': self.get_source,
            'set_value': self.set_value,
            'remove_value': self.remove_value,
            'copy_value': self.copy_value,
            'set_cwd': self.set_cwd,
            'get_cwd': self.get_cwd,
            'get_syspath': self.get_syspath,
            'get_env': self.get_env,
            'close_all_mpl_figures': self.close_all_mpl_figures,
            'show_mpl_backend_errors': self.show_mpl_backend_errors,
            'get_namespace_view': self.get_namespace_view,
            'set_namespace_view_settings': self.set_namespace_view_settings,
            'get_var_properties': self.get_var_properties,
            'set_sympy_forecolor': self.set_sympy_forecolor,
            'set_pdb_echo_code': self.set_pdb_echo_code,
        }
        for call_id in handlers:
            self.frontend_comm.register_call_handler(call_id,
                                                     handlers[call_id])

        self.namespace_view_settings = {}

        self._pdb_obj = None
        self._pdb_step = None
        self._pdb_print_code = True
        self._do_publish_pdb_state = True
        self._mpl_backend_error = None

    def frontend_call(self, blocking=False, broadcast=True):
        """Call the frontend."""
        # If not broadcast, send only to the calling comm
        if broadcast:
            comm_id = None
        else:
            comm_id = self.frontend_comm.calling_comm_id

        return self.frontend_comm.remote_call(blocking=blocking,
                                              comm_id=comm_id)

    @property
    def _pdb_frame(self):
        """Return current Pdb frame if there is any"""
        if self._pdb_obj is not None and self._pdb_obj.curframe is not None:
            return self._pdb_obj.curframe

    @property
    def _pdb_locals(self):
        """
        Return current Pdb frame locals if available. Otherwise
        return an empty dictionary
        """
        if self._pdb_frame:
            return self._pdb_obj.curframe_locals
        else:
            return {}

    def set_spyder_breakpoints(self, breakpoints):
        """
        Handle a message from the frontend
        """
        if self._pdb_obj:
            self._pdb_obj.set_spyder_breakpoints(breakpoints)

    def set_pdb_echo_code(self, state):
        """Set if pdb should echo the code.

        This might change for each pdb statment and is therefore not included
        in pdb settings.
        """
        self._pdb_print_code = state

    # -- Public API ---------------------------------------------------
    # --- For the Variable Explorer
    def set_namespace_view_settings(self, settings):
        """Set namespace_view_settings."""
        self.namespace_view_settings = settings

    def get_namespace_view(self):
        """
        Return the namespace view

        This is a dictionary with the following structure

        {'a': {'color': '#800000', 'size': 1, 'type': 'str', 'view': '1'}}

        Here:
        * 'a' is the variable name
        * 'color' is the color used to show it
        * 'size' and 'type' are self-evident
        * and'view' is its value or the text shown in the last column
        """
        from spyder_kernels.utils.nsview import make_remote_view

        settings = self.namespace_view_settings
        if settings:
            ns = self._get_current_namespace()
            view = make_remote_view(ns, settings, EXCLUDED_NAMES)
            return view
        else:
            return None

    def get_var_properties(self):
        """
        Get some properties of the variables in the current
        namespace
        """
        from spyder_kernels.utils.nsview import get_remote_data

        settings = self.namespace_view_settings
        if settings:
            ns = self._get_current_namespace()
            data = get_remote_data(ns,
                                   settings,
                                   mode='editable',
                                   more_excluded_names=EXCLUDED_NAMES)

            properties = {}
            for name, value in list(data.items()):
                properties[name] = {
                    'is_list': isinstance(value, (tuple, list)),
                    'is_dict': isinstance(value, dict),
                    'is_set': isinstance(value, set),
                    'len': self._get_len(value),
                    'is_array': self._is_array(value),
                    'is_image': self._is_image(value),
                    'is_data_frame': self._is_data_frame(value),
                    'is_series': self._is_series(value),
                    'array_shape': self._get_array_shape(value),
                    'array_ndim': self._get_array_ndim(value)
                }

            return properties
        else:
            return None

    def get_value(self, name):
        """Get the value of a variable"""
        ns = self._get_current_namespace()
        self._do_publish_pdb_state = False
        return ns[name]

    def set_value(self, name, value):
        """Set the value of a variable"""
        ns = self._get_reference_namespace(name)
        ns[name] = value
        self.log.debug(ns)

    def remove_value(self, name):
        """Remove a variable"""
        ns = self._get_reference_namespace(name)
        ns.pop(name)

    def copy_value(self, orig_name, new_name):
        """Copy a variable"""
        ns = self._get_reference_namespace(orig_name)
        ns[new_name] = ns[orig_name]

    def load_data(self, filename, ext):
        """Load data from filename"""
        from spyder_kernels.utils.iofuncs import iofunctions
        from spyder_kernels.utils.misc import fix_reference_name

        glbs = self._mglobals()

        load_func = iofunctions.load_funcs[ext]
        data, error_message = load_func(filename)

        if error_message:
            return error_message

        for key in list(data.keys()):
            new_key = fix_reference_name(key, blacklist=list(glbs.keys()))
            if new_key != key:
                data[new_key] = data.pop(key)

        try:
            glbs.update(data)
        except Exception as error:
            return str(error)

        return None

    def save_namespace(self, filename):
        """Save namespace into filename"""
        from spyder_kernels.utils.nsview import get_remote_data
        from spyder_kernels.utils.iofuncs import iofunctions

        ns = self._get_current_namespace()
        settings = self.namespace_view_settings
        data = get_remote_data(ns,
                               settings,
                               mode='picklable',
                               more_excluded_names=EXCLUDED_NAMES).copy()
        return iofunctions.save(data, filename)

    # --- For Pdb
    def is_debugging(self):
        """
        Check if we are currently debugging.
        """
        return bool(self._pdb_frame)

    def do_complete(self, code, cursor_pos):
        """
        Call PdB complete if we are debugging.

        Public method of ipykernel overwritten for debugging.
        """
        if cursor_pos is None:
            cursor_pos = len(code)
        if self.is_debugging():
            # Get text to complete
            text = code[:cursor_pos].split(' ')[-1]
            # Choose pdb function to complete, based on cmd.py
            origline = code
            line = origline.lstrip()
            if not line:
                return
            stripped = len(origline) - len(line)
            begidx = cursor_pos - len(text) - stripped
            endidx = cursor_pos - stripped
            if begidx > 0:
                cmd, args, _ = self._pdb_obj.parseline(line)
                if cmd == '':
                    compfunc = self._pdb_obj.completedefault
                else:
                    try:
                        compfunc = getattr(self._pdb_obj, 'complete_' + cmd)
                    except AttributeError:
                        compfunc = self._pdb_obj.completedefault
            elif line[0] != '!':
                compfunc = self._pdb_obj.completenames
            else:
                compfunc = self._pdb_obj.completedefault

            def is_name_or_composed(text):
                if not text or text[0] == '.':
                    return False
                # We want to keep value.subvalue
                return isidentifier(text.replace('.', ''))

            while text and not is_name_or_composed(text):
                text = text[1:]
                begidx += 1

            matches = compfunc(text, line, begidx, endidx)

            return {
                'matches': matches,
                'cursor_end': cursor_pos,
                'cursor_start': cursor_pos - len(text),
                'metadata': {},
                'status': 'ok'
            }
        return super(SpyderKernel, self).do_complete(code, cursor_pos)

    def publish_pdb_state(self):
        """
        Publish Variable Explorer state and Pdb step through
        send_spyder_msg.
        """
        if self._pdb_obj and self._do_publish_pdb_state:
            state = dict(namespace_view=self.get_namespace_view(),
                         var_properties=self.get_var_properties(),
                         step=self._pdb_step)
            self.frontend_call(blocking=False).pdb_state(state)
        self._do_publish_pdb_state = True

    def pdb_continue(self):
        """
        Tell the console to run 'continue' after entering a
        Pdb session to get to the first breakpoint.

        Fixes issue 2034
        """
        if self._pdb_obj:
            self.frontend_call(blocking=False).pdb_continue()

    # --- For the Help plugin
    def is_defined(self, obj, force_import=False):
        """Return True if object is defined in current namespace"""
        from spyder_kernels.utils.dochelpers import isdefined

        ns = self._get_current_namespace(with_magics=True)
        return isdefined(obj, force_import=force_import, namespace=ns)

    def get_doc(self, objtxt):
        """Get object documentation dictionary"""
        try:
            import matplotlib
            matplotlib.rcParams['docstring.hardcopy'] = True
        except:
            pass
        from spyder_kernels.utils.dochelpers import getdoc

        obj, valid = self._eval(objtxt)
        if valid:
            return getdoc(obj)

    def get_source(self, objtxt):
        """Get object source"""
        from spyder_kernels.utils.dochelpers import getsource

        obj, valid = self._eval(objtxt)
        if valid:
            return getsource(obj)

    # --- Additional methods
    def set_cwd(self, dirname):
        """Set current working directory."""
        os.chdir(dirname)

    def get_cwd(self):
        """Get current working directory."""
        return os.getcwd()

    def get_syspath(self):
        """Return sys.path contents."""
        return sys.path[:]

    def get_env(self):
        """Get environment variables."""
        return os.environ.copy()

    def close_all_mpl_figures(self):
        """Close all Matplotlib figures."""
        try:
            import matplotlib.pyplot as plt
            plt.close('all')
            del plt
        except:
            pass

    # -- Private API ---------------------------------------------------
    # --- For the Variable Explorer
    def _get_current_namespace(self, with_magics=False):
        """
        Return current namespace

        This is globals() if not debugging, or a dictionary containing
        both locals() and globals() for current frame when debugging
        """
        ns = {}
        glbs = self._mglobals()

        if self._pdb_frame is None:
            ns.update(glbs)
        else:
            ns.update(glbs)
            ns.update(self._pdb_locals)

        # Add magics to ns so we can show help about them on the Help
        # plugin
        if with_magics:
            line_magics = self.shell.magics_manager.magics['line']
            cell_magics = self.shell.magics_manager.magics['cell']
            ns.update(line_magics)
            ns.update(cell_magics)

        return ns

    def _get_reference_namespace(self, name):
        """
        Return namespace where reference name is defined

        It returns the globals() if reference has not yet been defined
        """
        glbs = self._mglobals()
        if self._pdb_frame is None:
            return glbs
        else:
            lcls = self._pdb_locals
            if name in lcls:
                return lcls
            else:
                return glbs

    def _mglobals(self):
        """Return current globals -- handles Pdb frames"""
        if self._pdb_frame is not None:
            return self._pdb_frame.f_globals
        else:
            return self.shell.user_ns

    def _get_len(self, var):
        """Return sequence length"""
        try:
            return len(var)
        except:
            return None

    def _is_array(self, var):
        """Return True if variable is a NumPy array"""
        try:
            import numpy
            return isinstance(var, numpy.ndarray)
        except:
            return False

    def _is_image(self, var):
        """Return True if variable is a PIL.Image image"""
        try:
            from PIL import Image
            return isinstance(var, Image.Image)
        except:
            return False

    def _is_data_frame(self, var):
        """Return True if variable is a DataFrame"""
        try:
            from pandas import DataFrame
            return isinstance(var, DataFrame)
        except:
            return False

    def _is_series(self, var):
        """Return True if variable is a Series"""
        try:
            from pandas import Series
            return isinstance(var, Series)
        except:
            return False

    def _get_array_shape(self, var):
        """Return array's shape"""
        try:
            if self._is_array(var):
                return var.shape
            else:
                return None
        except:
            return None

    def _get_array_ndim(self, var):
        """Return array's ndim"""
        try:
            if self._is_array(var):
                return var.ndim
            else:
                return None
        except:
            return None

    # --- For Pdb
    def _register_pdb_session(self, pdb_obj):
        """Register Pdb session to use it later"""
        self._pdb_obj = pdb_obj

    # --- For the Help plugin
    def _eval(self, text):
        """
        Evaluate text and return (obj, valid)
        where *obj* is the object represented by *text*
        and *valid* is True if object evaluation did not raise any exception
        """
        from spyder_kernels.py3compat import is_text_string

        assert is_text_string(text)
        ns = self._get_current_namespace(with_magics=True)
        try:
            return eval(text, ns), True
        except:
            return None, False

    # --- For Matplotlib
    def _set_mpl_backend(self, backend, pylab=False):
        """
        Set a backend for Matplotlib.

        backend: A parameter that can be passed to %matplotlib
                 (e.g. 'inline' or 'tk').
        """
        import traceback
        from IPython.core.getipython import get_ipython

        generic_error = ("\n" + "=" * 73 + "\n"
                         "NOTE: The following error appeared when setting "
                         "your Matplotlib backend!!\n" + "=" * 73 + "\n\n"
                         "{0}")

        magic = 'pylab' if pylab else 'matplotlib'

        error = None
        try:
            get_ipython().run_line_magic(magic, backend)
        except RuntimeError as err:
            # This catches errors generated by ipykernel when
            # trying to set a backend. See issue 5541
            if "GUI eventloops" in str(err):
                import matplotlib
                previous_backend = matplotlib.get_backend()
                if not backend in previous_backend.lower():
                    # Only inform about an error if the user selected backend
                    # and the one set by Matplotlib are different. Else this
                    # message is very confusing.
                    error = (
                        "\n"
                        "NOTE: Spyder *can't* set your selected Matplotlib "
                        "backend because there is a previous backend already "
                        "in use.\n\n"
                        "Your backend will be {0}".format(previous_backend))
                del matplotlib
            # This covers other RuntimeError's
            else:
                error = generic_error.format(traceback.format_exc())
        except Exception:
            error = generic_error.format(traceback.format_exc())

        self._mpl_backend_error = error

    def show_mpl_backend_errors(self):
        """Show Matplotlib backend errors after the prompt is ready."""
        if self._mpl_backend_error is not None:
            print(self._mpl_backend_error)  # spyder: test-skip

    def set_sympy_forecolor(self, background_color='dark'):
        """Set SymPy forecolor depending on console background."""
        if os.environ.get('SPY_SYMPY_O') == 'True':
            try:
                from sympy import init_printing
                from IPython.core.getipython import get_ipython
                if background_color == 'dark':
                    init_printing(forecolor='White', ip=get_ipython())
                elif background_color == 'light':
                    init_printing(forecolor='Black', ip=get_ipython())
            except Exception:
                pass

    # --- Others
    def _load_autoreload_magic(self):
        """Load %autoreload magic."""
        from IPython.core.getipython import get_ipython
        try:
            get_ipython().run_line_magic('reload_ext', 'autoreload')
            get_ipython().run_line_magic('autoreload', '2')
        except Exception:
            pass

    def _load_wurlitzer(self):
        """Load wurlitzer extension."""
        # Wurlitzer has no effect on Windows
        if not os.name == 'nt':
            from IPython.core.getipython import get_ipython
            # Enclose this in a try/except because if it fails the
            # console will be totally unusable.
            # Fixes spyder-ide/spyder#8668
            try:
                get_ipython().run_line_magic('reload_ext', 'wurlitzer')
            except Exception:
                pass