From 7372e05629331811ee8a752d3403bffb002136c3 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 20:32:30 -0500 Subject: [PATCH 01/30] Replace variableexplorer.utils with utils.nsview from spyder_kernels --- spyder/config/base.py | 34 ------------------- spyder/plugins/variableexplorer.py | 14 +++++++- spyder/utils/ipython/spyder_kernel.py | 13 ++----- .../variableexplorer/collectionseditor.py | 14 ++++---- .../variableexplorer/namespacebrowser.py | 4 +-- .../widgets/variableexplorer/objecteditor.py | 6 ++-- 6 files changed, 28 insertions(+), 57 deletions(-) diff --git a/spyder/config/base.py b/spyder/config/base.py index e390217e986..dd6d849fc09 100644 --- a/spyder/config/base.py +++ b/spyder/config/base.py @@ -418,42 +418,8 @@ def translate_gettext(x): #============================================================================== # Namespace Browser (Variable Explorer) configuration management #============================================================================== -def get_supported_types(): - """ - Return a dictionnary containing types lists supported by the - namespace browser: - dict(picklable=picklable_types, editable=editables_types) - - See: - get_remote_data function in spyder/widgets/variableexplorer/utils/monitor.py - - Note: - If you update this list, don't forget to update doc/variablexplorer.rst - """ - from datetime import date, timedelta - editable_types = [int, float, complex, list, dict, tuple, date, timedelta - ] + list(TEXT_TYPES) + list(INT_TYPES) - try: - from numpy import ndarray, matrix, generic - editable_types += [ndarray, matrix, generic] - except: - pass - try: - from pandas import DataFrame, Series, DatetimeIndex - editable_types += [DataFrame, Series, DatetimeIndex] - except: - pass - picklable_types = editable_types[:] - try: - from spyder.pil_patch import Image - editable_types.append(Image.Image) - except: - pass - return dict(picklable=picklable_types, editable=editable_types) - # Variable explorer display / check all elements data types for sequences: # (when saving the variable explorer contents, check_all is True, -# see widgets/variableexplorer/namespacebrowser.py:NamespaceBrowser.save_data) CHECK_ALL = False #XXX: If True, this should take too much to compute... EXCLUDED_NAMES = ['nan', 'inf', 'infty', 'little_endian', 'colorbar_doc', diff --git a/spyder/plugins/variableexplorer.py b/spyder/plugins/variableexplorer.py index 87f46d987be..a55977bc704 100644 --- a/spyder/plugins/variableexplorer.py +++ b/spyder/plugins/variableexplorer.py @@ -9,14 +9,26 @@ # Third party imports from qtpy.QtCore import QTimer, Signal, Slot from qtpy.QtWidgets import QGroupBox, QStackedWidget, QVBoxLayout, QWidget +from spyder_kernels.utils.nsview import REMOTE_SETTINGS # Local imports +from spyder import dependencies from spyder.config.base import _ from spyder.plugins import SpyderPluginMixin from spyder.plugins.configdialog import PluginConfigPage from spyder.utils import icon_manager as ima from spyder.widgets.variableexplorer.namespacebrowser import NamespaceBrowser -from spyder.widgets.variableexplorer.utils import REMOTE_SETTINGS + + +PANDAS_REQVER = '>=0.13.1' +dependencies.add('pandas', _("View and edit DataFrames and Series in the " + "Variable Explorer"), + required_version=PANDAS_REQVER, optional=True) + +NUMPY_REQVER = '>=1.7' +dependencies.add("numpy", _("View and edit two and three dimensional arrays " + "in the Variable Explorer"), + required_version=NUMPY_REQVER, optional=True) class VariableExplorerConfigPage(PluginConfigPage): diff --git a/spyder/utils/ipython/spyder_kernel.py b/spyder/utils/ipython/spyder_kernel.py index 9665d65192f..6dc79c8080b 100644 --- a/spyder/utils/ipython/spyder_kernel.py +++ b/spyder/utils/ipython/spyder_kernel.py @@ -78,10 +78,7 @@ def get_namespace_view(self): * 'size' and 'type' are self-evident * and'view' is its value or the text shown in the last column """ - if not IS_EXT_INTERPRETER: - from spyder.widgets.variableexplorer.utils import make_remote_view - else: - from widgets.variableexplorer.utils import make_remote_view + from spyder_kernels.utils.nsview import make_remote_view settings = self.namespace_view_settings if settings: @@ -96,10 +93,7 @@ def get_var_properties(self): Get some properties of the variables in the current namespace """ - if not IS_EXT_INTERPRETER: - from spyder.widgets.variableexplorer.utils import get_remote_data - else: - from widgets.variableexplorer.utils import get_remote_data + from spyder_kernels.utils.nsview import get_remote_data settings = self.namespace_view_settings if settings: @@ -227,12 +221,11 @@ def load_data(self, filename, ext): def save_namespace(self, filename): """Save namespace into filename""" + from spyder_kernels.utils.nsview import get_remote_data if not IS_EXT_INTERPRETER: from spyder.utils.iofuncs import iofunctions - from spyder.widgets.variableexplorer.utils import get_remote_data else: from utils.iofuncs import iofunctions - from widgets.variableexplorer.utils import get_remote_data ns = self._get_current_namespace() settings = self.namespace_view_settings diff --git a/spyder/widgets/variableexplorer/collectionseditor.py b/spyder/widgets/variableexplorer/collectionseditor.py index f9f1776504b..e9ea776189d 100644 --- a/spyder/widgets/variableexplorer/collectionseditor.py +++ b/spyder/widgets/variableexplorer/collectionseditor.py @@ -35,6 +35,12 @@ QInputDialog, QItemDelegate, QLineEdit, QMenu, QMessageBox, QPushButton, QTableView, QVBoxLayout, QWidget) +from spyder_kernels.utils.nsview import ( + array, DataFrame, DatetimeIndex, display_to_value, FakeObject, + get_color_name, get_human_readable_type, get_size, Image, is_editable_type, + is_known_type, MaskedArray, ndarray, np_savetxt, Series, sort_against, + try_to_eval, unsorted_unique, value_to_display, get_object_attrs, + get_type_string) # Local imports from spyder.config.base import _ @@ -48,12 +54,6 @@ mimedata2url) from spyder.widgets.variableexplorer.importwizard import ImportWizard from spyder.widgets.variableexplorer.texteditor import TextEditor -from spyder.widgets.variableexplorer.utils import ( - array, DataFrame, DatetimeIndex, display_to_value, FakeObject, - get_color_name, get_human_readable_type, get_size, Image, is_editable_type, - is_known_type, MaskedArray, ndarray, np_savetxt, Series, sort_against, - try_to_eval, unsorted_unique, value_to_display, get_object_attrs, - get_type_string) if ndarray is not FakeObject: from spyder.widgets.variableexplorer.arrayeditor import ArrayEditor @@ -1587,7 +1587,7 @@ def remote_editor_test(): app = qapplication() from spyder.plugins.variableexplorer import VariableExplorer - from spyder.widgets.variableexplorer.utils import make_remote_view + from spyder_kernels.utils.nsview import make_remote_view remote = make_remote_view(get_test_data(), VariableExplorer(None).get_settings()) diff --git a/spyder/widgets/variableexplorer/namespacebrowser.py b/spyder/widgets/variableexplorer/namespacebrowser.py index 307e233e371..20c9306f758 100644 --- a/spyder/widgets/variableexplorer/namespacebrowser.py +++ b/spyder/widgets/variableexplorer/namespacebrowser.py @@ -22,9 +22,10 @@ # Third party imports (others) import cloudpickle +from spyder_kernels.utils.nsview import get_supported_types, REMOTE_SETTINGS # Local imports -from spyder.config.base import _, get_supported_types +from spyder.config.base import _ from spyder.config.main import CONF from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding @@ -37,7 +38,6 @@ from spyder.widgets.variableexplorer.collectionseditor import ( RemoteCollectionsEditorTableView) from spyder.widgets.variableexplorer.importwizard import ImportWizard -from spyder.widgets.variableexplorer.utils import REMOTE_SETTINGS SUPPORTED_TYPES = get_supported_types() diff --git a/spyder/widgets/variableexplorer/objecteditor.py b/spyder/widgets/variableexplorer/objecteditor.py index 8858b2bc59e..150a900536c 100644 --- a/spyder/widgets/variableexplorer/objecteditor.py +++ b/spyder/widgets/variableexplorer/objecteditor.py @@ -58,10 +58,10 @@ def create_dialog(obj, obj_name): oedit to show eMZed related data) """ # Local import + from spyder_kernels.utils.nsview import (ndarray, FakeObject, + Image, is_known_type, DataFrame, + Series) from spyder.widgets.variableexplorer.texteditor import TextEditor - from spyder.widgets.variableexplorer.utils import (ndarray, FakeObject, - Image, is_known_type, DataFrame, - Series) from spyder.widgets.variableexplorer.collectionseditor import CollectionsEditor from spyder.widgets.variableexplorer.arrayeditor import ArrayEditor if DataFrame is not FakeObject: From 106558bfe63b50b71b90b788fd3f81f73dec537f Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 20:34:10 -0500 Subject: [PATCH 02/30] Remove variableexplorer.utils and its tests --- .../variableexplorer/tests/test_utils.py | 244 ------- spyder/widgets/variableexplorer/utils.py | 654 ------------------ 2 files changed, 898 deletions(-) delete mode 100644 spyder/widgets/variableexplorer/tests/test_utils.py delete mode 100644 spyder/widgets/variableexplorer/utils.py diff --git a/spyder/widgets/variableexplorer/tests/test_utils.py b/spyder/widgets/variableexplorer/tests/test_utils.py deleted file mode 100644 index bfc6c9adc7f..00000000000 --- a/spyder/widgets/variableexplorer/tests/test_utils.py +++ /dev/null @@ -1,244 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License - -""" -Tests for utils.py -""" - -from collections import defaultdict -import datetime - -# Third party imports -import numpy as np -import pandas as pd -import pytest - -# Local imports -from spyder.config.base import get_supported_types -from spyder.py3compat import PY2 -from spyder.widgets.variableexplorer.utils import (sort_against, - is_supported, - value_to_display) - -def generate_complex_object(): - """Taken from issue #4221.""" - bug = defaultdict(list) - for i in range(50000): - a = {j:np.random.rand(10) for j in range(10)} - bug[i] = a - return bug - - -COMPLEX_OBJECT = generate_complex_object() -DF = pd.DataFrame([1,2,3]) -PANEL = pd.Panel({0: pd.DataFrame([1,2]), 1:pd.DataFrame([3,4])}) - - -# --- Tests -# ----------------------------------------------------------------------------- -def test_sort_against(): - lista = [5, 6, 7] - listb = [2, 3, 1] - res = sort_against(lista, listb) - assert res == [7, 5, 6] - - -def test_sort_against_is_stable(): - lista = [3, 0, 1] - listb = [1, 1, 1] - res = sort_against(lista, listb) - assert res == lista - - -def test_none_values_are_supported(): - """Tests that None values are displayed by default""" - supported_types = get_supported_types() - mode = 'editable' - none_var = None - none_list = [2, None, 3, None] - none_dict = {'a': None, 'b': 4} - none_tuple = (None, [3, None, 4], 'eggs') - assert is_supported(none_var, filters=tuple(supported_types[mode])) - assert is_supported(none_list, filters=tuple(supported_types[mode])) - assert is_supported(none_dict, filters=tuple(supported_types[mode])) - assert is_supported(none_tuple, filters=tuple(supported_types[mode])) - - -def test_str_subclass_display(): - """Test for value_to_display of subclasses of str/basestring.""" - class Test(str): - def __repr__(self): - return 'test' - value = Test() - value_display = value_to_display(value) - assert 'Test object' in value_display - - -def test_default_display(): - """Tests for default_display.""" - # Display of defaultdict - assert (value_to_display(COMPLEX_OBJECT) == - 'defaultdict object of collections module') - - # Display of array of COMPLEX_OBJECT - assert (value_to_display(np.array(COMPLEX_OBJECT)) == - 'ndarray object of numpy module') - - # Display of Panel - assert (value_to_display(PANEL) == - 'Panel object of pandas.core.panel module') - - -def test_list_display(): - """Tests for display of lists.""" - long_list = list(range(100)) - - # Simple list - assert value_to_display([1, 2, 3]) == '[1, 2, 3]' - - # Long list - assert (value_to_display(long_list) == - '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...]') - - # Short list of lists - assert (value_to_display([long_list] * 3) == - '[[0, 1, 2, 3, 4, ...], [0, 1, 2, 3, 4, ...], [0, 1, 2, 3, 4, ...]]') - - # Long list of lists - result = '[' + ''.join('[0, 1, 2, 3, 4, ...], '*10)[:-2] + ']' - assert value_to_display([long_list] * 10) == result[:70] + ' ...' - - # Multiple level lists - assert (value_to_display([[1, 2, 3, [4], 5]] + long_list) == - '[[1, 2, 3, [...], 5], 0, 1, 2, 3, 4, 5, 6, 7, 8, ...]') - assert value_to_display([1, 2, [DF]]) == '[1, 2, [Dataframe]]' - assert value_to_display([1, 2, [[DF], PANEL]]) == '[1, 2, [[...], Panel]]' - - # List of complex object - assert value_to_display([COMPLEX_OBJECT]) == '[defaultdict]' - - # List of composed objects - li = [COMPLEX_OBJECT, PANEL, 1, {1:2, 3:4}, DF] - result = '[defaultdict, Panel, 1, {1:2, 3:4}, Dataframe]' - assert value_to_display(li) == result - - # List starting with a non-supported object (#5313) - supported_types = tuple(get_supported_types()['editable']) - li = [len, 1] - assert value_to_display(li) == '[builtin_function_or_method, 1]' - assert is_supported(li, filters=supported_types) - - -def test_dict_display(): - """Tests for display of dicts.""" - long_list = list(range(100)) - long_dict = dict(zip(list(range(100)), list(range(100)))) - - # Simple dict - assert value_to_display({0:0, 'a':'b'}) == "{0:0, 'a':'b'}" - - # Long dict - assert (value_to_display(long_dict) == - '{0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, ...}') - - # Short list of lists - assert (value_to_display({1:long_dict, 2:long_dict}) == - '{1:{0:0, 1:1, 2:2, 3:3, 4:4, ...}, 2:{0:0, 1:1, 2:2, 3:3, 4:4, ...}}') - - # Long dict of dicts - result = ('{(0, 0, 0, 0, 0, ...):[0, 1, 2, 3, 4, ...], ' - '(1, 1, 1, 1, 1, ...):[0, 1, 2, 3, 4, ...]}') - assert value_to_display({(0,)*100:long_list, (1,)*100:long_list}) == result[:70] + ' ...' - - # Multiple level dicts - assert (value_to_display({0: {1:1, 2:2, 3:3, 4:{0:0}, 5:5}, 1:1}) == - '{0:{1:1, 2:2, 3:3, 4:{...}, 5:5}, 1:1}') - assert value_to_display({0:0, 1:1, 2:2, 3:DF}) == '{0:0, 1:1, 2:2, 3:Dataframe}' - assert value_to_display({0:0, 1:1, 2:[[DF], PANEL]}) == '{0:0, 1:1, 2:[[...], Panel]}' - - # Dict of complex object - assert value_to_display({0:COMPLEX_OBJECT}) == '{0:defaultdict}' - - # Dict of composed objects - li = {0:COMPLEX_OBJECT, 1:PANEL, 2:2, 3:{0:0, 1:1}, 4:DF} - result = '{0:defaultdict, 1:Panel, 2:2, 3:{0:0, 1:1}, 4:Dataframe}' - assert value_to_display(li) == result - - # Dict starting with a non-supported object (#5313) - supported_types = tuple(get_supported_types()['editable']) - di = {max: len, 1: 1} - assert value_to_display(di) in ( - '{builtin_function_or_method:builtin_function_or_method, 1:1}', - '{1:1, builtin_function_or_method:builtin_function_or_method}') - assert is_supported(di, filters=supported_types) - - -def test_datetime_display(): - """Simple tests that dates, datetimes and timedeltas display correctly.""" - test_date = datetime.date(2017, 12, 18) - test_date_2 = datetime.date(2017, 2, 2) - - test_datetime = datetime.datetime(2017, 12, 18, 13, 43, 2) - test_datetime_2 = datetime.datetime(2017, 8, 18, 0, 41, 27) - - test_timedelta = datetime.timedelta(-1, 2000) - test_timedelta_2 = datetime.timedelta(0, 3600) - - # Simple dates/datetimes/timedeltas - assert value_to_display(test_date) == '2017-12-18' - assert value_to_display(test_datetime) == '2017-12-18 13:43:02' - assert value_to_display(test_timedelta) == '-1 day, 0:33:20' - - # Lists of dates/datetimes/timedeltas - assert (value_to_display([test_date, test_date_2]) == - '[2017-12-18, 2017-02-02]') - assert (value_to_display([test_datetime, test_datetime_2]) == - '[2017-12-18 13:43:02, 2017-08-18 00:41:27]') - assert (value_to_display([test_timedelta, test_timedelta_2]) == - '[-1 day, 0:33:20, 1:00:00]') - - # Tuple of dates/datetimes/timedeltas - assert (value_to_display((test_date, test_datetime, test_timedelta)) == - '(2017-12-18, 2017-12-18 13:43:02, -1 day, 0:33:20)') - - # Dict of dates/datetimes/timedeltas - assert (value_to_display({0: test_date, - 1: test_datetime, - 2: test_timedelta_2}) == - ("{0:2017-12-18, 1:2017-12-18 13:43:02, 2:1:00:00}")) - - -def test_str_in_container_display(): - """Test that strings are displayed correctly inside lists or dicts.""" - # Assert that both bytes and unicode return the right display - assert value_to_display([b'a', u'b']) == "['a', 'b']" - - # Encoded unicode gives bytes and it can't be transformed to - # unicode again. So this test the except part of - # is_binary_string(value) in value_to_display - if PY2: - assert value_to_display([u'Э'.encode('cp1251')]) == "['\xdd']" - - -def test_ellipses(tmpdir): - """ - Test that we're adding a binary ellipses when value_to_display of - a collection is too long and binary. - - For issue 6942 - """ - # Create binary file with all bytes - file = tmpdir.new(basename='bytes.txt') - file.write_binary(bytearray(list(range(255)))) - - # Read bytes back - buffer = file.read(mode='rb') - - # Assert that there's a binary ellipses in the representation - assert b' ...' in value_to_display(buffer) - - -if __name__ == "__main__": - pytest.main() diff --git a/spyder/widgets/variableexplorer/utils.py b/spyder/widgets/variableexplorer/utils.py deleted file mode 100644 index 280225af92a..00000000000 --- a/spyder/widgets/variableexplorer/utils.py +++ /dev/null @@ -1,654 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -""" -Utilities -""" - -from __future__ import print_function - -from itertools import islice -import re - -# Local imports -from spyder.config.base import get_supported_types -from spyder.py3compat import (NUMERIC_TYPES, TEXT_TYPES, to_text_string, - is_text_string, is_type_text_string, - is_binary_string, PY2, - to_binary_string, iteritems) -from spyder.utils import programs -from spyder import dependencies -from spyder.config.base import _ - - -#============================================================================== -# Dependencies -#============================================================================== -PANDAS_REQVER = '>=0.13.1' -dependencies.add('pandas', _("View and edit DataFrames and Series in the " - "Variable Explorer"), - required_version=PANDAS_REQVER, optional=True) - -NUMPY_REQVER = '>=1.7' -dependencies.add("numpy", _("View and edit two and three dimensional arrays " - "in the Variable Explorer"), - required_version=NUMPY_REQVER, optional=True) - -#============================================================================== -# FakeObject -#============================================================================== -class FakeObject(object): - """Fake class used in replacement of missing modules""" - pass - - -#============================================================================== -# Numpy arrays and numeric types support -#============================================================================== -try: - from numpy import (ndarray, array, matrix, recarray, - int64, int32, int16, int8, uint64, uint32, uint16, uint8, - float64, float32, float16, complex64, complex128, bool_) - from numpy.ma import MaskedArray - from numpy import savetxt as np_savetxt - from numpy import get_printoptions, set_printoptions -except: - ndarray = array = matrix = recarray = MaskedArray = np_savetxt = \ - int64 = int32 = int16 = int8 = uint64 = uint32 = uint16 = uint8 = \ - float64 = float32 = float16 = complex64 = complex128 = bool_ = FakeObject - - -def get_numpy_dtype(obj): - """Return NumPy data type associated to obj - Return None if NumPy is not available - or if obj is not a NumPy array or scalar""" - if ndarray is not FakeObject: - # NumPy is available - import numpy as np - if isinstance(obj, np.generic) or isinstance(obj, np.ndarray): - # Numpy scalars all inherit from np.generic. - # Numpy arrays all inherit from np.ndarray. - # If we check that we are certain we have one of these - # types then we are less likely to generate an exception below. - try: - return obj.dtype.type - except (AttributeError, RuntimeError): - # AttributeError: some NumPy objects have no dtype attribute - # RuntimeError: happens with NetCDF objects (Issue 998) - return - - -#============================================================================== -# Pandas support -#============================================================================== -if programs.is_module_installed('pandas', PANDAS_REQVER): - try: - from pandas import DataFrame, DatetimeIndex, Series - except: - DataFrame = DatetimeIndex = Series = FakeObject -else: - DataFrame = DatetimeIndex = Series = FakeObject # analysis:ignore - - -#============================================================================== -# PIL Images support -#============================================================================== -try: - from spyder import pil_patch - Image = pil_patch.Image.Image -except: - Image = FakeObject # analysis:ignore - - -#============================================================================== -# BeautifulSoup support (see Issue 2448) -#============================================================================== -try: - import bs4 - NavigableString = bs4.element.NavigableString -except: - NavigableString = FakeObject # analysis:ignore - - -#============================================================================== -# Misc. -#============================================================================== -def address(obj): - """Return object address as a string: ''""" - return "<%s @ %s>" % (obj.__class__.__name__, - hex(id(obj)).upper().replace('X', 'x')) - - -def try_to_eval(value): - """Try to eval value""" - try: - return eval(value) - except (NameError, SyntaxError, ImportError): - return value - - -def get_size(item): - """Return size of an item of arbitrary type""" - if isinstance(item, (list, tuple, dict)): - return len(item) - elif isinstance(item, (ndarray, MaskedArray)): - return item.shape - elif isinstance(item, Image): - return item.size - if isinstance(item, (DataFrame, DatetimeIndex, Series)): - return item.shape - else: - return 1 - - -def get_object_attrs(obj): - """ - Get the attributes of an object using dir. - - This filters protected attributes - """ - attrs = [k for k in dir(obj) if not k.startswith('__')] - if not attrs: - attrs = dir(obj) - return attrs - - -#============================================================================== -# Date and datetime objects support -#============================================================================== -import datetime - - -try: - from dateutil.parser import parse as dateparse -except: - def dateparse(datestr): # analysis:ignore - """Just for 'year, month, day' strings""" - return datetime.datetime( *list(map(int, datestr.split(','))) ) - - -def datestr_to_datetime(value): - rp = value.rfind('(')+1 - v = dateparse(value[rp:-1]) - print(value, "-->", v) # spyder: test-skip - return v - - -def str_to_timedelta(value): - """Convert a string to a datetime.timedelta value. - - The following strings are accepted: - - - 'datetime.timedelta(1, 5, 12345)' - - 'timedelta(1, 5, 12345)' - - '(1, 5, 12345)' - - '1, 5, 12345' - - '1' - - if there are less then three parameters, the missing parameters are - assumed to be 0. Variations in the spacing of the parameters are allowed. - - Raises: - ValueError for strings not matching the above criterion. - - """ - m = re.match(r'^(?:(?:datetime\.)?timedelta)?' - r'\(?' - r'([^)]*)' - r'\)?$', value) - if not m: - raise ValueError('Invalid string for datetime.timedelta') - args = [int(a.strip()) for a in m.group(1).split(',')] - return datetime.timedelta(*args) - - -#============================================================================== -# Background colors for supported types -#============================================================================== -ARRAY_COLOR = "#00ff00" -SCALAR_COLOR = "#0000ff" -COLORS = { - bool: "#ff00ff", - NUMERIC_TYPES: SCALAR_COLOR, - list: "#ffff00", - dict: "#00ffff", - tuple: "#c0c0c0", - TEXT_TYPES: "#800000", - (ndarray, - MaskedArray, - matrix, - DataFrame, - Series, - DatetimeIndex): ARRAY_COLOR, - Image: "#008000", - datetime.date: "#808000", - datetime.timedelta: "#808000", - } -CUSTOM_TYPE_COLOR = "#7755aa" -UNSUPPORTED_COLOR = "#ffffff" - -def get_color_name(value): - """Return color name depending on value type""" - if not is_known_type(value): - return CUSTOM_TYPE_COLOR - for typ, name in list(COLORS.items()): - if isinstance(value, typ): - return name - else: - np_dtype = get_numpy_dtype(value) - if np_dtype is None or not hasattr(value, 'size'): - return UNSUPPORTED_COLOR - elif value.size == 1: - return SCALAR_COLOR - else: - return ARRAY_COLOR - - -def is_editable_type(value): - """Return True if data type is editable with a standard GUI-based editor, - like CollectionsEditor, ArrayEditor, QDateEdit or a simple QLineEdit""" - return get_color_name(value) not in (UNSUPPORTED_COLOR, CUSTOM_TYPE_COLOR) - - -#============================================================================== -# Sorting -#============================================================================== -def sort_against(list1, list2, reverse=False): - """ - Arrange items of list1 in the same order as sorted(list2). - - In other words, apply to list1 the permutation which takes list2 - to sorted(list2, reverse). - """ - try: - return [item for _, item in - sorted(zip(list2, list1), key=lambda x: x[0], reverse=reverse)] - except: - return list1 - - -def unsorted_unique(lista): - """Removes duplicates from lista neglecting its initial ordering""" - return list(set(lista)) - - -#============================================================================== -# Display <--> Value -#============================================================================== -def default_display(value, with_module=True): - """Default display for unknown objects.""" - object_type = type(value) - try: - name = object_type.__name__ - module = object_type.__module__ - if with_module: - return name + ' object of ' + module + ' module' - else: - return name - except: - type_str = to_text_string(object_type) - return type_str[1:-1] - - -def collections_display(value, level): - """Display for collections (i.e. list, tuple and dict).""" - is_dict = isinstance(value, dict) - - # Get elements - if is_dict: - elements = iteritems(value) - else: - elements = value - - # Truncate values - truncate = False - if level == 1 and len(value) > 10: - elements = islice(elements, 10) if is_dict else value[:10] - truncate = True - elif level == 2 and len(value) > 5: - elements = islice(elements, 5) if is_dict else value[:5] - truncate = True - - # Get display of each element - if level <= 2: - if is_dict: - displays = [value_to_display(k, level=level) + ':' + - value_to_display(v, level=level) - for (k, v) in list(elements)] - else: - displays = [value_to_display(e, level=level) - for e in elements] - if truncate: - displays.append('...') - display = ', '.join(displays) - else: - display = '...' - - # Return display - if is_dict: - display = '{' + display + '}' - elif isinstance(value, list): - display = '[' + display + ']' - else: - display = '(' + display + ')' - - return display - - -def value_to_display(value, minmax=False, level=0): - """Convert value for display purpose""" - # To save current Numpy threshold - np_threshold = FakeObject - - try: - numeric_numpy_types = (int64, int32, int16, int8, - uint64, uint32, uint16, uint8, - float64, float32, float16, - complex128, complex64, bool_) - if ndarray is not FakeObject: - # Save threshold - np_threshold = get_printoptions().get('threshold') - # Set max number of elements to show for Numpy arrays - # in our display - set_printoptions(threshold=10) - if isinstance(value, recarray): - if level == 0: - fields = value.names - display = 'Field names: ' + ', '.join(fields) - else: - display = 'Recarray' - elif isinstance(value, MaskedArray): - display = 'Masked array' - elif isinstance(value, ndarray): - if level == 0: - if minmax: - try: - display = 'Min: %r\nMax: %r' % (value.min(), value.max()) - except (TypeError, ValueError): - if value.dtype.type in numeric_numpy_types: - display = str(value) - else: - display = default_display(value) - elif value.dtype.type in numeric_numpy_types: - display = str(value) - else: - display = default_display(value) - else: - display = 'Numpy array' - elif any([type(value) == t for t in [list, tuple, dict]]): - display = collections_display(value, level+1) - elif isinstance(value, Image): - if level == 0: - display = '%s Mode: %s' % (address(value), value.mode) - else: - display = 'Image' - elif isinstance(value, DataFrame): - if level == 0: - cols = value.columns - if PY2 and len(cols) > 0: - # Get rid of possible BOM utf-8 data present at the - # beginning of a file, which gets attached to the first - # column header when headers are present in the first - # row. - # Fixes Issue 2514 - try: - ini_col = to_text_string(cols[0], encoding='utf-8-sig') - except: - ini_col = to_text_string(cols[0]) - cols = [ini_col] + [to_text_string(c) for c in cols[1:]] - else: - cols = [to_text_string(c) for c in cols] - display = 'Column names: ' + ', '.join(list(cols)) - else: - display = 'Dataframe' - elif isinstance(value, NavigableString): - # Fixes Issue 2448 - display = to_text_string(value) - if level > 0: - display = u"'" + display + u"'" - elif isinstance(value, DatetimeIndex): - if level == 0: - display = value.summary() - else: - display = 'DatetimeIndex' - elif is_binary_string(value): - # We don't apply this to classes that extend string types - # See issue 5636 - if is_type_text_string(value): - try: - display = to_text_string(value, 'utf8') - if level > 0: - display = u"'" + display + u"'" - except: - display = value - if level > 0: - display = b"'" + display + b"'" - else: - display = default_display(value) - elif is_text_string(value): - # We don't apply this to classes that extend string types - # See issue 5636 - if is_type_text_string(value): - display = value - if level > 0: - display = u"'" + display + u"'" - else: - display = default_display(value) - elif (isinstance(value, datetime.date) or - isinstance(value, datetime.timedelta)): - display = str(value) - elif (isinstance(value, NUMERIC_TYPES) or - isinstance(value, bool) or - isinstance(value, numeric_numpy_types)): - display = repr(value) - else: - if level == 0: - display = default_display(value) - else: - display = default_display(value, with_module=False) - except: - display = default_display(value) - - # Truncate display at 70 chars to avoid freezing Spyder - # because of large displays - if len(display) > 70: - if is_binary_string(display): - ellipses = b' ...' - else: - ellipses = u' ...' - display = display[:70].rstrip() + ellipses - - # Restore Numpy threshold - if np_threshold is not FakeObject: - set_printoptions(threshold=np_threshold) - - return display - - -def display_to_value(value, default_value, ignore_errors=True): - """Convert back to value""" - from qtpy.compat import from_qvariant - value = from_qvariant(value, to_text_string) - try: - np_dtype = get_numpy_dtype(default_value) - if isinstance(default_value, bool): - # We must test for boolean before NumPy data types - # because `bool` class derives from `int` class - try: - value = bool(float(value)) - except ValueError: - value = value.lower() == "true" - elif np_dtype is not None: - if 'complex' in str(type(default_value)): - value = np_dtype(complex(value)) - else: - value = np_dtype(value) - elif is_binary_string(default_value): - value = to_binary_string(value, 'utf8') - elif is_text_string(default_value): - value = to_text_string(value) - elif isinstance(default_value, complex): - value = complex(value) - elif isinstance(default_value, float): - value = float(value) - elif isinstance(default_value, int): - try: - value = int(value) - except ValueError: - value = float(value) - elif isinstance(default_value, datetime.datetime): - value = datestr_to_datetime(value) - elif isinstance(default_value, datetime.date): - value = datestr_to_datetime(value).date() - elif isinstance(default_value, datetime.timedelta): - value = str_to_timedelta(value) - elif ignore_errors: - value = try_to_eval(value) - else: - value = eval(value) - except (ValueError, SyntaxError): - if ignore_errors: - value = try_to_eval(value) - else: - return default_value - return value - - -# ============================================================================= -# Types -# ============================================================================= -def get_type_string(item): - """Return type string of an object.""" - if isinstance(item, DataFrame): - return "DataFrame" - if isinstance(item, DatetimeIndex): - return "DatetimeIndex" - if isinstance(item, Series): - return "Series" - found = re.findall(r"<(?:type|class) '(\S*)'>", - to_text_string(type(item))) - if found: - return found[0] - - -def is_known_type(item): - """Return True if object has a known type""" - # Unfortunately, the masked array case is specific - return isinstance(item, MaskedArray) or get_type_string(item) is not None - - -def get_human_readable_type(item): - """Return human-readable type string of an item""" - if isinstance(item, (ndarray, MaskedArray)): - return item.dtype.name - elif isinstance(item, Image): - return "Image" - else: - text = get_type_string(item) - if text is None: - text = to_text_string('unknown') - else: - return text[text.find('.')+1:] - - -#============================================================================== -# Globals filter: filter namespace dictionaries (to be edited in -# CollectionsEditor) -#============================================================================== -def is_supported(value, check_all=False, filters=None, iterate=False): - """Return True if the value is supported, False otherwise""" - assert filters is not None - if value is None: - return True - if not is_editable_type(value): - return False - elif not isinstance(value, filters): - return False - elif iterate: - if isinstance(value, (list, tuple, set)): - valid_count = 0 - for val in value: - if is_supported(val, filters=filters, iterate=check_all): - valid_count += 1 - if not check_all: - break - return valid_count > 0 - elif isinstance(value, dict): - for key, val in list(value.items()): - if not is_supported(key, filters=filters, iterate=check_all) \ - or not is_supported(val, filters=filters, - iterate=check_all): - return False - if not check_all: - break - return True - - -def globalsfilter(input_dict, check_all=False, filters=None, - exclude_private=None, exclude_capitalized=None, - exclude_uppercase=None, exclude_unsupported=None, - excluded_names=None): - """Keep only objects that can be pickled""" - output_dict = {} - for key, value in list(input_dict.items()): - excluded = (exclude_private and key.startswith('_')) or \ - (exclude_capitalized and key[0].isupper()) or \ - (exclude_uppercase and key.isupper() - and len(key) > 1 and not key[1:].isdigit()) or \ - (key in excluded_names) or \ - (exclude_unsupported and \ - not is_supported(value, check_all=check_all, - filters=filters)) - if not excluded: - output_dict[key] = value - return output_dict - - -#============================================================================== -# Create view to be displayed by NamespaceBrowser -#============================================================================== -REMOTE_SETTINGS = ('check_all', 'exclude_private', 'exclude_uppercase', - 'exclude_capitalized', 'exclude_unsupported', - 'excluded_names', 'minmax') - - -def get_remote_data(data, settings, mode, more_excluded_names=None): - """ - Return globals according to filter described in *settings*: - * data: data to be filtered (dictionary) - * settings: variable explorer settings (dictionary) - * mode (string): 'editable' or 'picklable' - * more_excluded_names: additional excluded names (list) - """ - supported_types = get_supported_types() - assert mode in list(supported_types.keys()) - excluded_names = settings['excluded_names'] - if more_excluded_names is not None: - excluded_names += more_excluded_names - return globalsfilter(data, check_all=settings['check_all'], - filters=tuple(supported_types[mode]), - exclude_private=settings['exclude_private'], - exclude_uppercase=settings['exclude_uppercase'], - exclude_capitalized=settings['exclude_capitalized'], - exclude_unsupported=settings['exclude_unsupported'], - excluded_names=excluded_names) - - -def make_remote_view(data, settings, more_excluded_names=None): - """ - Make a remote view of dictionary *data* - -> globals explorer - """ - data = get_remote_data(data, settings, mode='editable', - more_excluded_names=more_excluded_names) - remote = {} - for key, value in list(data.items()): - view = value_to_display(value, minmax=settings['minmax']) - remote[key] = {'type': get_human_readable_type(value), - 'size': get_size(value), - 'color': get_color_name(value), - 'view': view} - return remote From 9f14284d46f83fa562ee0b4896826183da77dcaf Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 20:48:34 -0500 Subject: [PATCH 03/30] Other plugins: Only add to its list modules with the PLUGIN_CLASS attribute --- spyder/otherplugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/otherplugins.py b/spyder/otherplugins.py index cc43d2f7f3c..67e0b596592 100644 --- a/spyder/otherplugins.py +++ b/spyder/otherplugins.py @@ -87,7 +87,7 @@ def _import_plugin(module_name, plugin_path, modnames, modlist): module = None # Then restore the actual loaded module instead of the mock - if module: + if module and getattr(module, 'PLUGIN_CLASS', False): sys.modules[module_name] = module modlist.append(module) modnames.append(module_name) From 697d0f736c579045aa61b075d0f021c9ee65d010 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 21:16:04 -0500 Subject: [PATCH 04/30] Replace utils.dochelpers with dochelpers from spyder_kernels --- spyder/interpreter.py | 3 ++- spyder/plugins/help.py | 2 +- spyder/utils/introspection/jedi_plugin.py | 3 ++- spyder/utils/introspection/rope_patch.py | 2 +- spyder/utils/introspection/rope_plugin.py | 3 ++- spyder/utils/ipython/spyder_kernel.py | 16 +++------------- spyder/widgets/internalshell.py | 3 ++- spyder/widgets/ipythonconsole/help.py | 6 +++--- spyder/widgets/mixins.py | 4 ++-- spyder/widgets/sourcecode/codeeditor.py | 2 +- 10 files changed, 19 insertions(+), 25 deletions(-) diff --git a/spyder/interpreter.py b/spyder/interpreter.py index 5b461a1c579..cbb8e43fa8d 100644 --- a/spyder/interpreter.py +++ b/spyder/interpreter.py @@ -18,8 +18,9 @@ import pydoc from code import InteractiveConsole +from spyder_kernels.utils.dochelpers import isdefined + # Local imports: -from spyder.utils.dochelpers import isdefined from spyder.utils import encoding, programs from spyder.py3compat import is_text_string from spyder.utils.misc import remove_backslashes, getcwd_or_home diff --git a/spyder/plugins/help.py b/spyder/plugins/help.py index 506f116ba3f..f3bb79a9d1a 100644 --- a/spyder/plugins/help.py +++ b/spyder/plugins/help.py @@ -254,7 +254,7 @@ class SphinxThread(QThread): doc : str or dict A string containing a raw rst text or a dict containing the doc string components to be rendered. - See spyder.utils.dochelpers.getdoc for description. + See spyder_kernels.utils.dochelpers.getdoc for description. context : dict A dict containing the substitution variables for the layout template diff --git a/spyder/utils/introspection/jedi_plugin.py b/spyder/utils/introspection/jedi_plugin.py index 4e8e8374ea2..6b9c80d708e 100644 --- a/spyder/utils/introspection/jedi_plugin.py +++ b/spyder/utils/introspection/jedi_plugin.py @@ -12,10 +12,11 @@ import sys import time +from spyder_kernels.utils.dochelpers import getsignaturefromtext + from spyder.config.base import debug_print from spyder.utils import programs from spyder.utils.debug import log_last_error, log_dt -from spyder.utils.dochelpers import getsignaturefromtext from spyder.utils.introspection.manager import ( DEBUG_EDITOR, LOG_FILENAME, IntrospectionPlugin) from spyder.utils.introspection.utils import (default_info_response, diff --git a/spyder/utils/introspection/rope_patch.py b/spyder/utils/introspection/rope_patch.py index c7c74e67423..513bafc413e 100644 --- a/spyder/utils/introspection/rope_patch.py +++ b/spyder/utils/introspection/rope_patch.py @@ -130,7 +130,7 @@ def get_definition_location(self): # 3. get_calltip # To easily get calltips of forced builtins from rope.contrib import codeassist - from spyder.utils.dochelpers import getdoc + from spyder_kernels.utils.dochelpers import getdoc from rope.base import exceptions class PatchedPyDocExtractor(codeassist.PyDocExtractor): def get_builtin_doc(self, pyobject): diff --git a/spyder/utils/introspection/rope_plugin.py b/spyder/utils/introspection/rope_plugin.py index 5ab96628a2f..a3bddb66202 100644 --- a/spyder/utils/introspection/rope_plugin.py +++ b/spyder/utils/introspection/rope_plugin.py @@ -11,10 +11,11 @@ import time import imp +from spyder_kernels.utils.dochelpers import getsignaturefromtext + from spyder.config.base import get_conf_path, STDERR from spyder.utils import encoding, programs from spyder.py3compat import PY2 -from spyder.utils.dochelpers import getsignaturefromtext from spyder.utils import sourcecode from spyder.utils.debug import log_last_error, log_dt from spyder.utils.introspection.manager import ( diff --git a/spyder/utils/ipython/spyder_kernel.py b/spyder/utils/ipython/spyder_kernel.py index 6dc79c8080b..0d1c7307d1c 100644 --- a/spyder/utils/ipython/spyder_kernel.py +++ b/spyder/utils/ipython/spyder_kernel.py @@ -259,10 +259,7 @@ def pdb_continue(self): # --- For the Help plugin def is_defined(self, obj, force_import=False): """Return True if object is defined in current namespace""" - if not IS_EXT_INTERPRETER: - from spyder.utils.dochelpers import isdefined - else: - from utils.dochelpers import isdefined + from spyder_kernels.utils.dochelpers import isdefined ns = self._get_current_namespace(with_magics=True) return isdefined(obj, force_import=force_import, namespace=ns) @@ -274,11 +271,7 @@ def get_doc(self, objtxt): matplotlib.rcParams['docstring.hardcopy'] = True except: pass - - if not IS_EXT_INTERPRETER: - from spyder.utils.dochelpers import getdoc - else: - from utils.dochelpers import getdoc + from spyder_kernels.utils.dochelpers import getdoc obj, valid = self._eval(objtxt) if valid: @@ -286,10 +279,7 @@ def get_doc(self, objtxt): def get_source(self, objtxt): """Get object source""" - if not IS_EXT_INTERPRETER: - from spyder.utils.dochelpers import getsource - else: - from utils.dochelpers import getsource + from spyder_kernels.utils.dochelpers import getsource obj, valid = self._eval(objtxt) if valid: diff --git a/spyder/widgets/internalshell.py b/spyder/widgets/internalshell.py index f2047fd4cc9..9291d16449e 100644 --- a/spyder/widgets/internalshell.py +++ b/spyder/widgets/internalshell.py @@ -21,6 +21,8 @@ # Third party imports from qtpy.QtCore import QEventLoop, QObject, Signal, Slot from qtpy.QtWidgets import QMessageBox +from spyder_kernels.utils.dochelpers import (getargtxt, getdoc, getobjdir, + getsource) # Local imports from spyder import get_versions @@ -29,7 +31,6 @@ to_text_string) from spyder.utils import icon_manager as ima from spyder.utils import programs -from spyder.utils.dochelpers import getargtxt, getdoc, getobjdir, getsource from spyder.utils.misc import get_error_match, getcwd_or_home from spyder.utils.qthelpers import create_action from spyder.widgets.shell import PythonShellWidget diff --git a/spyder/widgets/ipythonconsole/help.py b/spyder/widgets/ipythonconsole/help.py index 3ceb0a549ac..bb11447aa63 100644 --- a/spyder/widgets/ipythonconsole/help.py +++ b/spyder/widgets/ipythonconsole/help.py @@ -13,14 +13,14 @@ import re -from qtpy.QtCore import QEventLoop - from qtconsole.ansi_code_processor import ANSI_OR_SPECIAL_PATTERN from qtconsole.rich_jupyter_widget import RichJupyterWidget +from qtpy.QtCore import QEventLoop +from spyder_kernels.utils.dochelpers import (getargspecfromtext, + getsignaturefromtext) from spyder.config.base import _ from spyder.py3compat import PY3 -from spyder.utils.dochelpers import getargspecfromtext, getsignaturefromtext class HelpWidget(RichJupyterWidget): diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py index f347f7394cd..ae146b391d5 100644 --- a/spyder/widgets/mixins.py +++ b/spyder/widgets/mixins.py @@ -24,13 +24,13 @@ from qtpy.QtGui import QCursor, QTextCursor, QTextDocument from qtpy.QtWidgets import QApplication, QToolTip from qtpy import QT_VERSION +from spyder_kernels.utils.dochelpers import (getargspecfromtext, getobj, + getsignaturefromtext) # Local imports from spyder.config.base import _ from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding, sourcecode, programs -from spyder.utils.dochelpers import (getargspecfromtext, getobj, - getsignaturefromtext) from spyder.utils.misc import get_error_match from spyder.widgets.arraybuilder import NumpyArrayDialog diff --git a/spyder/widgets/sourcecode/codeeditor.py b/spyder/widgets/sourcecode/codeeditor.py index d7b1f95fce1..5d78d2e01fb 100644 --- a/spyder/widgets/sourcecode/codeeditor.py +++ b/spyder/widgets/sourcecode/codeeditor.py @@ -39,6 +39,7 @@ QGridLayout, QHBoxLayout, QInputDialog, QLabel, QLineEdit, QMenu, QMessageBox, QSplitter, QTextEdit, QToolTip, QVBoxLayout, QWidget) +from spyder_kernels.utils.dochelpers import getobj # %% This line is for cell execution testing @@ -51,7 +52,6 @@ from spyder.utils import icon_manager as ima from spyder.utils import syntaxhighlighters as sh from spyder.utils import encoding, sourcecode -from spyder.utils.dochelpers import getobj from spyder.utils.programs import check_version from spyder.utils.qthelpers import add_actions, create_action, mimedata2url from spyder.utils.sourcecode import ALL_LANGUAGES, CELL_LANGUAGES From a81ac41942d265435994638b99da7ee7e4249e9d Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 22:43:42 -0500 Subject: [PATCH 05/30] Remove utils.dochelpers --- spyder/utils/dochelpers.py | 344 -------------------------- spyder/utils/tests/test_dochelpers.py | 87 ------- 2 files changed, 431 deletions(-) delete mode 100644 spyder/utils/dochelpers.py delete mode 100644 spyder/utils/tests/test_dochelpers.py diff --git a/spyder/utils/dochelpers.py b/spyder/utils/dochelpers.py deleted file mode 100644 index 6234714b2ba..00000000000 --- a/spyder/utils/dochelpers.py +++ /dev/null @@ -1,344 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -"""Utilities and wrappers around inspect module""" - -from __future__ import print_function - -import inspect -import re - -# Local imports: -from spyder.utils import encoding -from spyder.py3compat import (is_text_string, builtins, get_meth_func, - get_meth_class_inst, get_meth_class, - get_func_defaults, to_text_string, PY2) - - -SYMBOLS = r"[^\'\"a-zA-Z0-9_.]" - -def getobj(txt, last=False): - """Return the last valid object name in string""" - txt_end = "" - for startchar, endchar in ["[]", "()"]: - if txt.endswith(endchar): - pos = txt.rfind(startchar) - if pos: - txt_end = txt[pos:] - txt = txt[:pos] - tokens = re.split(SYMBOLS, txt) - token = None - try: - while token is None or re.match(SYMBOLS, token): - token = tokens.pop() - if token.endswith('.'): - token = token[:-1] - if token.startswith('.'): - # Invalid object name - return None - if last: - #XXX: remove this statement as well as the "last" argument - token += txt[ txt.rfind(token) + len(token) ] - token += txt_end - if token: - return token - except IndexError: - return None - - -def getobjdir(obj): - """ - For standard objects, will simply return dir(obj) - In special cases (e.g. WrapITK package), will return only string elements - of result returned by dir(obj) - """ - return [item for item in dir(obj) if is_text_string(item)] - - -def getdoc(obj): - """ - Return text documentation from an object. This comes in a form of - dictionary with four keys: - - name: - The name of the inspected object - argspec: - It's argspec - note: - A phrase describing the type of object (function or method) we are - inspecting, and the module it belongs to. - docstring: - It's docstring - """ - - docstring = inspect.getdoc(obj) or inspect.getcomments(obj) or '' - - # Most of the time doc will only contain ascii characters, but there are - # some docstrings that contain non-ascii characters. Not all source files - # declare their encoding in the first line, so querying for that might not - # yield anything, either. So assume the most commonly used - # multi-byte file encoding (which also covers ascii). - try: - docstring = to_text_string(docstring) - except: - pass - - # Doc dict keys - doc = {'name': '', - 'argspec': '', - 'note': '', - 'docstring': docstring} - - if callable(obj): - try: - name = obj.__name__ - except AttributeError: - doc['docstring'] = docstring - return doc - if inspect.ismethod(obj): - imclass = get_meth_class(obj) - if get_meth_class_inst(obj) is not None: - doc['note'] = 'Method of %s instance' \ - % get_meth_class_inst(obj).__class__.__name__ - else: - doc['note'] = 'Unbound %s method' % imclass.__name__ - obj = get_meth_func(obj) - elif hasattr(obj, '__module__'): - doc['note'] = 'Function of %s module' % obj.__module__ - else: - doc['note'] = 'Function' - doc['name'] = obj.__name__ - if inspect.isfunction(obj): - if PY2: - args, varargs, varkw, defaults = inspect.getargspec(obj) - doc['argspec'] = inspect.formatargspec( - args, varargs, varkw, defaults, - formatvalue=lambda o:'='+repr(o)) - else: - (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, - annotations) = inspect.getfullargspec(obj) - doc['argspec'] = inspect.formatargspec( - args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, - annotations, formatvalue=lambda o:'='+repr(o)) - if name == '': - doc['name'] = name + ' lambda ' - doc['argspec'] = doc['argspec'][1:-1] # remove parentheses - else: - argspec = getargspecfromtext(doc['docstring']) - if argspec: - doc['argspec'] = argspec - # Many scipy and numpy docstrings begin with a function - # signature on the first line. This ends up begin redundant - # when we are using title and argspec to create the - # rich text "Definition:" field. We'll carefully remove this - # redundancy but only under a strict set of conditions: - # Remove the starting charaters of the 'doc' portion *iff* - # the non-whitespace characters on the first line - # match *exactly* the combined function title - # and argspec we determined above. - signature = doc['name'] + doc['argspec'] - docstring_blocks = doc['docstring'].split("\n\n") - first_block = docstring_blocks[0].strip() - if first_block == signature: - doc['docstring'] = doc['docstring'].replace( - signature, '', 1).lstrip() - else: - doc['argspec'] = '(...)' - - # Remove self from argspec - argspec = doc['argspec'] - doc['argspec'] = argspec.replace('(self)', '()').replace('(self, ', '(') - - return doc - - -def getsource(obj): - """Wrapper around inspect.getsource""" - try: - try: - src = encoding.to_unicode( inspect.getsource(obj) ) - except TypeError: - if hasattr(obj, '__class__'): - src = encoding.to_unicode( inspect.getsource(obj.__class__) ) - else: - # Bindings like VTK or ITK require this case - src = getdoc(obj) - return src - except (TypeError, IOError): - return - - -def getsignaturefromtext(text, objname): - """Get object signatures from text (object documentation) - Return a list containing a single string in most cases - Example of multiple signatures: PyQt5 objects""" - if isinstance(text, dict): - text = text.get('docstring', '') - # Regexps - oneline_re = objname + r'\([^\)].+?(?<=[\w\]\}\'"])\)(?!,)' - multiline_re = objname + r'\([^\)]+(?<=[\w\]\}\'"])\)(?!,)' - multiline_end_parenleft_re = r'(%s\([^\)]+(\),\n.+)+(?<=[\w\]\}\'"])\))' - # Grabbing signatures - if not text: - text = '' - sigs_1 = re.findall(oneline_re + '|' + multiline_re, text) - sigs_2 = [g[0] for g in re.findall(multiline_end_parenleft_re % objname, text)] - all_sigs = sigs_1 + sigs_2 - # The most relevant signature is usually the first one. There could be - # others in doctests but those are not so important - if all_sigs: - return all_sigs[0] - else: - return '' - -# Fix for Issue 1953 -# TODO: Add more signatures and remove this hack in 2.4 -getsignaturesfromtext = getsignaturefromtext - - -def getargspecfromtext(text): - """ - Try to get the formatted argspec of a callable from the first block of its - docstring - - This will return something like - '(foo, bar, k=1)' - """ - blocks = text.split("\n\n") - first_block = blocks[0].strip() - return getsignaturefromtext(first_block, '') - - -def getargsfromtext(text, objname): - """Get arguments from text (object documentation)""" - signature = getsignaturefromtext(text, objname) - if signature: - argtxt = signature[signature.find('(')+1:-1] - return argtxt.split(',') - - -def getargsfromdoc(obj): - """Get arguments from object doc""" - if obj.__doc__ is not None: - return getargsfromtext(obj.__doc__, obj.__name__) - - -def getargs(obj): - """Get the names and default values of a function's arguments""" - if inspect.isfunction(obj) or inspect.isbuiltin(obj): - func_obj = obj - elif inspect.ismethod(obj): - func_obj = get_meth_func(obj) - elif inspect.isclass(obj) and hasattr(obj, '__init__'): - func_obj = getattr(obj, '__init__') - else: - return [] - if not hasattr(func_obj, 'func_code'): - # Builtin: try to extract info from doc - args = getargsfromdoc(func_obj) - if args is not None: - return args - else: - # Example: PyQt5 - return getargsfromdoc(obj) - args, _, _ = inspect.getargs(func_obj.func_code) - if not args: - return getargsfromdoc(obj) - - # Supporting tuple arguments in def statement: - for i_arg, arg in enumerate(args): - if isinstance(arg, list): - args[i_arg] = "(%s)" % ", ".join(arg) - - defaults = get_func_defaults(func_obj) - if defaults is not None: - for index, default in enumerate(defaults): - args[index+len(args)-len(defaults)] += '='+repr(default) - if inspect.isclass(obj) or inspect.ismethod(obj): - if len(args) == 1: - return None - if 'self' in args: - args.remove('self') - return args - - -def getargtxt(obj, one_arg_per_line=True): - """ - Get the names and default values of a function's arguments - Return list with separators (', ') formatted for calltips - """ - args = getargs(obj) - if args: - sep = ', ' - textlist = None - for i_arg, arg in enumerate(args): - if textlist is None: - textlist = [''] - textlist[-1] += arg - if i_arg < len(args)-1: - textlist[-1] += sep - if len(textlist[-1]) >= 32 or one_arg_per_line: - textlist.append('') - if inspect.isclass(obj) or inspect.ismethod(obj): - if len(textlist) == 1: - return None - if 'self'+sep in textlist: - textlist.remove('self'+sep) - return textlist - - -def isdefined(obj, force_import=False, namespace=None): - """Return True if object is defined in namespace - If namespace is None --> namespace = locals()""" - if namespace is None: - namespace = locals() - attr_list = obj.split('.') - base = attr_list.pop(0) - if len(base) == 0: - return False - if base not in builtins.__dict__ and base not in namespace: - if force_import: - try: - module = __import__(base, globals(), namespace) - if base not in globals(): - globals()[base] = module - namespace[base] = module - except Exception: - return False - else: - return False - for attr in attr_list: - try: - attr_not_found = not hasattr(eval(base, namespace), attr) - except (SyntaxError, AttributeError): - return False - if attr_not_found: - if force_import: - try: - __import__(base+'.'+attr, globals(), namespace) - except (ImportError, SyntaxError): - return False - else: - return False - base += '.'+attr - return True - - -if __name__ == "__main__": - class Test(object): - def method(self, x, y=2): - pass - print(getargtxt(Test.__init__)) # spyder: test-skip - print(getargtxt(Test.method)) # spyder: test-skip - print(isdefined('numpy.take', force_import=True)) # spyder: test-skip - print(isdefined('__import__')) # spyder: test-skip - print(isdefined('.keys', force_import=True)) # spyder: test-skip - print(getobj('globals')) # spyder: test-skip - print(getobj('globals().keys')) # spyder: test-skip - print(getobj('+scipy.signal.')) # spyder: test-skip - print(getobj('4.')) # spyder: test-skip - print(getdoc(sorted)) # spyder: test-skip - print(getargtxt(sorted)) # spyder: test-skip diff --git a/spyder/utils/tests/test_dochelpers.py b/spyder/utils/tests/test_dochelpers.py deleted file mode 100644 index d39637d4474..00000000000 --- a/spyder/utils/tests/test_dochelpers.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# - -""" -Tests for dochelpers.py -""" -# Standard library imports -import os -import sys - -# Test library imports -import pytest - -# Local imports -from spyder.utils.dochelpers import getargtxt, getdoc, getobj, isdefined -from spyder.py3compat import PY2 - - -class Test(object): - def method(self, x, y=2): - pass - - -@pytest.mark.skipif(not 'Continuum' in sys.version or not PY2, - reason="It fails when not run in Anaconda and in " - "Python 3") -def test_dochelpers(): - """Test dochelpers.""" - assert not getargtxt(Test.__init__) - if PY2: - assert getargtxt(Test.method) == ['x, ', 'y=2'] - assert getdoc(sorted) == {'note': 'Function of __builtin__ module', - 'argspec': u'(iterable, cmp=None, key=None, ' - 'reverse=False)', - 'docstring': u'sorted(iterable, cmp=None, ' - 'key=None, reverse=False) --> ' - 'new sorted list', - 'name': 'sorted'} - assert getargtxt(sorted) == ['iterable, ', ' cmp=None, ', - ' key=None, ', ' reverse=False'] - else: - assert not getargtxt(Test.method) - if os.name == 'nt': - assert getdoc(sorted) == {'note': 'Function of builtins module', - 'argspec': '(...)', - 'docstring': 'Return a new list ' - 'containing ' - 'all items from the ' - 'iterable in ascending ' - 'order.\n\nA custom ' - 'key function can be ' - 'supplied to customise the ' - 'sort order, and ' - 'the\nreverse flag can be ' - 'set to request the result ' - 'in descending order.', - 'name': 'sorted'} - else: - assert getdoc(sorted) == {'note': 'Function of builtins module', - 'argspec': '(...)', - 'docstring': 'Return a new list ' - 'containing ' - 'all items from the ' - 'iterable in ascending ' - 'order.\n\nA custom ' - 'key function can be ' - 'supplied to customize the ' - 'sort order, and ' - 'the\nreverse flag can be ' - 'set to request the result ' - 'in descending order.', - 'name': 'sorted'} - assert not getargtxt(sorted) - assert isdefined('numpy.take', force_import=True) - assert isdefined('__import__') - assert not isdefined('.keys', force_import=True) - assert getobj('globals') == 'globals' - assert not getobj('globals().keys') - assert getobj('+scipy.signal.') == 'scipy.signal' - assert getobj('4.') == '4' - - -if __name__ == "__main__": - pytest.main() From 2fbd9ea22e0ec852a42fda5c7597d77aa389f113 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 22:46:08 -0500 Subject: [PATCH 06/30] Replace utils.iofuncs with iofuncs from spyder_kernels --- spyder/config/utils.py | 3 ++- spyder/utils/ipython/spyder_kernel.py | 8 ++------ spyder/widgets/variableexplorer/namespacebrowser.py | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/spyder/config/utils.py b/spyder/config/utils.py index f941074956b..dd8a72bcd20 100644 --- a/spyder/config/utils.py +++ b/spyder/config/utils.py @@ -12,8 +12,9 @@ import os.path as osp import sys +from spyder_kernels.utils import iofuncs + from spyder.config.base import _ -from spyder.utils import iofuncs #============================================================================== diff --git a/spyder/utils/ipython/spyder_kernel.py b/spyder/utils/ipython/spyder_kernel.py index 0d1c7307d1c..7e7f729d72b 100644 --- a/spyder/utils/ipython/spyder_kernel.py +++ b/spyder/utils/ipython/spyder_kernel.py @@ -192,11 +192,10 @@ def copy_value(self, orig_name, new_name): def load_data(self, filename, ext): """Load data from filename""" + from spyder_kernels.utils.iofuncs import iofunctions if not IS_EXT_INTERPRETER: - from spyder.utils.iofuncs import iofunctions from spyder.utils.misc import fix_reference_name else: - from utils.iofuncs import iofunctions from utils.misc import fix_reference_name glbs = self._mglobals() @@ -222,10 +221,7 @@ def load_data(self, filename, ext): def save_namespace(self, filename): """Save namespace into filename""" from spyder_kernels.utils.nsview import get_remote_data - if not IS_EXT_INTERPRETER: - from spyder.utils.iofuncs import iofunctions - else: - from utils.iofuncs import iofunctions + from spyder_kernels.utils.iofuncs import iofunctions ns = self._get_current_namespace() settings = self.namespace_view_settings diff --git a/spyder/widgets/variableexplorer/namespacebrowser.py b/spyder/widgets/variableexplorer/namespacebrowser.py index 20c9306f758..baa2381b25e 100644 --- a/spyder/widgets/variableexplorer/namespacebrowser.py +++ b/spyder/widgets/variableexplorer/namespacebrowser.py @@ -22,6 +22,7 @@ # Third party imports (others) import cloudpickle +from spyder_kernels.utils.iofuncs import iofunctions from spyder_kernels.utils.nsview import get_supported_types, REMOTE_SETTINGS # Local imports @@ -30,7 +31,6 @@ from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding from spyder.utils import icon_manager as ima -from spyder.utils.iofuncs import iofunctions from spyder.utils.misc import fix_reference_name, getcwd_or_home from spyder.utils.programs import is_module_installed from spyder.utils.qthelpers import (add_actions, create_action, From 836e0243e7ef409bbf155ac295ce3365ecb5e2ec Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 22:48:24 -0500 Subject: [PATCH 07/30] Remove utils.iofuncs --- spyder/utils/iofuncs.py | 508 ------------------------- spyder/utils/tests/data.mat | Bin 482 -> 0 bytes spyder/utils/tests/export_data.spydata | Bin 10240 -> 0 bytes spyder/utils/tests/numpy_data.npz | Bin 1301 -> 0 bytes spyder/utils/tests/test_iofuncs.py | 165 -------- 5 files changed, 673 deletions(-) delete mode 100644 spyder/utils/iofuncs.py delete mode 100644 spyder/utils/tests/data.mat delete mode 100644 spyder/utils/tests/export_data.spydata delete mode 100644 spyder/utils/tests/numpy_data.npz delete mode 100644 spyder/utils/tests/test_iofuncs.py diff --git a/spyder/utils/iofuncs.py b/spyder/utils/iofuncs.py deleted file mode 100644 index 8aff728b0ca..00000000000 --- a/spyder/utils/iofuncs.py +++ /dev/null @@ -1,508 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright © Spyder Project Contributors -# -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) -# ---------------------------------------------------------------------------- - -""" -Input/Output Utilities - -Note: 'load' functions has to return a dictionary from which a globals() - namespace may be updated -""" - -from __future__ import print_function - -# Standard library imports -import sys -import os -import os.path as osp -import tarfile -import tempfile -import shutil -import warnings -import json -import inspect -import dis -import copy - -# Third party imports -# - If pandas fails to import here (for any reason), Spyder -# will crash at startup (e.g. see Issue 2300) -# - This also prevents Spyder to start IPython kernels -# (see Issue 2456) -try: - import pandas as pd -except: - pd = None #analysis:ignore - -# Local imports -from spyder.config.base import _, STDERR -from spyder.py3compat import pickle, to_text_string, PY2 -from spyder.utils.misc import getcwd_or_home - - -class MatlabStruct(dict): - """ - Matlab style struct, enhanced. - - Supports dictionary and attribute style access. Can be pickled, - and supports code completion in a REPL. - - Examples - ======== - >>> from spyder.utils.iofuncs import MatlabStruct - >>> a = MatlabStruct() - >>> a.b = 'spam' # a["b"] == 'spam' - >>> a.c["d"] = 'eggs' # a.c.d == 'eggs' - >>> print(a) - {'c': {'d': 'eggs'}, 'b': 'spam'} - - """ - def __getattr__(self, attr): - """Access the dictionary keys for unknown attributes.""" - try: - return self[attr] - except KeyError: - msg = "'MatlabStruct' object has no attribute %s" % attr - raise AttributeError(msg) - - def __getitem__(self, attr): - """ - Get a dict value; create a MatlabStruct if requesting a submember. - - Do not create a key if the attribute starts with an underscore. - """ - if attr in self.keys() or attr.startswith('_'): - return dict.__getitem__(self, attr) - frame = inspect.currentframe() - # step into the function that called us - if frame.f_back.f_back and self._is_allowed(frame.f_back.f_back): - dict.__setitem__(self, attr, MatlabStruct()) - elif self._is_allowed(frame.f_back): - dict.__setitem__(self, attr, MatlabStruct()) - return dict.__getitem__(self, attr) - - def _is_allowed(self, frame): - """Check for allowed op code in the calling frame""" - allowed = [dis.opmap['STORE_ATTR'], dis.opmap['LOAD_CONST'], - dis.opmap.get('STOP_CODE', 0)] - bytecode = frame.f_code.co_code - instruction = bytecode[frame.f_lasti + 3] - instruction = ord(instruction) if PY2 else instruction - return instruction in allowed - - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ - - @property - def __dict__(self): - """Allow for code completion in a REPL""" - return self.copy() - - -def get_matlab_value(val): - """ - Extract a value from a Matlab file - - From the oct2py project, see - http://pythonhosted.org/oct2py/conversions.html - """ - import numpy as np - - # Extract each item of a list. - if isinstance(val, list): - return [get_matlab_value(v) for v in val] - - # Ignore leaf objects. - if not isinstance(val, np.ndarray): - return val - - # Convert user defined classes. - if hasattr(val, 'classname'): - out = dict() - for name in val.dtype.names: - out[name] = get_matlab_value(val[name].squeeze().tolist()) - cls = type(val.classname, (object,), out) - return cls() - - # Extract struct data. - elif val.dtype.names: - out = MatlabStruct() - for name in val.dtype.names: - out[name] = get_matlab_value(val[name].squeeze().tolist()) - val = out - - # Extract cells. - elif val.dtype.kind == 'O': - val = val.squeeze().tolist() - if not isinstance(val, list): - val = [val] - val = get_matlab_value(val) - - # Compress singleton values. - elif val.size == 1: - val = val.item() - - # Compress empty values. - elif val.size == 0: - if val.dtype.kind in 'US': - val = '' - else: - val = [] - - return val - - -try: - import numpy as np - try: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - import scipy.io as spio - except AttributeError: - # Python 2.5: warnings.catch_warnings was introduced in Python 2.6 - import scipy.io as spio # analysis:ignore - except: - spio = None - - if spio is None: - load_matlab = None - save_matlab = None - else: - def load_matlab(filename): - try: - out = spio.loadmat(filename, struct_as_record=True) - data = dict() - for (key, value) in out.items(): - data[key] = get_matlab_value(value) - return data, None - except Exception as error: - return None, str(error) - - def save_matlab(data, filename): - try: - spio.savemat(filename, data, oned_as='row') - except Exception as error: - return str(error) -except: - load_matlab = None - save_matlab = None - - -try: - import numpy as np # analysis:ignore - - def load_array(filename): - try: - name = osp.splitext(osp.basename(filename))[0] - data = np.load(filename) - if hasattr(data, 'keys'): - return data, None - else: - return {name: data}, None - except Exception as error: - return None, str(error) - - def __save_array(data, basename, index): - """Save numpy array""" - fname = basename + '_%04d.npy' % index - np.save(fname, data) - return fname -except: - load_array = None - - -try: - from spyder.pil_patch import Image - - if sys.byteorder == 'little': - _ENDIAN = '<' - else: - _ENDIAN = '>' - DTYPES = { - "1": ('|b1', None), - "L": ('|u1', None), - "I": ('%si4' % _ENDIAN, None), - "F": ('%sf4' % _ENDIAN, None), - "I;16": ('|u2', None), - "I;16S": ('%si2' % _ENDIAN, None), - "P": ('|u1', None), - "RGB": ('|u1', 3), - "RGBX": ('|u1', 4), - "RGBA": ('|u1', 4), - "CMYK": ('|u1', 4), - "YCbCr": ('|u1', 4), - } - def __image_to_array(filename): - img = Image.open(filename) - try: - dtype, extra = DTYPES[img.mode] - except KeyError: - raise RuntimeError("%s mode is not supported" % img.mode) - shape = (img.size[1], img.size[0]) - if extra is not None: - shape += (extra,) - return np.array(img.getdata(), dtype=np.dtype(dtype)).reshape(shape) - - def load_image(filename): - try: - name = osp.splitext(osp.basename(filename))[0] - return {name: __image_to_array(filename)}, None - except Exception as error: - return None, str(error) -except: - load_image = None - - -def load_pickle(filename): - """Load a pickle file as a dictionary""" - try: - if pd: - return pd.read_pickle(filename), None - else: - with open(filename, 'rb') as fid: - data = pickle.load(fid) - return data, None - except Exception as err: - return None, str(err) - - -def load_json(filename): - """Load a json file as a dictionary""" - try: - if PY2: - args = 'rb' - else: - args = 'r' - with open(filename, args) as fid: - data = json.load(fid) - return data, None - except Exception as err: - return None, str(err) - - -def save_dictionary(data, filename): - """Save dictionary in a single file .spydata file""" - filename = osp.abspath(filename) - old_cwd = getcwd_or_home() - os.chdir(osp.dirname(filename)) - error_message = None - - # Copy dictionary before modifying to fix #6689 - try: - data = copy.deepcopy(data) - except NotImplementedError: - try: - data = copy.copy(data) - except Exception: - data = data - - try: - saved_arrays = {} - if load_array is not None: - # Saving numpy arrays with np.save - arr_fname = osp.splitext(filename)[0] - for name in list(data.keys()): - if isinstance(data[name], np.ndarray) and data[name].size > 0: - # Saving arrays at data root - fname = __save_array(data[name], arr_fname, - len(saved_arrays)) - saved_arrays[(name, None)] = osp.basename(fname) - data.pop(name) - elif isinstance(data[name], (list, dict)): - # Saving arrays nested in lists or dictionaries - if isinstance(data[name], list): - iterator = enumerate(data[name]) - else: - iterator = iter(list(data[name].items())) - to_remove = [] - for index, value in iterator: - if isinstance(value, np.ndarray) and value.size > 0: - fname = __save_array(value, arr_fname, - len(saved_arrays)) - saved_arrays[(name, index)] = osp.basename(fname) - to_remove.append(index) - for index in sorted(to_remove, reverse=True): - data[name].pop(index) - if saved_arrays: - data['__saved_arrays__'] = saved_arrays - pickle_filename = osp.splitext(filename)[0]+'.pickle' - with open(pickle_filename, 'w+b') as fdesc: - pickle.dump(data, fdesc, 2) - tar = tarfile.open(filename, "w") - for fname in [pickle_filename]+[fn for fn in list(saved_arrays.values())]: - tar.add(osp.basename(fname)) - os.remove(fname) - tar.close() - if saved_arrays: - data.pop('__saved_arrays__') - except (RuntimeError, pickle.PicklingError, TypeError) as error: - error_message = to_text_string(error) - os.chdir(old_cwd) - return error_message - - -def load_dictionary(filename): - """Load dictionary from .spydata file""" - filename = osp.abspath(filename) - old_cwd = getcwd_or_home() - tmp_folder = tempfile.mkdtemp() - os.chdir(tmp_folder) - data = None - error_message = None - try: - tar = tarfile.open(filename, "r") - tar.extractall() - data_file = osp.basename(filename) - pickle_filename = osp.splitext(data_file)[0]+'.pickle' - try: - # Old format (Spyder 2.0-2.1 for Python 2) - with open(pickle_filename, 'U') as fdesc: - data = pickle.loads(fdesc.read()) - except (pickle.PickleError, TypeError, UnicodeDecodeError): - # New format (Spyder >=2.2 for Python 2 and Python 3) - with open(pickle_filename, 'rb') as fdesc: - data = pickle.loads(fdesc.read()) - saved_arrays = {} - if load_array is not None: - # Loading numpy arrays saved with np.save - try: - saved_arrays = data.pop('__saved_arrays__') - for (name, index), fname in list(saved_arrays.items()): - arr = np.load( osp.join(tmp_folder, fname) ) - if index is None: - data[name] = arr - elif isinstance(data[name], dict): - data[name][index] = arr - else: - data[name].insert(index, arr) - except KeyError: - pass - except (EOFError, ValueError) as error: - error_message = to_text_string(error) - os.chdir(old_cwd) - try: - shutil.rmtree(tmp_folder) - except OSError as error: - error_message = to_text_string(error) - return data, error_message - - -class IOFunctions(object): - def __init__(self): - self.load_extensions = None - self.save_extensions = None - self.load_filters = None - self.save_filters = None - self.load_funcs = None - self.save_funcs = None - - def setup(self): - iofuncs = self.get_internal_funcs()+self.get_3rd_party_funcs() - load_extensions = {} - save_extensions = {} - load_funcs = {} - save_funcs = {} - load_filters = [] - save_filters = [] - load_ext = [] - for ext, name, loadfunc, savefunc in iofuncs: - filter_str = to_text_string(name + " (*%s)" % ext) - if loadfunc is not None: - load_filters.append(filter_str) - load_extensions[filter_str] = ext - load_funcs[ext] = loadfunc - load_ext.append(ext) - if savefunc is not None: - save_extensions[filter_str] = ext - save_filters.append(filter_str) - save_funcs[ext] = savefunc - load_filters.insert(0, to_text_string(_("Supported files")+" (*"+\ - " *".join(load_ext)+")")) - load_filters.append(to_text_string(_("All files (*.*)"))) - self.load_filters = "\n".join(load_filters) - self.save_filters = "\n".join(save_filters) - self.load_funcs = load_funcs - self.save_funcs = save_funcs - self.load_extensions = load_extensions - self.save_extensions = save_extensions - - def get_internal_funcs(self): - return [ - ('.spydata', _("Spyder data files"), - load_dictionary, save_dictionary), - ('.npy', _("NumPy arrays"), load_array, None), - ('.npz', _("NumPy zip arrays"), load_array, None), - ('.mat', _("Matlab files"), load_matlab, save_matlab), - ('.csv', _("CSV text files"), 'import_wizard', None), - ('.txt', _("Text files"), 'import_wizard', None), - ('.jpg', _("JPEG images"), load_image, None), - ('.png', _("PNG images"), load_image, None), - ('.gif', _("GIF images"), load_image, None), - ('.tif', _("TIFF images"), load_image, None), - ('.pkl', _("Pickle files"), load_pickle, None), - ('.pickle', _("Pickle files"), load_pickle, None), - ('.json', _("JSON files"), load_json, None), - ] - - def get_3rd_party_funcs(self): - other_funcs = [] - from spyder.otherplugins import get_spyderplugins_mods - for mod in get_spyderplugins_mods(io=True): - try: - other_funcs.append((mod.FORMAT_EXT, mod.FORMAT_NAME, - mod.FORMAT_LOAD, mod.FORMAT_SAVE)) - except AttributeError as error: - print("%s: %s" % (mod, str(error)), file=STDERR) - return other_funcs - - def save(self, data, filename): - ext = osp.splitext(filename)[1].lower() - if ext in self.save_funcs: - return self.save_funcs[ext](data, filename) - else: - return _("Unsupported file type '%s'") % ext - - def load(self, filename): - ext = osp.splitext(filename)[1].lower() - if ext in self.load_funcs: - return self.load_funcs[ext](filename) - else: - return None, _("Unsupported file type '%s'") % ext - -iofunctions = IOFunctions() -iofunctions.setup() - - -def save_auto(data, filename): - """Save data into filename, depending on file extension""" - pass - - -if __name__ == "__main__": - import datetime - testdict = {'d': 1, 'a': np.random.rand(10, 10), 'b': [1, 2]} - testdate = datetime.date(1945, 5, 8) - example = {'str': 'kjkj kj k j j kj k jkj', - 'unicode': u'éù', - 'list': [1, 3, [4, 5, 6], 'kjkj', None], - 'tuple': ([1, testdate, testdict], 'kjkj', None), - 'dict': testdict, - 'float': 1.2233, - 'array': np.random.rand(4000, 400), - 'empty_array': np.array([]), - 'date': testdate, - 'datetime': datetime.datetime(1945, 5, 8), - } - import time - t0 = time.time() - save_dictionary(example, "test.spydata") - print(" Data saved in %.3f seconds" % (time.time()-t0)) # spyder: test-skip - t0 = time.time() - example2, ok = load_dictionary("test.spydata") - os.remove("test.spydata") - - print("Data loaded in %.3f seconds" % (time.time()-t0)) # spyder: test-skip diff --git a/spyder/utils/tests/data.mat b/spyder/utils/tests/data.mat deleted file mode 100644 index d29f4b77deb8ed50e628dcd4ac6de5f42b801e71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQgHY2i*PhE(NS;n5-`93qo*%FkgWp56>}aZCnOj!B$+8Z zYq+Fvfa|I8Aw%V6pb^8!gh!jRp|XG#bEcZYGnSKvn+-M$h&huCNBn*K6g=D@RGa=)72#rT9l)!bja)E-KVC zio9Z4CU2+4vSBDhPrWCSgiWu(TtXx*{e^VGCDs(f|kpra-voeJt6mwjWZNt1w zVG6}UvzZV2{jdLJ^N=MBv! zE>={Hs26-^aM_K0>sA~;zQ?XShsV7tt2_LB(bR~!q>&bhU2;L6-qvM(>%@|_cQRq^ zGl7u*#rpbLk^C&m{}z8I2=D)VNx=O-Ir_*9a{rg9)zbn{&`2TC0??!XBZmu7Z&N#oJR?am-DqD)s-^>#1%eAsr`PzCnPe5X8kmyvN^=V;^^)_8QuT66b4oH3i;5B}x#Ejblk@Y6ONvU9 zOSlRdA*#6YQXsO0Ox_Hwp^QoGL50j3-i(@1>6DVnf>f?TmQW^t3vUK*MxZpShBtF4 zV~wAmpVxmN02AK!B}tvJg>1Ej>>3fE_)m-g#cpw8N@8&eSRgq$K0c{56X=?}_;{}5 z{M>?^)C#Ub4tM(xK!rfy?w|ms8QOvhIl)?z+#MMHsQ^c;iGVg~dlIRBt)K~JFwEkA);!08oT3wp{x aXxReHMc{N2;LQroFs#6&^B*J+&Q1Wg{SHY0 diff --git a/spyder/utils/tests/test_iofuncs.py b/spyder/utils/tests/test_iofuncs.py deleted file mode 100644 index edad2eb1a0a..00000000000 --- a/spyder/utils/tests/test_iofuncs.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright © Spyder Project Contributors -# -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) -# ---------------------------------------------------------------------------- - -""" -Tests for iofuncs.py -""" - -# Standard library imports -import io -import os - -# Third party imports -import pytest -import numpy as np - -# Local imports -import spyder.utils.iofuncs as iofuncs - - -# Full path to this file's parent directory for loading data -LOCATION = os.path.realpath(os.path.join(os.getcwd(), - os.path.dirname(__file__))) - - -# ============================================================================= -# Fixtures -# ============================================================================= -@pytest.fixture -def spydata_values(): - """ - Define spydata file ground truth values. - - The file export_data.spydata contains five variables to be loaded. - This fixture declares those variables in a static way. - """ - A = 1 - B = 'ham' - C = np.eye(3) - D = {'a': True, 'b': np.eye(4, dtype=np.complex)} - E = [np.eye(2, dtype=np.int64), 42.0, np.eye(3, dtype=np.bool_)] - return {'A': A, 'B': B, 'C': C, 'D': D, 'E': E} - - -@pytest.fixture -def real_values(): - """ - Load a Numpy pickled file. - - The file numpy_data.npz contains six variables, each one represents the - expected test values after a manual conversion of the same variables - defined and evaluated in MATLAB. The manual type conversion was done - over several variable types, such as: Matrices/Vectors, Scalar and - Complex numbers, Structs, Strings and Cell Arrays. The set of variables - was defined to allow and test the deep conversion of a compound type, - i.e., a struct that contains other types that need to be converted, - like other structs, matrices and Cell Arrays. - """ - path = os.path.join(LOCATION, 'numpy_data.npz') - file_s = np.load(path) - A = file_s['A'].item() - B = file_s['B'] - C = file_s['C'] - D = file_s['D'].item() - E = file_s['E'] - return {'A': A, 'B': B, 'C': C, 'D': D, 'E': E} - - -# ============================================================================= -# Tests -# ============================================================================= -@pytest.mark.skipif(iofuncs.load_matlab is None, reason="SciPy required") -def test_matlab_import(real_values): - """ - Test the automatic conversion and import of variables from MATLAB. - - This test loads a file stored in MATLAB, the variables defined are - equivalent to the manually converted values done over Numpy. This test - allows to evaluate the function which processes the conversion automa- - tically. i.e., The automatic conversion results should be equal to the - manual conversion of the variables. - """ - path = os.path.join(LOCATION, 'data.mat') - inf, _ = iofuncs.load_matlab(path) - valid = True - for var in sorted(real_values.keys()): - valid = valid and bool(np.mean(real_values[var] == inf[var])) - assert valid - - -def test_spydata_import(spydata_values): - """ - Test spydata handling and variable importing. - - This test loads all the variables contained inside a spydata tar - container and compares them against their static values. - """ - path = os.path.join(LOCATION, 'export_data.spydata') - data, error = iofuncs.load_dictionary(path) - assert error is None - valid = True - for var in sorted(spydata_values.keys()): - try: - valid = valid and bool(np.mean(spydata_values[var] == data[var])) - except ValueError: - valid = valid and all([np.all(obj1 == obj2) for obj1, obj2 in - zip(spydata_values[var], data[var])]) - assert valid - - -@pytest.mark.skipif(iofuncs.load_matlab is None, reason="SciPy required") -def test_matlabstruct(): - """Test support for matlab stlye struct.""" - a = iofuncs.MatlabStruct() - a.b = 'spam' - assert a["b"] == 'spam' - a.c["d"] = 'eggs' - assert a.c.d == 'eggs' - assert a == {'c': {'d': 'eggs'}, 'b': 'spam'} - a['d'] = [1, 2, 3] - - buf = io.BytesIO() - iofuncs.save_matlab(a, buf) - buf.seek(0) - data, error = iofuncs.load_matlab(buf) - - assert error is None - assert data['b'] == 'spam' - assert data['c'].d == 'eggs' - assert data['d'].tolist() == [[1, 2, 3]] - - -def test_spydata_export(spydata_values): - """ - Test spydata export and re-import. - - This test saves the variables in ``spydata`` format and then - reloads and checks them to make sure they save/restore properly - and no errors occur during the process. - """ - path = os.path.join(LOCATION, 'export_data_copy.spydata') - export_error = iofuncs.save_dictionary(spydata_values, path) - assert export_error is None - data, import_error = iofuncs.load_dictionary(path) - assert import_error is None - valid = True - for var in sorted(spydata_values.keys()): - try: - valid = valid and bool(np.mean(spydata_values[var] == data[var])) - except ValueError: - valid = valid and all([np.all(obj1 == obj2) for obj1, obj2 in - zip(spydata_values[var], data[var])]) - assert valid - try: - os.remove(path) - except (IOError, OSError, PermissionError): - pass - - -if __name__ == "__main__": - pytest.main() From 288be17e80438975669cc5c1bb97fbaea19c7827 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 23:01:21 -0500 Subject: [PATCH 08/30] Replace utils.misc.fix_reference_name with the one from spyder_kernels --- spyder/utils/ipython/spyder_kernel.py | 5 +---- spyder/utils/misc.py | 20 ------------------- .../variableexplorer/collectionseditor.py | 3 ++- .../variableexplorer/namespacebrowser.py | 3 ++- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/spyder/utils/ipython/spyder_kernel.py b/spyder/utils/ipython/spyder_kernel.py index 7e7f729d72b..9cb3b957ac4 100644 --- a/spyder/utils/ipython/spyder_kernel.py +++ b/spyder/utils/ipython/spyder_kernel.py @@ -193,10 +193,7 @@ def copy_value(self, orig_name, new_name): def load_data(self, filename, ext): """Load data from filename""" from spyder_kernels.utils.iofuncs import iofunctions - if not IS_EXT_INTERPRETER: - from spyder.utils.misc import fix_reference_name - else: - from utils.misc import fix_reference_name + from spyder_kernels.utils.misc import fix_reference_name glbs = self._mglobals() diff --git a/spyder/utils/misc.py b/spyder/utils/misc.py index 1535566817a..5f793f39feb 100644 --- a/spyder/utils/misc.py +++ b/spyder/utils/misc.py @@ -125,26 +125,6 @@ def get_filelines(path): return files, lines -def fix_reference_name(name, blacklist=None): - """Return a syntax-valid Python reference name from an arbitrary name""" - import re - name = "".join(re.split(r'[^0-9a-zA-Z_]', name)) - while name and not re.match(r'([a-zA-Z]+[0-9a-zA-Z_]*)$', name): - if not re.match(r'[a-zA-Z]', name[0]): - name = name[1:] - continue - name = str(name) - if not name: - name = "data" - if blacklist is not None and name in blacklist: - get_new_name = lambda index: name+('%03d' % index) - index = 0 - while get_new_name(index) in blacklist: - index += 1 - name = get_new_name(index) - return name - - def remove_backslashes(path): """Remove backslashes in *path* diff --git a/spyder/widgets/variableexplorer/collectionseditor.py b/spyder/widgets/variableexplorer/collectionseditor.py index e9ea776189d..a7a70bd65e7 100644 --- a/spyder/widgets/variableexplorer/collectionseditor.py +++ b/spyder/widgets/variableexplorer/collectionseditor.py @@ -35,6 +35,7 @@ QInputDialog, QItemDelegate, QLineEdit, QMenu, QMessageBox, QPushButton, QTableView, QVBoxLayout, QWidget) +from spyder_kernels.utils.misc import fix_reference_name from spyder_kernels.utils.nsview import ( array, DataFrame, DatetimeIndex, display_to_value, FakeObject, get_color_name, get_human_readable_type, get_size, Image, is_editable_type, @@ -49,7 +50,7 @@ from spyder.py3compat import (io, is_binary_string, is_text_string, PY3, to_text_string) from spyder.utils import icon_manager as ima -from spyder.utils.misc import fix_reference_name, getcwd_or_home +from spyder.utils.misc import getcwd_or_home from spyder.utils.qthelpers import (add_actions, create_action, mimedata2url) from spyder.widgets.variableexplorer.importwizard import ImportWizard diff --git a/spyder/widgets/variableexplorer/namespacebrowser.py b/spyder/widgets/variableexplorer/namespacebrowser.py index baa2381b25e..84fa77d3e5a 100644 --- a/spyder/widgets/variableexplorer/namespacebrowser.py +++ b/spyder/widgets/variableexplorer/namespacebrowser.py @@ -23,6 +23,7 @@ # Third party imports (others) import cloudpickle from spyder_kernels.utils.iofuncs import iofunctions +from spyder_kernels.utils.misc import fix_reference_name from spyder_kernels.utils.nsview import get_supported_types, REMOTE_SETTINGS # Local imports @@ -31,7 +32,7 @@ from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding from spyder.utils import icon_manager as ima -from spyder.utils.misc import fix_reference_name, getcwd_or_home +from spyder.utils.misc import getcwd_or_home from spyder.utils.programs import is_module_installed from spyder.utils.qthelpers import (add_actions, create_action, create_toolbutton, create_plugin_layout) From dcd36aa8fcbd14f11e08a7ceb789c5f664a50f03 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 23:29:17 -0500 Subject: [PATCH 09/30] Use kernel from spyder_kernels instead of the one here --- spyder/utils/ipython/kernelspec.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/spyder/utils/ipython/kernelspec.py b/spyder/utils/ipython/kernelspec.py index 6a73ff52a0e..aba33820eb0 100644 --- a/spyder/utils/ipython/kernelspec.py +++ b/spyder/utils/ipython/kernelspec.py @@ -26,7 +26,7 @@ class SpyderKernelSpec(KernelSpec): """Kernel spec for Spyder kernels""" - spy_path = get_module_source_path('spyder') + spykernel_path = get_module_source_path('spyder_kernels') def __init__(self, is_cython=False, **kwargs): super(SpyderKernelSpec, self).__init__(**kwargs) @@ -61,10 +61,10 @@ def argv(self): pyexec = pyexec_w # Command used to start kernels - utils_path = osp.join(self.spy_path, 'utils', 'ipython') + console_path = osp.join(self.spykernel_path, 'console') kernel_cmd = [ pyexec, - osp.join("%s" % utils_path, "start_kernel.py"), + osp.join("%s" % console_path, "start.py"), '-f', '{connection_file}' ] @@ -75,19 +75,14 @@ def argv(self): def env(self): """Env vars for kernels""" # Paths that we need to add to PYTHONPATH: - # 1. sc_path: Path to our sitecustomize - # 2. spy_path: Path to our main module, so we can use our config - # system to configure kernels started by exterrnal interpreters - # 3. spy_pythonpath: Paths saved by our users with our PYTHONPATH + # 1. sc_path: Path to the kernel sitecustomize + # 2. spy_pythonpath: Paths saved by our users with our PYTHONPATH # manager - sc_path = osp.join(self.spy_path, 'utils', 'site') + sc_path = osp.join(self.spykernel_path, 'site') spy_pythonpath = CONF.get('main', 'spyder_pythonpath', default=[]) + pathlist = [sc_path] + spy_pythonpath default_interpreter = CONF.get('main_interpreter', 'default') - if default_interpreter: - pathlist = [sc_path] + spy_pythonpath - else: - pathlist = [sc_path, self.spy_path] + spy_pythonpath pypath = add_pathlist_to_PYTHONPATH([], pathlist, ipyconsole=True, drop_env=(not default_interpreter)) From 6c42145bd4bbfd4f298691de027ba8f64877ea50 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 17 Jun 2018 23:37:32 -0500 Subject: [PATCH 10/30] Remove kernel files --- continuous_integration/circle/modules_test.sh | 6 - spyder/utils/ipython/spyder_kernel.py | 498 ------------------ spyder/utils/ipython/start_kernel.py | 281 ---------- spyder/widgets/ipythonconsole/debugging.py | 3 +- 4 files changed, 1 insertion(+), 787 deletions(-) delete mode 100644 spyder/utils/ipython/spyder_kernel.py delete mode 100644 spyder/utils/ipython/start_kernel.py diff --git a/continuous_integration/circle/modules_test.sh b/continuous_integration/circle/modules_test.sh index e4c4e549f0a..2ea66d16b36 100755 --- a/continuous_integration/circle/modules_test.sh +++ b/continuous_integration/circle/modules_test.sh @@ -77,12 +77,6 @@ for f in spyder/*/*/*.py; do if [[ $f == spyder/utils/help/*.py ]]; then continue fi - if [[ $f == spyder/utils/ipython/start_kernel.py ]]; then - continue - fi - if [[ $f == spyder/utils/ipython/spyder_kernel.py ]]; then - continue - fi if [[ $f == spyder/utils/site/sitecustomize.py ]]; then continue fi diff --git a/spyder/utils/ipython/spyder_kernel.py b/spyder/utils/ipython/spyder_kernel.py deleted file mode 100644 index 9cb3b957ac4..00000000000 --- a/spyder/utils/ipython/spyder_kernel.py +++ /dev/null @@ -1,498 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -""" -Spyder kernel for Jupyter -""" - -# Standard library imports -import os -import os.path as osp -import sys - -# Third-party imports -from ipykernel.ipkernel import IPythonKernel - - -PY2 = sys.version[0] == '2' - -# Check if we are running under an external interpreter -# We add "spyder" to sys.path for external interpreters, -# so relative imports work! -IS_EXT_INTERPRETER = os.environ.get('SPY_EXTERNAL_INTERPRETER') == "True" - -# Excluded variables from the Variable Explorer (i.e. they are not -# shown at all there) -EXCLUDED_NAMES = ['In', 'Out', 'exit', 'get_ipython', 'quit'] - -# To be able to get and set variables between Python 2 and 3 -PICKLE_PROTOCOL = 2 - - -class SpyderKernel(IPythonKernel): - """Spyder kernel for Jupyter""" - - def __init__(self, *args, **kwargs): - super(SpyderKernel, self).__init__(*args, **kwargs) - - self.namespace_view_settings = {} - - self._pdb_obj = None - self._pdb_step = None - self._do_publish_pdb_state = True - self._mpl_backend_error = None - - @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 {} - - # -- Public API --------------------------------------------------- - # --- For the Variable Explorer - 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 = repr(make_remote_view(ns, settings, EXCLUDED_NAMES)) - return view - else: - return repr(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), - '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 repr(properties) - else: - return repr(None) - - def send_spyder_msg(self, spyder_msg_type, content=None, data=None): - """ - Publish custom messages to the Spyder frontend. - - Parameters - ---------- - - spyder_msg_type: str - The spyder message type - content: dict - The (JSONable) content of the message - data: any - Any object that is serializable by cloudpickle (should be most - things). Will arrive as cloudpickled bytes in `.buffers[0]`. - """ - import cloudpickle - - if content is None: - content = {} - content['spyder_msg_type'] = spyder_msg_type - self.session.send( - self.iopub_socket, - 'spyder_msg', - content=content, - buffers=[cloudpickle.dumps(data, protocol=PICKLE_PROTOCOL)], - parent=self._parent_header, - ) - - def get_value(self, name): - """Get the value of a variable""" - ns = self._get_current_namespace() - value = ns[name] - try: - self.send_spyder_msg('data', data=value) - except: - # * There is no need to inform users about - # these errors. - # * value = None makes Spyder to ignore - # petitions to display a value - self.send_spyder_msg('data', data=None) - self._do_publish_pdb_state = False - - def set_value(self, name, value, PY2_frontend): - """Set the value of a variable""" - import cloudpickle - ns = self._get_reference_namespace(name) - - # We send serialized values in a list of one element - # from Spyder to the kernel, to be able to send them - # at all in Python 2 - svalue = value[0] - - # We need to convert svalue to bytes if the frontend - # runs in Python 2 and the kernel runs in Python 3 - if PY2_frontend and not PY2: - svalue = bytes(svalue, 'latin-1') - - # Deserialize and set value in namespace - dvalue = cloudpickle.loads(svalue) - ns[name] = dvalue - - 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 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.send_spyder_msg('pdb_state', content={'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.send_spyder_msg('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 - - def _set_spyder_breakpoints(self): - """Set all Spyder breakpoints in an active pdb session""" - if not self._pdb_obj: - return - self._pdb_obj.set_spyder_breakpoints() - - # --- 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 - """ - if not IS_EXT_INTERPRETER: - from spyder.py3compat import is_text_string - else: - from 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" - "NOTE: The following error appeared when setting " - "your Matplotlib backend\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() - 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 - - # --- Others - def _load_autoreload_magic(self): - """Load %autoreload magic.""" - from IPython.core.getipython import get_ipython - get_ipython().run_line_magic('reload_ext', 'autoreload') - get_ipython().run_line_magic('autoreload', '2') diff --git a/spyder/utils/ipython/start_kernel.py b/spyder/utils/ipython/start_kernel.py deleted file mode 100644 index 2fcb8d763b4..00000000000 --- a/spyder/utils/ipython/start_kernel.py +++ /dev/null @@ -1,281 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -""" -File used to start kernels for the IPython Console -""" - -# Standard library imports -from distutils.version import LooseVersion -import os -import os.path as osp -import sys - - -PY2 = sys.version[0] == '2' - - -def is_module_installed(module_name): - """ - Simpler version of spyder.utils.programs.is_module_installed - to improve startup time. - """ - try: - __import__(module_name) - return True - except: - # Module is not installed - return False - - -def sympy_config(mpl_backend): - """Sympy configuration""" - if mpl_backend is not None: - lines = """ -from sympy.interactive import init_session -init_session() -%matplotlib {0} -""".format(mpl_backend) - else: - lines = """ -from sympy.interactive import init_session -init_session() -""" - - return lines - - -def kernel_config(): - """Create a config object with IPython kernel options.""" - import ipykernel - from IPython.core.application import get_ipython_dir - from traitlets.config.loader import Config, load_pyconfig_files - - # ---- IPython config ---- - try: - profile_path = osp.join(get_ipython_dir(), 'profile_default') - cfg = load_pyconfig_files(['ipython_config.py', - 'ipython_kernel_config.py'], - profile_path) - except: - cfg = Config() - - # ---- Spyder config ---- - spy_cfg = Config() - - # Enable/disable certain features for testing - testing = os.environ.get('SPY_TESTING') == 'True' - if testing: - # Don't load nor save history in our IPython consoles. - spy_cfg.HistoryAccessor.enabled = False - - # Until we implement Issue 1052 - spy_cfg.InteractiveShell.xmode = 'Plain' - - # Jedi completer. It's only available in Python 3 - jedi_o = os.environ.get('SPY_JEDI_O') == 'True' - if not PY2: - spy_cfg.IPCompleter.use_jedi = jedi_o - - # Run lines of code at startup - run_lines_o = os.environ.get('SPY_RUN_LINES_O') - if run_lines_o is not None: - spy_cfg.IPKernelApp.exec_lines = [x.strip() for x in run_lines_o.split(',')] - else: - spy_cfg.IPKernelApp.exec_lines = [] - - # Clean terminal arguments input - clear_argv = "import sys;sys.argv = [''];del sys" - spy_cfg.IPKernelApp.exec_lines.append(clear_argv) - - # Load %autoreload magic - spy_cfg.IPKernelApp.exec_lines.append( - "get_ipython().kernel._load_autoreload_magic()") - - # Default inline backend configuration - # This is useful to have when people doesn't - # use our config system to configure the - # inline backend but want to use - # '%matplotlib inline' at runtime - if LooseVersion(ipykernel.__version__) < LooseVersion('4.5'): - dpi_option = 'savefig.dpi' - else: - dpi_option = 'figure.dpi' - - spy_cfg.InlineBackend.rc = {'figure.figsize': (6.0, 4.0), - dpi_option: 72, - 'font.size': 10, - 'figure.subplot.bottom': .125, - 'figure.facecolor': 'white', - 'figure.edgecolor': 'white'} - - # Pylab configuration - mpl_backend = None - pylab_o = os.environ.get('SPY_PYLAB_O') - - if pylab_o == 'True' and is_module_installed('matplotlib'): - # Set Matplotlib backend - backend_o = os.environ.get('SPY_BACKEND_O') - if backend_o is not None: - if backend_o == '1': - if is_module_installed('PyQt5'): - auto_backend = 'qt5' - elif is_module_installed('PyQt4'): - auto_backend = 'qt4' - elif is_module_installed('_tkinter'): - auto_backend = 'tk' - else: - auto_backend = 'inline' - else: - auto_backend = '' - backends = {'0': 'inline', - '1': auto_backend, - '2': 'qt5', - '3': 'qt4', - '4': 'osx', - '5': 'gtk3', - '6': 'gtk', - '7': 'wx', - '8': 'tk'} - mpl_backend = backends[backend_o] - - # Automatically load Pylab and Numpy, or only set Matplotlib - # backend - autoload_pylab_o = os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True' - command = "get_ipython().kernel._set_mpl_backend('{0}', {1})" - spy_cfg.IPKernelApp.exec_lines.append( - command.format(mpl_backend, autoload_pylab_o)) - - # Inline backend configuration - if mpl_backend == 'inline': - # Figure format - format_o = os.environ.get('SPY_FORMAT_O') - formats = {'0': 'png', - '1': 'svg'} - if format_o is not None: - spy_cfg.InlineBackend.figure_format = formats[format_o] - - # Resolution - resolution_o = os.environ.get('SPY_RESOLUTION_O') - if resolution_o is not None: - spy_cfg.InlineBackend.rc[dpi_option] = float(resolution_o) - - # Figure size - width_o = float(os.environ.get('SPY_WIDTH_O')) - height_o = float(os.environ.get('SPY_HEIGHT_O')) - if width_o is not None and height_o is not None: - spy_cfg.InlineBackend.rc['figure.figsize'] = (width_o, - height_o) - - # Print figure kwargs - bbox_inches_o = os.environ.get('SPY_BBOX_INCHES_O') - bbox_inches = 'tight' if bbox_inches_o == 'True' else None - spy_cfg.InlineBackend.print_figure_kwargs.update( - {'bbox_inches': bbox_inches}) - - # Enable Cython magic - run_cython = os.environ.get('SPY_RUN_CYTHON') == 'True' - if run_cython and is_module_installed('Cython'): - spy_cfg.IPKernelApp.exec_lines.append('%reload_ext Cython') - - # Run a file at startup - use_file_o = os.environ.get('SPY_USE_FILE_O') - run_file_o = os.environ.get('SPY_RUN_FILE_O') - if use_file_o == 'True' and run_file_o is not None: - spy_cfg.IPKernelApp.file_to_run = run_file_o - - # Autocall - autocall_o = os.environ.get('SPY_AUTOCALL_O') - if autocall_o is not None: - spy_cfg.ZMQInteractiveShell.autocall = int(autocall_o) - - # To handle the banner by ourselves in IPython 3+ - spy_cfg.ZMQInteractiveShell.banner1 = '' - - # Greedy completer - greedy_o = os.environ.get('SPY_GREEDY_O') == 'True' - spy_cfg.IPCompleter.greedy = greedy_o - - # Sympy loading - sympy_o = os.environ.get('SPY_SYMPY_O') == 'True' - if sympy_o and is_module_installed('sympy'): - lines = sympy_config(mpl_backend) - spy_cfg.IPKernelApp.exec_lines.append(lines) - - # Merge IPython and Spyder configs. Spyder prefs will have prevalence - # over IPython ones - cfg._merge(spy_cfg) - return cfg - - -def varexp(line): - """ - Spyder's variable explorer magic - - Used to generate plots, histograms and images of the variables displayed - on it. - """ - ip = get_ipython() #analysis:ignore - funcname, name = line.split() - import spyder.pyplot - __fig__ = spyder.pyplot.figure(); - __items__ = getattr(spyder.pyplot, funcname[2:])(ip.user_ns[name]) - spyder.pyplot.show() - del __fig__, __items__ - - -def main(): - # Remove this module's path from sys.path: - try: - sys.path.remove(osp.dirname(__file__)) - except ValueError: - pass - - try: - locals().pop('__file__') - except KeyError: - pass - __doc__ = '' - __name__ = '__main__' - - # Add current directory to sys.path (like for any standard Python interpreter - # executed in interactive mode): - sys.path.insert(0, '') - - # Fire up the kernel instance. - from ipykernel.kernelapp import IPKernelApp - - if not os.environ.get('SPY_EXTERNAL_INTERPRETER') == "True": - from spyder.utils.ipython.spyder_kernel import SpyderKernel - else: - # We add "spyder" to sys.path for external interpreters, - # so this works! - # See create_kernel_spec of plugins/ipythonconsole - from utils.ipython.spyder_kernel import SpyderKernel - - kernel = IPKernelApp.instance() - kernel.kernel_class = SpyderKernel - try: - kernel.config = kernel_config() - except: - pass - kernel.initialize() - - # Set our own magics - kernel.shell.register_magic_function(varexp) - - # Set Pdb class to be used by %debug and %pdb. - # This makes IPython consoles to use the class defined in our - # sitecustomize instead of their default one. - import pdb - kernel.shell.InteractiveTB.debugger_cls = pdb.Pdb - - # Start the (infinite) kernel event loop. - kernel.start() - - -if __name__ == '__main__': - main() diff --git a/spyder/widgets/ipythonconsole/debugging.py b/spyder/widgets/ipythonconsole/debugging.py index ce3fe0913a1..5fd1297c43f 100644 --- a/spyder/widgets/ipythonconsole/debugging.py +++ b/spyder/widgets/ipythonconsole/debugging.py @@ -45,8 +45,7 @@ def refresh_from_pdb(self, pdb_state): Refresh Variable Explorer and Editor from a Pdb session, after running any pdb command. - See publish_pdb_state in utils/ipython/spyder_kernel.py and - notify_spyder in utils/site/sitecustomize.py and + See publish_pdb_state and notify_spyder in spyder_kernels """ if 'step' in pdb_state and 'fname' in pdb_state['step']: fname = pdb_state['step']['fname'] From 7630e7a65abe1313414269d4e51833301837d497 Mon Sep 17 00:00:00 2001 From: dalthviz Date: Mon, 18 Jun 2018 18:26:55 -0500 Subject: [PATCH 11/30] Add spyder-kernels to CIs and setup. Change dependencies message for spyder-kernels. --- appveyor.yml | 2 +- continuous_integration/circle/install.sh | 3 ++- continuous_integration/travis/install.sh | 2 +- requirements/requirements.txt | 1 + setup.py | 3 ++- spyder/plugins/ipythonconsole.py | 31 ++++++++++-------------- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 752b57ed07a..3d95cf43497 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ environment: rope pyflakes sphinx pygments pylint pycodestyle psutil nbconvert qtawesome cloudpickle pickleshare pyzmq chardet mock pandas pytest pytest-cov numpydoc scipy pillow qtconsole matplotlib jedi pywin32 - PIP_DEPENDENCIES: "pytest-qt pytest-mock pytest-timeout flaky codecov" + PIP_DEPENDENCIES: "pytest-qt pytest-mock pytest-timeout flaky codecov spyder-kernels" matrix: - PYTHON_VERSION: "2.7" diff --git a/continuous_integration/circle/install.sh b/continuous_integration/circle/install.sh index 9e30aa83d1c..e2f11c82991 100755 --- a/continuous_integration/circle/install.sh +++ b/continuous_integration/circle/install.sh @@ -4,7 +4,8 @@ export CONDA_DEPENDENCIES_FLAGS="--quiet" export CONDA_DEPENDENCIES="rope pyflakes sphinx pygments pylint psutil nbconvert \ qtawesome pickleshare qtpy pyzmq chardet mock nomkl pandas \ pytest pytest-cov numpydoc scipy cython pillow cloudpickle" -export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-xvfb flaky jedi pycodestyle" +export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-xvfb flaky jedi pycodestyle \ + spyder-kernels" # Download and install miniconda and conda/pip dependencies # with astropy helpers diff --git a/continuous_integration/travis/install.sh b/continuous_integration/travis/install.sh index b0fa91c2c61..0826b2bfe82 100755 --- a/continuous_integration/travis/install.sh +++ b/continuous_integration/travis/install.sh @@ -9,7 +9,7 @@ else export CONDA_DEPENDENCIES="rope pyflakes sphinx pygments pylint psutil nbconvert \ qtawesome cloudpickle pickleshare qtpy pyzmq chardet mock nomkl pandas \ pytest pytest-cov numpydoc scipy cython pillow jedi pycodestyle" - export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-timeout flaky" + export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-timeout flaky spyder-kernels" fi diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 4adec53c468..497b3d31884 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -16,3 +16,4 @@ pyzmq chardet>=2.0.0 numpydoc pyqt5 +spyder-kernels<1.0 diff --git a/setup.py b/setup.py index 5dcd3029fdb..a6eca797244 100644 --- a/setup.py +++ b/setup.py @@ -208,7 +208,8 @@ def run(self): 'numpydoc', # Packages for pyqt5 are only available in # Python 3 - 'pyqt5<5.10;python_version>="3"' + 'pyqt5<5.10;python_version>="3"', + 'spyder-kernels<1.0' ] extras_require = { diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index 9c75f264d95..79daffd8966 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -1076,24 +1076,19 @@ def create_new_client(self, give_focus=True, filename='', is_cython=False): # Else we won't be able to create a client if not CONF.get('main_interpreter', 'default'): pyexec = CONF.get('main_interpreter', 'executable') - has_ipykernel = programs.is_module_installed('ipykernel', - interpreter=pyexec) - has_cloudpickle = programs.is_module_installed('cloudpickle', - interpreter=pyexec) - if not (has_ipykernel and has_cloudpickle): - client.show_kernel_error(_("Your Python environment or " - "installation doesn't " - "have the ipykernel and " - "cloudpickle modules " - "installed on it. Without these modules " - "is not possible for Spyder to create a " - "console for you.

