def test_dicts_natural_sorting_mixed_types(): """ Test that natural sorting actually does what it should do. testing for issue 13733, as mixed types were sorted incorrectly. Sorting for other columns will be tested as well. """ import pandas as pd dictionary = { 'DSeries': pd.Series(dtype=int), 'aStr': 'algName', 'kDict': { 2: 'asd', 3: 2 } } # put this here variable, as it might change later to reflect string length str_size = get_size(dictionary['aStr']) editor = CollectionsEditor() editor.setup(dictionary) cm = editor.widget.editor.source_model cm.sort(0) keys = cm.keys types = cm.types sizes = cm.sizes assert keys == ['aStr', 'DSeries', 'kDict'] assert types == ['str', 'Series', 'dict'] assert sizes == [str_size, (0, ), 2] assert data_table(cm, 3, 3) == [['aStr', 'DSeries', 'kDict'], ['str', 'Series', 'dict'], [str_size, '(0,)', 2]] # insert an item and check that it is still sorted correctly editor.widget.editor.new_value('List', [1, 2, 3]) assert data_table(cm, 4, 3) == [['aStr', 'DSeries', 'kDict', 'List'], ['str', 'Series', 'dict', 'list'], [str_size, '(0,)', 2, 3]] cm.sort(0) assert data_table(cm, 4, 3) == [['aStr', 'DSeries', 'kDict', 'List'], ['str', 'Series', 'dict', 'list'], [str_size, '(0,)', 2, 3]] # now sort for types cm.sort(1) assert data_table(cm, 4, 3) == [['DSeries', 'kDict', 'List', 'aStr'], ['Series', 'dict', 'list', 'str'], ['(0,)', 2, 3, str_size]] # now sort for sizes cm.sort(2) assert data_table(cm, 4, 3) == [['DSeries', 'kDict', 'List', 'aStr'], ['Series', 'dict', 'list', 'str'], ['(0,)', 2, 3, str_size]]
def show_syspath(self, syspath): """Show sys.path contents.""" if syspath is not None: editor = CollectionsEditor(self) editor.setup(syspath, title="sys.path contents", readonly=True, icon=ima.icon('syspath')) self.dialog_manager.show(editor) else: return
def test_dicts_with_mixed_types_as_key(qtbot): """ Test that we can show dictionaries with mixed data types as keys. This is a regression for spyder-ide/spyder#13481. """ colors = {1: 'red', 'Y': 'yellow'} editor = CollectionsEditor() editor.setup(colors) assert editor.widget.editor.source_model.keys == [1, 'Y']
def test_view_module_in_coledit(): """ Test that modules don't produce an error when opening in Variable Explorer. Also check that they are set as readonly. Regression test for spyder-ide/spyder#6080. """ editor = CollectionsEditor() editor.setup(os, "module_test", readonly=False) assert editor.widget.editor.readonly
def test_collectionseditor_with_class_having_buggy_copy(qtbot): """ Test that editor for object whose .copy() returns a different type is readonly; cf. spyder-ide/spyder#6936. """ class MyDictWithBuggyCopy(dict): pass md = MyDictWithBuggyCopy({1: 2}) editor = CollectionsEditor() editor.setup(md) assert editor.widget.editor.readonly
def test_pandas_dateoffset_view(): """ Test that pandas ``DateOffset`` objs can be viewied in CollectionsEditor. Regression test for spyder-ide/spyder#6729. """ test_dateoffset = pandas.DateOffset() col_editor = CollectionsEditor(None) col_editor.setup(test_dateoffset) col_editor.show() assert col_editor.get_value() col_editor.accept()
def show_syspath(self): """ Show `sys.path`. """ editor = CollectionsEditor(parent=self) editor.setup( sys.path, title="sys.path", readonly=True, icon=self.create_icon('syspath'), ) self.dialog_manager.show(editor)
def test_edit_nonsettable_objects(qtbot, nonsettable_objects_data): """ Test that errors trying to edit attributes in ColEdit are handled properly. Integration regression test for issues spyder-ide/spyder#6727 and spyder-ide/spyder#6728. """ for test_obj, expected_obj, keys in nonsettable_objects_data: col_editor = CollectionsEditor(None) col_editor.setup(test_obj) col_editor.show() qtbot.waitForWindowShown(col_editor) view = col_editor.widget.editor indicies = [view.source_model.get_index_from_key(key) for key in keys] for _ in range(3): qtbot.keyClick(view, Qt.Key_Right) last_row = -1 rows_to_test = [index.row() for index in indicies] for row in rows_to_test: for _ in range(row - last_row - 1): qtbot.keyClick(view, Qt.Key_Down) qtbot.keyClick(view, Qt.Key_Space) qtbot.keyClick(view.focusWidget(), Qt.Key_Backspace) qtbot.keyClicks(view.focusWidget(), "2") qtbot.keyClick(view.focusWidget(), Qt.Key_Down) last_row = row qtbot.wait(100) # Due to numpy's deliberate breakage of __eq__ comparison assert all([ key == "_typ" or (getattr(col_editor.get_value(), key) == getattr( expected_obj, key)) for key in keys ]) col_editor.accept() qtbot.wait(200) # Same reason as above assert all([ key == "_typ" or (getattr(col_editor.get_value(), key) == getattr( expected_obj, key)) for key in keys ]) if getattr(test_obj, "_typ", None) is None: keys.remove("_typ") assert all([ getattr(test_obj, key) == getattr(expected_obj, key) for key in keys ])
def test_collectionseditor_with_class_having_correct_copy(qtbot): """ Test that editor for object whose .copy() returns the same type is not readonly; cf. spyder-ide/spyder#6936. """ class MyDictWithCorrectCopy(dict): def copy(self): return MyDictWithCorrectCopy(self) md = MyDictWithCorrectCopy({1: 2}) editor = CollectionsEditor() editor.setup(md) assert not editor.widget.editor.readonly
def test_collectionseditor_when_clicking_on_header_and_large_rows(qtbot): """ Test that sorting works when clicking in its header and there's a large number of rows. """ li = [1] * 10000 editor = CollectionsEditor() editor.setup(li) # Perform the sorting. It should be done quite quickly because # there's a very small number of rows in display. view = editor.widget.editor header = view.horizontalHeader() with qtbot.waitSignal(header.sectionClicked, timeout=200): qtbot.mouseClick(header.viewport(), Qt.LeftButton, pos=QPoint(1, 1)) # Assert data was sorted correctly. assert data(view.model, 0, 0) == 9999
def test_xml_dom_element_view(): """ Test that XML DOM ``Element``s are able to be viewied in CollectionsEditor. Regression test for spyder-ide/spyder#5642. """ xml_path = path.join(LOCATION, 'dom_element_test.xml') with open(xml_path) as xml_file: xml_data = xml_file.read() xml_content = parseString(xml_data) xml_element = xml_content.getElementsByTagName("note")[0] col_editor = CollectionsEditor(None) col_editor.setup(xml_element) col_editor.show() assert col_editor.get_value() col_editor.accept()
def test_dicts_natural_sorting(qtbot): """ Test that natural sorting actually does what it should do """ import random numbers = list(range(100)) random.shuffle(numbers) dictionary = {'test{}'.format(i): None for i in numbers} data_sorted = sorted(list(dictionary.keys()), key=natsort) # numbers should be as a human would sort, e.g. test3 before test100 # regular sort would sort test1, test10, test11,..., test2, test20,... expected = ['test{}'.format(i) for i in list(range(100))] editor = CollectionsEditor() editor.setup(dictionary) editor.widget.editor.source_model.sort(0) assert data_sorted == expected, 'Function failed' assert editor.widget.editor.source_model.keys == expected, \ 'GUI sorting fail'
def createEditor(self, parent, option, index, object_explorer=False): """Overriding method createEditor""" val_type = index.sibling(index.row(), 1).data() self.sig_open_editor.emit() if index.column() < 3: return None if self.show_warning(index): answer = QMessageBox.warning( self.parent(), _("Warning"), _("Opening this variable can be slow\n\n" "Do you want to continue anyway?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.No: return None try: value = self.get_value(index) if value is None: return None except ImportError as msg: self.sig_editor_shown.emit() module = str(msg).split("'")[1] if module in ['pandas', 'numpy']: if module == 'numpy': val_type = 'array' else: val_type = 'dataframe, series' QMessageBox.critical( self.parent(), _("Error"), _("Spyder is unable to show the {val_type} or object " "you're trying to view because <tt>{module}</tt> was " "not installed alongside Spyder. Please install " "this package in your Spyder environment." "<br>").format(val_type=val_type, module=module)) return else: QMessageBox.critical( self.parent(), _("Error"), _("Spyder is unable to show the variable you're " "trying to view because the module " "<tt>{module}</tt> was not found in your " "Spyder environment. Please install " "this package in your Spyder environment." "<br>").format(module=module)) return except Exception as msg: QMessageBox.critical( self.parent(), _("Error"), _("Spyder was unable to retrieve the value of " "this variable from the console.<br><br>" "The error message was:<br>" "%s") % to_text_string(msg)) return key = index.model().get_key(index) readonly = (isinstance(value, (tuple, set)) or self.parent().readonly or not is_known_type(value)) # CollectionsEditor for a list, tuple, dict, etc. if isinstance(value, (list, set, tuple, dict)) and not object_explorer: from spyder.widgets.collectionseditor import CollectionsEditor editor = CollectionsEditor(parent=parent) editor.setup(value, key, icon=self.parent().windowIcon(), readonly=readonly) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # ArrayEditor for a Numpy array elif (isinstance(value, (ndarray, MaskedArray)) and ndarray is not FakeObject and not object_explorer): editor = ArrayEditor(parent=parent) if not editor.setup_and_check(value, title=key, readonly=readonly): return self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # ArrayEditor for an images elif (isinstance(value, Image) and ndarray is not FakeObject and Image is not FakeObject and not object_explorer): arr = array(value) editor = ArrayEditor(parent=parent) if not editor.setup_and_check(arr, title=key, readonly=readonly): return conv_func = lambda arr: Image.fromarray(arr, mode=value.mode) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly, conv=conv_func)) return None # DataFrameEditor for a pandas dataframe, series or index elif (isinstance(value, (DataFrame, Index, Series)) and DataFrame is not FakeObject and not object_explorer): editor = DataFrameEditor(parent=parent) if not editor.setup_and_check(value, title=key): return editor.dataModel.set_format(index.model().dataframe_format) editor.sig_option_changed.connect(self.change_option) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # QDateEdit and QDateTimeEdit for a dates or datetime respectively elif isinstance(value, datetime.date) and not object_explorer: # Needed to handle NaT values # See spyder-ide/spyder#8329 try: value.time() except ValueError: self.sig_editor_shown.emit() return None if readonly: self.sig_editor_shown.emit() return None else: if isinstance(value, datetime.datetime): editor = QDateTimeEdit(value, parent=parent) else: editor = QDateEdit(value, parent=parent) editor.setCalendarPopup(True) editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA)) self.sig_editor_shown.emit() return editor # TextEditor for a long string elif is_text_string(value) and len(value) > 40 and not object_explorer: te = TextEditor(None, parent=parent) if te.setup_and_check(value): editor = TextEditor(value, key, readonly=readonly, parent=parent) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # QLineEdit for an individual value (int, float, short string, etc) elif is_editable_type(value) and not object_explorer: if readonly: self.sig_editor_shown.emit() return None else: editor = QLineEdit(parent=parent) editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA)) editor.setAlignment(Qt.AlignLeft) # This is making Spyder crash because the QLineEdit that it's # been modified is removed and a new one is created after # evaluation. So the object on which this method is trying to # act doesn't exist anymore. # editor.returnPressed.connect(self.commitAndCloseEditor) self.sig_editor_shown.emit() return editor # ObjectExplorer for an arbitrary Python object else: show_callable_attributes = index.model().show_callable_attributes show_special_attributes = index.model().show_special_attributes dataframe_format = index.model().dataframe_format if show_callable_attributes is None: show_callable_attributes = False if show_special_attributes is None: show_special_attributes = False from spyder.plugins.variableexplorer.widgets.objectexplorer \ import ObjectExplorer editor = ObjectExplorer( value, name=key, parent=parent, show_callable_attributes=show_callable_attributes, show_special_attributes=show_special_attributes, dataframe_format=dataframe_format, readonly=readonly) editor.sig_option_changed.connect(self.change_option) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None
def createEditor(self, parent, option, index): """Overriding method createEditor""" if self.show_warning(index): answer = QMessageBox.warning( self.parent(), _("Warning"), _("Opening this variable can be slow\n\n" "Do you want to continue anyway?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.No: return None try: value = self.get_value(index) try: self.old_obj = value.copy() except AttributeError: self.old_obj = copy.deepcopy(value) if value is None: return None except Exception as msg: QMessageBox.critical( self.parent(), _("Error"), _("Spyder was unable to retrieve the value of " "this variable from the console.<br><br>" "The error message was:<br>" "<i>%s</i>") % to_text_string(msg)) return self.current_index = index key = index.model().get_key(index).obj_name readonly = (isinstance(value, (tuple, set)) or self.parent().readonly or not is_known_type(value)) # CollectionsEditor for a list, tuple, dict, etc. if isinstance(value, (list, set, tuple, dict)): from spyder.widgets.collectionseditor import CollectionsEditor editor = CollectionsEditor(parent=parent) editor.setup(value, key, icon=self.parent().windowIcon(), readonly=readonly) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # ArrayEditor for a Numpy array elif (isinstance(value, (ndarray, MaskedArray)) and ndarray is not FakeObject): editor = ArrayEditor(parent=parent) if not editor.setup_and_check(value, title=key, readonly=readonly): return self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # ArrayEditor for an images elif (isinstance(value, Image) and ndarray is not FakeObject and Image is not FakeObject): arr = array(value) editor = ArrayEditor(parent=parent) if not editor.setup_and_check(arr, title=key, readonly=readonly): return conv_func = lambda arr: Image.fromarray(arr, mode=value.mode) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly, conv=conv_func)) return None # DataFrameEditor for a pandas dataframe, series or index elif (isinstance(value, (DataFrame, Index, Series)) and DataFrame is not FakeObject): editor = DataFrameEditor(parent=parent) if not editor.setup_and_check(value, title=key): return editor.dataModel.set_format(index.model().dataframe_format) editor.sig_option_changed.connect(self.change_option) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # QDateEdit and QDateTimeEdit for a dates or datetime respectively elif isinstance(value, datetime.date): if readonly: return None else: if isinstance(value, datetime.datetime): editor = QDateTimeEdit(value, parent=parent) else: editor = QDateEdit(value, parent=parent) editor.setCalendarPopup(True) editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA)) return editor # TextEditor for a long string elif is_text_string(value) and len(value) > 40: te = TextEditor(None, parent=parent) if te.setup_and_check(value): editor = TextEditor(value, key, readonly=readonly, parent=parent) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # QLineEdit for an individual value (int, float, short string, etc) elif is_editable_type(value): if readonly: return None else: editor = QLineEdit(parent=parent) editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA)) editor.setAlignment(Qt.AlignLeft) # This is making Spyder crash because the QLineEdit that it's # been modified is removed and a new one is created after # evaluation. So the object on which this method is trying to # act doesn't exist anymore. # editor.returnPressed.connect(self.commitAndCloseEditor) return editor # An arbitrary Python object. # Since we are already in the Object Explorer no editor is needed else: return None
def createEditor(self, parent, option, index, object_explorer=False): """Overriding method createEditor""" val_type = index.sibling(index.row(), 1).data() self.sig_editor_creation_started.emit() if index.column() < 3: return None if self.show_warning(index): answer = QMessageBox.warning( self.parent(), _("Warning"), _("Opening this variable can be slow\n\n" "Do you want to continue anyway?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.No: self.sig_editor_shown.emit() return None try: value = self.get_value(index) if value is None: return None except ImportError as msg: self.sig_editor_shown.emit() module = str(msg).split("'")[1] if module in ['pandas', 'numpy']: if module == 'numpy': val_type = 'array' else: val_type = 'dataframe, series' message = _("Spyder is unable to show the {val_type} or object" " you're trying to view because <tt>{module}</tt>" " is not installed. ") if running_in_mac_app(): message += _("Please consider using the full version of " "the Spyder MacOS application.<br>") else: message += _("Please install this package in your Spyder " "environment.<br>") QMessageBox.critical( self.parent(), _("Error"), message.format(val_type=val_type, module=module)) return else: if running_in_mac_app() or is_pynsist(): message = _("Spyder is unable to show the variable you're" " trying to view because the module " "<tt>{module}</tt> is not supported in the " "Spyder Lite application.<br>") else: message = _("Spyder is unable to show the variable you're" " trying to view because the module " "<tt>{module}</tt> is not found in your " "Spyder environment. Please install this " "package in this environment.<br>") QMessageBox.critical(self.parent(), _("Error"), message.format(module=module)) return except Exception as msg: QMessageBox.critical( self.parent(), _("Error"), _("Spyder was unable to retrieve the value of " "this variable from the console.<br><br>" "The error message was:<br>" "%s") % to_text_string(msg)) return key = index.model().get_key(index) readonly = (isinstance(value, (tuple, set)) or self.parent().readonly or not is_known_type(value)) # We can't edit Numpy void objects because they could be anything, so # this might cause a crash. # Fixes spyder-ide/spyder#10603 if isinstance(value, np.void): self.sig_editor_shown.emit() return None # CollectionsEditor for a list, tuple, dict, etc. elif isinstance(value, (list, set, tuple, dict)) and not object_explorer: from spyder.widgets.collectionseditor import CollectionsEditor editor = CollectionsEditor(parent=parent) editor.setup(value, key, icon=self.parent().windowIcon(), readonly=readonly) self.create_dialog(editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # ArrayEditor for a Numpy array elif (isinstance(value, (np.ndarray, np.ma.MaskedArray)) and np.ndarray is not FakeObject and not object_explorer): # We need to leave this import here for tests to pass. from .arrayeditor import ArrayEditor editor = ArrayEditor(parent=parent) if not editor.setup_and_check(value, title=key, readonly=readonly): return self.create_dialog(editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # ArrayEditor for an images elif (isinstance(value, PIL.Image.Image) and np.ndarray is not FakeObject and PIL.Image is not FakeObject and not object_explorer): # Sometimes the ArrayEditor import above is not seen (don't know # why), so we need to reimport it here. # Fixes spyder-ide/spyder#16731 from .arrayeditor import ArrayEditor arr = np.array(value) editor = ArrayEditor(parent=parent) if not editor.setup_and_check(arr, title=key, readonly=readonly): return conv_func = lambda arr: PIL.Image.fromarray(arr, mode=value.mode) self.create_dialog(editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly, conv=conv_func)) return None # DataFrameEditor for a pandas dataframe, series or index elif (isinstance(value, (pd.DataFrame, pd.Index, pd.Series)) and pd.DataFrame is not FakeObject and not object_explorer): # We need to leave this import here for tests to pass. from .dataframeeditor import DataFrameEditor editor = DataFrameEditor(parent=parent) if not editor.setup_and_check(value, title=key): self.sig_editor_shown.emit() return self.create_dialog(editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # QDateEdit and QDateTimeEdit for a dates or datetime respectively elif isinstance(value, datetime.date) and not object_explorer: if readonly: self.sig_editor_shown.emit() return None else: if isinstance(value, datetime.datetime): editor = QDateTimeEdit(value, parent=parent) # Needed to handle NaT values # See spyder-ide/spyder#8329 try: value.time() except ValueError: self.sig_editor_shown.emit() return None else: editor = QDateEdit(value, parent=parent) editor.setCalendarPopup(True) editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA)) self.sig_editor_shown.emit() return editor # TextEditor for a long string elif is_text_string(value) and len(value) > 40 and not object_explorer: te = TextEditor(None, parent=parent) if te.setup_and_check(value): editor = TextEditor(value, key, readonly=readonly, parent=parent) self.create_dialog(editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None # QLineEdit for an individual value (int, float, short string, etc) elif is_editable_type(value) and not object_explorer: if readonly: self.sig_editor_shown.emit() return None else: editor = QLineEdit(parent=parent) editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA)) editor.setAlignment(Qt.AlignLeft) # This is making Spyder crash because the QLineEdit that it's # been modified is removed and a new one is created after # evaluation. So the object on which this method is trying to # act doesn't exist anymore. # editor.returnPressed.connect(self.commitAndCloseEditor) self.sig_editor_shown.emit() return editor # ObjectExplorer for an arbitrary Python object else: from spyder.plugins.variableexplorer.widgets.objectexplorer \ import ObjectExplorer editor = ObjectExplorer( value, name=key, parent=parent, readonly=readonly) self.create_dialog(editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly)) return None