diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f49c602f9da7732bbe717f46e6925f006e223a22..11fa01b996879f838dc0fe65d5a6eeead5b8b0e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,12 +24,26 @@ jobs: curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-flake8.sh . ./prepare-and-run-flake8.sh ./pudb ./test + pylint: + name: Pylint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: "Main Script" + run: | + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-pylint.sh + . ./prepare-and-run-pylint.sh "$(basename $GITHUB_REPOSITORY)" test/test_*.py + pytest: name: Pytest on Py${{ matrix.python-version }} runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, pypy3] + python-version: ["3.6", "3.7", "3.8", "3.9", pypy3] steps: - uses: actions/checkout@v2 - diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8b9f538c1c896de1bf160b620d54f8ce45b43e6..77eecd4f4cced33cedd63c13353d16416f824082 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,3 +30,13 @@ Documentation: - ". ./build-docs.sh" tags: - python3 + +Pylint: + script: | + export PY_EXE=python3 + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-pylint.sh + . ./prepare-and-run-pylint.sh "$CI_PROJECT_NAME" test/test_*.py + tags: + - python3 + except: + - tags diff --git a/.pylintrc-local.yml b/.pylintrc-local.yml new file mode 100644 index 0000000000000000000000000000000000000000..2b1336aa2c6be71a9d09f3586a6050c3c0d891d7 --- /dev/null +++ b/.pylintrc-local.yml @@ -0,0 +1,6 @@ +- arg: ignored-modules + val: + - bpython + - IPython + - py.test + - pytest diff --git a/pudb/__init__.py b/pudb/__init__.py index f013c5c9e8041261459560573d1954e932e6636f..b2da1ec3a16afe264141f45042bc48426abe5e2a 100644 --- a/pudb/__init__.py +++ b/pudb/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -26,7 +24,6 @@ THE SOFTWARE. """ -from pudb.py3compat import raw_input, PY3 from pudb.settings import load_config @@ -35,7 +32,7 @@ VERSION = ".".join(str(nv) for nv in NUM_VERSION) __version__ = VERSION -class PudbShortcuts(object): +class PudbShortcuts: @property def db(self): import sys @@ -57,12 +54,8 @@ class PudbShortcuts(object): dbg.set_trace(sys._getframe().f_back, paused=False) -if PY3: - import builtins - builtins.__dict__["pu"] = PudbShortcuts() -else: - import __builtin__ - __builtin__.__dict__["pu"] = PudbShortcuts() +import builtins +builtins.__dict__["pu"] = PudbShortcuts() CURRENT_DEBUGGER = [] @@ -156,7 +149,7 @@ def runscript(mainpyfile, args=None, pre_run="", steal_output=False, retcode = call(pre_run, close_fds=True, shell=True) if retcode: print("*** WARNING: pre-run process exited with code %d." % retcode) - raw_input("[Hit Enter]") + input("[Hit Enter]") status_msg = "" @@ -314,11 +307,11 @@ def set_interrupt_handler(interrupt_signal=None): try: signal.signal(interrupt_signal, _interrupt_handler) except ValueError: - from pudb.lowlevel import format_exception + from traceback import format_exception import sys from warnings import warn warn("setting interrupt handler on signal %d failed: %s" - % (interrupt_signal, "".join(format_exception(sys.exc_info())))) + % (interrupt_signal, "".join(format_exception(*sys.exc_info())))) def post_mortem(tb=None, e_type=None, e_value=None): diff --git a/pudb/__main__.py b/pudb/__main__.py index da84b07a810225fbf6a51a9ee69fd45ec5b2ab87..692919b01acfbfb5900bd79c94b3317e265cd3c4 100644 --- a/pudb/__main__.py +++ b/pudb/__main__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer diff --git a/pudb/b.py b/pudb/b.py index 8d7326651b5d324c902409e2006cd09d0ac3ef95..64e9cbaa460899fd0f746e7f08684f187bdf93a6 100644 --- a/pudb/b.py +++ b/pudb/b.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, division, print_function import sys from pudb import _get_debugger, set_interrupt_handler @@ -7,7 +6,7 @@ from pudb import _get_debugger, set_interrupt_handler def __myimport__(name, *args, **kwargs): # noqa: N807 if name == "pudb.b": set_trace() - return __origimport__(name, *args, **kwargs) # noqa: F821 + return __origimport__(name, *args, **kwargs) # noqa: F821, E501 # pylint: disable=undefined-variable # Will only be run on first import diff --git a/pudb/debugger.py b/pudb/debugger.py index a0d1c574345c4a012917d06ca746e5c859712731..efd1734487c62e8a676d881fe2df017dd0592803 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1,8 +1,3 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -41,7 +36,6 @@ from types import TracebackType from pudb.lowlevel import decode_lines, ui_log from pudb.settings import load_config, save_config -from pudb.py3compat import PY3, raw_input, execfile CONFIG = load_config() save_config(CONFIG) @@ -195,10 +189,7 @@ class Debugger(bdb.Bdb): if steal_output: raise NotImplementedError("output stealing") - if PY3: - from io import StringIO - else: - from cStringIO import StringIO + from io import StringIO self.stolen_output = sys.stderr = sys.stdout = StringIO() sys.stdin = StringIO("") # avoid spurious hangs @@ -497,11 +488,8 @@ class Debugger(bdb.Bdb): # user_call for details). self._wait_for_mainpyfile = 1 self.mainpyfile = self.canonic(filename) - if PY3: - statement = 'exec(compile(open("%s").read(), "%s", "exec"))' % ( - filename, filename) - else: - statement = 'execfile( "%s")' % filename + statement = 'exec(compile(open("{}").read(), "{}", "exec"))'.format( + filename, filename) # Set up an interrupt handler from pudb import set_interrupt_handler @@ -531,11 +519,10 @@ class Debugger(bdb.Bdb): "__builtins__": __builtins__, }) - if PY3: - __main__.__dict__.update({ - "__package__": mod_spec.parent, - "__loader__": mod_spec.loader, - }) + __main__.__dict__.update({ + "__package__": mod_spec.parent, + "__loader__": mod_spec.loader, + }) self._wait_for_mainpyfile = True @@ -567,20 +554,20 @@ except ImportError: CursesScreen = None -class ThreadsafeScreenMixin(object): +class ThreadsafeScreenMixin: """A Screen subclass that doesn't crash when running from a non-main thread.""" def signal_init(self): """Initialize signal handler, ignoring errors silently.""" try: - super(ThreadsafeScreenMixin, self).signal_init() + super().signal_init() except ValueError: pass def signal_restore(self): """Restore default signal handler, ignoring errors silently.""" try: - super(ThreadsafeScreenMixin, self).signal_restore() + super().signal_restore() except ValueError: pass @@ -592,7 +579,7 @@ class ThreadsafeRawScreen(ThreadsafeScreenMixin, RawScreen): class ThreadsafeFixedSizeRawScreen(ThreadsafeScreenMixin, RawScreen): def __init__(self, **kwargs): self._term_size = kwargs.pop("term_size", None) - super(ThreadsafeFixedSizeRawScreen, self).__init__(**kwargs) + super().__init__(**kwargs) def get_cols_rows(self): if self._term_size is not None: @@ -610,7 +597,7 @@ if curses is not None: # {{{ source code providers -class SourceCodeProvider(object): +class SourceCodeProvider: def __ne__(self, other): return not (self == other) @@ -684,7 +671,7 @@ class FileSourceCodeProvider(SourceCodeProvider): debugger_ui, list(decode_lines(lines)), set(breakpoints)) except Exception: from pudb.lowlevel import format_exception - debugger_ui.message("Could not load source file '%s':\n\n%s" % ( + debugger_ui.message("Could not load source file '{}':\n\n{}".format( self.file_name, "".join(format_exception(sys.exc_info()))), title="Source Code Load Error") return [SourceLine(debugger_ui, @@ -1132,9 +1119,9 @@ class DebuggerUI(FrameVarInfoKeeper): new_modification_time = os.path.getmtime(file_name) file_changed = new_modification_time - original_modification_time > 0 except Exception: - from pudb.lowlevel import format_exception + from traceback import format_exception self.message("Exception happened when trying to edit the file:" - "\n\n%s" % ("".join(format_exception(sys.exc_info()))), + "\n\n%s" % ("".join(format_exception(*sys.exc_info()))), title="File Edit Error") return @@ -1574,12 +1561,13 @@ class DebuggerUI(FrameVarInfoKeeper): try: __import__(str(new_mod_name)) except Exception: - from pudb.lowlevel import format_exception + from traceback import format_exception - self.message("Could not import module '%s':\n\n%s" % ( - new_mod_name, "".join( - format_exception(sys.exc_info()))), - title="Import Error") + self.message( + "Could not import module '{}':\n\n{}".format( + new_mod_name, "".join( + format_exception(*sys.exc_info()))), + title="Import Error") else: show_mod(__import__(str(new_mod_name))) break @@ -1593,11 +1581,8 @@ class DebuggerUI(FrameVarInfoKeeper): if widget is not new_mod_entry: mod_name = widget.base_widget.get_text()[0] mod = sys.modules[mod_name] - if PY3: - import importlib - importlib.reload(mod) - else: - reload(mod) # noqa (undef on Py3) + import importlib + importlib.reload(mod) self.message("'%s' was successfully reloaded." % mod_name) @@ -1743,23 +1728,15 @@ class DebuggerUI(FrameVarInfoKeeper): prev_sys_stdout = sys.stdout prev_sys_stderr = sys.stderr - if PY3: - from io import StringIO - else: - from cStringIO import StringIO + from io import StringIO sys.stdin = None sys.stderr = sys.stdout = StringIO() try: # Don't use cmdline_get_namespace() here in Python 2, as it # breaks things (issue #166). - if PY3: - eval(compile(cmd, "", "single"), - cmdline_get_namespace()) - else: - eval(compile(cmd, "", "single"), - self.debugger.curframe.f_globals, - self.debugger.curframe.f_locals) + eval(compile(cmd, "", "single"), + cmdline_get_namespace()) except Exception: tp, val, tb = sys.exc_info() @@ -1951,7 +1928,7 @@ class DebuggerUI(FrameVarInfoKeeper): def show_output(w, size, key): self.screen.stop() - raw_input("Hit Enter to return:") + input("Hit Enter to return:") self.screen.start() def reload_breakpoints(w, size, key): @@ -1963,11 +1940,11 @@ class DebuggerUI(FrameVarInfoKeeper): def show_traceback(w, size, key): if self.current_exc_tuple is not None: - from pudb.lowlevel import format_exception + from traceback import format_exception result = self.dialog( urwid.ListBox(urwid.SimpleListWalker([urwid.Text( - "".join(format_exception(self.current_exc_tuple)))])), + "".join(format_exception(*self.current_exc_tuple)))])), [ ("Close", "close"), ("Location", "location") @@ -2004,8 +1981,11 @@ class DebuggerUI(FrameVarInfoKeeper): try: if not shell.custom_shell_dict: # Only execfile once from os.path import expanduser - execfile( - expanduser(CONFIG["shell"]), shell.custom_shell_dict) + cshell_fname = expanduser(CONFIG["shell"]) + with open(cshell_fname) as inf: + exec(compile(inf.read(), cshell_fname, "exec"), + shell.custom_shell_dict, + shell.custom_shell_dict) except Exception: print("Error when importing custom shell:") from traceback import print_exc @@ -2041,7 +2021,7 @@ class DebuggerUI(FrameVarInfoKeeper): def __init__(self, idx): self.idx = idx - def __call__(subself, w, size, key): # noqa + def __call__(subself, w, size, key): # noqa # pylint: disable=no-self-argument self.columns.set_focus(self.rhs_col_sigwrap) self.rhs_col.set_focus(self.rhs_col.widget_list[subself.idx]) @@ -2202,10 +2182,10 @@ class DebuggerUI(FrameVarInfoKeeper): title=None, bind_enter_esc=True, focus_buttons=False, extra_bindings=[]): class ResultSetter: - def __init__(subself, res): # noqa + def __init__(subself, res): # noqa: N805, E501 # pylint: disable=no-self-argument subself.res = res - def __call__(subself, btn): # noqa + def __call__(subself, btn): # noqa: N805, E501 # pylint: disable=no-self-argument self.quit_event_loop = [subself.res] Attr = urwid.AttrMap # noqa @@ -2248,17 +2228,17 @@ class DebuggerUI(FrameVarInfoKeeper): ("fixed", 1, urwid.SolidFill()), w]) - class ResultSetter: - def __init__(subself, res): # noqa + class ResultSettingEventHandler: + def __init__(subself, res): # noqa: N805, E501 # pylint: disable=no-self-argument subself.res = res - def __call__(subself, w, size, key): # noqa + def __call__(subself, w, size, key): # noqa: N805, E501 # pylint: disable=no-self-argument self.quit_event_loop = [subself.res] w = SignalWrap(w) for key, binding in extra_bindings: if isinstance(binding, str): - w.listen(key, ResultSetter(binding)) + w.listen(key, ResultSettingEventHandler(binding)) else: w.listen(key, binding) @@ -2283,7 +2263,7 @@ class DebuggerUI(FrameVarInfoKeeper): get_palette(may_use_fancy_formats, CONFIG["theme"])) def show_exception_dialog(self, exc_tuple): - from pudb.lowlevel import format_exception + from traceback import format_exception desc = ( "The program has terminated abnormally because of an exception.\n\n" @@ -2291,7 +2271,7 @@ class DebuggerUI(FrameVarInfoKeeper): "time using the 'e' key. The debugger has entered post-mortem mode " "and will prevent further state changes." ) - tb_txt = "".join(format_exception(exc_tuple)) + tb_txt = "".join(format_exception(*exc_tuple)) self._show_exception_dialog( description=desc, error_info=tb_txt, @@ -2306,7 +2286,7 @@ class DebuggerUI(FrameVarInfoKeeper): ui_log.exception("Error while showing error dialog") def _show_internal_exc_dlg(self, exc_tuple): - from pudb.lowlevel import format_exception + from traceback import format_exception from pudb import VERSION desc = ( @@ -2325,7 +2305,7 @@ class DebuggerUI(FrameVarInfoKeeper): python=sys.version.replace("\n", " "), pudb=VERSION, urwid=".".join(map(str, urwid.version.VERSION)), - tb="".join(format_exception(exc_tuple)) + tb="".join(format_exception(*exc_tuple)) ) self._show_exception_dialog( @@ -2364,8 +2344,8 @@ class DebuggerUI(FrameVarInfoKeeper): self.message("Traceback saved as %s." % filename, title="Success") except Exception: - from pudb.lowlevel import format_exception - io_tb_txt = "".join(format_exception(sys.exc_info())) + from traceback import format_exception + io_tb_txt = "".join(format_exception(*sys.exc_info())) self.message( "An error occurred while trying to write " "the traceback:\n\n" + io_tb_txt, diff --git a/pudb/forked.py b/pudb/forked.py index 26dcc4c221ced08db14be140f611d8f66d7188db..3804ec98029680189f03c0fe52b6de6272b2f268 100644 --- a/pudb/forked.py +++ b/pudb/forked.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - import sys import fcntl import termios diff --git a/pudb/ipython.py b/pudb/ipython.py index 7ac9873a11486ecd1d8642aac6250f03ad6801b9..0b7ff5452af8a003e3b89b672b53ca5e09602b36 100644 --- a/pudb/ipython.py +++ b/pudb/ipython.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, with_statement - import sys import os diff --git a/pudb/lowlevel.py b/pudb/lowlevel.py index 83b5604c6aa116da3171f0ee4b8e08b68f9d3f10..8b1eafa49b4e21164d2d6776b6c37ef7435aa6a5 100644 --- a/pudb/lowlevel.py +++ b/pudb/lowlevel.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -28,7 +26,6 @@ THE SOFTWARE. import logging from datetime import datetime -from pudb.py3compat import PY3, text_type logfile = [None] @@ -66,7 +63,7 @@ class TerminalOrStreamHandler(logging.StreamHandler): message = self.format(record) dbg.ui.add_cmdline_content(message, "command line error") else: - super(TerminalOrStreamHandler, self).emit(record) + super().emit(record) finally: self.release() @@ -99,24 +96,15 @@ ui_log, settings_log = _init_loggers() def generate_executable_lines_for_code(code): lineno = code.co_firstlineno yield lineno - if PY3: - # See https://github.com/python/cpython/blob/master/Objects/lnotab_notes.txt - import sys - use_36_line_incr = sys.version_info >= (3, 6) - - for line_incr in code.co_lnotab[1::2]: - # This showed up between the v3.5 and v3.6 cycles: - # https://github.com/python/cpython/blob/v3.5.0/Objects/lnotab_notes.txt - # https://github.com/python/cpython/blob/v3.6.0/Objects/lnotab_notes.txt - # Gate the check on 3.6-or-newer. - if line_incr >= 0x80 and use_36_line_incr: - line_incr -= 0x100 - lineno += line_incr - yield lineno - else: - for c in code.co_lnotab[1::2]: - lineno += ord(c) - yield lineno + # See https://github.com/python/cpython/blob/master/Objects/lnotab_notes.txt + + for line_incr in code.co_lnotab[1::2]: + # NB: This code is specific to Python 3.6 and higher + # https://github.com/python/cpython/blob/v3.6.0/Objects/lnotab_notes.txt + if line_incr >= 0x80: + line_incr -= 0x100 + lineno += line_incr + yield lineno def get_executable_lines_for_codes_recursive(codes): @@ -228,10 +216,7 @@ def detect_encoding(line_iter): def find_cookie(line): try: - if PY3: - line_string = line - else: - line_string = line.decode("ascii") + line_string = line except UnicodeDecodeError: return None @@ -251,7 +236,7 @@ def detect_encoding(line_iter): return encoding first = read_or_stop() - if isinstance(first, text_type): + if isinstance(first, str): return None, [first] if first.startswith(BOM_UTF8): @@ -289,38 +274,4 @@ def decode_lines(lines): # }}} - -# {{{ traceback formatting - -class StringExceptionValueWrapper: - def __init__(self, string_val): - self.string_val = string_val - - def __str__(self): - return self.string_val - - __context__ = None - __cause__ = None - - -def format_exception(exc_tuple): - # Work around http://bugs.python.org/issue17413 - # See also https://github.com/inducer/pudb/issues/61 - - from traceback import format_exception - if PY3: - exc_type, exc_value, exc_tb = exc_tuple - - if isinstance(exc_value, str): - exc_value = StringExceptionValueWrapper(exc_value) - exc_tuple = exc_type, exc_value, exc_tb - - return format_exception( - *exc_tuple, - **dict(chain=hasattr(exc_value, "__context__"))) - else: - return format_exception(*exc_tuple) - -# }}} - # vim: foldmethod=marker diff --git a/pudb/py3compat.py b/pudb/py3compat.py deleted file mode 100644 index 2e7cdd00c5f64a37661b98f2c525ba6a83ce9362..0000000000000000000000000000000000000000 --- a/pudb/py3compat.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import absolute_import, division, print_function -import sys - -PY3 = sys.version_info[0] >= 3 -if PY3: - raw_input = input - xrange = range - integer_types = (int,) - string_types = (str,) - text_type = str - - def execfile(fname, globs, locs=None): - exec(compile(open(fname).read(), fname, "exec"), globs, locs or globs) - -else: - raw_input = raw_input - xrange = xrange - integer_types = (int, long) # noqa: F821 - string_types = (basestring,) # noqa: F821 - text_type = unicode # noqa: F821 - execfile = execfile - -try: - import builtins - from configparser import ConfigParser -except ImportError: - import __builtin__ as builtins # noqa: F401 - from ConfigParser import ConfigParser # noqa: F401 diff --git a/pudb/remote.py b/pudb/remote.py index 9ff197efa26c5fe063bac96afb3e19512ab1942f..3daa54b360a258fc91dec57c62b10aad42ea1ea3 100644 --- a/pudb/remote.py +++ b/pudb/remote.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -143,13 +139,13 @@ class RemoteDebugger(Debugger): if reverse: self.host, self.port = host, port client, address = self.get_reverse_socket_client(host, port) - self.ident = "{0}:{1}".format(self.me, self.port) + self.ident = f"{self.me}:{self.port}" else: self._sock, conn_info = self.get_socket_client( host, port, search_limit=search_limit, ) self.host, self.port = conn_info - self.ident = "{0}:{1}".format(self.me, self.port) + self.ident = f"{self.me}:{self.port}" self.say(BANNER.format(self=self)) client, address = self._sock.accept() client.setblocking(1) @@ -160,7 +156,7 @@ class RemoteDebugger(Debugger): try: _sock.connect((host, port)) _sock.setblocking(1) - except socket.error as exc: + except OSError as exc: if exc.errno == errno.ECONNREFUSED: raise ValueError(CONN_REFUSED.format(self=self)) raise exc @@ -180,7 +176,7 @@ class RemoteDebugger(Debugger): this_port = port + i try: _sock.bind((host, this_port)) - except socket.error as exc: + except OSError as exc: if exc.errno in [errno.EADDRINUSE, errno.EINVAL]: continue raise diff --git a/pudb/run.py b/pudb/run.py index 5b23feeef47ce8470b721e1ad257474fa5b1b473..76336e5837903a1b853f42b834ca4c3dcfd6e5bd 100644 --- a/pudb/run.py +++ b/pudb/run.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import, division, print_function - - def main(): import sys diff --git a/pudb/settings.py b/pudb/settings.py index 81560047b346ec7d8c28436a94d9dfbd62c5610f..9a5076e81b12e5a7ea6906a0231193a1a65445cb 100644 --- a/pudb/settings.py +++ b/pudb/settings.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -28,7 +26,7 @@ THE SOFTWARE. import os import sys -from pudb.py3compat import ConfigParser +from configparser import ConfigParser from pudb.lowlevel import (lookup_module, get_breakpoint_invalid_reason, settings_log) @@ -53,7 +51,10 @@ def get_save_config_path(*resource): return None if not resource: resource = [XDG_CONF_RESOURCE] - resource = os.path.join(*resource) + + # no idea what pylint's problem is here + resource = os.path.join(*resource) # pylint: disable=no-value-for-parameter + assert not resource.startswith("/") path = os.path.join(XDG_CONFIG_HOME, resource) if not os.path.isdir(path): @@ -580,8 +581,8 @@ def load_breakpoints(): lines = [] for fname in file_names: try: - rc_file = open(fname, "rt") - except IOError: + rc_file = open(fname) + except OSError: pass else: lines.extend([line.strip() for line in rc_file.readlines()]) @@ -599,7 +600,7 @@ def save_breakpoints(bp_list): return histfile = open(get_breakpoints_file_name(), "w") - bp_list = set([(bp.file, bp.line, bp.cond) for bp in bp_list]) + bp_list = {(bp.file, bp.line, bp.cond) for bp in bp_list} for bp in bp_list: line = "b %s:%d" % (bp[0], bp[1]) if bp[2]: diff --git a/pudb/shell.py b/pudb/shell.py index 4dad912975adea36b5bffee2f95d7a94bd5f0bf3..852e66421cd974bb45a18b1f7928b1aad53d8353 100644 --- a/pudb/shell.py +++ b/pudb/shell.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - try: import bpython # noqa # Access a property to verify module exists in case @@ -94,7 +92,7 @@ def run_classic_shell(globals, locals, first_time=[True]): readline.clear_history() try: readline.read_history_file(hist_file) - except IOError: + except OSError: pass from code import InteractiveConsole @@ -168,7 +166,7 @@ def _update_ipython_ns(shell, globals, locals): try: shell.user_global_ns = globals except AttributeError: - class DummyMod(object): + class DummyMod: """A dummy module used for IPython's interactive namespace.""" pass @@ -235,7 +233,7 @@ def run_ipython_shell(globals, locals): def run_ipython_kernel(globals, locals): from IPython import embed_kernel - class DummyMod(object): + class DummyMod: pass user_module = DummyMod() diff --git a/pudb/source_view.py b/pudb/source_view.py index 05f89f7ab4037b1b2b1f873c1fdfe79ed1d4ae72..520173c736f36ad3da1a69fc7336ed8375bc3dde 100644 --- a/pudb/source_view.py +++ b/pudb/source_view.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -238,13 +236,13 @@ def format_source(debugger_ui, lines, breakpoints): } class UrwidFormatter(Formatter): - def __init__(subself, **options): # noqa: N805 + def __init__(subself, **options): # noqa: N805, E501 # pylint: disable=no-self-argument Formatter.__init__(subself, **options) subself.current_line = "" subself.current_attr = [] subself.lineno = 1 - def format(subself, tokensource, outfile): # noqa: N805 + def format(subself, tokensource, outfile): # noqa: N805, E501 # pylint: disable=no-self-argument def add_snippet(ttype, s): if not s: return @@ -310,14 +308,14 @@ def format_source(debugger_ui, lines, breakpoints): return result -class ParseState(object): +class ParseState: """States for the ArgumentParser class""" idle = 1 found_function = 2 found_open_paren = 3 -class ArgumentParser(object): +class ArgumentParser: """Parse source code tokens and identify function arguments. This parser implements a state machine which accepts diff --git a/pudb/theme.py b/pudb/theme.py index 5e8fd59ee0cd63c37e239c44c7c173fb5416f655..8f6bae48156389556d9d9ba0bf71a44bf9bcf066 100644 --- a/pudb/theme.py +++ b/pudb/theme.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -37,7 +35,6 @@ THEMES = [ "monokai-256" ] -from pudb.py3compat import execfile, raw_input import urwid @@ -1013,12 +1010,14 @@ def get_palette(may_use_fancy_formats, theme="classic"): } from os.path import expanduser, expandvars - execfile(expanduser(expandvars(theme)), symbols) + fname = expanduser(expandvars(theme)) + with open(fname) as inf: + exec(compile(inf.read(), fname, "exec"), symbols) except Exception: print("Error when importing theme:") from traceback import print_exc print_exc() - raw_input("Hit enter:") + input("Hit enter:") # Apply style inheritance for child, parent in inheritance_map: diff --git a/pudb/ui_tools.py b/pudb/ui_tools.py index f0a690c3a93bb614234093b59ec0db45c0892e98..4931ff413ce0b8081c98de9e5022cd45221f3fc7 100644 --- a/pudb/ui_tools.py +++ b/pudb/ui_tools.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, division, print_function import urwid from urwid.util import calc_width, calc_text_pos @@ -24,7 +23,7 @@ def encode_like_urwid(s): # Consistent with # https://github.com/urwid/urwid/blob/2cc54891965283faf9113da72202f5d405f90fa3/urwid/util.py#L126-L128 - s = s.replace(escape.SI+escape.SO, u"") # remove redundant shifts + s = s.replace(escape.SI+escape.SO, "") # remove redundant shifts s = s.encode(_target_encoding, "replace") return s @@ -215,7 +214,7 @@ class BreakpointFrame(urwid.FlowWidget): return key -class SearchController(object): +class SearchController: def __init__(self, ui): self.ui = ui self.highlight_line = None @@ -308,15 +307,6 @@ class SearchBox(urwid.Edit): urwid.Edit.__init__(self, [("label", "Search: ")], "") self.controller = controller - def restart_search(self): - from time import time - now = time() - - if self.search_start_time > 5: - self.set_edit_text("") - - self.search_time = now - def keypress(self, size, key): result = urwid.Edit.keypress(self, size, key) txt = self.get_edit_text() diff --git a/pudb/var_view.py b/pudb/var_view.py index 9294ec7788096c8e26923b37328afb6885568573..859dde25a33d09845d653a17ddead78cbd746205 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function - __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer @@ -28,6 +24,8 @@ THE SOFTWARE. """ +from abc import ABC, abstractmethod + # {{{ constants and imports import urwid @@ -43,9 +41,6 @@ try: except ImportError: HAVE_NUMPY = 0 -from pudb.py3compat import execfile, raw_input, xrange, \ - integer_types, string_types, text_type - ELLIPSIS = "…" from pudb.ui_tools import text_width @@ -55,9 +50,6 @@ from pudb.ui_tools import text_width # {{{ abstract base classes for containers -from abc import ABC - - class PudbCollection(ABC): @classmethod def __subclasshook__(cls, c): @@ -80,7 +72,7 @@ class PudbCollection(ABC): assert isinstance(collection, cls) try: for count, entry in enumerate(collection): - yield None, entry, "[{k:d}]".format(k=count) + yield None, entry, f"[{count:d}]" except (AttributeError, TypeError) as error: ui_log.error("Object {l!r} appears to be a collection, but does " "not behave like one: {m}".format( @@ -109,7 +101,7 @@ class PudbSequence(ABC): assert isinstance(sequence, cls) try: for count, entry in enumerate(sequence): - yield str(count), entry, "[{k:d}]".format(k=count) + yield str(count), entry, f"[{count:d}]" except (AttributeError, TypeError) as error: ui_log.error("Object {l!r} appears to be a sequence, but does " "not behave like one: {m}".format( @@ -167,7 +159,7 @@ CONTAINER_CLASSES = ( # {{{ data -class FrameVarInfo(object): +class FrameVarInfo: def __init__(self): self.id_path_to_iinfo = {} self.watches = [] @@ -181,7 +173,7 @@ class FrameVarInfo(object): id_path, InspectInfo()) -class InspectInfo(object): +class InspectInfo: def __init__(self): # Do not globalize: cyclic import from pudb.debugger import CONFIG @@ -195,12 +187,12 @@ class InspectInfo(object): self.wrap = CONFIG["wrap_variables"] -class WatchExpression(object): +class WatchExpression: def __init__(self, expression): self.expression = expression -class WatchEvalError(object): +class WatchEvalError: def __str__(self): return "" @@ -276,7 +268,7 @@ class VariableWidget(urwid.FlowWidget): return [firstline] fulllines, rest = divmod(text_width(alltext) - maxcol, maxcol - 2) restlines = [alltext[(maxcol - 2)*i + maxcol:(maxcol - 2)*i + 2*maxcol - 2] - for i in xrange(fulllines + bool(rest))] + for i in range(fulllines + bool(rest))] return [firstline] + [self.prefix + " " + i for i in restlines] def rows(self, size: Tuple[int], focus: bool = False) -> int: @@ -333,7 +325,7 @@ class VariableWidget(urwid.FlowWidget): fullcols, rem = divmod(totallen, maxcol) attr = [rle_subseg(_attr, i*maxcol, (i + 1)*maxcol) - for i in xrange(fullcols + bool(rem))] + for i in range(fullcols + bool(rem))] return make_canvas(text, attr, maxcol, apfx+"value") @@ -372,22 +364,9 @@ class VariableWidget(urwid.FlowWidget): # Ellipses to show text was cut off #encoding = urwid.util.detected_encoding - if False: # encoding[:3] == "UTF": - # Unicode is supported, use single character ellipsis - for i in xrange(len(text)): - if len(text[i]) > maxcol: - text[i] = (unicode(text[i][:maxcol-1]) # noqa: F821 - + ELLIPSIS + unicode(text[i][maxcol:])) # noqa: F821 - # XXX: This doesn't work. It just gives a ? - # Strangely, the following does work (it gives the … - # three characters from the right): - # - # text[i] = (unicode(text[i][:maxcol-3]) - # + unicode(u'…')) + unicode(text[i][maxcol-2:]) - else: - for i in xrange(len(text)): - if text_width(text[i]) > maxcol: - text[i] = text[i][:maxcol-3] + "..." + for i in range(len(text)): + if text_width(text[i]) > maxcol: + text[i] = text[i][:maxcol-3] + "..." return make_canvas(text, attr, maxcol, apfx+"value") @@ -402,15 +381,15 @@ custom_stringifier_dict = {} def type_stringifier(value): if HAVE_NUMPY and isinstance(value, numpy.ndarray): - return text_type("%s(%s) %s") % ( + return "%s(%s) %s" % ( type(value).__name__, value.dtype, value.shape) elif HAVE_NUMPY and isinstance(value, numpy.number): - return text_type("%s (%s)" % (value, value.dtype)) + return str(f"{value} ({value.dtype})") elif isinstance(value, STR_SAFE_TYPES): try: - return text_type(value) + return str(value) except Exception: message = "string safe type stringifier failed" ui_log.exception(message) @@ -426,13 +405,13 @@ def type_stringifier(value): ui_log.exception(message) result = "!! %s !!" % message - if isinstance(result, string_types): - return text_type(result) + if isinstance(result, str): + return str(result) elif type(value) in [set, frozenset, list, tuple, dict]: - return text_type("%s (%s)") % (type(value).__name__, len(value)) + return "%s (%s)" % (type(value).__name__, len(value)) - return text_type(type(value).__name__) + return str(type(value).__name__) def id_stringifier(obj): @@ -458,7 +437,11 @@ def get_stringifier(iinfo): try: if not custom_stringifier_dict: # Only execfile once from os.path import expanduser - execfile(expanduser(iinfo.display_type), custom_stringifier_dict) + custom_stringifier_fname = expanduser(iinfo.display_type) + with open(custom_stringifier_fname) as inf: + exec(compile(inf.read(), custom_stringifier_fname, "exec"), + custom_stringifier_dict, + custom_stringifier_dict) except Exception: ui_log.exception("Error when importing custom stringifier") return error_stringifier @@ -466,22 +449,22 @@ def get_stringifier(iinfo): if "pudb_stringifier" not in custom_stringifier_dict: print("%s does not contain a function named pudb_stringifier at " "the module level." % iinfo.display_type) - raw_input("Hit enter:") - return lambda value: text_type( + input("Hit enter:") + return lambda value: str( "ERROR: Invalid custom stringifier file: " "pudb_stringifer not defined.") else: return (lambda value: - text_type(custom_stringifier_dict["pudb_stringifier"](value))) + str(custom_stringifier_dict["pudb_stringifier"](value))) # {{{ tree walking -class ValueWalker: +class ValueWalker(ABC): BASIC_TYPES = [] BASIC_TYPES.append(type(None)) - BASIC_TYPES.extend(integer_types) - BASIC_TYPES.extend(string_types) + BASIC_TYPES.append(int) + BASIC_TYPES.append(str) BASIC_TYPES.extend((float, complex)) BASIC_TYPES = tuple(BASIC_TYPES) @@ -494,6 +477,10 @@ class ValueWalker: def __init__(self, frame_var_info): self.frame_var_info = frame_var_info + @abstractmethod + def add_item(self, parent, var_label, value_str, id_path, attr_prefix=None): + pass + def add_continuation_item(self, parent: VariableWidget, id_path: str, count: int, length: int) -> bool: """ @@ -537,14 +524,14 @@ class ValueWalker: if self.add_continuation_item(parent, id_path, count, length): return True - entry_id_path = "%s%s" % (id_path, id_path_ext) + entry_id_path = f"{id_path}{id_path_ext}" self.walk_value(parent, "[{}]".format(entry_label if entry_label else ""), entry, entry_id_path) if is_empty: self.add_item(parent, self.EMPTY_LABEL, None, - id_path="%s%s" % (id_path, self.EMPTY_LABEL)) + id_path=f"{id_path}{self.EMPTY_LABEL}") return True @@ -574,7 +561,7 @@ class ValueWalker: self.walk_value(parent, ".%s" % key, attr_value, - "%s.%s" % (id_path, key)) + f"{id_path}.{key}") def walk_value(self, parent, label, value, id_path=None, attr_prefix=None): if id_path is None: @@ -739,13 +726,14 @@ def make_var_view(frame_var_info, locals, globals): return result -class FrameVarInfoKeeper(object): +class FrameVarInfoKeeper: def __init__(self): self.frame_var_info = {} def get_frame_var_info(self, read_only, ssid=None): if ssid is None: - ssid = self.debugger.get_stack_situation_id() + # self.debugger set by subclass + ssid = self.debugger.get_stack_situation_id() # noqa: E501 # pylint: disable=no-member if read_only: return self.frame_var_info.get(ssid, FrameVarInfo()) else: diff --git a/test/test_lowlevel.py b/test/test_lowlevel.py index 4212aa13d73cabe25782a78b8a567a7928c73a39..c839d5318b8e7ae7b6626b96303237cf0c91de22 100644 --- a/test/test_lowlevel.py +++ b/test/test_lowlevel.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- from pudb.lowlevel import detect_encoding, decode_lines -from pudb.py3compat import PY3 def test_detect_encoding_nocookie(): - lines = [u"Test Проверка"] + lines = ["Test Проверка"] lines = [line.encode("utf-8") for line in lines] encoding, _ = detect_encoding(iter(lines)) assert encoding == "utf-8" @@ -12,9 +10,9 @@ def test_detect_encoding_nocookie(): def test_detect_encoding_cookie(): lines = [ - u"# coding=utf-8", - u"Test", - u"Проверка" + "# coding=utf-8", + "Test", + "Проверка" ] lines = [line.encode("utf-8") for line in lines] encoding, _ = detect_encoding(iter(lines)) @@ -23,16 +21,12 @@ def test_detect_encoding_cookie(): def test_decode_lines(): unicode_lines = [ - u"# coding=utf-8", - u"Test", - u"Проверка", + "# coding=utf-8", + "Test", + "Проверка", ] lines = [line.encode("utf-8") for line in unicode_lines] - if PY3: - assert unicode_lines == list(decode_lines(iter(lines))) - else: - assert [line.decode("utf-8") - for line in lines] == list(decode_lines(iter(lines))) + assert unicode_lines == list(decode_lines(iter(lines))) # {{{ remove common indentation @@ -83,22 +77,15 @@ def test_executable_lines(): main() """ - assert get_exec_lines(test_code) == set([1, 2, 3, 4, 6, 8]) + assert get_exec_lines(test_code) == {1, 2, 3, 4, 6, 8} test_code = "a = 3*5\n" + 333 * "\n" + "b = 15" - if PY3: - assert get_exec_lines(test_code) == set([ - 1, - 128, # bogus, - 255, # bogus, - 335 - ]) - else: - assert get_exec_lines(test_code) == set([ - 1, - 256, # bogus, - 335 - ]) + assert get_exec_lines(test_code) == { + 1, + 128, # bogus, + 255, # bogus, + 335 + } if __name__ == "__main__": diff --git a/test/test_make_canvas.py b/test/test_make_canvas.py index bd7e20785ae7c4da31f7dc074d1ad88169d84300..120eb05c6a9cd357ba4b515ad363bca046534085 100644 --- a/test/test_make_canvas.py +++ b/test/test_make_canvas.py @@ -1,10 +1,8 @@ -# - encoding: utf-8 - - from pudb.ui_tools import make_canvas def test_simple(): - text = u"aaaaaa" + text = "aaaaaa" canvas = make_canvas( txt=[text], attr=[[("var value", len(text))]], @@ -18,7 +16,7 @@ def test_simple(): def test_multiple(): canvas = make_canvas( - txt=[u"Return: None"], + txt=["Return: None"], attr=[[("return label", 8), ("return value", 4)]], maxcol=100 ) @@ -31,7 +29,7 @@ def test_multiple(): def test_boundary(): - text = u"aaaaaa" + text = "aaaaaa" canvas = make_canvas( txt=[text], attr=[[("var value", len(text))]], @@ -41,7 +39,7 @@ def test_boundary(): def test_byte_boundary(): - text = u"aaaaaaé" + text = "aaaaaaé" canvas = make_canvas( txt=[text], attr=[[("var value", len(text))]], @@ -51,7 +49,7 @@ def test_byte_boundary(): def test_wide_chars(): - text = u"data: '中文'" + text = "data: '中文'" canvas = make_canvas( txt=[text], attr=[[("var label", 6), ("var value", 4)]], @@ -59,7 +57,7 @@ def test_wide_chars(): ) assert list(canvas.content()) == [[ ("var label", None, b"data: "), - ("var value", None, u"'中文'".encode("utf-8")), + ("var value", None, "'中文'".encode()), (None, None, b" "*(47 - 12)), # 10 chars, 2 of which are double width ]] diff --git a/test/test_settings.py b/test/test_settings.py index f709b0dedd5c969d909177a66ee08e53365ca5a2..a29a9561730135a7c2bb20e5cc297fc2b12ffe21 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -1,8 +1,8 @@ import collections +import builtins import pytest # noqa: F401 -from pudb.py3compat import builtins from pudb.settings import load_breakpoints, save_breakpoints diff --git a/test/test_var_view.py b/test/test_var_view.py index e4271a5ed6a380f2510243b8716b18ce83ba454c..948d7ef5416fd375503473e3e51cc6aa98b475ab 100644 --- a/test/test_var_view.py +++ b/test/test_var_view.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- - import contextlib import itertools import string import unittest -from pudb.py3compat import text_type, integer_types from pudb.var_view import ( BasicValueWalker, FrameVarInfo, @@ -21,7 +18,7 @@ class A: pass -class A2(object): +class A2: pass @@ -36,8 +33,8 @@ def test_get_stringifier(): numpy_values = [np.float32(5), np.zeros(5)] for value in [ - A, A2, A(), A2(), u"lól".encode("utf8"), u"lól", - 1233123, [u"lól".encode("utf8"), u"lól"], + A, A2, A(), A2(), "lól".encode(), "lól", + 1233123, ["lól".encode(), "lól"], ] + numpy_values: for display_type in ["type", "repr", "str", "id"]: iinfo = InspectInfo() @@ -46,18 +43,18 @@ def test_get_stringifier(): strifier = get_stringifier(iinfo) s = strifier(value) - assert isinstance(s, text_type) + assert isinstance(s, str) class FrameVarInfoForTesting(FrameVarInfo): def __init__(self, paths_to_expand=None): - super(FrameVarInfoForTesting, self).__init__() + super().__init__() if paths_to_expand is None: paths_to_expand = set() self.paths_to_expand = paths_to_expand def get_inspect_info(self, id_path, read_only): - iinfo = super(FrameVarInfoForTesting, self).get_inspect_info( + iinfo = super().get_inspect_info( id_path, read_only) iinfo.access_level = "all" iinfo.display_type = "repr" @@ -68,7 +65,7 @@ class FrameVarInfoForTesting(FrameVarInfo): return iinfo -class Reasonable(object): +class Reasonable: def __init__(self): self.x = 42 @@ -103,14 +100,14 @@ def method_factory(method_name): # Classes without __iter__ are expected to raise IndexError in this # sort of case. Frustrating, I know. if (method_name == "__getitem__" - and args and isinstance(args[0], integer_types)): + and args and isinstance(args[0], int)): raise IndexError raise return method def generate_containerlike_class(): - methods = set([ + methods = { "__contains__", "__getitem__", "__iter__", @@ -122,14 +119,14 @@ def generate_containerlike_class(): "items", "keys", "values", - ]) + } # Deliberately starting from 0 for r in range(0, len(methods) + 1): for selected_methods in sorted( map(sorted, itertools.combinations(methods, r))): - class ContainerlikeClass(object): + class ContainerlikeClass: def __init__(self, iterable): self.__internal_dict__ = dict(iterable) @@ -311,9 +308,9 @@ class ValueWalkerTest(BaseValueWalkerTestCase): self.assert_walks_contents(value) def test_set(self): - self.assert_walks_contents(set([ + self.assert_walks_contents({ 42, "foo", None, False, (), ("a", "tuple") - ])) + }) self.assert_class_counts_equal({"collections": 1}) def test_frozenset(self):