" - "You can install them by running " - "in a system terminal:

" - "pip install ipykernel cloudpickle" - "

" - "or

" - "conda install ipykernel cloudpickle")) + has_spyder_kernels = programs.is_module_installed( + 'spyder_kernels', + interpreter=pyexec) + if not has_spyder_kernels: + client.show_kernel_error( + _("Your Python environment or installation doesn't " + "have the spyder-kernels module installed " + "on it. Without this module is not possible for " + "Spyder to create a console for you.

You " + "can install them by running in a system terminal" + ":

pip install spyder-kernels" + "

or

" + "conda install spyder-kernels")) return self.connect_client_to_kernel(client, is_cython=is_cython) From 2c668e79e3460e438972b95fd422a1b2b9682485 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 19 Jun 2018 08:26:18 -0500 Subject: [PATCH 12/30] Testing: Move installation of spyder-kernels to be at the end of the install phase in our CIs --- appveyor.yml | 4 +++- continuous_integration/travis/install-qt.sh | 3 +++ continuous_integration/travis/install.sh | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3d95cf43497..be300c64360 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ environment: rope pyflakes sphinx pygments pylint pycodestyle psutil nbconvert qtawesome cloudpickle pickleshare pyzmq chardet mock pandas pytest pytest-cov numpydoc scipy pillow qtconsole matplotlib jedi pywin32 - PIP_DEPENDENCIES: "pytest-qt pytest-mock pytest-timeout flaky codecov spyder-kernels" + PIP_DEPENDENCIES: "pytest-qt pytest-mock pytest-timeout flaky codecov" matrix: - PYTHON_VERSION: "2.7" @@ -47,6 +47,8 @@ install: - "conda install jupyter_client=5.2.2" # Fix problems with latest pyqt - "conda install pyqt=5.6*" + # Install spyder-kernels + - "pip install -q --no-deps spyder-kernels" build: false diff --git a/continuous_integration/travis/install-qt.sh b/continuous_integration/travis/install-qt.sh index a4432d4ed43..1ad3e726c1d 100755 --- a/continuous_integration/travis/install-qt.sh +++ b/continuous_integration/travis/install-qt.sh @@ -10,8 +10,11 @@ if [ "$USE_CONDA" = "no" ]; then # Install qtpy from Github pip install git+https://github.com/spyder-ide/qtpy.git + pip install -q spyder-kernels elif [ "$USE_PYQT" = "pyqt5" ]; then conda install -q qt=5.* pyqt=5.* qtconsole matplotlib + pip install -q --no-deps spyder-kernels else conda install -q qt=4.* pyqt=4.* qtconsole matplotlib + pip install -q --no-deps spyder-kernels fi diff --git a/continuous_integration/travis/install.sh b/continuous_integration/travis/install.sh index 0826b2bfe82..b0fa91c2c61 100755 --- a/continuous_integration/travis/install.sh +++ b/continuous_integration/travis/install.sh @@ -9,7 +9,7 @@ else export CONDA_DEPENDENCIES="rope pyflakes sphinx pygments pylint psutil nbconvert \ qtawesome cloudpickle pickleshare qtpy pyzmq chardet mock nomkl pandas \ pytest pytest-cov numpydoc scipy cython pillow jedi pycodestyle" - export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-timeout flaky spyder-kernels" + export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-timeout flaky" fi From 32fb7d1b046caba68c29925fc01dde6b4ac1e8ef Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 19 Jun 2018 11:31:33 -0500 Subject: [PATCH 13/30] Remove utils.site --- spyder/utils/site/__init__.py | 16 - spyder/utils/site/osx_app_site.py | 163 ------ spyder/utils/site/sitecustomize.py | 768 ----------------------------- 3 files changed, 947 deletions(-) delete mode 100644 spyder/utils/site/__init__.py delete mode 100644 spyder/utils/site/osx_app_site.py delete mode 100644 spyder/utils/site/sitecustomize.py diff --git a/spyder/utils/site/__init__.py b/spyder/utils/site/__init__.py deleted file mode 100644 index c3976ea80b6..00000000000 --- a/spyder/utils/site/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -""" -spyder.utils.site -================= - -Site packages for Spyder consoles - -NOTE: This package shouldn't be imported at **any** place. - It's only used to set additional functionality for - our consoles. -""" diff --git a/spyder/utils/site/osx_app_site.py b/spyder/utils/site/osx_app_site.py deleted file mode 100644 index 953278629ec..00000000000 --- a/spyder/utils/site/osx_app_site.py +++ /dev/null @@ -1,163 +0,0 @@ -# -# IMPORTANT NOTE: Don't add a coding line here! It's not necessary for -# site files -# -# Spyder's MacOS X App site.py additions -# -# It includes missing variables and paths that are not added by -# py2app to its own site.py -# -# These functions were taken verbatim from Python 2.7.3 site.py -# - -import sys -import os -try: - import __builtin__ as builtins -except ImportError: - # Python 3 - import builtins - -# for distutils.commands.install -# These values are initialized by the getuserbase() and getusersitepackages() -# functions. -USER_SITE = None -USER_BASE = None - - -def getuserbase(): - """Returns the `user base` directory path. - - The `user base` directory can be used to store data. If the global - variable ``USER_BASE`` is not initialized yet, this function will also set - it. - """ - global USER_BASE - if USER_BASE is not None: - return USER_BASE - from sysconfig import get_config_var - USER_BASE = get_config_var('userbase') - return USER_BASE - -def getusersitepackages(): - """Returns the user-specific site-packages directory path. - - If the global variable ``USER_SITE`` is not initialized yet, this - function will also set it. - """ - global USER_SITE - user_base = getuserbase() # this will also set USER_BASE - - if USER_SITE is not None: - return USER_SITE - - from sysconfig import get_path - - if sys.platform == 'darwin': - from sysconfig import get_config_var - if get_config_var('PYTHONFRAMEWORK'): - USER_SITE = get_path('purelib', 'osx_framework_user') - return USER_SITE - - USER_SITE = get_path('purelib', '%s_user' % os.name) - return USER_SITE - - -class _Printer(object): - """interactive prompt objects for printing the license text, a list of - contributors and the copyright notice.""" - - MAXLINES = 23 - - def __init__(self, name, data, files=(), dirs=()): - self.__name = name - self.__data = data - self.__files = files - self.__dirs = dirs - self.__lines = None - - def __setup(self): - if self.__lines: - return - data = None - for dir in self.__dirs: - for filename in self.__files: - filename = os.path.join(dir, filename) - try: - fp = open(filename, "rU") - data = fp.read() - fp.close() - break - except IOError: - pass - if data: - break - if not data: - data = self.__data - self.__lines = data.split('\n') - self.__linecnt = len(self.__lines) - - def __repr__(self): - self.__setup() - if len(self.__lines) <= self.MAXLINES: - return "\n".join(self.__lines) - else: - return "Type %s() to see the full %s text" % ((self.__name,)*2) - - def __call__(self): - self.__setup() - prompt = 'Hit Return for more, or q (and Return) to quit: ' - lineno = 0 - while 1: - try: - for i in range(lineno, lineno + self.MAXLINES): - print(self.__lines[i]) # spyder: test-skip - except IndexError: - break - else: - lineno += self.MAXLINES - key = None - while key is None: - try: - key = raw_input(prompt) - except NameError: - # Python 3 - key = input(prompt) - if key not in ('', 'q'): - key = None - if key == 'q': - break - -def setcopyright(): - """Set 'copyright' and 'credits' in builtins""" - builtins.copyright = _Printer("copyright", sys.copyright) - if sys.platform[:4] == 'java': - builtins.credits = _Printer( - "credits", - "Jython is maintained by the Jython developers (www.jython.org).") - else: - builtins.credits = _Printer("credits", """\ - Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands - for supporting Python development. See www.python.org for more information.""") - here = os.path.dirname(os.__file__) - builtins.license = _Printer( - "license", "See http://www.python.org/%.3s/license.html" % sys.version, - ["LICENSE.txt", "LICENSE"], - [os.path.join(here, os.pardir), here, os.curdir]) - - -class _Helper(object): - """Define the builtin 'help'. - This is a wrapper around pydoc.help (with a twist). - - """ - - def __repr__(self): - return "Type help() for interactive help, " \ - "or help(object) for help about object." - def __call__(self, *args, **kwds): - import pydoc - return pydoc.help(*args, **kwds) - -def sethelper(): - builtins.help = _Helper() diff --git a/spyder/utils/site/sitecustomize.py b/spyder/utils/site/sitecustomize.py deleted file mode 100644 index 5bb7adf0103..00000000000 --- a/spyder/utils/site/sitecustomize.py +++ /dev/null @@ -1,768 +0,0 @@ -# -# Copyright (c) Spyder Project Contributors) -# Licensed under the terms of the MIT License) -# (see spyder/__init__.py for details) -# -# IMPORTANT NOTE: Don't add a coding line here! It's not necessary for -# site files -# -# Spyder consoles sitecustomize -# - -import bdb -from distutils.version import LooseVersion -import io -import os -import os.path as osp -import pdb -import shlex -import sys -import time -import warnings - -PY2 = sys.version[0] == '2' - - -#============================================================================== -# sys.argv can be missing when Python is embedded, taking care of it. -# Fixes Issue 1473 and other crazy crashes with IPython 0.13 trying to -# access it. -#============================================================================== -if not hasattr(sys, 'argv'): - sys.argv = [''] - - -#============================================================================== -# Main constants -#============================================================================== -IS_EXT_INTERPRETER = os.environ.get('SPY_EXTERNAL_INTERPRETER') == "True" - - -#============================================================================== -# Important Note: -# -# We avoid importing spyder here, so we are handling Python 3 compatiblity -# by hand. -#============================================================================== -def _print(*objects, **options): - end = options.get('end', '\n') - file = options.get('file', sys.stdout) - sep = options.get('sep', ' ') - string = sep.join([str(obj) for obj in objects]) - if not PY2: - # Python 3 - local_dict = {} - exec('printf = print', local_dict) # to avoid syntax error in Python 2 - local_dict['printf'](string, file=file, end=end, sep=sep) - else: - # Python 2 - if end: - print >>file, string - else: - print >>file, string, - - -#============================================================================== -# Execfile functions -# -# The definitions for Python 2 on Windows were taken from the IPython project -# Copyright (C) The IPython Development Team -# Distributed under the terms of the modified BSD license -#============================================================================== -try: - # Python 2 - import __builtin__ as builtins - if os.name == 'nt': - def encode(u): - return u.encode('utf8', 'replace') - def execfile(fname, glob=None, loc=None): - loc = loc if (loc is not None) else glob - scripttext = builtins.open(fname).read()+ '\n' - # compile converts unicode filename to str assuming - # ascii. Let's do the conversion before calling compile - if isinstance(fname, unicode): - filename = encode(fname) - else: - filename = fname - exec(compile(scripttext, filename, 'exec'), glob, loc) - else: - def execfile(fname, *where): - if isinstance(fname, unicode): - filename = fname.encode(sys.getfilesystemencoding()) - else: - filename = fname - builtins.execfile(filename, *where) -except ImportError: - # Python 3 - import builtins - basestring = (str,) - def execfile(filename, namespace): - # Open a source file correctly, whatever its encoding is - with open(filename, 'rb') as f: - exec(compile(f.read(), filename, 'exec'), namespace) - - -#============================================================================== -# Prepending this spyder package's path to sys.path to be sure -# that another version of spyder won't be imported instead: -#============================================================================== -spyder_path = osp.dirname(__file__) -while not osp.isdir(osp.join(spyder_path, 'spyder')): - spyder_path = osp.abspath(osp.join(spyder_path, os.pardir)) -if not spyder_path.startswith(sys.prefix): - # Spyder is not installed: moving its parent directory to the top of - # sys.path to be sure that this spyder package will be imported in - # the remote process (instead of another installed version of Spyder) - while spyder_path in sys.path: - sys.path.remove(spyder_path) - sys.path.insert(0, spyder_path) -os.environ['SPYDER_PARENT_DIR'] = spyder_path - - -#============================================================================== -# Setting console encoding (otherwise Python does not recognize encoding) -# for Windows platforms -#============================================================================== -if os.name == 'nt' and PY2: - try: - import locale, ctypes - _t, _cp = locale.getdefaultlocale('LANG') - try: - _cp = int(_cp[2:]) - ctypes.windll.kernel32.SetConsoleCP(_cp) - ctypes.windll.kernel32.SetConsoleOutputCP(_cp) - except (ValueError, TypeError): - # Code page number in locale is not valid - pass - except: - pass - - -#============================================================================== -# Settings for our MacOs X app -#============================================================================== -# FIXME: If/when we create new apps we need to revisit this! -if sys.platform == 'darwin': - from spyder.config.base import MAC_APP_NAME - if MAC_APP_NAME in __file__: - if IS_EXT_INTERPRETER: - # Add a minimal library (with spyder) at the end of sys.path to - # be able to connect our monitor to the external console - py_ver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - app_pythonpath = '%s/Contents/Resources/lib/python%s' % (MAC_APP_NAME, - py_ver) - full_pythonpath = [p for p in sys.path if p.endswith(app_pythonpath)] - if full_pythonpath: - sys.path.remove(full_pythonpath[0]) - sys.path.append(full_pythonpath[0] + osp.sep + 'minimal-lib') - else: - # Add missing variables and methods to the app's site module - import site - import osx_app_site - osx_app_site.setcopyright() - osx_app_site.sethelper() - site._Printer = osx_app_site._Printer - site.USER_BASE = osx_app_site.getuserbase() - site.USER_SITE = osx_app_site.getusersitepackages() - - -#============================================================================== -# Cython support -#============================================================================== -RUN_CYTHON = os.environ.get("SPY_RUN_CYTHON") == "True" -HAS_CYTHON = False - -if RUN_CYTHON: - try: - __import__('Cython') - HAS_CYTHON = True - except Exception: - pass - - if HAS_CYTHON: - # Import pyximport to enable Cython files support for - # import statement - import pyximport - pyx_setup_args = {} - - # Add Numpy include dir to pyximport/distutils - try: - import numpy - pyx_setup_args['include_dirs'] = numpy.get_include() - except Exception: - pass - - # Setup pyximport and enable Cython files reload - pyximport.install(setup_args=pyx_setup_args, reload_support=True) - - -#============================================================================== -# Prevent subprocess.Popen calls to create visible console windows on Windows. -# See issue #4932 -#============================================================================== -if os.name == 'nt': - import subprocess - creation_flag = 0x08000000 # CREATE_NO_WINDOW - - class SubprocessPopen(subprocess.Popen): - def __init__(self, *args, **kwargs): - kwargs['creationflags'] = creation_flag - super(SubprocessPopen, self).__init__(*args, **kwargs) - - subprocess.Popen = SubprocessPopen - -#============================================================================== -# Importing user's sitecustomize -#============================================================================== -try: - import sitecustomize #analysis:ignore -except: - pass - - -#============================================================================== -# Add default filesystem encoding on Linux to avoid an error with -# Matplotlib 1.5 in Python 2 (Fixes Issue 2793) -#============================================================================== -if PY2 and sys.platform.startswith('linux'): - def _getfilesystemencoding_wrapper(): - return 'utf-8' - - sys.getfilesystemencoding = _getfilesystemencoding_wrapper - - -#============================================================================== -# Set PyQt API to #2 -#============================================================================== -if os.environ["QT_API"] == 'pyqt': - try: - import sip - for qtype in ('QString', 'QVariant', 'QDate', 'QDateTime', - 'QTextStream', 'QTime', 'QUrl'): - sip.setapi(qtype, 2) - except: - pass -else: - os.environ.pop('QT_API') - - -#============================================================================== -# IPython kernel adjustments -#============================================================================== -# Use ipydb as the debugger to patch on IPython consoles -from IPython.core.debugger import Pdb as ipyPdb -pdb.Pdb = ipyPdb - -# Patch unittest.main so that errors are printed directly in the console. -# See http://comments.gmane.org/gmane.comp.python.ipython.devel/10557 -# Fixes Issue 1370 -import unittest -from unittest import TestProgram -class IPyTesProgram(TestProgram): - def __init__(self, *args, **kwargs): - test_runner = unittest.TextTestRunner(stream=sys.stderr) - kwargs['testRunner'] = kwargs.pop('testRunner', test_runner) - kwargs['exit'] = False - TestProgram.__init__(self, *args, **kwargs) -unittest.main = IPyTesProgram - -# Patch ipykernel to avoid errors when setting the Qt5 Matplotlib -# backemd -# Fixes Issue 6091 -import ipykernel -import IPython -if LooseVersion(ipykernel.__version__) <= LooseVersion('4.7.0'): - if ((PY2 and LooseVersion(IPython.__version__) >= LooseVersion('5.5.0')) or - (not PY2 and LooseVersion(IPython.__version__) >= LooseVersion('6.2.0')) - ): - from ipykernel import eventloops - eventloops.loop_map['qt'] = eventloops.loop_map['qt5'] - - -#============================================================================== -# Pandas adjustments -#============================================================================== -try: - import pandas as pd - - # Set Pandas output encoding - pd.options.display.encoding = 'utf-8' - - # Filter warning that appears for DataFrames with np.nan values - # Example: - # >>> import pandas as pd, numpy as np - # >>> pd.Series([np.nan,np.nan,np.nan],index=[1,2,3]) - # Fixes Issue 2991 - # For 0.18- - warnings.filterwarnings(action='ignore', category=RuntimeWarning, - module='pandas.core.format', - message=".*invalid value encountered in.*") - # For 0.18.1+ - warnings.filterwarnings(action='ignore', category=RuntimeWarning, - module='pandas.formats.format', - message=".*invalid value encountered in.*") -except: - pass - - -# ============================================================================= -# Numpy adjustments -# ============================================================================= -try: - # Filter warning that appears when users have 'Show max/min' - # turned on and Numpy arrays contain a nan value. - # Fixes Issue 7063 - # Note: It only happens in Numpy 1.14+ - warnings.filterwarnings(action='ignore', category=RuntimeWarning, - module='numpy.core._methods', - message=".*invalid value encountered in.*") -except: - pass - - -#============================================================================== -# Pdb adjustments -#============================================================================== -class SpyderPdb(pdb.Pdb): - - send_initial_notification = True - starting = True - - def set_spyder_breakpoints(self): - self.clear_all_breaks() - #------Really deleting all breakpoints: - for bp in bdb.Breakpoint.bpbynumber: - if bp: - bp.deleteMe() - bdb.Breakpoint.next = 1 - bdb.Breakpoint.bplist = {} - bdb.Breakpoint.bpbynumber = [None] - #------ - from spyder.config.main import CONF - CONF.load_from_ini() - if CONF.get('run', 'breakpoints/enabled', True): - breakpoints = CONF.get('run', 'breakpoints', {}) - i = 0 - for fname, data in list(breakpoints.items()): - for linenumber, condition in data: - i += 1 - self.set_break(self.canonic(fname), linenumber, - cond=condition) - - def notify_spyder(self, frame): - if not frame: - return - - from IPython.core.getipython import get_ipython - kernel = get_ipython().kernel - - # Get filename and line number of the current frame - fname = self.canonic(frame.f_code.co_filename) - if PY2: - try: - fname = unicode(fname, "utf-8") - except TypeError: - pass - lineno = frame.f_lineno - - # Jump to first breakpoint. - # Fixes issue 2034 - if self.starting: - # Only run this after a Pdb session is created - self.starting = False - - # Get all breakpoints for the file we're going to debug - breaks = self.get_file_breaks(frame.f_code.co_filename) - - # Do 'continue' if the first breakpoint is *not* placed - # where the debugger is going to land. - # Fixes issue 4681 - if breaks and lineno != breaks[0] and osp.isfile(fname): - kernel.pdb_continue() - - # Set step of the current frame (if any) - step = {} - if isinstance(fname, basestring) and isinstance(lineno, int): - if osp.isfile(fname): - step = dict(fname=fname, lineno=lineno) - - # Publish Pdb state so we can update the Variable Explorer - # and the Editor on the Spyder side - kernel._pdb_step = step - kernel.publish_pdb_state() - -pdb.Pdb = SpyderPdb - - -#XXX: I know, this function is now also implemented as is in utils/misc.py but -# I'm kind of reluctant to import spyder in sitecustomize, even if this -# import is very clean. -def monkeypatch_method(cls, patch_name): - # This function's code was inspired from the following thread: - # "[Python-Dev] Monkeypatching idioms -- elegant or ugly?" - # by Robert Brewer - # (Tue Jan 15 19:13:25 CET 2008) - """ - Add the decorated method to the given class; replace as needed. - - If the named method already exists on the given class, it will - be replaced, and a reference to the old method is created as - cls._old. If the "_old__" attribute - already exists, KeyError is raised. - """ - def decorator(func): - fname = func.__name__ - old_func = getattr(cls, fname, None) - if old_func is not None: - # Add the old func to a list of old funcs. - old_ref = "_old_%s_%s" % (patch_name, fname) - - old_attr = getattr(cls, old_ref, None) - if old_attr is None: - setattr(cls, old_ref, old_func) - else: - raise KeyError("%s.%s already exists." - % (cls.__name__, old_ref)) - setattr(cls, fname, func) - return func - return decorator - - -@monkeypatch_method(pdb.Pdb, 'Pdb') -def __init__(self, completekey='tab', stdin=None, stdout=None, - skip=None, nosigint=False): - self._old_Pdb___init__() - - -@monkeypatch_method(pdb.Pdb, 'Pdb') -def user_return(self, frame, return_value): - """This function is called when a return trap is set here.""" - # This is useful when debugging in an active interpreter (otherwise, - # the debugger will stop before reaching the target file) - if self._wait_for_mainpyfile: - if (self.mainpyfile != self.canonic(frame.f_code.co_filename) - or frame.f_lineno<= 0): - return - self._wait_for_mainpyfile = 0 - self._old_Pdb_user_return(frame, return_value) - - -@monkeypatch_method(pdb.Pdb, 'Pdb') -def interaction(self, frame, traceback): - if frame is not None and "sitecustomize.py" in frame.f_code.co_filename: - self.run('exit') - else: - self.setup(frame, traceback) - if self.send_initial_notification: - self.notify_spyder(frame) - self.print_stack_entry(self.stack[self.curindex]) - self._cmdloop() - self.forget() - - -@monkeypatch_method(pdb.Pdb, 'Pdb') -def _cmdloop(self): - while True: - try: - # keyboard interrupts allow for an easy way to cancel - # the current command, so allow them during interactive input - self.allow_kbdint = True - self.cmdloop() - self.allow_kbdint = False - break - except KeyboardInterrupt: - _print("--KeyboardInterrupt--\n" - "For copying text while debugging, use Ctrl+Shift+C", - file=self.stdout) - - -@monkeypatch_method(pdb.Pdb, 'Pdb') -def reset(self): - self._old_Pdb_reset() - - from IPython.core.getipython import get_ipython - kernel = get_ipython().kernel - kernel._register_pdb_session(self) - self.set_spyder_breakpoints() - - -#XXX: notify spyder on any pdb command (is that good or too lazy? i.e. is more -# specific behaviour desired?) -@monkeypatch_method(pdb.Pdb, 'Pdb') -def postcmd(self, stop, line): - if line != "!get_ipython().kernel._set_spyder_breakpoints()": - self.notify_spyder(self.curframe) - return self._old_Pdb_postcmd(stop, line) - - -# Breakpoints don't work for files with non-ascii chars in Python 2 -# Fixes Issue 1484 -if PY2: - @monkeypatch_method(pdb.Pdb, 'Pdb') - def break_here(self, frame): - from bdb import effective - filename = self.canonic(frame.f_code.co_filename) - try: - filename = unicode(filename, "utf-8") - except TypeError: - pass - if not filename in self.breaks: - return False - lineno = frame.f_lineno - if not lineno in self.breaks[filename]: - # The line itself has no breakpoint, but maybe the line is the - # first line of a function with breakpoint set by function name. - lineno = frame.f_code.co_firstlineno - if not lineno in self.breaks[filename]: - return False - - # flag says ok to delete temp. bp - (bp, flag) = effective(filename, lineno, frame) - if bp: - self.currentbp = bp.number - if (flag and bp.temporary): - self.do_clear(str(bp.number)) - return True - else: - return False - - -#============================================================================== -# Restoring (almost) original sys.path: -# -# NOTE: do not remove spyder_path from sys.path because if Spyder has been -# installed using python setup.py install, then this could remove the -# 'site-packages' directory from sys.path! -#============================================================================== -try: - sys.path.remove(osp.join(spyder_path, "spyder", "utils", "site")) -except ValueError: - pass - - -#============================================================================== -# User module reloader -#============================================================================== -class UserModuleReloader(object): - """ - User Module Reloader (UMR) aims at deleting user modules - to force Python to deeply reload them during import - - pathlist [list]: blacklist in terms of module path - namelist [list]: blacklist in terms of module name - """ - def __init__(self, namelist=None, pathlist=None): - if namelist is None: - namelist = [] - spy_modules = ['sitecustomize', 'spyder', 'spyderplugins'] - mpl_modules = ['matplotlib', 'tkinter', 'Tkinter'] - # Add other, necessary modules to the UMR blacklist - # astropy: see issue 6962 - # pytorch: see issue 7041 - # fastmat: see issue 7190 - # pythoncom: see issue 7190 - other_modules = ['pytorch', 'pythoncom'] - if PY2: - py2_modules = ['astropy', 'fastmat'] - other_modules = other_modules + py2_modules - self.namelist = namelist + spy_modules + mpl_modules + other_modules - - if pathlist is None: - pathlist = [] - self.pathlist = pathlist - self.previous_modules = list(sys.modules.keys()) - - def is_module_blacklisted(self, modname, modpath): - if HAS_CYTHON: - # Don't return cached inline compiled .PYX files - return True - for path in [sys.prefix]+self.pathlist: - if modpath.startswith(path): - return True - else: - return set(modname.split('.')) & set(self.namelist) - - def run(self, verbose=False): - """ - Del user modules to force Python to deeply reload them - - Do not del modules which are considered as system modules, i.e. - modules installed in subdirectories of Python interpreter's binary - Do not del C modules - """ - log = [] - for modname, module in list(sys.modules.items()): - if modname not in self.previous_modules: - modpath = getattr(module, '__file__', None) - if modpath is None: - # *module* is a C module that is statically linked into the - # interpreter. There is no way to know its path, so we - # choose to ignore it. - continue - if not self.is_module_blacklisted(modname, modpath): - log.append(modname) - del sys.modules[modname] - if verbose and log: - _print("\x1b[4;33m%s\x1b[24m%s\x1b[0m"\ - % ("Reloaded modules", ": "+", ".join(log))) - -__umr__ = None - - -#============================================================================== -# Handle Post Mortem Debugging and Traceback Linkage to Spyder -#============================================================================== -def clear_post_mortem(): - """ - Remove the post mortem excepthook and replace with a standard one. - """ - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - ipython_shell.set_custom_exc((), None) - - -def post_mortem_excepthook(type, value, tb): - """ - For post mortem exception handling, print a banner and enable post - mortem debugging. - """ - clear_post_mortem() - - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - ipython_shell.showtraceback((type, value, tb)) - p = pdb.Pdb(ipython_shell.colors) - - if not type == SyntaxError: - # wait for stderr to print (stderr.flush does not work in this case) - time.sleep(0.1) - _print('*' * 40) - _print('Entering post mortem debugging...') - _print('*' * 40) - # add ability to move between frames - p.send_initial_notification = False - p.reset() - frame = tb.tb_frame - prev = frame - while frame.f_back: - prev = frame - frame = frame.f_back - frame = prev - # wait for stdout to print - time.sleep(0.1) - p.interaction(frame, tb) - - -def set_post_mortem(): - """ - Enable the post mortem debugging excepthook. - """ - from IPython.core.getipython import get_ipython - def ipython_post_mortem_debug(shell, etype, evalue, tb, - tb_offset=None): - post_mortem_excepthook(etype, evalue, tb) - ipython_shell = get_ipython() - ipython_shell.set_custom_exc((Exception,), ipython_post_mortem_debug) - -# Add post mortem debugging if requested and in a dedicated interpreter -# existing interpreters use "runfile" below -if "SPYDER_EXCEPTHOOK" in os.environ: - set_post_mortem() - - -#============================================================================== -# runfile and debugfile commands -#============================================================================== -def _get_globals(): - """Return current namespace""" - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - return ipython_shell.user_ns - - -def runfile(filename, args=None, wdir=None, namespace=None, post_mortem=False): - """ - Run filename - args: command line arguments (string) - wdir: working directory - post_mortem: boolean, whether to enter post-mortem mode on error - """ - try: - filename = filename.decode('utf-8') - except (UnicodeError, TypeError, AttributeError): - # UnicodeError, TypeError --> eventually raised in Python 2 - # AttributeError --> systematically raised in Python 3 - pass - global __umr__ - if os.environ.get("SPY_UMR_ENABLED", "").lower() == "true": - if __umr__ is None: - namelist = os.environ.get("SPY_UMR_NAMELIST", None) - if namelist is not None: - namelist = namelist.split(',') - __umr__ = UserModuleReloader(namelist=namelist) - else: - verbose = os.environ.get("SPY_UMR_VERBOSE", "").lower() == "true" - __umr__.run(verbose=verbose) - if args is not None and not isinstance(args, basestring): - raise TypeError("expected a character buffer object") - if namespace is None: - namespace = _get_globals() - namespace['__file__'] = filename - sys.argv = [filename] - if args is not None: - for arg in shlex.split(args): - sys.argv.append(arg) - if wdir is not None: - try: - wdir = wdir.decode('utf-8') - except (UnicodeError, TypeError, AttributeError): - # UnicodeError, TypeError --> eventually raised in Python 2 - # AttributeError --> systematically raised in Python 3 - pass - os.chdir(wdir) - if post_mortem: - set_post_mortem() - if HAS_CYTHON: - # Cython files - with io.open(filename, encoding='utf-8') as f: - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - ipython_shell.run_cell_magic('cython', '', f.read()) - else: - execfile(filename, namespace) - - clear_post_mortem() - sys.argv = [''] - namespace.pop('__file__') - -builtins.runfile = runfile - - -def debugfile(filename, args=None, wdir=None, post_mortem=False): - """ - Debug filename - args: command line arguments (string) - wdir: working directory - post_mortem: boolean, included for compatiblity with runfile - """ - debugger = pdb.Pdb() - filename = debugger.canonic(filename) - debugger._wait_for_mainpyfile = 1 - debugger.mainpyfile = filename - debugger._user_requested_quit = 0 - if os.name == 'nt': - filename = filename.replace('\\', '/') - debugger.run("runfile(%r, args=%r, wdir=%r)" % (filename, args, wdir)) - -builtins.debugfile = debugfile - - -#============================================================================== -# Restoring original PYTHONPATH -#============================================================================== -try: - os.environ['PYTHONPATH'] = os.environ['OLD_PYTHONPATH'] - del os.environ['OLD_PYTHONPATH'] -except KeyError: - if os.environ.get('PYTHONPATH') is not None: - del os.environ['PYTHONPATH'] From 37fab3556e1aa0e5cf31ccef1b567fe133d44c3c Mon Sep 17 00:00:00 2001 From: dalthviz Date: Tue, 19 Jun 2018 23:12:24 -0500 Subject: [PATCH 14/30] Prevent spyder-kernels to be import as a third-party plugin. --- spyder/otherplugins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spyder/otherplugins.py b/spyder/otherplugins.py index 67e0b596592..6be35ce9236 100644 --- a/spyder/otherplugins.py +++ b/spyder/otherplugins.py @@ -60,7 +60,8 @@ def _get_spyderplugins(plugin_path, is_io, modnames, modlist): continue # Skip names that end in certain suffixes - forbidden_suffixes = ['dist-info', 'egg.info', 'egg-info', 'egg-link'] + forbidden_suffixes = ['dist-info', 'egg.info', 'egg-info', 'egg-link', + 'kernels'] if any([name.endswith(s) for s in forbidden_suffixes]): continue From 339bedde5390e7f2caa702c7a870ace488fadcde Mon Sep 17 00:00:00 2001 From: dalthviz Date: Wed, 20 Jun 2018 00:32:23 -0500 Subject: [PATCH 15/30] Testing waitSignal. --- spyder/app/tests/test_mainwindow.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index 2fb8c766d6c..e3277639a27 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -237,11 +237,10 @@ def test_filter_numpy_warning(main_window, qtbot): """ shell = main_window.ipyconsole.get_current_shellwidget() control = shell._control - qtbot.waitUntil(lambda: shell._prompt_html is not None, - timeout=SHELL_TIMEOUT) - - # Create an array with a nan value - with qtbot.waitSignal(shell.executed): + with qtbot.waitSignal(shell.sig_prompt_ready, timeout=SHELL_TIMEOUT): + assert shell._prompt_html + + with qtbot.waitSignal(shell.executed, timeout=SHELL_TIMEOUT): shell.execute('import numpy as np; A=np.full(16, np.nan)') qtbot.wait(1000) From 9128cbd5c8ae1f2ed6752870cf13593ef392a719 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 20 Jun 2018 11:06:33 -0500 Subject: [PATCH 16/30] Kernel spec: Simplify how we start kernels --- spyder/utils/ipython/kernelspec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder/utils/ipython/kernelspec.py b/spyder/utils/ipython/kernelspec.py index aba33820eb0..1f01b9a7609 100644 --- a/spyder/utils/ipython/kernelspec.py +++ b/spyder/utils/ipython/kernelspec.py @@ -61,10 +61,10 @@ def argv(self): pyexec = pyexec_w # Command used to start kernels - console_path = osp.join(self.spykernel_path, 'console') kernel_cmd = [ pyexec, - osp.join("%s" % console_path, "start.py"), + '-m', + 'spyder_kernels.console', '-f', '{connection_file}' ] From 09f54726eb3a5834858bc80fb712a327e3f3e767 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 20 Jun 2018 13:49:53 -0500 Subject: [PATCH 17/30] Kernelspec: Don't add the kernel sitecustomize path to PYTHONPATH This is not needed anymore with spyder-kernels 0.2.0 --- spyder/utils/ipython/kernelspec.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/spyder/utils/ipython/kernelspec.py b/spyder/utils/ipython/kernelspec.py index 1f01b9a7609..ffb611845db 100644 --- a/spyder/utils/ipython/kernelspec.py +++ b/spyder/utils/ipython/kernelspec.py @@ -26,8 +26,6 @@ class SpyderKernelSpec(KernelSpec): """Kernel spec for Spyder kernels""" - spykernel_path = get_module_source_path('spyder_kernels') - def __init__(self, is_cython=False, **kwargs): super(SpyderKernelSpec, self).__init__(**kwargs) self.is_cython = is_cython @@ -74,14 +72,8 @@ def argv(self): @property def env(self): """Env vars for kernels""" - # Paths that we need to add to PYTHONPATH: - # 1. sc_path: Path to the kernel sitecustomize - # 2. spy_pythonpath: Paths saved by our users with our PYTHONPATH - # manager - sc_path = osp.join(self.spykernel_path, 'site') - spy_pythonpath = CONF.get('main', 'spyder_pythonpath', default=[]) - pathlist = [sc_path] + spy_pythonpath - + # Add our PYTHONPATH to the kernel + pathlist = CONF.get('main', 'spyder_pythonpath', default=[]) default_interpreter = CONF.get('main_interpreter', 'default') pypath = add_pathlist_to_PYTHONPATH([], pathlist, ipyconsole=True, drop_env=(not default_interpreter)) From 32d82685c88986dc00b9778565bafd6bb5023fc1 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 20 Jun 2018 13:50:43 -0500 Subject: [PATCH 18/30] IPython console: Remove check of PYTHONPATH in kernelspec env vars This is not needed anymore --- spyder/plugins/ipythonconsole.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index 79daffd8966..5d978055871 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -1543,14 +1543,6 @@ def create_kernel_manager_and_kernel_client(self, connection_file, """Create kernel manager and client.""" # Kernel spec kernel_spec = self.create_kernel_spec(is_cython=is_cython) - if not kernel_spec.env.get('PYTHONPATH'): - error_msg = _("This error is most probably caused by installing " - "Spyder in a directory with non-ascii characters " - "(i.e. characters with tildes, apostrophes or " - "non-latin symbols).

