From 21c28ac87cd3d7a02fb6972536a1b752bafeb550 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 12 Jan 2023 20:43:43 +0100 Subject: [PATCH 1/3] Remove Python 2 support --- scripts/spyder_win_post_install.py | 7 +- spyder/api/config/mixins.py | 6 +- spyder/app/mainwindow.py | 5 +- spyder/app/restart.py | 1 - spyder/app/start.py | 4 +- spyder/app/tests/test_mainwindow.py | 14 +- spyder/config/base.py | 24 +- spyder/config/tests/conftest.py | 36 +- spyder/config/tests/test_manager.py | 2 +- spyder/config/tests/test_user.py | 13 +- spyder/config/user.py | 49 +- spyder/plugins/base.py | 3 +- .../completion/providers/kite/client.py | 3 +- .../providers/kite/parsing/__init__.py | 6 - .../providers/kite/utils/install.py | 1 - .../languageserver/providers/utils.py | 20 +- .../languageserver/transport/main.py | 3 +- .../languageserver/transport/tcp/producer.py | 1 - spyder/plugins/console/utils/interpreter.py | 2 - .../plugins/console/widgets/internalshell.py | 4 +- spyder/plugins/console/widgets/shell.py | 26 +- spyder/plugins/editor/panels/scrollflag.py | 1 - spyder/plugins/editor/plugin.py | 4 +- spyder/plugins/editor/utils/autosave.py | 6 +- spyder/plugins/editor/widgets/base.py | 4 +- spyder/plugins/editor/widgets/codeeditor.py | 8 +- .../editor/widgets/editorstack_helpers.py | 3 +- .../plugins/editor/widgets/tests/conftest.py | 4 +- .../editor/widgets/tests/test_editor.py | 2 - .../widgets/tests/test_hints_and_calltips.py | 7 - .../widgets/tests/test_introspection.py | 23 +- .../editor/widgets/tests/test_recover.py | 11 +- spyder/plugins/explorer/widgets/explorer.py | 2 - .../explorer/widgets/fileassociations.py | 2 - .../widgets/tests/test_fileassociations.py | 4 - spyder/plugins/explorer/widgets/utils.py | 3 +- spyder/plugins/help/utils/sphinxify.py | 6 +- spyder/plugins/help/utils/tutorial.rst | 1 - spyder/plugins/help/widgets.py | 4 +- spyder/plugins/io_hdf5/plugin.py | 2 - .../ipythonconsole/comms/kernelcomm.py | 1 - .../tests/test_ipythonconsole.py | 16 +- .../ipythonconsole/widgets/figurebrowser.py | 3 +- spyder/plugins/ipythonconsole/widgets/help.py | 3 - .../ipythonconsole/widgets/main_widget.py | 3 +- .../plugins/ipythonconsole/widgets/shell.py | 2 - spyder/plugins/maininterpreter/confpage.py | 18 +- spyder/plugins/onlinehelp/pydoc_patch.py | 1123 ++++++++--------- .../plugins/projects/widgets/main_widget.py | 2 - .../projects/widgets/projectexplorer.py | 2 - .../variableexplorer/widgets/arrayeditor.py | 11 +- .../widgets/dataframeeditor.py | 11 +- .../variableexplorer/widgets/importwizard.py | 4 +- .../widgets/objectexplorer/objectexplorer.py | 3 - .../tests/test_objectexplorer.py | 1 - .../widgets/objectexplorer/tree_model.py | 7 +- .../widgets/tests/test_dataframeeditor.py | 2 - .../widgets/tests/test_namespacebrowser.py | 2 - .../widgets/tests/test_texteditor.py | 11 - .../variableexplorer/widgets/texteditor.py | 1 - spyder/py3compat.py | 281 +---- spyder/utils/bsdsocket.py | 2 +- spyder/utils/debug.py | 9 +- spyder/utils/encoding.py | 14 +- spyder/utils/external/lockfile.py | 7 +- spyder/utils/external/pybloom_pyqt/pybloom.py | 1 - .../tests/test_modulecompletion.py | 5 +- spyder/utils/misc.py | 3 +- spyder/utils/programs.py | 2 - spyder/utils/qstringhelpers.py | 5 - spyder/utils/qthelpers.py | 10 +- spyder/utils/sourcecode.py | 2 +- spyder/utils/switcher.py | 5 +- spyder/utils/syntaxhighlighters.py | 39 +- spyder/utils/tests/test_encoding.py | 9 +- spyder/utils/tests/test_syntaxhighlighters.py | 11 +- spyder/utils/vcs.py | 21 +- spyder/utils/workers.py | 4 +- spyder/widgets/arraybuilder.py | 1 - spyder/widgets/collectionseditor.py | 23 +- spyder/widgets/github/backend.py | 6 +- spyder/widgets/github/gh_login.py | 7 +- .../github/tests/test_github_backend.py | 1 - spyder/widgets/mixins.py | 1 - spyder/widgets/switcher.py | 1 - spyder/widgets/tests/test_collectioneditor.py | 3 +- 86 files changed, 765 insertions(+), 1261 deletions(-) diff --git a/scripts/spyder_win_post_install.py b/scripts/spyder_win_post_install.py index 32bfacbdea6..0879803a9df 100644 --- a/scripts/spyder_win_post_install.py +++ b/scripts/spyder_win_post_install.py @@ -7,12 +7,7 @@ import sys import os.path as osp import struct -try: - # Python 2 - import _winreg as winreg -except ImportError: - # Python 3 - import winreg # analysis:ignore +import winreg EWS = "Edit with Spyder" diff --git a/spyder/api/config/mixins.py b/spyder/api/config/mixins.py index 3f3f40f0ecf..0a9452df49b 100644 --- a/spyder/api/config/mixins.py +++ b/spyder/api/config/mixins.py @@ -63,7 +63,7 @@ def get_conf(self, Raises ------ - spyder.py3compat.configparser.NoOptionError + configparser.NoOptionError If the section does not exist in the configuration. """ section = self.CONF_SECTION if section is None else section @@ -92,7 +92,7 @@ def get_conf_options(self, section: Optional[str] = None): Raises ------ - spyder.py3compat.configparser.NoOptionError + configparser.NoOptionError If the section does not exist in the configuration. """ section = self.CONF_SECTION if section is None else section @@ -207,7 +207,7 @@ def get_shortcut( Raises ------ - spyder.py3compat.configparser.NoOptionError + configparser.NoOptionError If the section does not exist in the configuration. """ context = self.CONF_SECTION if context is None else context diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 0e0ab3e27e7..2068934d721 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -20,6 +20,7 @@ # Stdlib imports # ============================================================================= from collections import OrderedDict +import configparser as cp from enum import Enum import errno import gc @@ -81,7 +82,7 @@ from spyder.config.manager import CONF from spyder.config.utils import IMPORT_EXT, is_gtk_desktop from spyder.otherplugins import get_spyderplugins_mods -from spyder.py3compat import configparser as cp, PY3, to_text_string +from spyder.py3compat import to_text_string from spyder.utils import encoding, programs from spyder.utils.icon_manager import ima from spyder.utils.misc import (select_port, getcwd_or_home, @@ -1811,7 +1812,7 @@ def main(options, args): # **** Create main window **** mainwindow = None try: - if PY3 and options.report_segfault: + if options.report_segfault: import faulthandler with open(faulthandler_file, 'w') as f: faulthandler.enable(file=f) diff --git a/spyder/app/restart.py b/spyder/app/restart.py index b85dd03cb1e..9bbf7254921 100644 --- a/spyder/app/restart.py +++ b/spyder/app/restart.py @@ -34,7 +34,6 @@ from spyder.config.manager import CONF -PY2 = sys.version[0] == '2' IS_WINDOWS = os.name == 'nt' SLEEP_TIME = 0.2 # Seconds for throttling control CLOSE_ERROR, RESET_ERROR, RESTART_ERROR = [1, 2, 3] # Spyder error codes diff --git a/spyder/app/start.py b/spyder/app/start.py index e1286162f96..1b4a96945dd 100644 --- a/spyder/app/start.py +++ b/spyder/app/start.py @@ -53,7 +53,7 @@ from spyder.config.base import (get_conf_path, running_in_mac_app, reset_config_files, running_under_pytest) from spyder.utils.external import lockfile -from spyder.py3compat import is_unicode +from spyder.py3compat import is_text_string # Get argv @@ -91,7 +91,7 @@ def send_args_to_spyder(args): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) client.connect(("127.0.0.1", port)) - if is_unicode(arg): + if is_text_string(arg): arg = arg.encode('utf-8') client.send(osp.abspath(arg)) client.close() diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index 94c55770141..b627c2e1b9f 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -57,7 +57,7 @@ from spyder.plugins.help.tests.test_plugin import check_text from spyder.plugins.layout.layouts import DefaultLayouts from spyder.plugins.toolbar.api import ApplicationToolbars -from spyder.py3compat import PY2, qbytearray_to_str, to_text_string +from spyder.py3compat import qbytearray_to_str, to_text_string from spyder.utils.misc import remove_backslashes from spyder.utils.clipboard_helper import CLIPBOARD_HELPER from spyder.widgets.dock import DockTitleBar @@ -290,7 +290,7 @@ def test_opengl_implementation(main_window, qtbot): @pytest.mark.slow @flaky(max_runs=3) @pytest.mark.skipif( - np.__version__ < '1.14.0' or (os.name == 'nt' and PY2), + np.__version__ < '1.14.0', reason="This only happens in Numpy 1.14+" ) @pytest.mark.parametrize( @@ -326,8 +326,8 @@ def test_filter_numpy_warning(main_window, qtbot): @pytest.mark.slow @flaky(max_runs=3) -@pytest.mark.skipif(PY2 or not sys.platform == 'darwin', - reason="Times out in PY2 and fails on other than macOS") +@pytest.mark.skipif(not sys.platform == 'darwin', + reason="Fails on other than macOS") @pytest.mark.known_leak # Opens Spyder/QtWebEngine/Default/Cookies def test_get_help_combo(main_window, qtbot): """ @@ -382,7 +382,6 @@ def test_get_help_combo(main_window, qtbot): @pytest.mark.slow -@pytest.mark.skipif(PY2, reason="Invalid definition of function in Python 2.") @pytest.mark.known_leak # Opens Spyder/QtWebEngine/Default/Cookies def test_get_help_ipython_console_dot_notation(main_window, qtbot, tmpdir): """ @@ -1977,7 +1976,6 @@ def test_varexp_magic_dbg(main_window, qtbot): @pytest.mark.slow @flaky(max_runs=3) -@pytest.mark.skipif(PY2, reason="It times out sometimes") @pytest.mark.parametrize( 'main_window', [{'spy_config': ('ipython_console', 'pylab/inline/figure_format', 1)}, @@ -3035,8 +3033,8 @@ def _get_filenames(): @pytest.mark.slow @flaky(max_runs=3) -@pytest.mark.skipif(sys.platform == 'darwin' and not PY2, - reason="It times out on macOS/PY3") +@pytest.mark.skipif(sys.platform == 'darwin', + reason="It times out on macOS") def test_debug_unsaved_file(main_window, qtbot): """Test that we can debug an unsaved file.""" # Wait until the window is fully up diff --git a/spyder/config/base.py b/spyder/config/base.py index c0b9a4b29c0..4bda76f9305 100644 --- a/spyder/config/base.py +++ b/spyder/config/base.py @@ -25,7 +25,7 @@ # Local imports from spyder import __version__ -from spyder.py3compat import is_unicode, PY3, to_text_string, is_text_string +from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding #============================================================================== @@ -102,7 +102,7 @@ def use_dev_config_dir(use_dev_config_dir=USE_DEV_CONFIG_DIR): # Debug helpers #============================================================================== # This is needed after restarting and using debug_print -STDOUT = sys.stdout if PY3 else codecs.getwriter('utf-8')(sys.stdout) +STDOUT = sys.stdout STDERR = sys.stderr @@ -118,13 +118,10 @@ def debug_print(*message): warnings.warn("debug_print is deprecated; use the logging module instead.") if get_debug_level(): ss = STDOUT - if PY3: - # This is needed after restarting and using debug_print - for m in message: - ss.buffer.write(str(m).encode('utf-8')) - print('', file=ss) - else: - print(*message, file=ss) + # This is needed after restarting and using debug_print + for m in message: + ss.buffer.write(str(m).encode('utf-8')) + print('', file=ss) #============================================================================== @@ -147,8 +144,7 @@ def get_conf_subfolder(): # embed a PY2 interpreter in PY3) # 2. We need to save the list of installed modules (for code # completion) separately for each version - if PY3: - SUBFOLDER = SUBFOLDER + '-py3' + SUBFOLDER = SUBFOLDER + '-py3' # If running a development/beta version, save config in a separate # directory to avoid wiping or contaiminating the user's saved stable @@ -490,7 +486,7 @@ def get_translation(modname, dirname=None): def translate_dumb(x): """Dumb function to not use translations.""" - if not is_unicode(x): + if not is_text_string(x): return to_text_string(x, "utf-8") return x @@ -517,10 +513,8 @@ def translate_dumb(x): lgettext = _trans.lgettext def translate_gettext(x): - if not PY3 and is_unicode(x): - x = x.encode("utf-8") y = lgettext(x) - if is_text_string(y) and PY3: + if is_text_string(y): return y else: return to_text_string(y, "utf-8") diff --git a/spyder/config/tests/conftest.py b/spyder/config/tests/conftest.py index 84c1119cac6..53eb431c0f7 100644 --- a/spyder/config/tests/conftest.py +++ b/spyder/config/tests/conftest.py @@ -15,7 +15,6 @@ # Local imports from spyder.config.base import get_conf_path from spyder.config.user import DefaultsConfig, SpyderUserConfig, UserConfig -from spyder.py3compat import PY2 @pytest.fixture @@ -37,11 +36,7 @@ def defaultconfig(tmpdir, request): @pytest.fixture def userconfig(tmpdir, request): ini_contents = '[main]\nversion = 1.0.0\n\n' - if PY2: - # strings are quoted in Python2 but not in Python3 - ini_contents += "[section]\noption = 'value'\n\n" - else: - ini_contents += "[section]\noption = value\n\n" + ini_contents += "[section]\noption = value\n\n" name = 'spyder-test' path = str(tmpdir) @@ -72,14 +67,7 @@ def userconfig(tmpdir, request): @pytest.fixture def spyderconfig(tmpdir, request): ini_contents = '[main]\nversion = 1.0.0\n\n' - if PY2: - # strings are quoted in Python2 but not in Python3 - ini_contents += """[ipython_console] -startup/run_lines = 'value1,value2' - -""" - else: - ini_contents += """[ipython_console] + ini_contents += """[ipython_console] startup/run_lines = value1,value2 """ @@ -114,12 +102,7 @@ def spyderconfig(tmpdir, request): @pytest.fixture def spyderconfig_patches_42(tmpdir): ini_contents = '[main]\nversion = 42.0.0\n\n' - if PY2: - # Strings are quoted in Python2 but not in Python3 - ini_contents += ("[ipython_console]\nstartup/run_lines = " - "'value1,value2'") - else: - ini_contents += '[ipython_console]\nstartup/run_lines = value1,value2' + ini_contents += '[ipython_console]\nstartup/run_lines = value1,value2' name = 'spyder' inifile = tmpdir.join('{}.ini'.format(name)) @@ -133,12 +116,7 @@ def spyderconfig_patches_42(tmpdir): @pytest.fixture def spyderconfig_patches_45(tmpdir): ini_contents = '[main]\nversion = 45.0.0\n\n' - if PY2: - # Strings are quoted in Python2 but not in Python3 - ini_contents += ("[ipython_console]\nstartup/run_lines = " - "'value1,value2'") - else: - ini_contents += '[ipython_console]\nstartup/run_lines = value1,value2' + ini_contents += '[ipython_console]\nstartup/run_lines = value1,value2' name = 'spyder' inifile = tmpdir.join('{}.ini'.format(name)) @@ -152,11 +130,7 @@ def spyderconfig_patches_45(tmpdir): @pytest.fixture def spyderconfig_previous(tmpdir, mocker): ini_contents = '[main]\nversion = 50.0.0\n\n' - if PY2: - # Strings are quoted in Python2 but not in Python3 - ini_contents += "[section]\noption = 'value'" - else: - ini_contents += "[section]\noption = value" + ini_contents += "[section]\noption = value" name = 'spyder' path = str(tmpdir) diff --git a/spyder/config/tests/test_manager.py b/spyder/config/tests/test_manager.py index abb92b1f617..046270b90f0 100644 --- a/spyder/config/tests/test_manager.py +++ b/spyder/config/tests/test_manager.py @@ -7,6 +7,7 @@ """Tests for config/manager.py.""" # Standard library imports +import configparser import os import os.path as osp import shutil @@ -19,7 +20,6 @@ from spyder.config.base import get_conf_path, get_conf_paths from spyder.config.manager import ConfigurationManager from spyder.plugins.console.plugin import Console -from spyder.py3compat import configparser def clear_site_config(): diff --git a/spyder/config/tests/test_user.py b/spyder/config/tests/test_user.py index 57a698b41a8..8eeade67c77 100644 --- a/spyder/config/tests/test_user.py +++ b/spyder/config/tests/test_user.py @@ -7,6 +7,7 @@ """Tests for config/user.py.""" # Standard library imports +import configparser as cp import os # Third party imports @@ -15,8 +16,6 @@ # Local imports from spyder.config.main import CONF_VERSION, DEFAULTS from spyder.config.user import NoDefault, UserConfig -from spyder.py3compat import PY2 -from spyder.py3compat import configparser as cp from spyder.utils.fixtures import tmpconfig @@ -29,8 +28,7 @@ (('sec', 'opt', 50), '[sec][opt] = 50\n'), (('sec', 'opt', [50]), '[sec][opt] = [50]\n'), (('sec', 'opt', (50, 2)), '[sec][opt] = (50, 2)\n'), - (('sec', 'opt', {50}), '[sec][opt] = {}\n'.format( - 'set([50])' if PY2 else '{50}')), + (('sec', 'opt', {50}), '[sec][opt] = {50}\n'), (('sec', 'opt', {'k': 50}), "[sec][opt] = {'k': 50}\n"), (('sec', 'opt', False), '[sec][opt] = False\n'), (('sec', 'opt', True), '[sec][opt] = True\n'), @@ -292,7 +290,7 @@ def test_userconfig_set_default(userconfig): userconfig.set_as_defaults() value = userconfig.get_default('section', 'option') - expected = "'value'" if PY2 else 'value' + expected = 'value' assert value == expected value = userconfig.set_default('section', 'option', default_value) value = userconfig.get_default('section', 'option') @@ -349,10 +347,7 @@ def test_userconfig_set_with_string(self, userconfig): ini_contents = inifile.read() expected = '[main]\nversion = 1.0.0\n\n' - if PY2: - expected += "[section]\noption = 'new value'\n\n" - else: - expected += "[section]\noption = new value\n\n" + expected += "[section]\noption = new value\n\n" assert ini_contents == expected diff --git a/spyder/config/user.py b/spyder/config/user.py index 69ec15be297..9b4b38ae198 100644 --- a/spyder/config/user.py +++ b/spyder/config/user.py @@ -10,10 +10,9 @@ It is based on the ConfigParser module present in the standard library. """ -from __future__ import print_function, unicode_literals - # Standard library imports import ast +import configparser as cp import copy import io import os @@ -24,8 +23,7 @@ # Local imports from spyder.config.base import get_conf_path, get_module_source_path -from spyder.py3compat import configparser as cp -from spyder.py3compat import is_text_string, PY2, to_text_string +from spyder.py3compat import is_text_string, to_text_string from spyder.utils.programs import check_version @@ -48,10 +46,7 @@ def __init__(self, name, path): """ Class used to save defaults to a file and as UserConfig base class. """ - if PY2: - super(DefaultsConfig, self).__init__() - else: - super(DefaultsConfig, self).__init__(interpolation=None) + super(DefaultsConfig, self).__init__(interpolation=None) self._name = name self._path = path @@ -108,10 +103,7 @@ def _save(self): def _write_file(fpath): with io.open(fpath, 'w', encoding='utf-8') as configfile: - if PY2: - self._write(configfile) - else: - self.write(configfile) + self.write(configfile) # See spyder-ide/spyder#1086 and spyder-ide/spyder#1242 for background # on why this method contains all the exception handling. @@ -319,18 +311,7 @@ def _make_backup(self, version=None, old_version=None): def _load_from_ini(self, fpath): """Load config from the associated .ini file found at `fpath`.""" try: - if PY2: - # Python 2 - if osp.isfile(fpath): - try: - with io.open(fpath, encoding='utf-8') as configfile: - self.readfp(configfile) - except IOError: - error_text = "Failed reading file", fpath - print(error_text) # spyder: test-skip - else: - # Python 3 - self.read(fpath, encoding='utf-8') + self.read(fpath, encoding='utf-8') except cp.MissingSectionHeaderError: error_text = 'Warning: File contains no section headers.' print(error_text) # spyder: test-skip @@ -529,19 +510,7 @@ def get(self, section, option, default=NoDefault): elif isinstance(default_value, int): value = int(value) elif is_text_string(default_value): - if PY2: - try: - value = value.decode('utf-8') - try: - # Some str config values expect to be eval after - # decoding - new_value = ast.literal_eval(value) - if is_text_string(new_value): - value = new_value - except (SyntaxError, ValueError): - pass - except (UnicodeEncodeError, UnicodeDecodeError): - pass + pass else: try: # Lists, tuples, ... @@ -574,12 +543,6 @@ def set(self, section, option, value, verbose=False, save=True): default_value = self.get_default(section, option) if default_value is NoDefault: - # This let us save correctly string value options with - # no config default that contain non-ascii chars in - # Python 2 - if PY2 and is_text_string(value): - value = repr(value) - default_value = value self.set_default(section, option, default_value) diff --git a/spyder/plugins/base.py b/spyder/plugins/base.py index bf8743fc469..5b0f18104f3 100644 --- a/spyder/plugins/base.py +++ b/spyder/plugins/base.py @@ -9,6 +9,7 @@ """ # Standard library imports +import configparser import inspect import os import sys @@ -24,7 +25,7 @@ from spyder.config.gui import get_color_scheme, get_font from spyder.config.manager import CONF from spyder.config.user import NoDefault -from spyder.py3compat import configparser, is_text_string, qbytearray_to_str +from spyder.py3compat import is_text_string, qbytearray_to_str from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import ( add_actions, create_action, create_toolbutton, MENU_SEPARATOR, diff --git a/spyder/plugins/completion/providers/kite/client.py b/spyder/plugins/completion/providers/kite/client.py index 28fec60e53f..78799a1b637 100644 --- a/spyder/plugins/completion/providers/kite/client.py +++ b/spyder/plugins/completion/providers/kite/client.py @@ -22,8 +22,7 @@ from spyder.plugins.completion.providers.kite.providers import ( KiteMethodProviderMixIn) from spyder.plugins.completion.providers.kite.utils.status import status -from spyder.py3compat import ( - ConnectionError, ConnectionRefusedError, TEXT_TYPES) +from spyder.py3compat import TEXT_TYPES logger = logging.getLogger(__name__) diff --git a/spyder/plugins/completion/providers/kite/parsing/__init__.py b/spyder/plugins/completion/providers/kite/parsing/__init__.py index 59d2f885e62..4edd87bbcc2 100644 --- a/spyder/plugins/completion/providers/kite/parsing/__init__.py +++ b/spyder/plugins/completion/providers/kite/parsing/__init__.py @@ -8,9 +8,6 @@ import logging import re -# Local imports -from spyder.py3compat import PY2 - ident_re = r'[a-zA-Z_][a-zA-Z0-9_]*' dotted_path_re = r'{ident}(?:\.{ident})*'.format(ident=ident_re) @@ -46,9 +43,6 @@ def find_returning_function_path(text, cursor, line_start='\n'): if not re.match(ident_full_re, name): return None - if PY2: - line_start = line_start.encode('utf-8') - assign_re = r'{line_start}\s*{name}\s*=\s*({dotted_path})\('.format( line_start=re.escape(line_start), name=re.escape(name), diff --git a/spyder/plugins/completion/providers/kite/utils/install.py b/spyder/plugins/completion/providers/kite/utils/install.py index 2e09d6afe92..2656dce43b5 100644 --- a/spyder/plugins/completion/providers/kite/utils/install.py +++ b/spyder/plugins/completion/providers/kite/utils/install.py @@ -7,7 +7,6 @@ """Kite installation functions.""" # Standard library imports -from __future__ import print_function import logging import os import os.path as osp diff --git a/spyder/plugins/completion/providers/languageserver/providers/utils.py b/spyder/plugins/completion/providers/languageserver/providers/utils.py index f24a04bc555..f9fdfd092e5 100644 --- a/spyder/plugins/completion/providers/languageserver/providers/utils.py +++ b/spyder/plugins/completion/providers/languageserver/providers/utils.py @@ -9,20 +9,13 @@ import re import os import os.path as osp -from spyder.py3compat import PY2 from spyder.utils.encoding import to_unicode -if PY2: - import pathlib2 as pathlib - from urlparse import urlparse - from urllib import url2pathname - from urllib import quote as urlquote_from_bytes -else: - import pathlib - from urllib.parse import urlparse - from urllib.parse import quote_from_bytes as urlquote_from_bytes - from urllib.request import url2pathname +import pathlib +from urllib.parse import urlparse +from urllib.parse import quote_from_bytes as urlquote_from_bytes +from urllib.request import url2pathname def as_posix(path_obj): @@ -50,10 +43,7 @@ def make_as_uri(path): def path_as_uri(path): path_obj = pathlib.Path(osp.abspath(path)) - if os.name == 'nt' and PY2: - return make_as_uri(path_obj) - else: - return path_obj.as_uri() + return path_obj.as_uri() def process_uri(uri): diff --git a/spyder/plugins/completion/providers/languageserver/transport/main.py b/spyder/plugins/completion/providers/languageserver/transport/main.py index cf3c9ada30b..6427ef59338 100644 --- a/spyder/plugins/completion/providers/languageserver/transport/main.py +++ b/spyder/plugins/completion/providers/languageserver/transport/main.py @@ -27,7 +27,6 @@ TCPLanguageServerClient) from spyder.plugins.completion.providers.languageserver.transport.stdio.producer import ( StdioLanguageServerClient) -from spyder.py3compat import getcwd logger = logging.getLogger(__name__) @@ -53,7 +52,7 @@ default=None, help="Log file to register ls-server activity") parser.add_argument('--folder', - default=getcwd(), + default=os.getcwd(), help="Initial current working directory used to " "initialize ls-server") parser.add_argument('--external-server', diff --git a/spyder/plugins/completion/providers/languageserver/transport/tcp/producer.py b/spyder/plugins/completion/providers/languageserver/transport/tcp/producer.py index f2b4f681a7b..b07f583fb5b 100644 --- a/spyder/plugins/completion/providers/languageserver/transport/tcp/producer.py +++ b/spyder/plugins/completion/providers/languageserver/transport/tcp/producer.py @@ -25,7 +25,6 @@ TCPIncomingMessageThread) from spyder.plugins.completion.providers.languageserver.transport.common.producer import ( LanguageServerClient) -from spyder.py3compat import ConnectionError, BrokenPipeError logger = logging.getLogger(__name__) diff --git a/spyder/plugins/console/utils/interpreter.py b/spyder/plugins/console/utils/interpreter.py index 84ed99a34d8..81389130b9e 100644 --- a/spyder/plugins/console/utils/interpreter.py +++ b/spyder/plugins/console/utils/interpreter.py @@ -6,8 +6,6 @@ """Shell Interpreter""" -from __future__ import print_function - import sys import atexit import threading diff --git a/spyder/plugins/console/widgets/internalshell.py b/spyder/plugins/console/widgets/internalshell.py index 05919cf9998..5840d9b8f44 100644 --- a/spyder/plugins/console/widgets/internalshell.py +++ b/spyder/plugins/console/widgets/internalshell.py @@ -14,6 +14,7 @@ #FIXME: Internal shell MT: for i in range(100000): print i -> bug # Standard library imports +import builtins from time import time import os import threading @@ -28,8 +29,7 @@ from spyder import get_versions from spyder.api.translations import get_translation from spyder.plugins.console.utils.interpreter import Interpreter -from spyder.py3compat import (builtins, to_binary_string, - to_text_string) +from spyder.py3compat import to_binary_string, to_text_string from spyder.utils.icon_manager import ima from spyder.utils import programs from spyder.utils.misc import get_error_match, getcwd_or_home diff --git a/spyder/plugins/console/widgets/shell.py b/spyder/plugins/console/widgets/shell.py index 8473a4a72da..bf8451c073e 100644 --- a/spyder/plugins/console/widgets/shell.py +++ b/spyder/plugins/console/widgets/shell.py @@ -12,6 +12,7 @@ # pylint: disable=R0201 # Standard library imports +import builtins import keyword import locale import os @@ -29,8 +30,8 @@ # Local import from spyder.config.base import _, get_conf_path, get_debug_level, STDERR from spyder.config.manager import CONF -from spyder.py3compat import (builtins, is_string, is_text_string, - PY3, str_lower, to_text_string) +from spyder.py3compat import (is_string, is_text_string, + to_text_string) from spyder.utils import encoding from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import (add_actions, create_action, keybinding, @@ -556,17 +557,14 @@ def write(self, text, flush=False, error=False, prompt=False): def flush(self, error=False, prompt=False): """Flush buffer, write text to console""" # Fix for spyder-ide/spyder#2452 - if PY3: - try: - text = "".join(self.__buffer) - except TypeError: - text = b"".join(self.__buffer) - try: - text = text.decode( locale.getdefaultlocale()[1] ) - except: - pass - else: + try: text = "".join(self.__buffer) + except TypeError: + text = b"".join(self.__buffer) + try: + text = text.decode( locale.getdefaultlocale()[1] ) + except: + pass self.__buffer = [] self.insert_text(text, at_end=True, error=error, prompt=prompt) @@ -930,8 +928,8 @@ def show_completion_list(self, completions, completion_text=""): if comp.startswith('_')]) completions = sorted(set(completions) - underscore, - key=lambda x: str_lower(x[0])) - completions += sorted(underscore, key=lambda x: str_lower(x[0])) + key=lambda x: str.lower(x[0])) + completions += sorted(underscore, key=lambda x: str.lower(x[0])) self.show_completion_widget(completions) def show_code_completion(self): diff --git a/spyder/plugins/editor/panels/scrollflag.py b/spyder/plugins/editor/panels/scrollflag.py index 1ae496f039d..89411965201 100644 --- a/spyder/plugins/editor/panels/scrollflag.py +++ b/spyder/plugins/editor/panels/scrollflag.py @@ -8,7 +8,6 @@ """ # Standard library imports -from __future__ import division import sys from math import ceil diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index 9414250135e..8b7d1550cb6 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -37,7 +37,7 @@ from spyder.config.manager import CONF from spyder.config.utils import (get_edit_filetypes, get_edit_filters, get_filter) -from spyder.py3compat import PY2, qbytearray_to_str, to_text_string +from spyder.py3compat import qbytearray_to_str, to_text_string from spyder.utils import encoding, programs, sourcecode from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import create_action, add_actions, MENU_SEPARATOR @@ -169,7 +169,7 @@ def __init__(self, parent, ignore_last_opened_files=False): if os.name == "nt": shebang = [] else: - shebang = ['#!/usr/bin/env python' + ('2' if PY2 else '3')] + shebang = ['#!/usr/bin/env python3'] header = shebang + [ '# -*- coding: utf-8 -*-', '"""', 'Created on %(date)s', '', diff --git a/spyder/plugins/editor/utils/autosave.py b/spyder/plugins/editor/utils/autosave.py index 8d1c1085134..7958ac8187b 100644 --- a/spyder/plugins/editor/utils/autosave.py +++ b/spyder/plugins/editor/utils/autosave.py @@ -39,7 +39,6 @@ from spyder.config.base import _, get_conf_path, running_under_pytest from spyder.plugins.editor.widgets.autosaveerror import AutosaveErrorDialog from spyder.plugins.editor.widgets.recover import RecoveryDialog -from spyder.py3compat import PY2 from spyder.utils.programs import is_spyder_process @@ -286,10 +285,7 @@ def save_autosave_mapping(self): pidfile_name = osp.join(autosave_dir, 'pid{}.txt'.format(my_pid)) if self.name_mapping: with open(pidfile_name, 'w') as pidfile: - if PY2: - pidfile.write(repr(self.name_mapping)) - else: - pidfile.write(ascii(self.name_mapping)) + pidfile.write(ascii(self.name_mapping)) else: try: os.remove(pidfile_name) diff --git a/spyder/plugins/editor/widgets/base.py b/spyder/plugins/editor/widgets/base.py index efc660f7091..a3861f75f37 100644 --- a/spyder/plugins/editor/widgets/base.py +++ b/spyder/plugins/editor/widgets/base.py @@ -25,7 +25,7 @@ # Local imports from spyder.config.gui import get_font from spyder.config.manager import CONF -from spyder.py3compat import PY3, to_text_string +from spyder.py3compat import to_text_string from spyder.widgets.calltip import CallTipWidget, ToolTipWidget from spyder.widgets.mixins import BaseEditMixin from spyder.plugins.editor.api.decoration import TextDecoration, DRAW_ORDERS @@ -483,7 +483,7 @@ def toPlainText(self): # corruptions when saving files with certain combinations # of unicode chars on them (like the one attached on # spyder-ide/spyder#1546). - if os.name == 'nt' and PY3: + if os.name == 'nt': text = self.get_text('sof', 'eof') return text.replace('\u2028', '\n').replace('\u2029', '\n')\ .replace('\u0085', '\n') diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index 45bbc0b8b97..1418d9bf22e 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -78,7 +78,7 @@ from spyder.plugins.editor.widgets.base import TextEditBaseWidget from spyder.plugins.outlineexplorer.api import (OutlineExplorerData as OED, is_cell_header) -from spyder.py3compat import PY2, to_text_string, is_string, is_text_string +from spyder.py3compat import to_text_string, is_string, is_text_string from spyder.utils import encoding, sourcecode from spyder.utils.clipboard_helper import CLIPBOARD_HELPER from spyder.utils.icon_manager import ima @@ -1097,11 +1097,7 @@ def log_lsp_handle_errors(self, message): # there. So we intentionally leave an error in this call # to get the entire stack info generated by it, which # gives the info we need from users. - if PY2: - logger.error(message, exc_info=True) - print(message, file=sys.stderr) - else: - logger.error('%', 1, stack_info=True) + logger.error('%', 1, stack_info=True) # ---- LSP: Configuration and start/stop # ------------------------------------------------------------------------- diff --git a/spyder/plugins/editor/widgets/editorstack_helpers.py b/spyder/plugins/editor/widgets/editorstack_helpers.py index f3ae62014e0..5c5aedae639 100644 --- a/spyder/plugins/editor/widgets/editorstack_helpers.py +++ b/spyder/plugins/editor/widgets/editorstack_helpers.py @@ -5,6 +5,7 @@ # (see spyder/__init__.py for details) # Standard library imports +from collections.abc import MutableSequence import logging # Third party imports @@ -13,7 +14,7 @@ # Local imports from spyder.plugins.editor.utils.findtasks import find_tasks -from spyder.py3compat import to_text_string, MutableSequence +from spyder.py3compat import to_text_string logger = logging.getLogger(__name__) diff --git a/spyder/plugins/editor/widgets/tests/conftest.py b/spyder/plugins/editor/widgets/tests/conftest.py index 4fc664d9d38..d030067df56 100644 --- a/spyder/plugins/editor/widgets/tests/conftest.py +++ b/spyder/plugins/editor/widgets/tests/conftest.py @@ -28,7 +28,7 @@ from spyder.plugins.editor.widgets.codeeditor import CodeEditor from spyder.plugins.editor.widgets.editor import EditorStack from spyder.plugins.explorer.widgets.tests.conftest import create_folders_files -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string from spyder.widgets.findreplace import FindReplace @@ -242,8 +242,6 @@ def teardown(): sys_stream = capsys.readouterr() sys_err = sys_stream.err - if PY2: - sys_err = to_text_string(sys_err).encode('utf-8') assert sys_err == '' request.addfinalizer(teardown) diff --git a/spyder/plugins/editor/widgets/tests/test_editor.py b/spyder/plugins/editor/widgets/tests/test_editor.py index 86d0ee80d84..7cb9abbce8a 100644 --- a/spyder/plugins/editor/widgets/tests/test_editor.py +++ b/spyder/plugins/editor/widgets/tests/test_editor.py @@ -26,7 +26,6 @@ from spyder.plugins.editor.widgets.editor import EditorStack from spyder.utils.stylesheet import APP_STYLESHEET from spyder.widgets.findreplace import FindReplace -from spyder.py3compat import PY2 HERE = osp.abspath(osp.dirname(__file__)) @@ -560,7 +559,6 @@ def test_advance_cell(editor_cells_bot): assert editor.get_cursor_line_column() == (6, 0) -@pytest.mark.skipif(PY2, reason="Python2 does not support unicode very well") def test_get_current_word(base_editor_bot, qtbot): """Test getting selected valid python word.""" editor_stack = base_editor_bot diff --git a/spyder/plugins/editor/widgets/tests/test_hints_and_calltips.py b/spyder/plugins/editor/widgets/tests/test_hints_and_calltips.py index beb3a372d6c..a4f58b18e39 100644 --- a/spyder/plugins/editor/widgets/tests/test_hints_and_calltips.py +++ b/spyder/plugins/editor/widgets/tests/test_hints_and_calltips.py @@ -19,7 +19,6 @@ CloseBracketsExtension) # Constants -PY2 = sys.version[0] == '2' TEST_SIG = 'some_function(foo={}, hello=None)' TEST_DOCSTRING = "This is the test docstring." TEST_TEXT = """'''Testing something''' @@ -32,8 +31,6 @@ def {SIG}: @pytest.mark.slow @pytest.mark.order(2) -@pytest.mark.skipif(sys.platform == 'darwin' and PY2, - reason='Fails on Mac and Python 2') def test_hide_calltip(completions_codeeditor, qtbot): """Test that calltips are hidden when a matching ')' is found.""" code_editor, _ = completions_codeeditor @@ -64,10 +61,6 @@ def test_hide_calltip(completions_codeeditor, qtbot): @pytest.mark.slow @pytest.mark.order(2) -@pytest.mark.skipif( - os.name == 'nt' and PY2, - reason='Fails on Win', -) @pytest.mark.parametrize('params', [ # Parameter, Expected Output ('dict', 'dict'), diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py index 221046bf9e2..eedcf950789 100644 --- a/spyder/plugins/editor/widgets/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/tests/test_introspection.py @@ -29,7 +29,6 @@ path_as_uri) from spyder.plugins.completion.providers.kite.utils.status import ( check_if_kite_installed, check_if_kite_running) -from spyder.py3compat import PY2 from spyder.config.manager import CONF @@ -55,8 +54,8 @@ def set_executable_config_helper(executable=None): @pytest.mark.slow @pytest.mark.order(1) -@pytest.mark.skipif(not sys.platform.startswith('linux') or PY2, - reason='Only works on Linux and Python 3') +@pytest.mark.skipif(not sys.platform.startswith('linux'), + reason='Only works on Linux') @flaky(max_runs=5) def test_fallback_completions(completions_codeeditor, qtbot): code_editor, completion_plugin = completions_codeeditor @@ -516,12 +515,9 @@ def test_completions(completions_codeeditor, qtbot): qtbot.keyPress(completion, Qt.Key_Tab) - if PY2: - assert "hypot(x, y)" in [x['label'] for x in sig.args[0]] - else: - assert [x['label'] for x in sig.args[0]][0] in ["hypot(x, y)", - "hypot(*coordinates)", - 'hypot(coordinates)'] + assert [x['label'] for x in sig.args[0]][0] in ["hypot(x, y)", + "hypot(*coordinates)", + 'hypot(coordinates)'] print([(x['label'], x['provider']) for x in sig.args[0]]) @@ -553,12 +549,9 @@ def test_completions(completions_codeeditor, qtbot): with qtbot.waitSignal(completion.sig_show_completions, timeout=10000) as sig: qtbot.keyPress(code_editor, Qt.Key_Tab) - if PY2: - assert "hypot(x, y)" in [x['label'] for x in sig.args[0]] - else: - assert [x['label'] for x in sig.args[0]][0] in ["hypot(x, y)", - "hypot(*coordinates)", - 'hypot(coordinates)'] + assert [x['label'] for x in sig.args[0]][0] in ["hypot(x, y)", + "hypot(*coordinates)", + 'hypot(coordinates)'] # right for () + enter for new line qtbot.keyPress(code_editor, Qt.Key_Right, delay=300) diff --git a/spyder/plugins/editor/widgets/tests/test_recover.py b/spyder/plugins/editor/widgets/tests/test_recover.py index 926fe9b2a6c..392fecd39db 100644 --- a/spyder/plugins/editor/widgets/tests/test_recover.py +++ b/spyder/plugins/editor/widgets/tests/test_recover.py @@ -15,7 +15,6 @@ from qtpy.QtWidgets import QDialogButtonBox, QPushButton, QTableWidget # Local imports -from spyder.py3compat import PY2 from spyder.plugins.editor.widgets.recover import (make_temporary_files, RecoveryDialog) @@ -180,9 +179,8 @@ def test_recoverydialog_restore_fallback(qtbot, recovery_env, mocker): Regression test for spyder-ide/spyder#8631. """ orig_dir, autosave_dir, autosave_mapping = recovery_env - if not PY2: - mocker.patch('spyder.plugins.editor.widgets.recover.os.replace', - side_effect=OSError) + mocker.patch('spyder.plugins.editor.widgets.recover.os.replace', + side_effect=OSError) dialog = RecoveryDialog(autosave_mapping) table = dialog.findChild(QTableWidget) button = table.cellWidget(0, 2).findChildren(QPushButton)[0] @@ -203,9 +201,8 @@ def test_recoverydialog_restore_when_error(qtbot, recovery_env, mocker): in the grid is not deactivated. """ orig_dir, autosave_dir, autosave_mapping = recovery_env - if not PY2: - mocker.patch('spyder.plugins.editor.widgets.recover.os.replace', - side_effect=OSError) + mocker.patch('spyder.plugins.editor.widgets.recover.os.replace', + side_effect=OSError) mocker.patch('spyder.plugins.editor.widgets.recover.shutil.copy2', side_effect=IOError) mock_QMessageBox = mocker.patch( diff --git a/spyder/plugins/explorer/widgets/explorer.py b/spyder/plugins/explorer/widgets/explorer.py index 1757ba88588..aad88da57ce 100644 --- a/spyder/plugins/explorer/widgets/explorer.py +++ b/spyder/plugins/explorer/widgets/explorer.py @@ -11,8 +11,6 @@ # pylint: disable=R0911 # pylint: disable=R0201 -from __future__ import with_statement - # Standard library imports import os import os.path as osp diff --git a/spyder/plugins/explorer/widgets/fileassociations.py b/spyder/plugins/explorer/widgets/fileassociations.py index 2ea7eef8670..6bb02a28025 100644 --- a/spyder/plugins/explorer/widgets/fileassociations.py +++ b/spyder/plugins/explorer/widgets/fileassociations.py @@ -8,8 +8,6 @@ File associations widget for use in global and project preferences. """ -from __future__ import print_function - # Standard library imports import os import re diff --git a/spyder/plugins/explorer/widgets/tests/test_fileassociations.py b/spyder/plugins/explorer/widgets/tests/test_fileassociations.py index 56a32389d80..0bb2ef5bd5d 100644 --- a/spyder/plugins/explorer/widgets/tests/test_fileassociations.py +++ b/spyder/plugins/explorer/widgets/tests/test_fileassociations.py @@ -20,11 +20,9 @@ # Local imports from spyder.plugins.explorer.widgets.fileassociations import ( ApplicationsDialog, InputTextDialog) -from spyder.py3compat import PY2 @pytest.mark.order(1) -@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!') def test_input_text_dialog(qtbot): widget = InputTextDialog() qtbot.addWidget(widget) @@ -56,7 +54,6 @@ def test_input_text_dialog(qtbot): @pytest.mark.order(1) -@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!') def test_apps_dialog(qtbot, tmp_path): widget = ApplicationsDialog() qtbot.addWidget(widget) @@ -145,7 +142,6 @@ def create_timer(func, interval=500): @pytest.mark.order(1) -@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!') def test_file_assoc_widget(file_assoc_widget): qtbot, widget = file_assoc_widget diff --git a/spyder/plugins/explorer/widgets/utils.py b/spyder/plugins/explorer/widgets/utils.py index 6ae0cf4d411..9ab54708e73 100644 --- a/spyder/plugins/explorer/widgets/utils.py +++ b/spyder/plugins/explorer/widgets/utils.py @@ -19,7 +19,6 @@ # Local imports from spyder.api.translations import get_translation -from spyder.py3compat import str_lower from spyder.utils import encoding from spyder.utils.icon_manager import ima @@ -83,7 +82,7 @@ def listdir(path, include=r'.', exclude=r'\.pyc$|^\.', folders_only=False): continue elif re.search(include, item): namelist.append(item) - return sorted(dirlist, key=str_lower) + sorted(namelist, key=str_lower) + return sorted(dirlist, key=str.lower) + sorted(namelist, key=str.lower) def has_subdirectories(path, include, exclude): diff --git a/spyder/plugins/help/utils/sphinxify.py b/spyder/plugins/help/utils/sphinxify.py index f6688a01f69..e3cdd771ec8 100644 --- a/spyder/plugins/help/utils/sphinxify.py +++ b/spyder/plugins/help/utils/sphinxify.py @@ -24,6 +24,7 @@ import codecs import os import os.path as osp +import pathlib import shutil import sys from tempfile import mkdtemp @@ -38,13 +39,8 @@ # Local imports from spyder.config.base import (_, get_module_data_path, get_module_source_path) -from spyder.py3compat import PY2 from spyder.utils import encoding -if PY2: - import pathlib2 as pathlib -else: - import pathlib #----------------------------------------------------------------------------- # Globals and constants diff --git a/spyder/plugins/help/utils/tutorial.rst b/spyder/plugins/help/utils/tutorial.rst index 00c1db23f05..79fdf512df5 100644 --- a/spyder/plugins/help/utils/tutorial.rst +++ b/spyder/plugins/help/utils/tutorial.rst @@ -323,7 +323,6 @@ You'll need to have SymPy installed for it to work, and a LaTeX distribution on .. code-block:: python These commands were executed: - >>> from __future__ import division >>> from sympy import * >>> x, y, z, t = symbols('x y z t') >>> k, m, n = symbols('k m n', integer=True) diff --git a/spyder/plugins/help/widgets.py b/spyder/plugins/help/widgets.py index d5f5151961f..1eff89887a2 100644 --- a/spyder/plugins/help/widgets.py +++ b/spyder/plugins/help/widgets.py @@ -31,7 +31,7 @@ from spyder.plugins.help.utils.sphinxify import (CSS_PATH, generate_context, loading, usage, warning) from spyder.plugins.help.utils.sphinxthread import SphinxThread -from spyder.py3compat import get_meth_class_inst, to_text_string +from spyder.py3compat import to_text_string from spyder.utils import programs from spyder.utils.image_path_manager import get_image_path from spyder.utils.palette import QStylePalette @@ -682,7 +682,7 @@ def restore_text(self): func = cb[0] args = cb[1:] func(*args) - if get_meth_class_inst(func) is self.rich_text: + if func.__self__ is self.rich_text: self.switch_to_rich_text() else: self.switch_to_plain_text() diff --git a/spyder/plugins/io_hdf5/plugin.py b/spyder/plugins/io_hdf5/plugin.py index 228e154156a..1a866ede0e4 100644 --- a/spyder/plugins/io_hdf5/plugin.py +++ b/spyder/plugins/io_hdf5/plugin.py @@ -32,8 +32,6 @@ TODO: Check issues with valid python names vs valid h5f5 names """ -from __future__ import print_function - import importlib # Do not import h5py here because it will try to import IPython, # and this is freezing the Spyder GUI diff --git a/spyder/plugins/ipythonconsole/comms/kernelcomm.py b/spyder/plugins/ipythonconsole/comms/kernelcomm.py index 99707f39f10..c0a4ebc2f3f 100644 --- a/spyder/plugins/ipythonconsole/comms/kernelcomm.py +++ b/spyder/plugins/ipythonconsole/comms/kernelcomm.py @@ -19,7 +19,6 @@ from zmq.ssh import tunnel as zmqtunnel from spyder_kernels.comms.commbase import CommBase, CommError -from spyder.py3compat import TimeoutError from spyder.plugins.ipythonconsole.utils.ssh import openssh_tunnel logger = logging.getLogger(__name__) diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py index 2ec718cc012..c741f5bc13a 100644 --- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py +++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py @@ -38,7 +38,7 @@ running_in_ci, running_in_ci_with_conda) from spyder.config.gui import get_color_scheme from spyder.config.utils import is_anaconda -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string from spyder.plugins.help.tests.test_plugin import check_text from spyder.plugins.ipythonconsole.tests.conftest import ( get_conda_test_env, get_console_background_color, get_console_font_color, @@ -344,8 +344,6 @@ def test_non_ascii_stderr_file(ipyconsole, qtbot): @flaky(max_runs=3) -@pytest.mark.skipif(PY2 and sys.platform == 'darwin', - reason="It hangs frequently on Python 2.7 and macOS") def test_console_import_namespace(ipyconsole, qtbot): """Test an import of the form 'from foo import *'.""" # Wait until the window is fully up @@ -594,8 +592,8 @@ def test_save_history_dbg(ipyconsole, qtbot): @flaky(max_runs=3) -@pytest.mark.skipif(PY2 or IPython.version_info < (7, 17), - reason="insert is not the same in py2") +@pytest.mark.skipif(IPython.version_info < (7, 17), + reason="insert is not the same in pre 7.17 ipython") def test_dbg_input(ipyconsole, qtbot): """Test that spyder doesn't send pdb commands to unrelated input calls.""" shell = ipyconsole.get_current_shellwidget() @@ -624,7 +622,6 @@ def test_dbg_input(ipyconsole, qtbot): @flaky(max_runs=3) -@pytest.mark.skipif(PY2, reason="It doesn't work on PY2") def test_unicode_vars(ipyconsole, qtbot): """ Test that the Variable Explorer Works with unicode variables. @@ -800,7 +797,7 @@ def add(x, y): @flaky(max_runs=3) -@pytest.mark.skipif(os.name == 'nt' or (PY2 and PYQT5), +@pytest.mark.skipif(os.name == 'nt', reason="It times out frequently") def test_mpl_backend_change(ipyconsole, qtbot): """ @@ -1149,8 +1146,8 @@ def test_startup_working_directory(ipyconsole, qtbot): @flaky(max_runs=3) -@pytest.mark.skipif(not sys.platform.startswith('linux') or PY2, - reason="It only works on Linux with python 3.") +@pytest.mark.skipif(not sys.platform.startswith('linux'), + reason="It only works on Linux.") def test_console_complete(ipyconsole, qtbot, tmpdir): """Test code completions in the console.""" shell = ipyconsole.get_current_shellwidget() @@ -1729,7 +1726,6 @@ def check_value(name, value): @flaky(max_runs=3) -@pytest.mark.skipif(PY2, reason="Doesn't work on Python 2.7") def test_pdb_code_and_cmd_separation(ipyconsole, qtbot): """Check commands and code are separted.""" shell = ipyconsole.get_current_shellwidget() diff --git a/spyder/plugins/ipythonconsole/widgets/figurebrowser.py b/spyder/plugins/ipythonconsole/widgets/figurebrowser.py index 6b71b00f13c..e4d04814bcb 100644 --- a/spyder/plugins/ipythonconsole/widgets/figurebrowser.py +++ b/spyder/plugins/ipythonconsole/widgets/figurebrowser.py @@ -8,13 +8,14 @@ Widget that handles communications between the IPython Console and the Plots plugin """ +# Standard library imports +from base64 import decodebytes # ---- Third party library imports from qtconsole.rich_jupyter_widget import RichJupyterWidget # ---- Local library imports from spyder.config.base import _ -from spyder.py3compat import decodebytes class FigureBrowserWidget(RichJupyterWidget): diff --git a/spyder/plugins/ipythonconsole/widgets/help.py b/spyder/plugins/ipythonconsole/widgets/help.py index 46783f43f16..60e03bbad7f 100644 --- a/spyder/plugins/ipythonconsole/widgets/help.py +++ b/spyder/plugins/ipythonconsole/widgets/help.py @@ -10,8 +10,6 @@ """ # Standard library imports -from __future__ import absolute_import - import re # Third party imports @@ -21,7 +19,6 @@ from qtpy.QtCore import QEventLoop # Local imports -from spyder.py3compat import TimeoutError from spyder_kernels.utils.dochelpers import (getargspecfromtext, getsignaturefromtext) from spyder_kernels.comms.commbase import CommError diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index a12c77a6fa8..e07642d280b 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -43,7 +43,6 @@ from spyder.plugins.ipythonconsole.widgets import ( ClientWidget, ConsoleRestartDialog, COMPLETION_WIDGET_TYPE, KernelConnectionDialog, PageControlWidget, ShellWidget) -from spyder.py3compat import PY38_OR_MORE from spyder.utils import encoding, programs, sourcecode from spyder.utils.misc import get_error_match, remove_backslashes from spyder.utils.palette import QStylePalette @@ -1068,7 +1067,7 @@ def _init_asyncio_patch(self): - Do this as early as possible to make it a low priority and overrideable. """ - if os.name == 'nt' and PY38_OR_MORE: + if os.name == 'nt': # Tests on Linux hang if we don't leave this import here. import tornado if tornado.version_info >= (6, 1): diff --git a/spyder/plugins/ipythonconsole/widgets/shell.py b/spyder/plugins/ipythonconsole/widgets/shell.py index b27cd2a5fb3..ee63856d3bd 100644 --- a/spyder/plugins/ipythonconsole/widgets/shell.py +++ b/spyder/plugins/ipythonconsole/widgets/shell.py @@ -500,7 +500,6 @@ def long_banner(self): if sympy_o: lines = """ These commands were executed: ->>> from __future__ import division >>> from sympy import * >>> x, y, z, t = symbols('x y z t') >>> k, m, n = symbols('k m n', integer=True) @@ -617,7 +616,6 @@ def _perform_reset(self, message): self.silent_execute("from pylab import *") if kernel_env.get('SPY_SYMPY_O') == 'True': sympy_init = """ - from __future__ import division from sympy import * x, y, z, t = symbols('x y z t') k, m, n = symbols('k m n', integer=True) diff --git a/spyder/plugins/maininterpreter/confpage.py b/spyder/plugins/maininterpreter/confpage.py index 0f4c3ff795d..617c9041a50 100644 --- a/spyder/plugins/maininterpreter/confpage.py +++ b/spyder/plugins/maininterpreter/confpage.py @@ -18,7 +18,7 @@ # Local imports from spyder.api.translations import get_translation from spyder.api.preferences import PluginConfigPage -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string from spyder.utils import programs from spyder.utils.conda import get_list_conda_envs_cache from spyder.utils.misc import get_python_executable @@ -206,21 +206,7 @@ def set_umr_namelist(self): fixed_namelist = [] non_ascii_namelist = [] for module_name in namelist: - if PY2: - if all(ord(c) < 128 for c in module_name): - if programs.is_module_installed(module_name): - fixed_namelist.append(module_name) - else: - QMessageBox.warning( - self, - _('Warning'), - _("You are working with Python 2, this means " - "that you can not import a module that " - "contains non-ascii characters."), - QMessageBox.Ok, - ) - non_ascii_namelist.append(module_name) - elif programs.is_module_installed(module_name): + if programs.is_module_installed(module_name): fixed_namelist.append(module_name) invalid = ", ".join(set(namelist)-set(fixed_namelist)- diff --git a/spyder/plugins/onlinehelp/pydoc_patch.py b/spyder/plugins/onlinehelp/pydoc_patch.py index e66ffbf5b56..9cd44dd3fba 100644 --- a/spyder/plugins/onlinehelp/pydoc_patch.py +++ b/spyder/plugins/onlinehelp/pydoc_patch.py @@ -22,31 +22,30 @@ # Local imports from spyder.config.base import _, DEV from spyder.config.gui import is_dark_interface, get_font -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string -if not PY2: - from pydoc import ( - classname, classify_class_attrs, describe, Doc, format_exception_only, - Helper, HTMLRepr, _is_bound_method, ModuleScanner, locate, replace, - visiblename, isdata, getdoc, deque, _split_list) +from pydoc import ( + classname, classify_class_attrs, describe, Doc, format_exception_only, + Helper, HTMLRepr, _is_bound_method, ModuleScanner, locate, replace, + visiblename, isdata, getdoc, deque, _split_list) - class CustomHTMLDoc(Doc): - """ - Formatter class for HTML documentation. +class CustomHTMLDoc(Doc): + """ + Formatter class for HTML documentation. - See: - https://github.com/aroberge/mod_pydoc/blob/master/mod_pydoc/pydoc.py - """ + See: + https://github.com/aroberge/mod_pydoc/blob/master/mod_pydoc/pydoc.py + """ - # ------------------------------------------ HTML formatting utilities + # ------------------------------------------ HTML formatting utilities - _repr_instance = HTMLRepr() - repr = _repr_instance.repr - escape = _repr_instance.escape + _repr_instance = HTMLRepr() + repr = _repr_instance.repr + escape = _repr_instance.escape - def page(self, title, contents): - """Format an HTML page.""" - return '''\ + def page(self, title, contents): + """Format an HTML page.""" + return '''\ Python: %s @@ -54,565 +53,565 @@ def page(self, title, contents): %s ''' % (title, contents) - def heading(self, title, extras=''): - """Format a page heading.""" - return ''' - -
{}{}
- '''.format(title, extras or ' ') - - def html_section( - self, title, contents, width=6, - prelude='', marginalia=None, gap=' ', - css_class=''): - """Format a section with a heading.""" - result = ''' - - - - '''.format(css_class, title) - if prelude: - result = result + ''' - - - '''.format(marginalia, prelude, gap) - elif marginalia: - result = result + ''' - '''.format(marginalia, gap) - - contents = '{}
- {}
{}{}
{}
{}{}

'.format(contents) - return result + '\n' + contents - - def bigsection(self, title, *args, **kwargs): - """Format a section with a big heading.""" - title = '{}'.format(title) - return self.html_section(title, *args, **kwargs) - - def preformat(self, text): - """Format literal preformatted text.""" - text = self.escape(text.expandtabs()) - return replace(text, '\n\n', '\n \n', '\n\n', '\n \n', - ' ', ' ', '\n', '
\n') - - def multicolumn(self, list, format, cols=4): - """Format a list of items into a multi-column list.""" - result = '' - rows = (len(list)+cols-1)//cols - for col in range(cols): - result = ( - result + '' - % (100//cols)) - for i in range(rows*col, rows*col+rows): - if i < len(list): - result = result + format(list[i]) + '
\n' - result = result + '' - return '%s
' % result - - def grey(self, text): - """Grey span.""" - return '%s' % text - - def namelink(self, name, *dicts): - """Make a link for an identifier, given name-to-URL mappings.""" - for dict in dicts: - if name in dict: - return '%s' % (dict[name], name) - return name - - def classlink(self, object, modname): - """Make a link for a class.""" - name, module = object.__name__, sys.modules.get(object.__module__) - if hasattr(module, name) and getattr(module, name) is object: - return '%s' % ( - module.__name__, name, classname(object, modname)) - return classname(object, modname) - - def modulelink(self, object): - """Make a link for a module.""" - return '%s' % ( - object.__name__, object.__name__) - - def modpkglink(self, modpkginfo): - """Make a link for a module or package to display in an index.""" - name, path, ispackage, shadowed = modpkginfo - if shadowed: - return self.grey(name) - if path: - url = '%s.%s.html' % (path, name) - else: - url = '%s.html' % name - if ispackage: - text = '%s (package)' % name - else: - text = name - return '%s' % (url, text) + def heading(self, title, extras=''): + """Format a page heading.""" + return ''' + +
{}{}
+ '''.format(title, extras or ' ') + + def html_section( + self, title, contents, width=6, + prelude='', marginalia=None, gap=' ', + css_class=''): + """Format a section with a heading.""" + result = ''' + + + + '''.format(css_class, title) + if prelude: + result = result + ''' + + +'''.format(marginalia, prelude, gap) + elif marginalia: + result = result + ''' +'''.format(marginalia, gap) + + contents = '{}
+{}
{}{}
{}
{}{}

'.format(contents) + return result + '\n' + contents + + def bigsection(self, title, *args, **kwargs): + """Format a section with a big heading.""" + title = '{}'.format(title) + return self.html_section(title, *args, **kwargs) + + def preformat(self, text): + """Format literal preformatted text.""" + text = self.escape(text.expandtabs()) + return replace(text, '\n\n', '\n \n', '\n\n', '\n \n', + ' ', ' ', '\n', '
\n') + + def multicolumn(self, list, format, cols=4): + """Format a list of items into a multi-column list.""" + result = '' + rows = (len(list)+cols-1)//cols + for col in range(cols): + result = ( + result + '' + % (100//cols)) + for i in range(rows*col, rows*col+rows): + if i < len(list): + result = result + format(list[i]) + '
\n' + result = result + '' + return '%s
' % result + + def grey(self, text): + """Grey span.""" + return '%s' % text + + def namelink(self, name, *dicts): + """Make a link for an identifier, given name-to-URL mappings.""" + for dict in dicts: + if name in dict: + return '%s' % (dict[name], name) + return name + + def classlink(self, object, modname): + """Make a link for a class.""" + name, module = object.__name__, sys.modules.get(object.__module__) + if hasattr(module, name) and getattr(module, name) is object: + return '%s' % ( + module.__name__, name, classname(object, modname)) + return classname(object, modname) + + def modulelink(self, object): + """Make a link for a module.""" + return '%s' % ( + object.__name__, object.__name__) + + def modpkglink(self, modpkginfo): + """Make a link for a module or package to display in an index.""" + name, path, ispackage, shadowed = modpkginfo + if shadowed: + return self.grey(name) + if path: + url = '%s.%s.html' % (path, name) + else: + url = '%s.html' % name + if ispackage: + text = '%s (package)' % name + else: + text = name + return '%s' % (url, text) - def filelink(self, url, path): - """Make a link to source file.""" - return '%s' % (url, path) + def filelink(self, url, path): + """Make a link to source file.""" + return '%s' % (url, path) - def markup(self, text, escape=None, funcs={}, classes={}, methods={}): - """ - Mark up some plain text, given a context of symbols to look for. + def markup(self, text, escape=None, funcs={}, classes={}, methods={}): + """ + Mark up some plain text, given a context of symbols to look for. - Each context dictionary maps object names to anchor names. - """ - escape = escape or self.escape - results = [] - here = 0 - pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|' - r'RFC[- ]?(\d+)|' - r'PEP[- ]?(\d+)|' - r'(self\.)?(\w+))') - while True: - match = pattern.search(text, here) - if not match: - break - start, end = match.span() - results.append(escape(text[here:start])) - - all, scheme, rfc, pep, selfdot, name = match.groups() - if scheme: - url = escape(all).replace('"', '"') - results.append('%s' % (url, url)) - elif rfc: - url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) - results.append('%s' % (url, escape(all))) - elif pep: - url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep) - results.append('%s' % (url, escape(all))) - elif text[end:end+1] == '(': - results.append( - self.namelink(name, methods, funcs, classes)) - elif selfdot: - results.append('self.%s' % name) - else: - results.append(self.namelink(name, classes)) - here = end - results.append(escape(text[here:])) - return ''.join(results) + Each context dictionary maps object names to anchor names. + """ + escape = escape or self.escape + results = [] + here = 0 + pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|' + r'RFC[- ]?(\d+)|' + r'PEP[- ]?(\d+)|' + r'(self\.)?(\w+))') + while True: + match = pattern.search(text, here) + if not match: + break + start, end = match.span() + results.append(escape(text[here:start])) + + all, scheme, rfc, pep, selfdot, name = match.groups() + if scheme: + url = escape(all).replace('"', '"') + results.append('%s' % (url, url)) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + results.append('%s' % (url, escape(all))) + elif pep: + url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep) + results.append('%s' % (url, escape(all))) + elif text[end:end+1] == '(': + results.append( + self.namelink(name, methods, funcs, classes)) + elif selfdot: + results.append('self.%s' % name) + else: + results.append(self.namelink(name, classes)) + here = end + results.append(escape(text[here:])) + return ''.join(results) - # --------------------------------------------- type-specific routines + # --------------------------------------------- type-specific routines - def formattree(self, tree, modname, parent=None): - """ - Produce HTML for a class tree as given by inspect.getclasstree(). - """ - result = '' - for entry in tree: - if type(entry) is type(()): - c, bases = entry - result = result + '
' - result = result + self.classlink(c, modname) - if bases and bases != (parent,): - parents = [] - for base in bases: - parents.append(self.classlink(base, modname)) - result = result + '(' + ', '.join(parents) + ')' - result = result + '\n
' - elif type(entry) is type([]): - result = result + '
\n%s
\n' % self.formattree( - entry, modname, c) - return '
\n%s
\n' % result - - def docmodule(self, object, name=None, mod=None, *ignored): - """Produce HTML documentation for a module object.""" - name = object.__name__ # ignore the passed-in name - try: - all = object.__all__ - except AttributeError: - all = None - parts = name.split('.') - links = [] - for i in range(len(parts)-1): - links.append( - '{}'.format( - '.'.join(parts[:i+1]), parts[i])) - head = '.'.join(links + parts[-1:]) - try: - path = inspect.getabsfile(object) - url = path - if sys.platform == 'win32': - import nturl2path - url = nturl2path.pathname2url(path) - filelink = self.filelink(url, path) - except TypeError: - filelink = '(built-in)' - info = [] - if hasattr(object, '__version__'): - version = str(object.__version__) - if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': - version = version[11:-1].strip() - info.append('version %s' % self.escape(version)) - if hasattr(object, '__date__'): - info.append(self.escape(str(object.__date__))) - if info: - head = head + ' (%s)' % ', '.join(info) - docloc = self.getdocloc(object) - if docloc is not None: - docloc = ( - '
Module Reference' % locals()) - else: - docloc = '' - extras = 'index
' + filelink + docloc - result = self.heading(head, extras) - - modules = inspect.getmembers(object, inspect.ismodule) - - classes, cdict = [], {} - for key, value in inspect.getmembers(object, inspect.isclass): - # if __all__ exists, believe it. Otherwise use old heuristic. - if (all is not None or - (inspect.getmodule(value) or object) is object): - if visiblename(key, all, object): - classes.append((key, value)) - cdict[key] = cdict[value] = '#' + key - for key, value in classes: - for base in value.__bases__: - key, modname = base.__name__, base.__module__ - module = sys.modules.get(modname) - if modname != name and module and hasattr(module, key): - if getattr(module, key) is base: - if key not in cdict: - cdict[key] = cdict[base] = ( - modname + '.html#' + key) - funcs, fdict = [], {} - for key, value in inspect.getmembers(object, inspect.isroutine): - # if __all__ exists, believe it. Otherwise use old heuristic. - if (all is not None or - inspect.isbuiltin(value) or - inspect.getmodule(value) is object): - if visiblename(key, all, object): - funcs.append((key, value)) - fdict[key] = '#-' + key - if inspect.isfunction(value): - fdict[value] = fdict[key] - data = [] - for key, value in inspect.getmembers(object, isdata): - if visiblename(key, all, object): - data.append((key, value)) - - doc = self.markup(getdoc(object), self.preformat, fdict, cdict) - doc = doc and '{}'.format(doc) - result = result + '

%s

\n' % doc - - if hasattr(object, '__path__'): - modpkgs = [] - for importer, modname, ispkg in pkgutil.iter_modules( - object.__path__): - modpkgs.append((modname, name, ispkg, 0)) - modpkgs.sort() - contents = self.multicolumn(modpkgs, self.modpkglink) - result = result + self.bigsection( - 'Package Contents', contents, css_class="package") - elif modules: - contents = self.multicolumn( - modules, lambda t: self.modulelink(t[1])) - result = result + self.bigsection( - 'Modules', contents, css_class="module") - - if classes: - classlist = [value for (key, value) in classes] - contents = [ - self.formattree(inspect.getclasstree(classlist, 1), name)] - for key, value in classes: - contents.append( - self.document(value, key, name, fdict, cdict)) - result = result + self.bigsection( - 'Classes', ' '.join(contents), css_class="classes") - if funcs: - contents = [] - for key, value in funcs: - contents.append( - self.document(value, key, name, fdict, cdict)) - result = result + self.bigsection( - 'Functions', ' '.join(contents), css_class="functions") - if data: - contents = [] - for key, value in data: - contents.append(self.document(value, key)) - result = result + self.bigsection( - 'Data', '
\n'.join(contents), css_class="data") - if hasattr(object, '__author__'): - contents = self.markup(str(object.__author__), self.preformat) - result = result + self.bigsection( - 'Author', contents, css_class="author") - if hasattr(object, '__credits__'): - contents = self.markup(str(object.__credits__), self.preformat) - result = result + self.bigsection( - 'Credits', contents, css_class="credits") - - return result - - def docclass(self, object, name=None, mod=None, funcs={}, classes={}, - *ignored): - """Produce HTML documentation for a class object.""" - realname = object.__name__ - name = name or realname - bases = object.__bases__ + def formattree(self, tree, modname, parent=None): + """ + Produce HTML for a class tree as given by inspect.getclasstree(). + """ + result = '' + for entry in tree: + if type(entry) is type(()): + c, bases = entry + result = result + '
' + result = result + self.classlink(c, modname) + if bases and bases != (parent,): + parents = [] + for base in bases: + parents.append(self.classlink(base, modname)) + result = result + '(' + ', '.join(parents) + ')' + result = result + '\n
' + elif type(entry) is type([]): + result = result + '
\n%s
\n' % self.formattree( + entry, modname, c) + return '
\n%s
\n' % result + + def docmodule(self, object, name=None, mod=None, *ignored): + """Produce HTML documentation for a module object.""" + name = object.__name__ # ignore the passed-in name + try: + all = object.__all__ + except AttributeError: + all = None + parts = name.split('.') + links = [] + for i in range(len(parts)-1): + links.append( + '{}'.format( + '.'.join(parts[:i+1]), parts[i])) + head = '.'.join(links + parts[-1:]) + try: + path = inspect.getabsfile(object) + url = path + if sys.platform == 'win32': + import nturl2path + url = nturl2path.pathname2url(path) + filelink = self.filelink(url, path) + except TypeError: + filelink = '(built-in)' + info = [] + if hasattr(object, '__version__'): + version = str(object.__version__) + if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': + version = version[11:-1].strip() + info.append('version %s' % self.escape(version)) + if hasattr(object, '__date__'): + info.append(self.escape(str(object.__date__))) + if info: + head = head + ' (%s)' % ', '.join(info) + docloc = self.getdocloc(object) + if docloc is not None: + docloc = ( + '
Module Reference' % locals()) + else: + docloc = '' + extras = 'index
' + filelink + docloc + result = self.heading(head, extras) + modules = inspect.getmembers(object, inspect.ismodule) + + classes, cdict = [], {} + for key, value in inspect.getmembers(object, inspect.isclass): + # if __all__ exists, believe it. Otherwise use old heuristic. + if (all is not None or + (inspect.getmodule(value) or object) is object): + if visiblename(key, all, object): + classes.append((key, value)) + cdict[key] = cdict[value] = '#' + key + for key, value in classes: + for base in value.__bases__: + key, modname = base.__name__, base.__module__ + module = sys.modules.get(modname) + if modname != name and module and hasattr(module, key): + if getattr(module, key) is base: + if key not in cdict: + cdict[key] = cdict[base] = ( + modname + '.html#' + key) + funcs, fdict = [], {} + for key, value in inspect.getmembers(object, inspect.isroutine): + # if __all__ exists, believe it. Otherwise use old heuristic. + if (all is not None or + inspect.isbuiltin(value) or + inspect.getmodule(value) is object): + if visiblename(key, all, object): + funcs.append((key, value)) + fdict[key] = '#-' + key + if inspect.isfunction(value): + fdict[value] = fdict[key] + data = [] + for key, value in inspect.getmembers(object, isdata): + if visiblename(key, all, object): + data.append((key, value)) + + doc = self.markup(getdoc(object), self.preformat, fdict, cdict) + doc = doc and '{}'.format(doc) + result = result + '

%s

\n' % doc + + if hasattr(object, '__path__'): + modpkgs = [] + for importer, modname, ispkg in pkgutil.iter_modules( + object.__path__): + modpkgs.append((modname, name, ispkg, 0)) + modpkgs.sort() + contents = self.multicolumn(modpkgs, self.modpkglink) + result = result + self.bigsection( + 'Package Contents', contents, css_class="package") + elif modules: + contents = self.multicolumn( + modules, lambda t: self.modulelink(t[1])) + result = result + self.bigsection( + 'Modules', contents, css_class="module") + + if classes: + classlist = [value for (key, value) in classes] + contents = [ + self.formattree(inspect.getclasstree(classlist, 1), name)] + for key, value in classes: + contents.append( + self.document(value, key, name, fdict, cdict)) + result = result + self.bigsection( + 'Classes', ' '.join(contents), css_class="classes") + if funcs: + contents = [] + for key, value in funcs: + contents.append( + self.document(value, key, name, fdict, cdict)) + result = result + self.bigsection( + 'Functions', ' '.join(contents), css_class="functions") + if data: contents = [] - push = contents.append - - # Cute little class to pump out a horizontal rule between sections. - class HorizontalRule: - def __init__(self): - self.needone = 0 - - def maybe(self): - if self.needone: - push('
\n') - self.needone = 1 - hr = HorizontalRule() - - # List the mro, if non-trivial. - mro = deque(inspect.getmro(object)) - if len(mro) > 2: + for key, value in data: + contents.append(self.document(value, key)) + result = result + self.bigsection( + 'Data', '
\n'.join(contents), css_class="data") + if hasattr(object, '__author__'): + contents = self.markup(str(object.__author__), self.preformat) + result = result + self.bigsection( + 'Author', contents, css_class="author") + if hasattr(object, '__credits__'): + contents = self.markup(str(object.__credits__), self.preformat) + result = result + self.bigsection( + 'Credits', contents, css_class="credits") + + return result + + def docclass(self, object, name=None, mod=None, funcs={}, classes={}, + *ignored): + """Produce HTML documentation for a class object.""" + realname = object.__name__ + name = name or realname + bases = object.__bases__ + + contents = [] + push = contents.append + + # Cute little class to pump out a horizontal rule between sections. + class HorizontalRule: + def __init__(self): + self.needone = 0 + + def maybe(self): + if self.needone: + push('
\n') + self.needone = 1 + hr = HorizontalRule() + + # List the mro, if non-trivial. + mro = deque(inspect.getmro(object)) + if len(mro) > 2: + hr.maybe() + push('
Method resolution order:
\n') + for base in mro: + push('
%s
\n' % self.classlink(base, + object.__module__)) + push('
\n') + + def spill(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: hr.maybe() - push('
Method resolution order:
\n') - for base in mro: - push('
%s
\n' % self.classlink(base, - object.__module__)) - push('
\n') - - def spill(msg, attrs, predicate): - ok, attrs = _split_list(attrs, predicate) - if ok: - hr.maybe() - push(msg) - for name, kind, homecls, value in ok: - try: - value = getattr(object, name) - except Exception: - # Some descriptors may meet a failure - # in their __get__. - # (bug aroberge/mod_pydoc#1785) - push(self._docdescriptor(name, value, mod)) - else: - push(self.document( - value, name, mod, funcs, classes, mdict, - object)) - push('\n') - return attrs - - def spilldescriptors(msg, attrs, predicate): - ok, attrs = _split_list(attrs, predicate) - if ok: - hr.maybe() - push(msg) - for name, kind, homecls, value in ok: + push(msg) + for name, kind, homecls, value in ok: + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure + # in their __get__. + # (bug aroberge/mod_pydoc#1785) push(self._docdescriptor(name, value, mod)) - return attrs - - def spilldata(msg, attrs, predicate): - ok, attrs = _split_list(attrs, predicate) - if ok: - hr.maybe() - push(msg) - for name, kind, homecls, value in ok: - base = self.docother(getattr(object, name), name, mod) - if callable(value) or inspect.isdatadescriptor(value): - doc = getattr(value, "__doc__", None) - else: - doc = None - if doc is None: - push('
%s
\n' % base) - else: - doc = self.markup(getdoc(value), self.preformat, - funcs, classes, mdict) - doc = '
%s
' % doc - push('
%s%s
\n' % (base, doc)) - push('\n') - return attrs - - attrs = [(name, kind, cls, value) - for name, kind, cls, value in classify_class_attrs(object) - if visiblename(name, obj=object)] - - mdict = {} - for key, kind, homecls, value in attrs: - mdict[key] = anchor = '#' + name + '-' + key - try: - value = getattr(object, name) - except Exception: - # Some descriptors may meet a failure in their __get__. - # (bug #1785) - pass - try: - # The value may not be hashable (e.g., a data attr with - # a dict or list value). - mdict[value] = anchor - except TypeError: - pass - - while attrs: - if mro: - thisclass = mro.popleft() - else: - thisclass = attrs[0][2] - attrs, inherited = _split_list( - attrs, lambda t: t[2] is thisclass) - - if thisclass is builtins.object: - attrs = inherited - continue - elif thisclass is object: - tag = 'defined here' - else: - tag = 'inherited from %s' % self.classlink( - thisclass, object.__module__) - tag += ':
\n' - - # Sort attrs by name. - attrs.sort(key=lambda t: t[0]) - - # Pump out the attrs, segregated by kind. - attrs = spill('Methods %s' % tag, attrs, - lambda t: t[1] == 'method') - attrs = spill('Class methods %s' % tag, attrs, - lambda t: t[1] == 'class method') - attrs = spill('Static methods %s' % tag, attrs, - lambda t: t[1] == 'static method') - attrs = spilldescriptors('Data descriptors %s' % tag, attrs, - lambda t: t[1] == 'data descriptor') - attrs = spilldata('Data and other attributes %s' % tag, attrs, - lambda t: t[1] == 'data') - assert attrs == [] - attrs = inherited - - contents = ''.join(contents) + else: + push(self.document( + value, name, mod, funcs, classes, mdict, + object)) + push('\n') + return attrs + + def spilldescriptors(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + push(self._docdescriptor(name, value, mod)) + return attrs + + def spilldata(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + base = self.docother(getattr(object, name), name, mod) + if callable(value) or inspect.isdatadescriptor(value): + doc = getattr(value, "__doc__", None) + else: + doc = None + if doc is None: + push('
%s
\n' % base) + else: + doc = self.markup(getdoc(value), self.preformat, + funcs, classes, mdict) + doc = '
%s
' % doc + push('
%s%s
\n' % (base, doc)) + push('\n') + return attrs + + attrs = [(name, kind, cls, value) + for name, kind, cls, value in classify_class_attrs(object) + if visiblename(name, obj=object)] + + mdict = {} + for key, kind, homecls, value in attrs: + mdict[key] = anchor = '#' + name + '-' + key + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + pass + try: + # The value may not be hashable (e.g., a data attr with + # a dict or list value). + mdict[value] = anchor + except TypeError: + pass - if name == realname: - title = ' class %s' % ( - name, realname) + while attrs: + if mro: + thisclass = mro.popleft() else: - title = ( - '%s = class %s' % ( - name, name, realname)) - if bases: - parents = [] - for base in bases: - parents.append(self.classlink(base, object.__module__)) - title = title + '(%s)' % ', '.join(parents) - doc = self.markup( - getdoc(object), self.preformat, funcs, classes, mdict) - doc = doc and '%s
 
' % doc - - return self.html_section( - title, contents, 3, doc, css_class="docclass") - - def formatvalue(self, object): - """Format an argument default value as text.""" - return self.grey('=' + self.repr(object)) - - def docroutine(self, object, name=None, mod=None, - funcs={}, classes={}, methods={}, cl=None): - """Produce HTML documentation for a function or method object.""" - realname = object.__name__ - name = name or realname - anchor = (cl and cl.__name__ or '') + '-' + name - note = '' - skipdocs = 0 - if _is_bound_method(object): - imclass = object.__self__.__class__ - if cl: - if imclass is not cl: - note = ' from ' + self.classlink(imclass, mod) - else: - if object.__self__ is not None: - note = ' method of %s instance' % self.classlink( - object.__self__.__class__, mod) - else: - note = ' unbound %s method' % self.classlink( - imclass, mod) + thisclass = attrs[0][2] + attrs, inherited = _split_list( + attrs, lambda t: t[2] is thisclass) - if name == realname: - title = '%s' % ( - anchor, realname) + if thisclass is builtins.object: + attrs = inherited + continue + elif thisclass is object: + tag = 'defined here' else: - if (cl and realname in cl.__dict__ and - cl.__dict__[realname] is object): - reallink = '%s' % ( - cl.__name__ + '-' + realname, realname) - skipdocs = 1 - else: - reallink = realname - title = '%s = %s' % ( - anchor, name, reallink) - argspec = None - if inspect.isroutine(object): - try: - signature = inspect.signature(object) - except (ValueError, TypeError): - signature = None - if signature: - argspec = str(signature) - if realname == '': - title = '%s lambda ' % name - # XXX lambda's won't usually have - # func_annotations['return'] - # since the syntax doesn't support but it is possible. - # So removing parentheses isn't truly safe. - argspec = argspec[1:-1] # remove parentheses - if not argspec: - argspec = '(...)' - - decl = title + argspec + (note and self.grey(note)) - - if skipdocs: - return '
%s
\n' % decl + tag = 'inherited from %s' % self.classlink( + thisclass, object.__module__) + tag += ':
\n' + + # Sort attrs by name. + attrs.sort(key=lambda t: t[0]) + + # Pump out the attrs, segregated by kind. + attrs = spill('Methods %s' % tag, attrs, + lambda t: t[1] == 'method') + attrs = spill('Class methods %s' % tag, attrs, + lambda t: t[1] == 'class method') + attrs = spill('Static methods %s' % tag, attrs, + lambda t: t[1] == 'static method') + attrs = spilldescriptors('Data descriptors %s' % tag, attrs, + lambda t: t[1] == 'data descriptor') + attrs = spilldata('Data and other attributes %s' % tag, attrs, + lambda t: t[1] == 'data') + assert attrs == [] + attrs = inherited + + contents = ''.join(contents) + + if name == realname: + title = ' class %s' % ( + name, realname) + else: + title = ( + '%s = class %s' % ( + name, name, realname)) + if bases: + parents = [] + for base in bases: + parents.append(self.classlink(base, object.__module__)) + title = title + '(%s)' % ', '.join(parents) + doc = self.markup( + getdoc(object), self.preformat, funcs, classes, mdict) + doc = doc and '%s
 
' % doc + + return self.html_section( + title, contents, 3, doc, css_class="docclass") + + def formatvalue(self, object): + """Format an argument default value as text.""" + return self.grey('=' + self.repr(object)) + + def docroutine(self, object, name=None, mod=None, + funcs={}, classes={}, methods={}, cl=None): + """Produce HTML documentation for a function or method object.""" + realname = object.__name__ + name = name or realname + anchor = (cl and cl.__name__ or '') + '-' + name + note = '' + skipdocs = 0 + if _is_bound_method(object): + imclass = object.__self__.__class__ + if cl: + if imclass is not cl: + note = ' from ' + self.classlink(imclass, mod) else: - doc = self.markup( - getdoc(object), self.preformat, funcs, classes, methods) - doc = doc and '
%s
' % doc - return '
%s
%s
\n' % (decl, doc) - - def _docdescriptor(self, name, value, mod): - results = [] - push = results.append - - if name: - push('
%s
\n' % name) - if value.__doc__ is not None: - doc = self.markup(getdoc(value), self.preformat) - push('
%s
\n' % doc) - push('
\n') - - return ''.join(results) - - def docproperty(self, object, name=None, mod=None, cl=None): - """Produce html documentation for a property.""" - return self._docdescriptor(name, object, mod) - - def docother(self, object, name=None, mod=None, *ignored): - """Produce HTML documentation for a data object.""" - lhs = name and '%s = ' % name or '' - return lhs + self.repr(object) - - def docdata(self, object, name=None, mod=None, cl=None): - """Produce html documentation for a data descriptor.""" - return self._docdescriptor(name, object, mod) - - def index(self, dir, shadowed=None): - """Generate an HTML index for a directory of modules.""" - modpkgs = [] - if shadowed is None: - shadowed = {} - for importer, name, ispkg in pkgutil.iter_modules([dir]): - if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name): - # ignore a module if its name contains a - # surrogate character - continue - modpkgs.append((name, '', ispkg, name in shadowed)) - shadowed[name] = 1 + if object.__self__ is not None: + note = ' method of %s instance' % self.classlink( + object.__self__.__class__, mod) + else: + note = ' unbound %s method' % self.classlink( + imclass, mod) - modpkgs.sort() - if len(modpkgs): - contents = self.multicolumn(modpkgs, self.modpkglink) - return self.bigsection(dir, contents, css_class="index") + if name == realname: + title = '%s' % ( + anchor, realname) + else: + if (cl and realname in cl.__dict__ and + cl.__dict__[realname] is object): + reallink = '%s' % ( + cl.__name__ + '-' + realname, realname) + skipdocs = 1 else: - return '' + reallink = realname + title = '%s = %s' % ( + anchor, name, reallink) + argspec = None + if inspect.isroutine(object): + try: + signature = inspect.signature(object) + except (ValueError, TypeError): + signature = None + if signature: + argspec = str(signature) + if realname == '': + title = '%s lambda ' % name + # XXX lambda's won't usually have + # func_annotations['return'] + # since the syntax doesn't support but it is possible. + # So removing parentheses isn't truly safe. + argspec = argspec[1:-1] # remove parentheses + if not argspec: + argspec = '(...)' + + decl = title + argspec + (note and self.grey(note)) + + if skipdocs: + return '
%s
\n' % decl + else: + doc = self.markup( + getdoc(object), self.preformat, funcs, classes, methods) + doc = doc and '
%s
' % doc + return '
%s
%s
\n' % (decl, doc) + + def _docdescriptor(self, name, value, mod): + results = [] + push = results.append + + if name: + push('
%s
\n' % name) + if value.__doc__ is not None: + doc = self.markup(getdoc(value), self.preformat) + push('
%s
\n' % doc) + push('
\n') + + return ''.join(results) + + def docproperty(self, object, name=None, mod=None, cl=None): + """Produce html documentation for a property.""" + return self._docdescriptor(name, object, mod) + + def docother(self, object, name=None, mod=None, *ignored): + """Produce HTML documentation for a data object.""" + lhs = name and '%s = ' % name or '' + return lhs + self.repr(object) + + def docdata(self, object, name=None, mod=None, cl=None): + """Produce html documentation for a data descriptor.""" + return self._docdescriptor(name, object, mod) + + def index(self, dir, shadowed=None): + """Generate an HTML index for a directory of modules.""" + modpkgs = [] + if shadowed is None: + shadowed = {} + for importer, name, ispkg in pkgutil.iter_modules([dir]): + if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name): + # ignore a module if its name contains a + # surrogate character + continue + modpkgs.append((name, '', ispkg, name in shadowed)) + shadowed[name] = 1 + + modpkgs.sort() + if len(modpkgs): + contents = self.multicolumn(modpkgs, self.modpkglink) + return self.bigsection(dir, contents, css_class="index") + else: + return '' def _url_handler(url, content_type="text/html"): diff --git a/spyder/plugins/projects/widgets/main_widget.py b/spyder/plugins/projects/widgets/main_widget.py index 074b525d5e4..9e83dda2471 100644 --- a/spyder/plugins/projects/widgets/main_widget.py +++ b/spyder/plugins/projects/widgets/main_widget.py @@ -9,8 +9,6 @@ # pylint: disable=C0103 # Standard library imports -from __future__ import print_function - import os.path as osp # Third party imports diff --git a/spyder/plugins/projects/widgets/projectexplorer.py b/spyder/plugins/projects/widgets/projectexplorer.py index c7cbb4383ec..3fa9c8582ab 100644 --- a/spyder/plugins/projects/widgets/projectexplorer.py +++ b/spyder/plugins/projects/widgets/projectexplorer.py @@ -9,8 +9,6 @@ # pylint: disable=C0103 # Standard library imports -from __future__ import print_function - import os import os.path as osp import shutil diff --git a/spyder/plugins/variableexplorer/widgets/arrayeditor.py b/spyder/plugins/variableexplorer/widgets/arrayeditor.py index 0aa4579d56f..7b2fc17ce10 100644 --- a/spyder/plugins/variableexplorer/widgets/arrayeditor.py +++ b/spyder/plugins/variableexplorer/widgets/arrayeditor.py @@ -14,7 +14,7 @@ # pylint: disable=R0201 # Standard library imports -from __future__ import print_function +import io # Third party imports from qtpy.compat import from_qvariant, to_qvariant @@ -35,8 +35,8 @@ from spyder.config.fonts import DEFAULT_SMALL_DELTA from spyder.config.gui import get_font from spyder.config.manager import CONF -from spyder.py3compat import (io, is_binary_string, is_string, - is_text_string, PY3, to_binary_string, +from spyder.py3compat import (is_binary_string, is_string, + is_text_string, to_binary_string, to_text_string) from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import add_actions, create_action, keybinding @@ -558,10 +558,7 @@ def _sel_to_text(self, cell_range): row_max = self.model().total_rows-1 _data = self.model().get_data() - if PY3: - output = io.BytesIO() - else: - output = io.StringIO() + output = io.BytesIO() try: np.savetxt(output, _data[row_min:row_max+1, col_min:col_max+1], delimiter='\t', fmt=self.model().get_format()) diff --git a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py index 615de975c44..3dd8c4b5cb3 100644 --- a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py +++ b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py @@ -33,6 +33,8 @@ """ # Standard library imports +import io +from time import perf_counter # Third party imports from qtpy.compat import from_qvariant, to_qvariant @@ -51,8 +53,8 @@ from spyder.config.base import _ from spyder.config.fonts import DEFAULT_SMALL_DELTA from spyder.config.gui import get_font -from spyder.py3compat import (io, is_text_string, is_type_text_string, PY2, - to_text_string, perf_counter) +from spyder.py3compat import (is_text_string, is_type_text_string, + to_text_string) from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import (add_actions, create_action, keybinding, qapplication) @@ -661,10 +663,7 @@ def copy(self): self, _("Error"), _("Text can't be copied.")) - if not PY2: - contents = output.getvalue() - else: - contents = output.getvalue().decode('utf-8') + contents = output.getvalue() output.close() clipboard = QApplication.clipboard() clipboard.setText(contents) diff --git a/spyder/plugins/variableexplorer/widgets/importwizard.py b/spyder/plugins/variableexplorer/widgets/importwizard.py index 7fa46403e02..6d2c3471d27 100644 --- a/spyder/plugins/variableexplorer/widgets/importwizard.py +++ b/spyder/plugins/variableexplorer/widgets/importwizard.py @@ -11,6 +11,7 @@ # Standard library imports import datetime from functools import partial as ft_partial +from itertools import zip_longest # Third party imports from qtpy.compat import to_qvariant @@ -26,8 +27,7 @@ # Local import from spyder.config.base import _ -from spyder.py3compat import (INT_TYPES, io, TEXT_TYPES, to_text_string, - zip_longest) +from spyder.py3compat import INT_TYPES, TEXT_TYPES, to_text_string from spyder.utils import programs from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import add_actions, create_action diff --git a/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py b/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py index 9ea20116b87..32c0821d2ed 100644 --- a/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py +++ b/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py @@ -8,9 +8,6 @@ # see NOTICE.txt in the Spyder root directory for details # ----------------------------------------------------------------------------- -from __future__ import absolute_import -from __future__ import print_function - # Standard library imports import logging import traceback diff --git a/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py b/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py index 769b8d860a3..ccb3dc26b2c 100644 --- a/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py +++ b/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py @@ -22,7 +22,6 @@ from spyder.config.manager import CONF from spyder.plugins.variableexplorer.widgets.objectexplorer import ( ObjectExplorer) -from spyder.py3compat import PY2 # ============================================================================= # Fixtures diff --git a/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py b/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py index 54718738160..a42ed2e96cd 100644 --- a/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py +++ b/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py @@ -29,7 +29,6 @@ cut_off_str) from spyder.plugins.variableexplorer.widgets.objectexplorer.tree_item import ( TreeItem) -from spyder.py3compat import to_unichr from spyder.utils.icon_manager import ima logger = logging.getLogger(__name__) @@ -146,9 +145,9 @@ def data(self, index, role): attr = self._attr_cols[col].data_fn(tree_item) # Replace carriage returns and line feeds with unicode glyphs # so that all table rows fit on one line. - return (attr.replace('\r\n', to_unichr(0x21B5)) - .replace('\n', to_unichr(0x21B5)) - .replace('\r', to_unichr(0x21B5))) + return (attr.replace('\r\n', chr(0x21B5)) + .replace('\n', chr(0x21B5)) + .replace('\r', chr(0x21B5))) except Exception as ex: # logger.exception(ex) return "**ERROR**: {}".format(ex) diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py b/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py index 4bc08156076..318776e9fc0 100644 --- a/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py +++ b/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py @@ -10,8 +10,6 @@ Tests for the dataframe editor. """ -from __future__ import division - # Standard library imports import os import sys diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py b/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py index 7d761754026..cdd11ed2c8d 100644 --- a/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py +++ b/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py @@ -7,8 +7,6 @@ Tests for namespacebrowser.py """ -from __future__ import division - # Standard library imports import string from unittest.mock import Mock diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_texteditor.py b/spyder/plugins/variableexplorer/widgets/tests/test_texteditor.py index 8f754ffdcbd..fcc8980dbae 100644 --- a/spyder/plugins/variableexplorer/widgets/tests/test_texteditor.py +++ b/spyder/plugins/variableexplorer/widgets/tests/test_texteditor.py @@ -12,7 +12,6 @@ import pytest # Local imports -from spyder.py3compat import PY3 from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor @@ -39,16 +38,6 @@ def test_texteditor(texteditor): assert TEXT == dlg_text -@pytest.mark.skipif(PY3, reason="It makes no sense in Python 3") -def test_texteditor_setup_and_check(texteditor): - import string - dig_its = string.digits - translate_digits = string.maketrans(dig_its,len(dig_its)*' ') - - editor = texteditor(None) - assert not editor.setup_and_check(translate_digits) - - @pytest.mark.parametrize("title", [u"ñ", u"r"]) def test_title(texteditor, title): editor = texteditor(TEXT, title=title) diff --git a/spyder/plugins/variableexplorer/widgets/texteditor.py b/spyder/plugins/variableexplorer/widgets/texteditor.py index 37e5bc52caa..fce9295f5fd 100644 --- a/spyder/plugins/variableexplorer/widgets/texteditor.py +++ b/spyder/plugins/variableexplorer/widgets/texteditor.py @@ -9,7 +9,6 @@ """ # Standard library imports -from __future__ import print_function import sys # Third party imports diff --git a/spyder/py3compat.py b/spyder/py3compat.py index d93a71cc197..71e3f94d6dd 100644 --- a/spyder/py3compat.py +++ b/spyder/py3compat.py @@ -16,303 +16,82 @@ * Python 3 """ -from __future__ import print_function - import operator -import os -import sys -PY2 = sys.version[0] == '2' -PY3 = sys.version[0] == '3' -PY36_OR_MORE = sys.version_info[0] >= 3 and sys.version_info[1] >= 6 -PY38_OR_MORE = sys.version_info[0] >= 3 and sys.version_info[1] >= 8 #============================================================================== # Data types #============================================================================== -if PY2: - # Python 2 - TEXT_TYPES = (str, unicode) - INT_TYPES = (int, long) -else: - # Python 3 - TEXT_TYPES = (str,) - INT_TYPES = (int,) -NUMERIC_TYPES = tuple(list(INT_TYPES) + [float]) - - -#============================================================================== -# Renamed/Reorganized modules -#============================================================================== -if PY2: - # Python 2 - import __builtin__ as builtins - import ConfigParser as configparser - try: - import _winreg as winreg - except ImportError: - pass - from sys import maxint as maxsize - try: - import CStringIO as io - except ImportError: - import StringIO as io - try: - import cPickle as pickle - except ImportError: - import pickle - from UserDict import DictMixin as MutableMapping - from collections import MutableSequence - import thread as _thread - import repr as reprlib - import Queue - from time import clock as perf_counter - from base64 import decodestring as decodebytes -else: - # Python 3 - import builtins - import configparser - try: - import winreg - except ImportError: - pass - from sys import maxsize - import io - import pickle - from collections.abc import MutableMapping, MutableSequence - import _thread - import reprlib - import queue as Queue - from time import perf_counter - from base64 import decodebytes +# Python 3 +TEXT_TYPES = (str,) +INT_TYPES = (int,) #============================================================================== # Strings #============================================================================== -def to_unichr(character_code): - """ - Return the Unicode string of the character with the given Unicode code. - """ - if PY2: - return unichr(character_code) - else: - return chr(character_code) - def is_type_text_string(obj): """Return True if `obj` is type text string, False if it is anything else, like an instance of a class that extends the basestring class.""" - if PY2: - # Python 2 - return type(obj) in [str, unicode] - else: - # Python 3 - return type(obj) in [str, bytes] + return type(obj) in [str, bytes] def is_text_string(obj): """Return True if `obj` is a text string, False if it is anything else, - like binary data (Python 3) or QString (Python 2, PyQt API #1)""" - if PY2: - # Python 2 - return isinstance(obj, basestring) - else: - # Python 3 - return isinstance(obj, str) + like binary data (Python 3) or QString (PyQt API #1)""" + return isinstance(obj, str) def is_binary_string(obj): """Return True if `obj` is a binary string, False if it is anything else""" - if PY2: - # Python 2 - return isinstance(obj, str) - else: - # Python 3 - return isinstance(obj, bytes) + return isinstance(obj, bytes) def is_string(obj): """Return True if `obj` is a text or binary Python string object, - False if it is anything else, like a QString (Python 2, PyQt API #1)""" + False if it is anything else, like a QString (PyQt API #1)""" return is_text_string(obj) or is_binary_string(obj) -def is_unicode(obj): - """Return True if `obj` is unicode""" - if PY2: - # Python 2 - return isinstance(obj, unicode) - else: - # Python 3 - return isinstance(obj, str) - def to_text_string(obj, encoding=None): """Convert `obj` to (unicode) text string""" - if PY2: - if isinstance(obj, unicode): - return obj - # Python 2 - if encoding is None: - return unicode(obj) - else: - return unicode(obj, encoding) - else: - # Python 3 - if encoding is None: - return str(obj) - elif isinstance(obj, str): - # In case this function is not used properly, this could happen - return obj - else: - return str(obj, encoding) - -def to_binary_string(obj, encoding=None): - """Convert `obj` to binary string (bytes in Python 3, str in Python 2)""" - if PY2: - # Python 2 - if encoding is None: - return str(obj) - else: - return obj.encode(encoding) - else: - # Python 3 - return bytes(obj, 'utf-8' if encoding is None else encoding) - - -#============================================================================== -# Function attributes -#============================================================================== -def get_func_code(func): - """Return function code object""" - if PY2: - # Python 2 - return func.func_code - else: - # Python 3 - return func.__code__ - -def get_func_name(func): - """Return function name""" - if PY2: - # Python 2 - return func.func_name - else: - # Python 3 - return func.__name__ - -def get_func_defaults(func): - """Return function default argument values""" - if PY2: - # Python 2 - return func.func_defaults - else: - # Python 3 - return func.__defaults__ - - -#============================================================================== -# Special method attributes -#============================================================================== -def get_meth_func(obj): - """Return method function object""" - if PY2: - # Python 2 - return obj.im_func - else: - # Python 3 - return obj.__func__ - -def get_meth_class_inst(obj): - """Return method class instance""" - if PY2: - # Python 2 - return obj.im_self + if encoding is None: + return str(obj) + elif isinstance(obj, str): + # In case this function is not used properly, this could happen + return obj else: - # Python 3 - return obj.__self__ - -def get_meth_class(obj): - """Return method class""" - if PY2: - # Python 2 - return obj.im_class - else: - # Python 3 - return obj.__self__.__class__ + return str(obj, encoding) +def to_binary_string(obj, encoding='utf-8'): + """Convert `obj` to binary string (bytes)""" + return bytes(obj, encoding) #============================================================================== # Misc. #============================================================================== -if PY2: - # Python 2 - input = raw_input - getcwd = os.getcwdu - cmp = cmp - import string - str_lower = string.lower - from itertools import izip_longest as zip_longest -else: - # Python 3 - input = input - getcwd = os.getcwd - def cmp(a, b): - return (a > b) - (a < b) - str_lower = str.lower - from itertools import zip_longest +# Python 3 def qbytearray_to_str(qba): - """Convert QByteArray object to str in a way compatible with Python 2/3""" + """Convert QByteArray object to str in a way compatible with Python 3""" return str(bytes(qba.toHex().data()).decode()) # ============================================================================= # Dict funcs # ============================================================================= -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) +def iterkeys(d, **kw): + return iter(d.keys(**kw)) - def iterlists(d, **kw): - return d.iterlists(**kw) +def itervalues(d, **kw): + return iter(d.values(**kw)) - viewkeys = operator.methodcaller("viewkeys") +def iteritems(d, **kw): + return iter(d.items(**kw)) - viewvalues = operator.methodcaller("viewvalues") +def iterlists(d, **kw): + return iter(d.lists(**kw)) - viewitems = operator.methodcaller("viewitems") +viewkeys = operator.methodcaller("keys") +viewvalues = operator.methodcaller("values") -# ============================================================================ -# Exceptions -# ============================================================================ -if PY2: - ConnectionRefusedError = ConnectionError = BrokenPipeError = OSError - TimeoutError = RuntimeError -else: - ConnectionError = ConnectionError - ConnectionRefusedError = ConnectionRefusedError - TimeoutError = TimeoutError - BrokenPipeError = BrokenPipeError +viewitems = operator.methodcaller("items") if __name__ == '__main__': diff --git a/spyder/utils/bsdsocket.py b/spyder/utils/bsdsocket.py index 2d9ef4d023d..21c4ab6babe 100644 --- a/spyder/utils/bsdsocket.py +++ b/spyder/utils/bsdsocket.py @@ -11,6 +11,7 @@ # and failure to read out buffers will most likely lock up Spyder. import os +import pickle import socket import struct import threading @@ -20,7 +21,6 @@ # Local imports from spyder.config.base import get_debug_level, STDERR DEBUG_EDITOR = get_debug_level() >= 3 -from spyder.py3compat import pickle PICKLE_HIGHEST_PROTOCOL = 2 diff --git a/spyder/utils/debug.py b/spyder/utils/debug.py index 655a350dfe7..dc0006eaea3 100644 --- a/spyder/utils/debug.py +++ b/spyder/utils/debug.py @@ -11,14 +11,10 @@ See spyder.config.base for other helpers. """ -from __future__ import print_function - import inspect import traceback import time -from spyder.py3compat import PY2 - def log_time(fd): timestr = "Logging time: %s" % time.ctime(time.time()) @@ -35,10 +31,7 @@ def log_last_error(fname, context=None): print("Context", file=fd) print("-------", file=fd) print("", file=fd) - if PY2: - print(u' '.join(context).encode('utf-8').strip(), file=fd) - else: - print(context, file=fd) + print(context, file=fd) print("", file=fd) print("Traceback", file=fd) print("---------", file=fd) diff --git a/spyder/utils/encoding.py b/spyder/utils/encoding.py index 43a7de9862d..fe1be443c64 100644 --- a/spyder/utils/encoding.py +++ b/spyder/utils/encoding.py @@ -18,6 +18,7 @@ import re import os import os.path as osp +import pathlib import sys import time import errno @@ -28,14 +29,9 @@ # Local imports from spyder.py3compat import (is_string, to_text_string, is_binary_string, - is_unicode, PY2) + is_text_string) from spyder.utils.external.binaryornot.check import is_binary -if PY2: - import pathlib2 as pathlib -else: - import pathlib - PREFERRED_ENCODING = locale.getpreferredencoding() @@ -90,7 +86,7 @@ def to_fs_from_unicode(unic): Return a byte string version of unic encoded using the file system encoding. """ - if is_unicode(unic): + if is_text_string(unic): try: string = unic.encode(FS_ENCODING) except (UnicodeError, TypeError): @@ -215,7 +211,7 @@ def encode(text, orig_coding): def to_unicode(string): """Convert a string to unicode""" - if not is_unicode(string): + if not is_text_string(string): for codec in CODECS: try: unic = to_text_string(string, codec) @@ -258,7 +254,7 @@ def write(text, filename, encoding='utf-8', mode='wb'): file_stat = os.stat(absolute_filename) original_mode = file_stat.st_mode creation = file_stat.st_atime - except OSError: # Change to FileNotFoundError for PY3 + except FileNotFoundError: # Creating a new file, emulate what os.open() does umask = os.umask(0) os.umask(umask) diff --git a/spyder/utils/external/lockfile.py b/spyder/utils/external/lockfile.py index 41bf0f7b095..0855717d745 100644 --- a/spyder/utils/external/lockfile.py +++ b/spyder/utils/external/lockfile.py @@ -27,14 +27,11 @@ import errno, os from time import time as _uniquefloat -from spyder.py3compat import PY2, to_binary_string +from spyder.py3compat import to_binary_string from spyder.utils.programs import is_spyder_process def unique(): - if PY2: - return str(long(_uniquefloat() * 1000)) - else: - return str(int(_uniquefloat() * 1000)) + return str(int(_uniquefloat() * 1000)) from os import rename if not os.name == 'nt': diff --git a/spyder/utils/external/pybloom_pyqt/pybloom.py b/spyder/utils/external/pybloom_pyqt/pybloom.py index 71f7755c956..152fe23fd57 100644 --- a/spyder/utils/external/pybloom_pyqt/pybloom.py +++ b/spyder/utils/external/pybloom_pyqt/pybloom.py @@ -4,7 +4,6 @@ Requires the bitarray library: http://pypi.python.org/pypi/bitarray/ """ -from __future__ import absolute_import import math import hashlib from .utils import range_fn, is_string_io, running_python_3 diff --git a/spyder/utils/introspection/tests/test_modulecompletion.py b/spyder/utils/introspection/tests/test_modulecompletion.py index 974760bc042..1c74b8d8b02 100644 --- a/spyder/utils/introspection/tests/test_modulecompletion.py +++ b/spyder/utils/introspection/tests/test_modulecompletion.py @@ -15,12 +15,11 @@ import pytest # Local imports -from spyder.py3compat import PY3 from spyder.utils.introspection.module_completion import get_preferred_submodules -@pytest.mark.skipif(sys.platform == 'darwin' and PY3, - reason="It's very slow in this combo") +@pytest.mark.skipif(sys.platform == 'darwin', + reason="It's very slow on Mac") def test_module_completion(): """Test module_completion.""" assert 'numpy.linalg' in get_preferred_submodules() diff --git a/spyder/utils/misc.py b/spyder/utils/misc.py index 681534b0eb1..c4bc201dd1b 100644 --- a/spyder/utils/misc.py +++ b/spyder/utils/misc.py @@ -15,7 +15,6 @@ import stat import socket -from spyder.py3compat import getcwd from spyder.config.base import get_home_dir @@ -254,7 +253,7 @@ def getcwd_or_home(): was removed for an external program. """ try: - return getcwd() + return os.getcwd() except OSError: logger.debug("WARNING: Current working directory was deleted, " "falling back to home dirertory") diff --git a/spyder/utils/programs.py b/spyder/utils/programs.py index 78aa742bd65..8204129f521 100644 --- a/spyder/utils/programs.py +++ b/spyder/utils/programs.py @@ -6,8 +6,6 @@ """Running programs utilities.""" -from __future__ import print_function - # Standard library imports from ast import literal_eval from getpass import getuser diff --git a/spyder/utils/qstringhelpers.py b/spyder/utils/qstringhelpers.py index d37531e53a8..5884dfbbda0 100644 --- a/spyder/utils/qstringhelpers.py +++ b/spyder/utils/qstringhelpers.py @@ -6,16 +6,11 @@ """QString compatibility.""" -from spyder.py3compat import PY2 - def qstring_length(text): """ Tries to compute what the length of an utf16-encoded QString would be. """ - if PY2: - # I don't know what this is encoded in, so there is nothing I can do. - return len(text) utf16_text = text.encode('utf16') length = len(utf16_text) // 2 # Remove Byte order mark. diff --git a/spyder/utils/qthelpers.py b/spyder/utils/qthelpers.py index 70f81e03f54..1833d4e5b82 100644 --- a/spyder/utils/qthelpers.py +++ b/spyder/utils/qthelpers.py @@ -7,6 +7,7 @@ """Qt utilities.""" # Standard library imports +import configparser import functools from math import pi import logging @@ -15,6 +16,8 @@ import re import sys import types +from urllib.parse import unquote + # Third party imports from qtpy.compat import from_qvariant, to_qvariant @@ -29,7 +32,7 @@ # Local imports from spyder.config.base import running_in_mac_app from spyder.config.manager import CONF -from spyder.py3compat import configparser, is_text_string, to_text_string, PY2 +from spyder.py3compat import is_text_string, to_text_string from spyder.utils.icon_manager import ima from spyder.utils import programs from spyder.utils.image_path_manager import get_image_path @@ -41,11 +44,6 @@ if sys.platform == "darwin" and not running_in_mac_app(): import applaunchservices as als -if PY2: - from urllib import unquote -else: - from urllib.parse import unquote - # Note: How to redirect a signal from widget *a* to widget *b* ? # ---- diff --git a/spyder/utils/sourcecode.py b/spyder/utils/sourcecode.py index 4d955f3f990..c7133a45627 100644 --- a/spyder/utils/sourcecode.py +++ b/spyder/utils/sourcecode.py @@ -91,7 +91,7 @@ def fix_indentation(text, indent_chars): def is_builtin(text): """Test if passed string is the name of a Python builtin object""" - from spyder.py3compat import builtins + import builtins return text in [str(name) for name in dir(builtins) if not name.startswith('_')] diff --git a/spyder/utils/switcher.py b/spyder/utils/switcher.py index 2c8a64a13a8..43461d18d4f 100644 --- a/spyder/utils/switcher.py +++ b/spyder/utils/switcher.py @@ -15,12 +15,9 @@ # Local imports from spyder.config.base import _ -from spyder.py3compat import iteritems, PY2 +from spyder.py3compat import iteritems from spyder.utils.icon_manager import ima -if PY2: - from itertools import izip as zip - def shorten_paths(path_list, is_unsaved): """ diff --git a/spyder/utils/syntaxhighlighters.py b/spyder/utils/syntaxhighlighters.py index 40d2999eac0..b35361afbdf 100644 --- a/spyder/utils/syntaxhighlighters.py +++ b/spyder/utils/syntaxhighlighters.py @@ -10,7 +10,7 @@ """ # Standard library imports -from __future__ import print_function +import builtins import keyword import os import re @@ -29,8 +29,7 @@ # Local imports from spyder.config.base import _ from spyder.config.manager import CONF -from spyder.py3compat import (builtins, is_text_string, to_text_string, PY3, - PY36_OR_MORE) +from spyder.py3compat import is_text_string, to_text_string from spyder.plugins.editor.utils.languages import CELL_LANGUAGES from spyder.plugins.editor.utils.editor import TextBlockHelper as tbh from spyder.plugins.editor.utils.editor import BlockUserData @@ -442,10 +441,7 @@ def make_python_patterns(additional_keywords=[], additional_builtins=[]): r"\b[+-]?0[oO][0-7]+[lL]?\b", r"\b[+-]?0[bB][01]+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?[jJ]?\b"] - if PY3: - prefix = "r|u|R|U|f|F|fr|Fr|fR|FR|rf|rF|Rf|RF|b|B|br|Br|bR|BR|rb|rB|Rb|RB" - else: - prefix = "r|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR" + prefix = "r|u|R|U|f|F|fr|Fr|fR|FR|rf|rF|Rf|RF|b|B|br|Br|bR|BR|rb|rB|Rb|RB" sqstring = r"(\b(%s))?'[^'\\\n]*(\\.[^'\\\n]*)*'?" % prefix dqstring = r'(\b(%s))?"[^"\\\n]*(\\.[^"\\\n]*)*"?' % prefix uf_sqstring = r"(\b(%s))?'[^'\\\n]*(\\.[^'\\\n]*)*(\\)$(?!')$" % prefix @@ -460,21 +456,20 @@ def make_python_patterns(additional_keywords=[], additional_builtins=[]): % prefix # Needed to achieve correct highlighting in Python 3.6+ # See spyder-ide/spyder#7324. - if PY36_OR_MORE: - # Based on - # https://github.com/python/cpython/blob/ - # 81950495ba2c36056e0ce48fd37d514816c26747/Lib/tokenize.py#L117 - # In order: Hexnumber, Binnumber, Octnumber, Decnumber, - # Pointfloat + Exponent, Expfloat, Imagnumber - number_regex = [ - r"\b[+-]?0[xX](?:_?[0-9A-Fa-f])+[lL]?\b", - r"\b[+-]?0[bB](?:_?[01])+[lL]?\b", - r"\b[+-]?0[oO](?:_?[0-7])+[lL]?\b", - r"\b[+-]?(?:0(?:_?0)*|[1-9](?:_?[0-9])*)[lL]?\b", - r"\b((\.[0-9](?:_?[0-9])*')|\.[0-9](?:_?[0-9])*)" - "([eE][+-]?[0-9](?:_?[0-9])*)?[jJ]?\b", - r"\b[0-9](?:_?[0-9])*([eE][+-]?[0-9](?:_?[0-9])*)?[jJ]?\b", - r"\b[0-9](?:_?[0-9])*[jJ]\b"] + # Based on + # https://github.com/python/cpython/blob/ + # 81950495ba2c36056e0ce48fd37d514816c26747/Lib/tokenize.py#L117 + # In order: Hexnumber, Binnumber, Octnumber, Decnumber, + # Pointfloat + Exponent, Expfloat, Imagnumber + number_regex = [ + r"\b[+-]?0[xX](?:_?[0-9A-Fa-f])+[lL]?\b", + r"\b[+-]?0[bB](?:_?[01])+[lL]?\b", + r"\b[+-]?0[oO](?:_?[0-7])+[lL]?\b", + r"\b[+-]?(?:0(?:_?0)*|[1-9](?:_?[0-9])*)[lL]?\b", + r"\b((\.[0-9](?:_?[0-9])*')|\.[0-9](?:_?[0-9])*)" + "([eE][+-]?[0-9](?:_?[0-9])*)?[jJ]?\b", + r"\b[0-9](?:_?[0-9])*([eE][+-]?[0-9](?:_?[0-9])*)?[jJ]?\b", + r"\b[0-9](?:_?[0-9])*[jJ]\b"] number = any("number", number_regex) string = any("string", [sq3string, dq3string, sqstring, dqstring]) diff --git a/spyder/utils/tests/test_encoding.py b/spyder/utils/tests/test_encoding.py index ee544032d92..973220c95d0 100644 --- a/spyder/utils/tests/test_encoding.py +++ b/spyder/utils/tests/test_encoding.py @@ -6,25 +6,20 @@ """Tests for encodings.py""" import os +import pathlib import stat from flaky import flaky import pytest from spyder.utils.encoding import is_text_file, get_coding, write -from spyder.py3compat import to_text_string, PY2 - -if PY2: - import pathlib2 as pathlib -else: - import pathlib +from spyder.py3compat import to_text_string __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @pytest.mark.order(1) -@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on Win!') def test_symlinks(tmpdir): """ Check that modifying symlinks files changes source file and keeps symlinks. diff --git a/spyder/utils/tests/test_syntaxhighlighters.py b/spyder/utils/tests/test_syntaxhighlighters.py index 94c8482a49b..6b96dee6d01 100644 --- a/spyder/utils/tests/test_syntaxhighlighters.py +++ b/spyder/utils/tests/test_syntaxhighlighters.py @@ -10,7 +10,6 @@ from qtpy.QtGui import QTextDocument from spyder.utils.syntaxhighlighters import HtmlSH, PythonSH, MarkdownSH -from spyder.py3compat import PY3 def compare_formats(actualFormats, expectedFormats, sh): assert len(actualFormats) == len(expectedFormats) @@ -69,13 +68,9 @@ def test_PythonSH_UTF16_string(): def test_python_string_prefix(): - if PY3: - prefixes = ("r", "u", "R", "U", "f", "F", "fr", "Fr", "fR", "FR", - "rf", "rF", "Rf", "RF", "b", "B", "br", "Br", "bR", "BR", - "rb", "rB", "Rb", "RB") - else: - prefixes = ("r", "u", "ur", "R", "U", "UR", "Ur", "uR", "b", "B", - "br", "Br", "bR", "BR") + prefixes = ("r", "u", "R", "U", "f", "F", "fr", "Fr", "fR", "FR", + "rf", "rF", "Rf", "RF", "b", "B", "br", "Br", "bR", "BR", + "rb", "rB", "Rb", "RB") for prefix in prefixes: txt = "[%s'test', %s'''test''']" % (prefix, prefix) diff --git a/spyder/utils/vcs.py b/spyder/utils/vcs.py index 7e7a6e0cb5b..a04a6170026 100644 --- a/spyder/utils/vcs.py +++ b/spyder/utils/vcs.py @@ -6,8 +6,6 @@ """Utilities for version control systems""" -from __future__ import print_function - import os import os.path as osp import subprocess @@ -17,7 +15,6 @@ from spyder.config.base import running_under_pytest from spyder.utils import programs from spyder.utils.misc import abspardir -from spyder.py3compat import PY3 SUPPORTED = [ @@ -129,15 +126,13 @@ def get_git_revision(repopath): commit = programs.run_program(git, ['rev-parse', '--short', 'HEAD'], cwd=repopath).communicate() commit = commit[0].strip() - if PY3: - commit = commit.decode(sys.getdefaultencoding()) + commit = commit.decode(sys.getdefaultencoding()) # Branch branches = programs.run_program(git, ['branch'], cwd=repopath).communicate() branches = branches[0] - if PY3: - branches = branches.decode(sys.getdefaultencoding()) + branches = branches.decode(sys.getdefaultencoding()) branches = branches.split('\n') active_branch = [b for b in branches if b.startswith('*')] if len(active_branch) != 1: @@ -173,8 +168,7 @@ def get_git_refs(repopath): cwd=repopath, ).communicate() - if PY3: - out = out.decode(sys.getdefaultencoding()) + out = out.decode(sys.getdefaultencoding()) files_modifed = [line.strip() for line in out.split('\n') if line] # Tags @@ -183,8 +177,7 @@ def get_git_refs(repopath): cwd=repopath, ).communicate() - if PY3: - out = out.decode(sys.getdefaultencoding()) + out = out.decode(sys.getdefaultencoding()) tags = [line.strip() for line in out.split('\n') if line] # Branches @@ -193,8 +186,7 @@ def get_git_refs(repopath): cwd=repopath, ).communicate() - if PY3: - out = out.decode(sys.getdefaultencoding()) + out = out.decode(sys.getdefaultencoding()) lines = [line.strip() for line in out.split('\n') if line] for line in lines: @@ -219,8 +211,7 @@ def get_git_remotes(fpath): cwd=osp.dirname(fpath), ).communicate() - if PY3: - data = data.decode(sys.getdefaultencoding()) + data = data.decode(sys.getdefaultencoding()) lines = [line.strip() for line in data.split('\n') if line] for line in lines: diff --git a/spyder/utils/workers.py b/spyder/utils/workers.py index 2913a8f7e81..5dbd1f9eebe 100644 --- a/spyder/utils/workers.py +++ b/spyder/utils/workers.py @@ -20,7 +20,7 @@ Signal) # Local imports -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string WIN = os.name == 'nt' @@ -175,8 +175,6 @@ def communicate(self): stderr = handle_qbytearray(raw_stderr, enco) result = [stdout.encode(enco), stderr.encode(enco)] - if PY2: - stderr = stderr.decode() result[-1] = '' self._result = result diff --git a/spyder/widgets/arraybuilder.py b/spyder/widgets/arraybuilder.py index a5c512db3c0..fae62011882 100644 --- a/spyder/widgets/arraybuilder.py +++ b/spyder/widgets/arraybuilder.py @@ -14,7 +14,6 @@ # - Generalize API for registering new array builders # Standard library imports -from __future__ import division import re # Third party imports diff --git a/spyder/widgets/collectionseditor.py b/spyder/widgets/collectionseditor.py index 60fdefdc73b..3c0f6d32be8 100644 --- a/spyder/widgets/collectionseditor.py +++ b/spyder/widgets/collectionseditor.py @@ -19,8 +19,8 @@ # pylint: disable=R0201 # Standard library imports -from __future__ import print_function import datetime +import io import re import sys import warnings @@ -48,8 +48,8 @@ from spyder.config.base import _, running_under_pytest from spyder.config.fonts import DEFAULT_SMALL_DELTA from spyder.config.gui import get_font -from spyder.py3compat import (io, is_binary_string, PY3, to_text_string, - is_type_text_string, NUMERIC_TYPES) +from spyder.py3compat import (is_binary_string, to_text_string, + is_type_text_string) from spyder.utils.icon_manager import ima from spyder.utils.misc import getcwd_or_home from spyder.utils.qthelpers import ( @@ -70,6 +70,7 @@ LARGE_NROWS = 100 ROWS_TO_LOAD = 50 +NUMERIC_TYPES = (int, float) + get_numeric_numpy_types() def natsort(s): """ @@ -408,14 +409,12 @@ def data(self, index, role=Qt.DisplayRole): else: if is_type_text_string(value): display = to_text_string(value, encoding="utf-8") - elif not isinstance( - value, NUMERIC_TYPES + get_numeric_numpy_types() - ): + elif not isinstance(value, NUMERIC_TYPES): display = to_text_string(value) else: display = value if role == Qt.UserRole: - if isinstance(value, NUMERIC_TYPES + get_numeric_numpy_types()): + if isinstance(value, NUMERIC_TYPES): return to_qvariant(value) else: return to_qvariant(display) @@ -1217,10 +1216,7 @@ def copy(self): # to copy the whole thing in a tab separated format if (isinstance(obj, (np.ndarray, np.ma.MaskedArray)) and np.ndarray is not FakeObject): - if PY3: - output = io.BytesIO() - else: - output = io.StringIO() + output = io.BytesIO() try: np.savetxt(output, obj, delimiter='\t') except Exception: @@ -1240,10 +1236,7 @@ def copy(self): _("It was not possible to copy " "this dataframe")) return - if PY3: - obj = output.getvalue() - else: - obj = output.getvalue().decode('utf-8') + obj = output.getvalue() output.close() elif is_binary_string(obj): obj = to_text_string(obj, 'utf8') diff --git a/spyder/widgets/github/backend.py b/spyder/widgets/github/backend.py index 69116e32e75..704116ca795 100644 --- a/spyder/widgets/github/backend.py +++ b/spyder/widgets/github/backend.py @@ -32,7 +32,6 @@ from spyder.config.manager import CONF from spyder.config.base import _, running_under_pytest -from spyder.py3compat import PY2 from spyder.utils.external import github from spyder.widgets.github.gh_login import DlgGitHubLogin @@ -198,8 +197,7 @@ def get_user_credentials(self): """Get user credentials with the login dialog.""" token = None remember_token = self._get_credentials_from_settings() - valid_py_os = not (PY2 and sys.platform.startswith('linux')) - if remember_token and valid_py_os: + if remember_token: # Get token from keyring try: token = keyring.get_password('github', 'token') @@ -218,7 +216,7 @@ def get_user_credentials(self): token, remember_token) - if credentials['token'] and valid_py_os: + if credentials['token']: self._store_token(credentials['token'], credentials['remember_token']) CONF.set('main', 'report_error/remember_token', diff --git a/spyder/widgets/github/gh_login.py b/spyder/widgets/github/gh_login.py index b78f9cebf46..672dd77e679 100644 --- a/spyder/widgets/github/gh_login.py +++ b/spyder/widgets/github/gh_login.py @@ -22,7 +22,7 @@ QVBoxLayout, QWidget) from spyder.config.base import _ -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string TOKEN_URL = "https://github.com/settings/tokens/new?scopes=public_repo" @@ -73,10 +73,7 @@ def __init__(self, parent, token, remember_token=False): token_form_layout.setWidget(1, QFormLayout.FieldRole, self.le_token) self.cb_remember_token = None - # Check if we are not in Python 2 and Linux because - # there's no keyring backend there - valid_py_os = not (PY2 and sys.platform.startswith('linux')) - if self.is_keyring_available() and valid_py_os: + if self.is_keyring_available(): self.cb_remember_token = QCheckBox(_("Remember token")) self.cb_remember_token.setToolTip(_("Spyder will save your " "token safely")) diff --git a/spyder/widgets/github/tests/test_github_backend.py b/spyder/widgets/github/tests/test_github_backend.py index 8cd5b4fe7c0..81155183f8a 100644 --- a/spyder/widgets/github/tests/test_github_backend.py +++ b/spyder/widgets/github/tests/test_github_backend.py @@ -21,7 +21,6 @@ from spyder.config.base import running_in_ci from spyder.config.manager import CONF -from spyder.py3compat import PY2 from spyder.widgets.github import backend diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py index ee968ec2295..849ea090119 100644 --- a/spyder/widgets/mixins.py +++ b/spyder/widgets/mixins.py @@ -12,7 +12,6 @@ """ # Standard library imports -from __future__ import print_function import os import os.path as osp import re diff --git a/spyder/widgets/switcher.py b/spyder/widgets/switcher.py index 72bcc002a0f..7e55e4b8e34 100644 --- a/spyder/widgets/switcher.py +++ b/spyder/widgets/switcher.py @@ -8,7 +8,6 @@ """Switcher widget interface.""" # Standard library imports -from __future__ import print_function import os import sys diff --git a/spyder/widgets/tests/test_collectioneditor.py b/spyder/widgets/tests/test_collectioneditor.py index 056191f48ec..11590f551b3 100644 --- a/spyder/widgets/tests/test_collectioneditor.py +++ b/spyder/widgets/tests/test_collectioneditor.py @@ -35,7 +35,7 @@ NamespacesBrowserFinder) from spyder.plugins.variableexplorer.widgets.tests.test_dataframeeditor import \ generate_pandas_indexes -from spyder.py3compat import PY2, to_text_string +from spyder.py3compat import to_text_string from spyder_kernels.utils.nsview import get_size @@ -383,7 +383,6 @@ def test_sort_float_collectionsmodel(): '0.1', '1.0', '10.0', '1e+16']] -@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on Win and py2') def test_sort_collectionsmodel(): var_list1 = [0, 1, 2] var_list2 = [3, 4, 5, 6] From 4971374c51eaa62db454f45fd023d83e0a5489aa Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 22 Jan 2023 13:31:19 -0500 Subject: [PATCH 2/3] Fix minor style issues - Restore missing import and remove unnecessary code. - Forcibly change line endings of a QDarkstyle file after trying to update its subrepo. --- external-deps/qdarkstyle/docs/make.bat | 72 +++++++++---------- spyder/app/tests/test_mainwindow.py | 3 +- .../languageserver/providers/utils.py | 9 +-- .../plugins/console/widgets/internalshell.py | 2 +- spyder/plugins/console/widgets/shell.py | 5 +- .../tests/test_ipythonconsole.py | 4 +- .../variableexplorer/widgets/arrayeditor.py | 5 +- .../variableexplorer/widgets/importwizard.py | 1 + spyder/widgets/collectionseditor.py | 3 + 9 files changed, 52 insertions(+), 52 deletions(-) diff --git a/external-deps/qdarkstyle/docs/make.bat b/external-deps/qdarkstyle/docs/make.bat index 656d8d87e21..fe9d1e4775d 100644 --- a/external-deps/qdarkstyle/docs/make.bat +++ b/external-deps/qdarkstyle/docs/make.bat @@ -1,36 +1,36 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=build -set SPHINXPROJ=QDarkStyle - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=build +set SPHINXPROJ=QDarkStyle + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index b627c2e1b9f..788879039ac 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -3033,8 +3033,7 @@ def _get_filenames(): @pytest.mark.slow @flaky(max_runs=3) -@pytest.mark.skipif(sys.platform == 'darwin', - reason="It times out on macOS") +@pytest.mark.skipif(sys.platform == 'darwin', reason="It times out on macOS") def test_debug_unsaved_file(main_window, qtbot): """Test that we can debug an unsaved file.""" # Wait until the window is fully up diff --git a/spyder/plugins/completion/providers/languageserver/providers/utils.py b/spyder/plugins/completion/providers/languageserver/providers/utils.py index f9fdfd092e5..1753409d47c 100644 --- a/spyder/plugins/completion/providers/languageserver/providers/utils.py +++ b/spyder/plugins/completion/providers/languageserver/providers/utils.py @@ -6,17 +6,18 @@ """Spyder Language Server Protocol Client common util functions.""" -import re +# Standard library imports import os import os.path as osp -from spyder.utils.encoding import to_unicode - - import pathlib +import re from urllib.parse import urlparse from urllib.parse import quote_from_bytes as urlquote_from_bytes from urllib.request import url2pathname +# Local imports +from spyder.utils.encoding import to_unicode + def as_posix(path_obj): """Get path as_posix as unicode using correct separator.""" diff --git a/spyder/plugins/console/widgets/internalshell.py b/spyder/plugins/console/widgets/internalshell.py index 5840d9b8f44..e73ef4e7843 100644 --- a/spyder/plugins/console/widgets/internalshell.py +++ b/spyder/plugins/console/widgets/internalshell.py @@ -15,9 +15,9 @@ # Standard library imports import builtins -from time import time import os import threading +from time import time # Third party imports from qtpy.QtCore import QEventLoop, QObject, Signal, Slot diff --git a/spyder/plugins/console/widgets/shell.py b/spyder/plugins/console/widgets/shell.py index bf8451c073e..3419291ee53 100644 --- a/spyder/plugins/console/widgets/shell.py +++ b/spyder/plugins/console/widgets/shell.py @@ -30,8 +30,7 @@ # Local import from spyder.config.base import _, get_conf_path, get_debug_level, STDERR from spyder.config.manager import CONF -from spyder.py3compat import (is_string, is_text_string, - to_text_string) +from spyder.py3compat import is_string, is_text_string, to_text_string from spyder.utils import encoding from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import (add_actions, create_action, keybinding, @@ -562,7 +561,7 @@ def flush(self, error=False, prompt=False): except TypeError: text = b"".join(self.__buffer) try: - text = text.decode( locale.getdefaultlocale()[1] ) + text = text.decode(locale.getdefaultlocale()[1]) except: pass diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py index c741f5bc13a..18072ea1586 100644 --- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py +++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py @@ -26,7 +26,6 @@ from flaky import flaky from packaging.version import parse import pytest -from qtpy import PYQT5 from qtpy.QtCore import Qt from qtpy.QtWebEngineWidgets import WEBENGINE from qtpy.QtWidgets import QMessageBox @@ -797,8 +796,7 @@ def add(x, y): @flaky(max_runs=3) -@pytest.mark.skipif(os.name == 'nt', - reason="It times out frequently") +@pytest.mark.skipif(os.name == 'nt', reason="It times out frequently") def test_mpl_backend_change(ipyconsole, qtbot): """ Test that Matplotlib backend is changed correctly when diff --git a/spyder/plugins/variableexplorer/widgets/arrayeditor.py b/spyder/plugins/variableexplorer/widgets/arrayeditor.py index 7b2fc17ce10..b75a0017e64 100644 --- a/spyder/plugins/variableexplorer/widgets/arrayeditor.py +++ b/spyder/plugins/variableexplorer/widgets/arrayeditor.py @@ -35,9 +35,8 @@ from spyder.config.fonts import DEFAULT_SMALL_DELTA from spyder.config.gui import get_font from spyder.config.manager import CONF -from spyder.py3compat import (is_binary_string, is_string, - is_text_string, to_binary_string, - to_text_string) +from spyder.py3compat import (is_binary_string, is_string, is_text_string, + to_binary_string, to_text_string) from spyder.utils.icon_manager import ima from spyder.utils.qthelpers import add_actions, create_action, keybinding from spyder.plugins.variableexplorer.widgets.basedialog import BaseDialog diff --git a/spyder/plugins/variableexplorer/widgets/importwizard.py b/spyder/plugins/variableexplorer/widgets/importwizard.py index 6d2c3471d27..a54dd5262ac 100644 --- a/spyder/plugins/variableexplorer/widgets/importwizard.py +++ b/spyder/plugins/variableexplorer/widgets/importwizard.py @@ -11,6 +11,7 @@ # Standard library imports import datetime from functools import partial as ft_partial +import io from itertools import zip_longest # Third party imports diff --git a/spyder/widgets/collectionseditor.py b/spyder/widgets/collectionseditor.py index 3c0f6d32be8..5d28ce866d5 100644 --- a/spyder/widgets/collectionseditor.py +++ b/spyder/widgets/collectionseditor.py @@ -67,11 +67,14 @@ # Maximum length of a serialized variable to be set in the kernel MAX_SERIALIZED_LENGHT = 1e6 +# To handle large collections LARGE_NROWS = 100 ROWS_TO_LOAD = 50 +# Numeric types NUMERIC_TYPES = (int, float) + get_numeric_numpy_types() + def natsort(s): """ Natural sorting, e.g. test3 comes before test100. From 166ab5d0fd4a25bf42e05ed97b0617eb21aa9210 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 22 Jan 2023 13:32:35 -0500 Subject: [PATCH 3/3] IPython console: Restore condition that only applies for Python 3.8+ --- spyder/plugins/ipythonconsole/widgets/main_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index e07642d280b..874774b6374 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -1067,7 +1067,7 @@ def _init_asyncio_patch(self): - Do this as early as possible to make it a low priority and overrideable. """ - if os.name == 'nt': + if os.name == 'nt' and sys.version_info[:2] >= (3, 8): # Tests on Linux hang if we don't leave this import here. import tornado if tornado.version_info >= (6, 1):