" - "To fix it, please reinstall Spyder in a " - "different location.") - return (error_msg, None) # Kernel manager try: From 93d237a7c61b3de8ca2d8e529be1833033b29f1d Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 20 Jun 2018 13:55:56 -0500 Subject: [PATCH 19/30] Revert testing change --- spyder/app/tests/test_mainwindow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index e3277639a27..2fb8c766d6c 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -237,10 +237,11 @@ def test_filter_numpy_warning(main_window, qtbot): """ shell = main_window.ipyconsole.get_current_shellwidget() control = shell._control - with qtbot.waitSignal(shell.sig_prompt_ready, timeout=SHELL_TIMEOUT): - assert shell._prompt_html - - with qtbot.waitSignal(shell.executed, timeout=SHELL_TIMEOUT): + qtbot.waitUntil(lambda: shell._prompt_html is not None, + timeout=SHELL_TIMEOUT) + + # Create an array with a nan value + with qtbot.waitSignal(shell.executed): shell.execute('import numpy as np; A=np.full(16, np.nan)') qtbot.wait(1000) From a683dd739ffe6358d5f4bcb44b9269e654879095 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 14:06:12 -0500 Subject: [PATCH 20/30] IPython console: Simplify how we handle %edit magic petitions --- spyder/plugins/ipythonconsole.py | 50 ++++++-------------------- spyder/widgets/ipythonconsole/shell.py | 1 + 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index 5d978055871..ad916e5f5f1 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -43,8 +43,7 @@ # Local imports from spyder import dependencies -from spyder.config.base import (_, DEV, get_conf_path, get_home_dir, - get_module_path, running_under_pytest) +from spyder.config.base import _, get_conf_path, get_home_dir from spyder.config.main import CONF from spyder.plugins import SpyderPluginWidget from spyder.plugins.configdialog import PluginConfigPage @@ -1135,39 +1134,13 @@ def connect_client_to_kernel(self, client, is_cython=False): shellwidget.kernel_manager = km shellwidget.kernel_client = kc - def set_editor(self): - """Set the editor used by the %edit magic""" - # Get Python executable used by Spyder - python = sys.executable - if PY2: - python = encoding.to_unicode_from_fs(python) - - # Compose command for %edit - spy_dir = osp.dirname(get_module_path('spyder')) - if DEV: - bootstrap = osp.join(spy_dir, 'bootstrap.py') - if PY2: - bootstrap = encoding.to_unicode_from_fs(bootstrap) - editor = u'"{0}" "{1}" --'.format(python, bootstrap) - else: - import1 = "import sys" - # We need to add spy_dir to sys.path so this test can be - # run in our CIs - if running_under_pytest(): - if os.name == 'nt': - import1 = (import1 + - '; sys.path.append(""{}"")'.format(spy_dir)) - else: - import1 = (import1 + - "; sys.path.append('{}')".format(spy_dir)) - import2 = "from spyder.app.start import send_args_to_spyder" - code = "send_args_to_spyder([sys.argv[-1]])" - editor = u"\"{0}\" -c \"{1}; {2}; {3}\"".format(python, - import1, - import2, - code) - - return editor + @Slot(object, object) + def edit_file(self, filename, line): + """Handle %edit magic petitions.""" + if encoding.is_text_file(filename): + # The default line number sent by ipykernel is always the last + # one, but we prefer to use the first. + self.edit_goto.emit(filename, 1, '') def config_options(self): """ @@ -1227,10 +1200,6 @@ def config_options(self): spy_cfg.JupyterWidget.style_sheet = style_sheet spy_cfg.JupyterWidget.syntax_style = color_scheme - # Editor for %edit - if CONF.get('main', 'single_instance'): - spy_cfg.JupyterWidget.editor = self.set_editor() - # Merge QtConsole and Spyder configs. Spyder prefs will have # prevalence over QtConsole ones cfg._merge(spy_cfg) @@ -1299,6 +1268,9 @@ def register_client(self, client, give_focus=True): lambda fname, lineno, shellwidget=shellwidget: self.pdb_has_stopped(fname, lineno, shellwidget)) + # To handle %edit magic petitions + shellwidget.custom_edit_requested.connect(self.edit_file) + # Set shell cwd according to preferences cwd_path = '' if CONF.get('workingdir', 'console/use_project_or_home_directory'): diff --git a/spyder/widgets/ipythonconsole/shell.py b/spyder/widgets/ipythonconsole/shell.py index 16e63debcfe..93da0f9b482 100644 --- a/spyder/widgets/ipythonconsole/shell.py +++ b/spyder/widgets/ipythonconsole/shell.py @@ -62,6 +62,7 @@ def __init__(self, ipyclient, additional_options, interpreter_versions, # To override the Qt widget used by RichJupyterWidget self.custom_control = ControlWidget self.custom_page_control = PageControlWidget + self.custom_edit = True super(ShellWidget, self).__init__(*args, **kw) self.ipyclient = ipyclient From 1141da4dbd26d335994357ce07692aa8cccfe35f Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 14:07:28 -0500 Subject: [PATCH 21/30] Testing: Fix test_single_instance_and_edit_magic --- spyder/app/tests/test_mainwindow.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index 2fb8c766d6c..ef8fb9bb7a0 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -40,7 +40,7 @@ from spyder import __trouble_url__, __project_url__ from spyder.app import start from spyder.app.mainwindow import MainWindow # Tests fail without this import -from spyder.config.base import get_home_dir +from spyder.config.base import get_home_dir, get_module_path from spyder.config.main import CONF from spyder.plugins import TabFilter from spyder.plugins.help import ObjectComboBox @@ -332,11 +332,14 @@ def test_single_instance_and_edit_magic(main_window, qtbot, tmpdir): shell = main_window.ipyconsole.get_current_shellwidget() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) - lock_code = ("from spyder.config.base import get_conf_path\n" + spy_dir = osp.dirname(get_module_path('spyder')) + lock_code = ("import sys\n" + "sys.path.append('{}')\n" + "from spyder.config.base import get_conf_path\n" "from spyder.utils.external import lockfile\n" "lock_file = get_conf_path('spyder.lock')\n" "lock = lockfile.FilesystemLock(lock_file)\n" - "lock_created = lock.lock()") + "lock_created = lock.lock()".format(spy_dir)) # Test single instance with qtbot.waitSignal(shell.executed): From 604ccb0ea87a537d5fe38c359f1eecd176befc0d Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 19:18:00 -0500 Subject: [PATCH 22/30] Variable Explorer: Move PICKLE_PROTOCOL to config.base --- spyder/config/base.py | 3 +++ spyder/widgets/variableexplorer/namespacebrowser.py | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spyder/config/base.py b/spyder/config/base.py index dd6d849fc09..6dff042a617 100644 --- a/spyder/config/base.py +++ b/spyder/config/base.py @@ -427,6 +427,9 @@ def translate_gettext(x): 'Inf', 'Infinity', 'sctypes', 'rcParams', 'rcParamsDefault', 'sctypeNA', 'typeNA', 'False_', 'True_',] +# To be able to get and set variables between Python 2 and 3 +PICKLE_PROTOCOL = 2 + #============================================================================== # Mac application utilities diff --git a/spyder/widgets/variableexplorer/namespacebrowser.py b/spyder/widgets/variableexplorer/namespacebrowser.py index 84fa77d3e5a..4a5fe62fbc0 100644 --- a/spyder/widgets/variableexplorer/namespacebrowser.py +++ b/spyder/widgets/variableexplorer/namespacebrowser.py @@ -27,7 +27,7 @@ from spyder_kernels.utils.nsview import get_supported_types, REMOTE_SETTINGS # Local imports -from spyder.config.base import _ +from spyder.config.base import _, PICKLE_PROTOCOL from spyder.config.main import CONF from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding @@ -43,9 +43,6 @@ SUPPORTED_TYPES = get_supported_types() -# To be able to get and set variables between Python 2 and 3 -PICKLE_PROTOCOL = 2 - # Maximum length of a serialized variable to be set in the kernel MAX_SERIALIZED_LENGHT = 1e6 From a15abb8d666a0981d278899d7337bdc4f667d8ee Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 19:23:31 -0500 Subject: [PATCH 23/30] IPython console: Send breakpoints from Spyder to the kernel Before we were reading them in the kernel, using Spyder's config system --- spyder/widgets/ipythonconsole/debugging.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/spyder/widgets/ipythonconsole/debugging.py b/spyder/widgets/ipythonconsole/debugging.py index 5fd1297c43f..72c582973c4 100644 --- a/spyder/widgets/ipythonconsole/debugging.py +++ b/spyder/widgets/ipythonconsole/debugging.py @@ -10,11 +10,14 @@ """ import ast +import pickle from qtpy.QtCore import Qt - from qtconsole.rich_jupyter_widget import RichJupyterWidget +from spyder.config.base import PICKLE_PROTOCOL +from spyder.config.main import CONF + class DebuggingWidget(RichJupyterWidget): """ @@ -28,11 +31,14 @@ def write_to_stdin(self, line): """Send raw characters to the IPython kernel through stdin""" self.kernel_client.input(line) - def set_spyder_breakpoints(self): + def set_spyder_breakpoints(self, force=False): """Set Spyder breakpoints into a debugging session""" - if self._reading: - self.kernel_client.input( - "!get_ipython().kernel._set_spyder_breakpoints()") + if self._reading or force: + breakpoints = CONF.get('run', 'breakpoints', {}) + breakpoints_pkl = pickle.dumps(breakpoints, + protocol=PICKLE_PROTOCOL) + cmd = "!get_ipython().kernel._set_spyder_breakpoints({})" + self.kernel_client.input(cmd.format(breakpoints_pkl)) def dbg_exec_magic(self, magic, args=''): """Run an IPython magic while debugging.""" From 76582b66dba3ca8834eec1b18afd6381b954f8e4 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 19:25:26 -0500 Subject: [PATCH 24/30] IPython console: Handle a new Spyder message from the kernel called set_breakpoints This necessary because the kernel needs to ask Spyder for its breakpoints as soon as it enters debugging. --- spyder/widgets/ipythonconsole/namespacebrowser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spyder/widgets/ipythonconsole/namespacebrowser.py b/spyder/widgets/ipythonconsole/namespacebrowser.py index 0aebb28568f..483263eef8b 100644 --- a/spyder/widgets/ipythonconsole/namespacebrowser.py +++ b/spyder/widgets/ipythonconsole/namespacebrowser.py @@ -195,6 +195,8 @@ def _handle_spyder_msg(self, msg): # Run Pdb continue to get to the first breakpoint # Fixes 2034 self.write_to_stdin('continue') + elif spyder_msg_type == 'set_breakpoints': + self.set_spyder_breakpoints(force=True) else: debug_print("No such spyder message type: %s" % spyder_msg_type) From e0c6acefbc8f9abef88d05768e3af4809bbb942b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 22:18:58 -0500 Subject: [PATCH 25/30] Testing: Skip a couple of tests that are failing locally --- spyder/app/tests/test_mainwindow.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index ef8fb9bb7a0..6bfe61cc19d 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -326,6 +326,8 @@ def test_window_title(main_window, tmpdir): @pytest.mark.slow @pytest.mark.single_instance +@pytest.mark.skipif(PY2 and os.environ.get('CI', None) is None, + reason="It's not meant to be run outside of CIs in Python 2") def test_single_instance_and_edit_magic(main_window, qtbot, tmpdir): """Test single instance mode and for %edit magic.""" editorstack = main_window.editor.get_current_editorstack() @@ -1492,6 +1494,8 @@ def test_tabfilter_typeerror_full(main_window): @flaky(max_runs=3) @pytest.mark.slow +@pytest.mark.skipif(os.environ.get('CI', None) is None, + reason="It's not meant to be run outside of CIs") def test_help_opens_when_show_tutorial_full(main_window, qtbot): """Test fix for #6317 : 'Show tutorial' opens the help plugin if closed.""" HELP_STR = "Help" From b6c15ede9e7612b6d0e7b94cb82c0f68ed44ebe9 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 22:20:09 -0500 Subject: [PATCH 26/30] IPython console: Send serialized breakpoints to the kernel inside a list This way they can be sent in Python 2 --- spyder/widgets/ipythonconsole/debugging.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/spyder/widgets/ipythonconsole/debugging.py b/spyder/widgets/ipythonconsole/debugging.py index 72c582973c4..11e2c2a842b 100644 --- a/spyder/widgets/ipythonconsole/debugging.py +++ b/spyder/widgets/ipythonconsole/debugging.py @@ -17,6 +17,7 @@ from spyder.config.base import PICKLE_PROTOCOL from spyder.config.main import CONF +from spyder.py3compat import to_text_string class DebuggingWidget(RichJupyterWidget): @@ -34,11 +35,16 @@ def write_to_stdin(self, line): def set_spyder_breakpoints(self, force=False): """Set Spyder breakpoints into a debugging session""" if self._reading or force: - breakpoints = CONF.get('run', 'breakpoints', {}) - breakpoints_pkl = pickle.dumps(breakpoints, - protocol=PICKLE_PROTOCOL) - cmd = "!get_ipython().kernel._set_spyder_breakpoints({})" - self.kernel_client.input(cmd.format(breakpoints_pkl)) + breakpoints_dict = CONF.get('run', 'breakpoints', {}) + + # We need to enclose pickled values in a list to be able to + # send them to the kernel in Python 2 + serialiazed_breakpoints = [pickle.dumps(breakpoints_dict, + protocol=PICKLE_PROTOCOL)] + breakpoints = to_text_string(serialiazed_breakpoints) + + cmd = u"!get_ipython().kernel._set_spyder_breakpoints({})" + self.kernel_client.input(cmd.format(breakpoints)) def dbg_exec_magic(self, magic, args=''): """Run an IPython magic while debugging.""" From 1c558478b300a715404bb3957c30da690ddc3fcb Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 23:13:15 -0500 Subject: [PATCH 27/30] IPython console: Check for spyder_kernels<1.0 in external interpreters --- spyder/plugins/ipythonconsole.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index ad916e5f5f1..942a860dead 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -1076,8 +1076,9 @@ def create_new_client(self, give_focus=True, filename='', is_cython=False): if not CONF.get('main_interpreter', 'default'): pyexec = CONF.get('main_interpreter', 'executable') has_spyder_kernels = programs.is_module_installed( - 'spyder_kernels', - interpreter=pyexec) + 'spyder_kernels', + interpreter=pyexec, + version='<1.0.0') if not has_spyder_kernels: client.show_kernel_error( _("Your Python environment or installation doesn't " From 00eb14ac27ee34c6b4e253aaf7ccba2b1581f639 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 23:14:30 -0500 Subject: [PATCH 28/30] Testing: Pin spyder-kernels to 0.* in all our CIs --- appveyor.yml | 2 +- continuous_integration/circle/install.sh | 7 +++---- continuous_integration/travis/install-qt.sh | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index be300c64360..6593daa2d7e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -48,7 +48,7 @@ install: # Fix problems with latest pyqt - "conda install pyqt=5.6*" # Install spyder-kernels - - "pip install -q --no-deps spyder-kernels" + - "pip install -q --no-deps spyder-kernels==0.*" build: false diff --git a/continuous_integration/circle/install.sh b/continuous_integration/circle/install.sh index e2f11c82991..18a927616cc 100755 --- a/continuous_integration/circle/install.sh +++ b/continuous_integration/circle/install.sh @@ -4,8 +4,7 @@ export CONDA_DEPENDENCIES_FLAGS="--quiet" export CONDA_DEPENDENCIES="rope pyflakes sphinx pygments pylint psutil nbconvert \ qtawesome pickleshare qtpy pyzmq chardet mock nomkl pandas \ pytest pytest-cov numpydoc scipy cython pillow cloudpickle" -export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-xvfb flaky jedi pycodestyle \ - spyder-kernels" +export PIP_DEPENDENCIES="coveralls pytest-qt pytest-mock pytest-xvfb flaky jedi pycodestyle" # Download and install miniconda and conda/pip dependencies # with astropy helpers @@ -18,5 +17,5 @@ source ci-helpers/travis/setup_conda_$TRAVIS_OS_NAME.sh export PATH="$HOME/miniconda/bin:$PATH" source activate test -# Install ciocheck (not working right now) -#conda install -q ciocheck -c spyder-ide --no-update-deps +# Install spyder-kernels +pip install -q --no-deps spyder-kernels==0.* diff --git a/continuous_integration/travis/install-qt.sh b/continuous_integration/travis/install-qt.sh index 1ad3e726c1d..2b44ef74d9e 100755 --- a/continuous_integration/travis/install-qt.sh +++ b/continuous_integration/travis/install-qt.sh @@ -10,11 +10,11 @@ if [ "$USE_CONDA" = "no" ]; then # Install qtpy from Github pip install git+https://github.com/spyder-ide/qtpy.git - pip install -q spyder-kernels + pip install -q spyder-kernels==0.* elif [ "$USE_PYQT" = "pyqt5" ]; then conda install -q qt=5.* pyqt=5.* qtconsole matplotlib - pip install -q --no-deps spyder-kernels + pip install -q --no-deps spyder-kernels==0.* else conda install -q qt=4.* pyqt=4.* qtconsole matplotlib - pip install -q --no-deps spyder-kernels + pip install -q --no-deps spyder-kernels==0.* fi From 391abde9240f348fb7676c54ecac87f4eb97506b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 21 Jun 2018 23:19:51 -0500 Subject: [PATCH 29/30] IPython console: Fix grammar in spyder_kernels error message --- spyder/plugins/ipythonconsole.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index 942a860dead..a2ca76562db 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -1084,11 +1084,11 @@ def create_new_client(self, give_focus=True, filename='', is_cython=False): _("Your Python environment or installation doesn't " "have the spyder-kernels module installed " "on it. Without this module is not possible for " - "Spyder to create a console for you.

You " - "can install them by running in a system terminal" - ":

pip install spyder-kernels" + "Spyder to create a console for you.

" + "You can install it by running in a system terminal" + ":

conda install spyder-kernels" "

or

" - "conda install spyder-kernels")) + "pip install spyder-kernels")) return self.connect_client_to_kernel(client, is_cython=is_cython) From f1cafb5b43098619edf086295c857d90074fdb34 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sat, 23 Jun 2018 18:33:31 -0500 Subject: [PATCH 30/30] Add startup validation for spyder-kernels --- spyder/app/mainwindow.py | 1 + spyder/requirements.py | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 1431a2c560e..4cb996dee7a 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -48,6 +48,7 @@ from spyder import requirements requirements.check_path() requirements.check_qt() +requirements.check_spyder_kernels() #============================================================================== diff --git a/spyder/requirements.py b/spyder/requirements.py index 0bd8afc9840..5a94718ca72 100644 --- a/spyder/requirements.py +++ b/spyder/requirements.py @@ -23,6 +23,7 @@ def show_warning(message): pass raise RuntimeError(message) + def check_path(): """Check sys.path: is Spyder properly installed?""" dirname = osp.abspath(osp.join(osp.dirname(__file__), osp.pardir)) @@ -32,6 +33,7 @@ def check_path(): "or directory '%s' must be in PYTHONPATH " "environment variable." % dirname) + def check_qt(): """Check Qt binding requirements""" qt_infos = dict(pyqt5=("PyQt5", "5.5")) @@ -46,8 +48,23 @@ def check_qt(): except ImportError: show_warning("Failed to import qtpy.\n" "Please check Spyder installation requirements:\n\n" - "qtpy 1.1.0+ and either\n" - "%s %s+ or\n" + "qtpy 1.2.0+ and\n" "%s %s+\n\n" "are required to run Spyder." - % (qt_infos['pyqt5'] + qt_infos['pyqt'])) + % (qt_infos['pyqt5'])) + + +def check_spyder_kernels(): + """Check spyder-kernel requirement.""" + try: + import spyder_kernels + required_ver = '1.0.0' + actual_ver = spyder_kernels.__version__ + if LooseVersion(actual_ver) >= LooseVersion(required_ver): + show_warning("Please check Spyder installation requirements:\n" + "spyder-kernels < 1.0 is required (found %s)." + % actual_ver) + except ImportError: + show_warning("Failed to import spyder-kernels.\n" + "Please check Spyder installation requirements:\n\n" + "spyder-kernels < 1.0 is required")