From 7c910b4ebbdb25e1597fb792acabfb1d0f059a5e Mon Sep 17 00:00:00 2001 From: Aivar Annamaa Date: Fri, 24 Jul 2020 14:17:44 +0300 Subject: [PATCH] Fixed Pylint warnings --- .gitignore | 3 +- .pylintrc | 14 +++- thonny/__init__.py | 5 +- thonny/ast_utils.py | 6 +- thonny/backend.py | 9 ++- thonny/backend_launcher.py | 2 +- thonny/base_file_browser.py | 18 ++--- thonny/codeview.py | 4 +- thonny/common.py | 2 +- thonny/config_ui.py | 12 +-- thonny/{code.py => editors.py} | 12 +-- thonny/languages.py | 2 + thonny/misc_utils.py | 10 +-- thonny/plugins/ast_view.py | 4 +- thonny/plugins/autocomplete.py | 1 - thonny/plugins/backend_config_page.py | 3 + thonny/plugins/birdseye_frontend.py | 2 +- thonny/plugins/debugger.py | 21 +++++- thonny/plugins/esp/__init__.py | 5 +- thonny/plugins/files.py | 3 +- thonny/plugins/heap.py | 3 +- thonny/plugins/highlight_names.py | 2 +- thonny/plugins/locals_marker.py | 2 +- thonny/plugins/microbit/__init__.py | 4 +- thonny/plugins/micropython/__init__.py | 8 +- thonny/plugins/micropython/backend.py | 22 ++++-- .../plugins/micropython/bare_metal_backend.py | 10 +-- thonny/plugins/micropython/os_backend.py | 2 +- .../plugins/micropython/webrepl_connection.py | 2 +- thonny/plugins/misc_analyzers.py | 2 +- thonny/plugins/paren_matcher.py | 11 ++- thonny/plugins/pylint/__init__.py | 4 +- thonny/plugins/ssh/__init__.py | 46 ++---------- thonny/plugins/statement_boxes.py | 1 + thonny/plugins/stdlib_error_helpers.py | 2 +- thonny/plugins/system_shell/__init__.py | 2 +- .../system_shell/explain_environment.py | 4 +- thonny/running.py | 35 +++++---- thonny/running_config_page.py | 2 +- thonny/shell.py | 13 ++-- thonny/tktextext.py | 2 +- thonny/ui_utils.py | 75 ++++++++++++++----- thonny/workbench.py | 14 ++-- 43 files changed, 231 insertions(+), 175 deletions(-) rename thonny/{code.py => editors.py} (99%) diff --git a/.gitignore b/.gitignore index 43f5f0a56..fb8aeb049 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ .DS_Store __pycache__ dist +venv packaging/linux/build build resources_build @@ -47,4 +48,4 @@ packaging/mac/freezing.log thonny.egg-info temp_build_dir thonnyapp/thonnyapp.egg-info -thonnycontrib \ No newline at end of file +thonnycontrib diff --git a/.pylintrc b/.pylintrc index e4e2a44f4..082097fcd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -57,9 +57,13 @@ disable=locally-disabled, trailing-newlines, ungrouped-imports, wrong-import-order, + check-imports-order, unused-argument, no-self-use, no-else-return, + no-else-break, + no-else-continue, + no-else-raise, len-as-condition, bad-continuation, missing-docstring, @@ -98,6 +102,14 @@ disable=locally-disabled, simplifiable-if-statement, cyclic-import, # gives many false alarms unused-import, # typing imports seem to cause this + import-outside-toplevel, # it's OK + import-error, # some imports are done in a conditional path + unnecessary-pass, + subprocess-run-check, + assignment-from-none, # mypy is more precise about this + redefined-outer-name, # many false positives, trips on valid uses, mypy would be more precise + + @@ -201,7 +213,7 @@ dummy-variables-rgx=^_|^dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. -additional-builtins= +additional-builtins=_ # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. diff --git a/thonny/__init__.py b/thonny/__init__.py index c72c11e5d..9ff1d1b46 100644 --- a/thonny/__init__.py +++ b/thonny/__init__.py @@ -107,6 +107,7 @@ def get_ipc_file_path(): for name in ("LOGNAME", "USER", "LNAME", "USERNAME"): if name in os.environ: username = os.environ.get(name) + break else: username = os.path.basename(os.path.expanduser("~")) @@ -157,7 +158,7 @@ def launch(): _prepare_thonny_user_dir() if not _check_welcome(): - return + return 0 if _should_delegate(): try: @@ -231,7 +232,7 @@ def copy_contents(src_dir, dest_dir): def _should_delegate(): if not os.path.exists(get_ipc_file_path()): # no previous instance - return + return False from thonny.config import try_load_configuration diff --git a/thonny/ast_utils.py b/thonny/ast_utils.py index a3fbaac01..025d1dd2d 100644 --- a/thonny/ast_utils.py +++ b/thonny/ast_utils.py @@ -19,8 +19,8 @@ def extract_text_range(source, text_range): return "".join(lines) -def find_expression(node, text_range): - for node in ast.walk(node): +def find_expression(start_node, text_range): + for node in ast.walk(start_node): if ( isinstance(node, ast.expr) and node.lineno == text_range.lineno @@ -30,6 +30,8 @@ def find_expression(node, text_range): ): return node + return None + def parse_source(source: bytes, filename="", mode="exec", fallback_to_one_char=False): root = ast.parse(source, filename, mode) diff --git a/thonny/backend.py b/thonny/backend.py index 9f300d109..dbfb8aea5 100644 --- a/thonny/backend.py +++ b/thonny/backend.py @@ -23,8 +23,9 @@ from collections import namedtuple from importlib.machinery import PathFinder, SourceFileLoader -import __main__ # @UnresolvedImport import _ast + +import __main__ # @UnresolvedImport import thonny from thonny.common import path_startswith # TODO: try to get rid of this from thonny.common import ( @@ -373,7 +374,7 @@ def signal_handler(signal_, frame): raise KeyboardInterrupt("Execution interrupted") if os.name == "nt": - signal.signal(signal.SIGBREAK, signal_handler) # @UndefinedVariable + signal.signal(signal.SIGBREAK, signal_handler) # pylint: disable=no-member else: signal.signal(signal.SIGINT, signal_handler) @@ -547,8 +548,8 @@ def _cmd_get_active_distributions(self, cmd): "location": dist.location, "version": dist.version, } - for dist in pkg_resources.working_set - } # pylint: disable=not-an-iterable + for dist in pkg_resources.working_set # pylint: disable=not-an-iterable + } return InlineResponse( "get_active_distributions", diff --git a/thonny/backend_launcher.py b/thonny/backend_launcher.py index 380d6714e..be8a9c3fe 100644 --- a/thonny/backend_launcher.py +++ b/thonny/backend_launcher.py @@ -32,7 +32,7 @@ + "Choose another interpreter from Tools => Options => Interpreter", file=sys.stderr, ) - exit() + sys.exit() import logging import os.path diff --git a/thonny/base_file_browser.py b/thonny/base_file_browser.py index fb4b7541c..88c03771c 100644 --- a/thonny/base_file_browser.py +++ b/thonny/base_file_browser.py @@ -667,16 +667,19 @@ def create_new_file(self): name = askstring("File name", "Provide filename", initialvalue="") if not name: - return + return None path = self.join(parent_path, name) if name in self._cached_child_data[parent_path]: # TODO: ignore case in windows messagebox.showerror("Error", "The file '" + path + "' already exists") + return self.create_new_file() else: self.open_file(path) + return path + def delete(self): selection = self.get_selection_info(True) if not selection: @@ -811,13 +814,6 @@ def request_fs_info(self, path): def perform_delete(self, paths, description): # Deprecated. moving to trash should be used instead raise NotImplementedError() - """ - for path in sorted(paths, key=len, reverse=True): - if os.path.isdir(path): - shutil.rmtree(path) - else: - os.remove(path) - """ def perform_move_to_trash(self, paths, description): # TODO: do it with subprocess dialog @@ -949,6 +945,8 @@ def request_focus_into(self, path): else: self.request_new_focus(path) + return True + def request_new_focus(self, path): # Overridden in active browser self.focus_into(path) @@ -1143,9 +1141,9 @@ def on_remote(self, event=None): def on_return(self, event=None): if self.focus_get() == self.local_button: - return self.on_local(event) + self.on_local(event) elif self.focus_get() == self.remote_button: - return self.on_remote(event) + self.on_remote(event) def on_down(self, event=None): if self.focus_get() == self.local_button: diff --git a/thonny/codeview.py b/thonny/codeview.py index f0aa51c9e..0b3c8c787 100644 --- a/thonny/codeview.py +++ b/thonny/codeview.py @@ -236,9 +236,7 @@ def __init__(self, master, propose_remove_line_numbers=False, **text_frame_args) assert self._first_line_number is not None - self._syntax_theme_change_binding = get_workbench().bind( - "SyntaxThemeChanged", self._reload_theme_options, True - ) + get_workbench().bind("SyntaxThemeChanged", self._reload_theme_options, True) self._original_newlines = os.linesep self._reload_theme_options() self._gutter.bind("", self._toggle_breakpoint, True) diff --git a/thonny/common.py b/thonny/common.py index 7d4a174da..d45a0e05e 100644 --- a/thonny/common.py +++ b/thonny/common.py @@ -535,7 +535,7 @@ def get_windows_network_locations(): "size": None, "time": None, } - except: + except Exception: logging.getLogger("thonny").error( "Can't get target from %s", lnk_path, exc_info=True ) diff --git a/thonny/config_ui.py b/thonny/config_ui.py index 86c317cf4..a6d766904 100644 --- a/thonny/config_ui.py +++ b/thonny/config_ui.py @@ -9,7 +9,7 @@ class ConfigurationDialog(CommonDialog): last_shown_tab_index = 0 - def __init__(self, master, page_records): + def __init__(self, master, page_records_with_order): super().__init__(master) width = ems_to_pixels(53) height = ems_to_pixels(43) @@ -33,7 +33,9 @@ def __init__(self, master, page_records): self._cancel_button.grid(row=1, column=2, padx=(0, 11), pady=(0, 10)) self._page_records = [] - for key, title, page_class, order in sorted(page_records, key=lambda r: (r[3], r[0])): + for key, title, page_class, _ in sorted( + page_records_with_order, key=lambda r: (r[3], r[0]) + ): try: spacer = ttk.Frame(self) spacer.rowconfigure(0, weight=1) @@ -57,9 +59,9 @@ def select_page(self, key): self._notebook.select(tab) def _ok(self, event=None): - for key, title, page in self._page_records: + for _, title, page in self._page_records: try: - if page.apply() == False: + if not page.apply(): return except Exception: get_workbench().report_exception("Error when applying options in " + title) @@ -67,7 +69,7 @@ def _ok(self, event=None): self.destroy() def _cancel(self, event=None): - for key, title, page in self._page_records: + for _, title, page in self._page_records: try: page.cancel() except Exception: diff --git a/thonny/code.py b/thonny/editors.py similarity index 99% rename from thonny/code.py rename to thonny/editors.py index edf5d841c..a83897dbf 100644 --- a/thonny/code.py +++ b/thonny/editors.py @@ -4,10 +4,11 @@ import sys import tkinter as tk import traceback -from _tkinter import TclError from logging import exception from tkinter import messagebox, ttk +from _tkinter import TclError + from thonny import get_runner, get_workbench, ui_utils from thonny.base_file_browser import ask_backend_path, choose_node_for_file_operations from thonny.codeview import CodeView @@ -164,7 +165,6 @@ def _load_file(self, filename, keep_undo=False): "This file seems to have problems with encoding.\n\n" + "Make sure it is in UTF-8 or contains proper encoding hint.", ) - return False self.update_appearance() @@ -692,7 +692,7 @@ def _cmd_new_file(self): def _cmd_open_file(self): node = choose_node_for_file_operations(self.winfo_toplevel(), "Where to open from?") if not node: - return None + return if node == "local": initialdir = get_workbench().get_local_cwd() @@ -706,7 +706,7 @@ def _cmd_open_file(self): assert node == "remote" target_path = ask_backend_path(self.winfo_toplevel(), "open") if not target_path: - return None + return path = make_remote_path(target_path) @@ -979,11 +979,11 @@ def get_current_breakpoints(): def get_saved_current_script_filename(force=True): editor = get_workbench().get_editor_notebook().get_current_editor() if not editor: - return + return None filename = editor.get_filename(force) if not filename: - return + return None if editor.is_modified(): filename = editor.save_file() diff --git a/thonny/languages.py b/thonny/languages.py index a275cb9e4..d37847aa2 100644 --- a/thonny/languages.py +++ b/thonny/languages.py @@ -43,3 +43,5 @@ def get_language_code_by_name(name): for code in LANGUAGES_DICT: if LANGUAGES_DICT[code] == name: return code + + raise RuntimeError("Unknown language name '%s'" % name) diff --git a/thonny/misc_utils.py b/thonny/misc_utils.py index 418a6d875..92d7cea79 100644 --- a/thonny/misc_utils.py +++ b/thonny/misc_utils.py @@ -237,7 +237,7 @@ def parse_cmd_line(s): def levenshtein_distance(s1, s2): # https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python if len(s1) < len(s2): - return levenshtein_distance(s2, s1) + return levenshtein_distance(s2, s1) # pylint: disable=arguments-out-of-order # len(s1) >= len(s2) if len(s2) == 0: @@ -286,15 +286,15 @@ def levenshtein_damerau_distance(s1, s2, maxDistance): # match a, change e->i change i->e => aie # Damarau-Levenshtein has a cost of 1 # match a, transpose ei to ie => aie - transpositionRow = None - prevRow = None + transpositionRow = [] + prevRow = [] # build first leven matrix row # The first row represents transformation from an empty string # to the shorter string making it static [0-n] # since this row is static we can set it as # curRow and start computation at the second row or index 1 - curRow = [x for x in range(0, l1 + 1)] + curRow = list(range(0, l1 + 1)) # use second length to loop through all the rows being built # we start at row one @@ -384,7 +384,7 @@ def time_left(self): def copy_to_clipboard(data): if running_on_windows(): - return _copy_to_windows_clipboard(data) + _copy_to_windows_clipboard(data) elif running_on_mac_os(): command = ["pbcopy"] else: diff --git a/thonny/plugins/ast_view.py b/thonny/plugins/ast_view.py index d229650ab..c7cf2f6ae 100644 --- a/thonny/plugins/ast_view.py +++ b/thonny/plugins/ast_view.py @@ -78,7 +78,7 @@ def _update(self, event): def _format(key, node, parent_id): if isinstance(node, ast.AST): - fields = [(key, val) for key, val in ast.iter_fields(node)] + fields = list(ast.iter_fields(node)) value_label = node.__class__.__name__ @@ -164,7 +164,7 @@ def _find_closest_containing_node(tree, text_range): def pretty(node, key="/", level=0): """Used for exporting ASTView content""" if isinstance(node, ast.AST): - fields = [(key, val) for key, val in ast.iter_fields(node)] + fields = list(ast.iter_fields(node)) value_label = node.__class__.__name__ if isinstance(node, ast.Call): # Try to make 3.4 AST-s more similar to 3.5 diff --git a/thonny/plugins/autocomplete.py b/thonny/plugins/autocomplete.py index b6f2d0810..a04d83f39 100644 --- a/thonny/plugins/autocomplete.py +++ b/thonny/plugins/autocomplete.py @@ -6,7 +6,6 @@ from thonny.common import InlineCommand from thonny.shell import ShellText - # TODO: adjust the window position in cases where it's too close to bottom or right edge - but make sure the current line is shown """Completions get computed on the backend, therefore getting the completions is asynchronous. diff --git a/thonny/plugins/backend_config_page.py b/thonny/plugins/backend_config_page.py index 5c4f4d334..e14d985cc 100644 --- a/thonny/plugins/backend_config_page.py +++ b/thonny/plugins/backend_config_page.py @@ -10,6 +10,9 @@ class BackendDetailsConfigPage(ConfigurationPage): def should_restart(self): raise NotImplementedError() + def _on_change(self): + pass + def _add_text_field(self, label_text, variable_name, row, show=None): entry_label = ttk.Label(self, text=label_text) entry_label.grid(row=row, column=0, sticky="w") diff --git a/thonny/plugins/birdseye_frontend.py b/thonny/plugins/birdseye_frontend.py index 0289908a5..e557c6a65 100644 --- a/thonny/plugins/birdseye_frontend.py +++ b/thonny/plugins/birdseye_frontend.py @@ -39,7 +39,7 @@ def close_server(): if _server_process is not None: try: _server_process.kill() - except: + except Exception: pass diff --git a/thonny/plugins/debugger.py b/thonny/plugins/debugger.py index 6e50c5229..17dfec24f 100644 --- a/thonny/plugins/debugger.py +++ b/thonny/plugins/debugger.py @@ -8,12 +8,22 @@ import logging import os.path import tkinter as tk -from _tkinter import TclError from tkinter import ttk from tkinter.messagebox import showinfo from typing import List, Union # @UnusedImport -from thonny import ast_utils, code, get_runner, get_workbench, memory, misc_utils, running, ui_utils +from _tkinter import TclError + +from thonny import ( + ast_utils, + editors, + get_runner, + get_workbench, + memory, + misc_utils, + running, + ui_utils, +) from thonny.codeview import CodeView, SyntaxText, get_syntax_options_for_tag from thonny.common import DebuggerCommand, InlineCommand from thonny.memory import VariablesFrame @@ -63,11 +73,11 @@ def get_run_to_cursor_breakpoint(self): return None def get_effective_breakpoints(self, command): - result = code.get_current_breakpoints() + result = editors.get_current_breakpoints() if command == "run_to_cursor": bp = self.get_run_to_cursor_breakpoint() - if bp != None: + if bp is not None: filename, lineno = bp result.setdefault(filename, set()) result[filename].add(lineno) @@ -801,6 +811,9 @@ def _update_size(self): self.text["height"] = len(lines) self.text["width"] = max(map(len, lines)) + def _set_position_make_visible(self, rel_x, rel_y): + raise NotImplementedError() + class PlacedExpressionBox(BaseExpressionBox, tk.Text): def __init__(self, codeview): diff --git a/thonny/plugins/esp/__init__.py b/thonny/plugins/esp/__init__.py index f4394c967..5cbc17c02 100644 --- a/thonny/plugins/esp/__init__.py +++ b/thonny/plugins/esp/__init__.py @@ -113,7 +113,8 @@ def __init__(self, master, chip, start_address, initial_port_desc=""): + "Install it via 'Tools => Manage plug-ins'\n" + "or using your OP-system package manager.", ) - return self._close() + self._close() + return self.main_frame.columnconfigure(2, weight=1) @@ -147,7 +148,7 @@ def __init__(self, master, chip, start_address, initial_port_desc=""): browse_button.grid(row=2, column=3, sticky="we", padx=(0, epadx), pady=(ipady, 0)) # FLASH_MODE - self._flashmode = tkinter.StringVar(None, "keep") + self._flashmode = tk.StringVar(None, "keep") flashmode_group = LabelFrame(self.main_frame, text="Flash mode", padx=10, pady=10) flashmode_group.grid( row=4, column=1, columnspan=2, sticky="w", padx=(epadx, 0), pady=(ipady * 2, 0) diff --git a/thonny/plugins/files.py b/thonny/plugins/files.py index daa05b2d3..e51f8d5d1 100644 --- a/thonny/plugins/files.py +++ b/thonny/plugins/files.py @@ -136,7 +136,8 @@ def request_focus_into(self, path): if path == "": if running_on_windows(): # list of drives, can't cd - return self.focus_into(path) + self.focus_into(path) + return else: path = "/" diff --git a/thonny/plugins/heap.py b/thonny/plugins/heap.py index e45e036f3..0e9b33c7b 100644 --- a/thonny/plugins/heap.py +++ b/thonny/plugins/heap.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- import tkinter as tk -from _tkinter import TclError from tkinter import ttk +from _tkinter import TclError + from thonny import get_runner, get_workbench from thonny.common import InlineCommand from thonny.memory import MAX_REPR_LENGTH_IN_GRID, MemoryFrame, format_object_id, parse_object_id diff --git a/thonny/plugins/highlight_names.py b/thonny/plugins/highlight_names.py index 5becede07..de16382b1 100644 --- a/thonny/plugins/highlight_names.py +++ b/thonny/plugins/highlight_names.py @@ -325,7 +325,7 @@ def update_highlighting(event): global tree if not tree: # using lazy importing to speed up Thonny loading - from parso.python import tree + from parso.python import tree # pylint: disable=redefined-outer-name assert isinstance(event.widget, tk.Text) text = event.widget diff --git a/thonny/plugins/locals_marker.py b/thonny/plugins/locals_marker.py index a143b8bd4..758c7e5b6 100644 --- a/thonny/plugins/locals_marker.py +++ b/thonny/plugins/locals_marker.py @@ -11,8 +11,8 @@ def __init__(self, text): self._update_scheduled = False def get_positions(self): - from parso.python import tree from jedi import parser_utils + from parso.python import tree locs = [] diff --git a/thonny/plugins/microbit/__init__.py b/thonny/plugins/microbit/__init__.py index 68cfa07aa..7d491c25a 100644 --- a/thonny/plugins/microbit/__init__.py +++ b/thonny/plugins/microbit/__init__.py @@ -159,11 +159,11 @@ def _process_release_info(self): and 400000 < asset["size"] < 800000 ] if len(candidates) == 0: - return self._close_with_error( + self._close_with_error( "Could not find the right hex file from the release info (%s)" % LATEST_RELEASE_URL ) elif len(candidates) > 1: - return self._close_with_error( + self._close_with_error( "Found several possible hex files from the release info (%s)" % LATEST_RELEASE_URL ) else: diff --git a/thonny/plugins/micropython/__init__.py b/thonny/plugins/micropython/__init__.py index ae0c94309..745375548 100644 --- a/thonny/plugins/micropython/__init__.py +++ b/thonny/plugins/micropython/__init__.py @@ -332,12 +332,12 @@ def get_selected_port_name(self): def is_modified(self): return ( - self._port_desc_variable.modified # pylint: disable=no-member + self._port_desc_variable.modified or self.webrepl_selected() - and self._webrepl_password_var.modified # pylint: disable=no-member + and self._webrepl_password_var.modified or self.webrepl_selected() and self._webrepl_url_var.modified - ) # pylint: disable=no-member + ) def webrepl_selected(self): return self.get_selected_port_name() == "webrepl" @@ -373,7 +373,7 @@ def _on_change_port(self, *args): if self._webrepl_frame and self._webrepl_frame.winfo_ismapped(): self._webrepl_frame.grid_forget() - def _get_usb_driver_url(self): + def _get_usb_driver_url(self) -> Optional[str]: return None def _has_flashing_dialog(self): diff --git a/thonny/plugins/micropython/backend.py b/thonny/plugins/micropython/backend.py index 1b9d7d2dc..d443b76cc 100644 --- a/thonny/plugins/micropython/backend.py +++ b/thonny/plugins/micropython/backend.py @@ -39,11 +39,12 @@ import os import re import sys +import textwrap import threading import time import traceback from queue import Empty, Queue -from textwrap import dedent, indent +from textwrap import dedent from threading import Lock from thonny.common import ( @@ -95,7 +96,7 @@ def debug(msg): return - print(msg, file=sys.stderr) + # print(msg, file=sys.stderr) class MicroPythonBackend: @@ -192,7 +193,7 @@ def listdir(cls, x): mgmt_marker=MGMT_VALUE_START.decode(ENCODING), ) + "\n" - + indent(self._get_custom_helpers(), " ") + + textwrap.indent(self._get_custom_helpers(), " ") ) def _get_custom_helpers(self): @@ -312,7 +313,7 @@ def handle_command(self, cmd): self._local_cwd = cmd["local_cwd"] def create_error_response(**kw): - if not "error" in kw: + if "error" not in kw: kw["error"] = traceback.format_exc() if isinstance(cmd, ToplevelCommand): @@ -415,6 +416,7 @@ def consume_output(data, stream_name): ] else: self._execute_with_consumer(script, self._send_output) + return b"", b"" def _execute_with_consumer(self, script, output_consumer): """Ensures prompt and submits the script. @@ -567,7 +569,7 @@ def _cmd_write_file(self, cmd): def _cmd_delete(self, cmd): assert cmd.paths - self._delete_sorted_paths(sorted(cmd.paths, key=lambda x: len(x), reverse=True)) + self._delete_sorted_paths(sorted(cmd.paths, key=len, reverse=True)) def _delete_sorted_paths(self, paths): self._execute_without_output( @@ -638,6 +640,8 @@ def notify(current_file_progress): assert written_bytes is None or written_bytes == item["size"] completed_files_size += item["size"] + return {} + def _cmd_upload(self, cmd): completed_files_size = 0 # TODO: also deal with empty directories @@ -687,6 +691,8 @@ def notify(current_file_progress): completed_files_size += item["size"] + return {} + def _cmd_mkdir(self, cmd): assert self._supports_directories() assert cmd.path.startswith("/") @@ -795,8 +801,8 @@ def _cmd_shell_autocomplete(self, cmd): if "." in prefix: obj, prefix = prefix.rsplit(".", 1) names = self._evaluate( - "dir({}) if '{}' in locals() or '{}' in globals() else []".format( - obj, obj, obj + "dir({obj}) if '{obj}' in locals() or '{obj}' in globals() else []".format( + obj=obj ) ) else: @@ -890,7 +896,7 @@ def _dump_object_stubs(self, fp, object_expr, indent): continue print("DUMPING", indent, object_expr, name) - self._send_text_to_shell(" * " + name + " : " + typ, "stdout") + print(" * " + name + " : " + typ) if typ in ["", ""]: fp.write(indent + "def " + name + "():\n") diff --git a/thonny/plugins/micropython/bare_metal_backend.py b/thonny/plugins/micropython/bare_metal_backend.py index 74a247e21..f00fa3c1b 100644 --- a/thonny/plugins/micropython/bare_metal_backend.py +++ b/thonny/plugins/micropython/bare_metal_backend.py @@ -101,7 +101,7 @@ def debug(msg): return - print(msg, file=sys.stderr) + # print(msg, file=sys.stderr) class MicroPythonBareMetalBackend(MicroPythonBackend): @@ -261,7 +261,7 @@ def _submit_input(self, cdata: str) -> None: bdata = cdata.encode(ENCODING) with self._writing_lock: - self.write(bdata) + self._write(bdata) # Try to consume the echo try: @@ -644,10 +644,6 @@ def _cmd_get_dirs_child_data(self, cmd): else: return super()._cmd_get_dirs_child_data(cmd) - data = self._get_dirs_child_data_generic(cmd["paths"]) - dir_separator = "/" - return {"node_id": cmd["node_id"], "dir_separator": dir_separator, "data": data} - def _internal_path_to_mounted_path(self, path): mount_path = self._get_fs_mount() if mount_path is None: @@ -991,7 +987,7 @@ def _is_connected(self): import argparse parser = argparse.ArgumentParser() - parser.add_argument("--clean", type=lambda s: True if s == "True" else False) + parser.add_argument("--clean", type=lambda s: s == "True") parser.add_argument("--port", type=str) parser.add_argument("--url", type=str) parser.add_argument("--password", type=str) diff --git a/thonny/plugins/micropython/os_backend.py b/thonny/plugins/micropython/os_backend.py index e015dc148..72c57b700 100644 --- a/thonny/plugins/micropython/os_backend.py +++ b/thonny/plugins/micropython/os_backend.py @@ -216,7 +216,7 @@ def _write(self, data): def _cmd_Run(self, cmd): self._connection.close() self._report_time("befconn") - args = cmd.args[:] + args = cmd.args if cmd.source and args[0] == "-c": if len(args) > 1: self._send_error_message( diff --git a/thonny/plugins/micropython/webrepl_connection.py b/thonny/plugins/micropython/webrepl_connection.py index fbc99d02c..23ba8f149 100644 --- a/thonny/plugins/micropython/webrepl_connection.py +++ b/thonny/plugins/micropython/webrepl_connection.py @@ -88,7 +88,7 @@ async def _ws_keep_reading(self): break self.num_bytes_received += len(data) - self._make_output_available.put(data, block=False) + self._make_output_available(data, block=False) async def _ws_keep_writing(self): import asyncio diff --git a/thonny/plugins/misc_analyzers.py b/thonny/plugins/misc_analyzers.py index 152bb48f0..445f85c70 100644 --- a/thonny/plugins/misc_analyzers.py +++ b/thonny/plugins/misc_analyzers.py @@ -232,7 +232,7 @@ def _get_warnings(self, main_file_path): # TODO: current dir may be different main_file_dir = os.path.dirname(main_file_path) if not os.path.isdir(main_file_dir): - return [] + return library_modules = known_stdlib_modules | self._get_3rd_party_modules() diff --git a/thonny/plugins/paren_matcher.py b/thonny/plugins/paren_matcher.py index 032ed72c1..2af80071f 100644 --- a/thonny/plugins/paren_matcher.py +++ b/thonny/plugins/paren_matcher.py @@ -1,6 +1,6 @@ import io import time -import token +import token as token_module from thonny import get_workbench from thonny.codeview import CodeViewText @@ -8,7 +8,14 @@ _OPENERS = {")": "(", "]": "[", "}": "{"} -TOKTYPES = {token.LPAR, token.RPAR, token.LBRACE, token.RBRACE, token.LSQB, token.RSQB} +TOKTYPES = { + token_module.LPAR, + token_module.RPAR, + token_module.LBRACE, + token_module.RBRACE, + token_module.LSQB, + token_module.RSQB, +} BLANK_LINE_REGEX_STR = r"\n\s*\r?\n" BLANK_LINE_REGEX_STR = r"^\s*$" diff --git a/thonny/plugins/pylint/__init__.py b/thonny/plugins/pylint/__init__.py index 29e3c1f5c..af3703f46 100644 --- a/thonny/plugins/pylint/__init__.py +++ b/thonny/plugins/pylint/__init__.py @@ -14,7 +14,9 @@ def is_enabled(self): def start_analysis(self, main_file_path, imported_file_paths): relevant_symbols = { - checks_by_id[key]["msg_sym"] for key in checks_by_id if checks_by_id[key]["usage"] == "warning" + checks_by_id[key]["msg_sym"] + for key in checks_by_id + if checks_by_id[key]["usage"] == "warning" } if "bad-python3-import" in relevant_symbols: diff --git a/thonny/plugins/ssh/__init__.py b/thonny/plugins/ssh/__init__.py index ee7dd7b59..16a26c41d 100644 --- a/thonny/plugins/ssh/__init__.py +++ b/thonny/plugins/ssh/__init__.py @@ -1,49 +1,15 @@ import collections -import io -import logging import os -import platform -import queue -import re import shlex -import subprocess -import sys -import textwrap -import threading -import time -import traceback from logging import debug -from queue import Queue -from textwrap import dedent from threading import Thread -from time import sleep -from tkinter import messagebox, ttk from typing import Optional import thonny -from thonny import common, get_runner, get_shell, get_workbench, running, ui_utils -from thonny.common import ( - BackendEvent, - CommandToBackend, - EOFCommand, - InlineCommand, - InlineResponse, - InterruptCommand, - MessageFromBackend, - ToplevelCommand, - ToplevelResponse, -) -from thonny.config_ui import ConfigurationPage -from thonny.misc_utils import TimeHelper, find_volumes_by_name -from thonny.plugins.backend_config_page import BackendDetailsConfigPage, BaseSshProxyConfigPage -from thonny.running import BackendProxy, SubprocessProxy -from thonny.ui_utils import ( - SubprocessDialog, - askopenfilename, - create_string_var, - create_url_label, - show_dialog, -) +from thonny import get_runner, get_shell, get_workbench +from thonny.common import CommandToBackend, InlineCommand, ToplevelCommand +from thonny.plugins.backend_config_page import BaseSshProxyConfigPage +from thonny.running import SubprocessProxy class SshProxy(SubprocessProxy): @@ -82,7 +48,7 @@ def _process_thread_commands(self): elif cmd.name == "connect": self._connect() elif cmd.name == "launch": - self._execute_backend + self._execute_backend() def _connect(self): pass @@ -138,7 +104,7 @@ def _start_background_process(self, clean=None, extra_args=[]): # Don't remain waiting self._starting = True - self._thread_commands.append(InlineCommand("launch"), cmd_line=cmd_line_str) + self._thread_commands.append(InlineCommand("launch", cmd_line=cmd_line_str)) def _connect_in_background(self, cmd_line_str): try: diff --git a/thonny/plugins/statement_boxes.py b/thonny/plugins/statement_boxes.py index 5ba7135c5..807c220d1 100644 --- a/thonny/plugins/statement_boxes.py +++ b/thonny/plugins/statement_boxes.py @@ -126,6 +126,7 @@ def predicate( def print_tree(node, level=0): from parso.python import tree as python_tree + indent = " " * level # if (isinstance(node, python_tree.PythonNode) and node.type == "sim" if node.type in ("simple_stmt",) or isinstance(node, python_tree.Flow): diff --git a/thonny/plugins/stdlib_error_helpers.py b/thonny/plugins/stdlib_error_helpers.py index ab85d2e15..0599f87ff 100644 --- a/thonny/plugins/stdlib_error_helpers.py +++ b/thonny/plugins/stdlib_error_helpers.py @@ -500,7 +500,7 @@ def _sug_wrong_attribute_instead_of_len(self): elif self.type_name == "dict": goal = "number of entries" else: - return + return None return Suggestion( "wrong-attribute-instead-of-len", diff --git a/thonny/plugins/system_shell/__init__.py b/thonny/plugins/system_shell/__init__.py index 03f10ee15..c369113b1 100644 --- a/thonny/plugins/system_shell/__init__.py +++ b/thonny/plugins/system_shell/__init__.py @@ -4,8 +4,8 @@ import sys from thonny import get_runner, get_workbench, terminal -from thonny.code import get_saved_current_script_filename from thonny.common import get_augmented_system_path, get_exe_dirs +from thonny.editors import get_saved_current_script_filename from thonny.running import get_environment_overrides_for_python_subprocess diff --git a/thonny/plugins/system_shell/explain_environment.py b/thonny/plugins/system_shell/explain_environment.py index 408cbdd8d..7ef471498 100755 --- a/thonny/plugins/system_shell/explain_environment.py +++ b/thonny/plugins/system_shell/explain_environment.py @@ -27,7 +27,7 @@ def equivalent_realpath(p): if os.path.dirname(link) == pdir: return link return p - except: + except Exception: return p else: return os.path.realpath(p) @@ -84,7 +84,7 @@ def can_use_ansi_codes(): ver = platform.win32_ver() try: return int(ver[0]) >= 10 - except: + except Exception: warnings.warn("Can't determine Windows version %s" % (ver,)) return False else: diff --git a/thonny/running.py b/thonny/running.py index 6129183ee..fd23c221b 100644 --- a/thonny/running.py +++ b/thonny/running.py @@ -26,7 +26,6 @@ from tkinter import messagebox, ttk from thonny import THONNY_USER_DIR, common, get_runner, get_shell, get_workbench, ui_utils -from thonny.code import get_current_breakpoints, get_saved_current_script_filename, is_remote_path from thonny.common import ( BackendEvent, CommandToBackend, @@ -45,6 +44,11 @@ serialize_message, update_system_path, ) +from thonny.editors import ( + get_current_breakpoints, + get_saved_current_script_filename, + is_remote_path, +) from thonny.misc_utils import construct_cmd_line, running_on_mac_os, running_on_windows from thonny.terminal import run_in_terminal from thonny.ui_utils import CommonDialogEx, select_sequence, show_dialog @@ -220,13 +224,13 @@ def get_sys_path(self) -> List[str]: def send_command(self, cmd: CommandToBackend) -> None: if self._proxy is None: - return + return None if self._publishing_events: # allow all event handlers to complete before sending the commands # issued by first event handlers self._postpone_command(cmd) - return + return None # First sanity check if ( @@ -240,7 +244,7 @@ def send_command(self, cmd: CommandToBackend) -> None: logging.warning( "RUNNER: Command %s was attempted at state %s" % (cmd, self.get_state()) ) - return + return None # Attach extra info if "debug" in cmd.name.lower(): @@ -256,10 +260,10 @@ def send_command(self, cmd: CommandToBackend) -> None: response = self._proxy.send_command(cmd) if response == "discard": - return + return None elif response == "postpone": self._postpone_command(cmd) - return + return None else: assert response is None get_workbench().event_generate("CommandAccepted", command=cmd) @@ -274,6 +278,8 @@ def send_command(self, cmd: CommandToBackend) -> None: dlg = BlockingDialog(get_workbench(), cmd) show_dialog(dlg) return dlg.response + else: + return None def _postpone_command(self, cmd: CommandToBackend) -> None: # in case of InlineCommands, discard older same type command @@ -407,7 +413,7 @@ def _cmd_interrupt(self) -> None: def _cmd_interrupt_with_shortcut(self, event=None): if not self._cmd_interrupt_enabled(): - return + return None if not running_on_mac_os(): # on Mac Ctrl+C is not used for Copy. # Disable Ctrl+C interrupt in editor and shell, when some text is selected @@ -416,7 +422,7 @@ def _cmd_interrupt_with_shortcut(self, event=None): if isinstance(widget, tk.Text): if len(widget.tag_ranges("sel")) > 0: # this test is reliable, unlike selection_get below - return + return None elif isinstance(widget, (tk.Listbox, ttk.Entry, tk.Entry, tk.Spinbox)): try: selection = widget.selection_get() @@ -428,7 +434,7 @@ def _cmd_interrupt_with_shortcut(self, event=None): # ie. there may be no selection in Thonny actually. # In other words, Ctrl+C interrupt may be dropped without reason # when given inside the widgets listed above. - return + return None except Exception: # widget either doesn't have selection_get or it # gave error (can happen without selection on Ubuntu) @@ -712,7 +718,7 @@ def fetch_next_message(self): """Read next message from the queue or None if queue is empty""" raise NotImplementedError() - def run_script_in_terminal(self, script_path, interactive, keep_open): + def run_script_in_terminal(self, script_path, args, interactive, keep_open): raise NotImplementedError() def get_sys_path(self): @@ -780,7 +786,7 @@ def __init__(self, clean: bool, executable: str) -> None: self._usersitepackages = None self._gui_update_loop_id = None self._in_venv = None - self._cwd = self._get_initial_cwd() + self._cwd = self._get_initial_cwd() # pylint: disable=assignment-from-none self._start_background_process(clean=clean) def _get_initial_cwd(self): @@ -864,7 +870,7 @@ def send_command(self, cmd: CommandToBackend) -> Optional[str]: """Send the command to backend. Return None, 'discard' or 'postpone'""" method_name = "_cmd_" + cmd.name if hasattr(self, method_name): - return getattr(self, method_name)(cmd) + getattr(self, method_name)(cmd) if isinstance(cmd, ToplevelCommand) and cmd.name[0].isupper(): self._clear_environment() @@ -897,7 +903,7 @@ def interrupt(self): if self._proc is not None and self._proc.poll() is None: if running_on_windows(): try: - os.kill(self._proc.pid, signal.CTRL_BREAK_EVENT) # @UndefinedVariable + os.kill(self._proc.pid, signal.CTRL_BREAK_EVENT) # pylint: disable=no-member except Exception: logging.exception("Could not interrupt backend process") else: @@ -1220,8 +1226,7 @@ def _create_private_venv(self, path, description, clear=False, upgrade=False): args.append("--upgrade") try: - # pylint: disable=unused-variable - import ensurepip # @UnusedImport + import ensurepip except ImportError: args.append("--without-pip") diff --git a/thonny/running_config_page.py b/thonny/running_config_page.py index 6ae52ecf8..143d17cf8 100644 --- a/thonny/running_config_page.py +++ b/thonny/running_config_page.py @@ -246,7 +246,7 @@ def _get_interpreters_from_windows_registry(self): return result def should_restart(self): - return self._configuration_variable.modified # pylint: disable=no-member + return self._configuration_variable.modified def apply(self): if not self.should_restart(): diff --git a/thonny/shell.py b/thonny/shell.py index 779ed760d..8da77a154 100644 --- a/thonny/shell.py +++ b/thonny/shell.py @@ -5,9 +5,10 @@ import re import tkinter as tk import traceback -from _tkinter import TclError from tkinter import ttk +from _tkinter import TclError + from thonny import get_runner, get_workbench, memory, roughparse, running, ui_utils from thonny.codeview import PythonText, get_syntax_options_for_tag from thonny.common import ( @@ -602,7 +603,7 @@ def _apply_io_event(self, data, stream_name, extra_tags=set()): self._io_cursor_offset = 0 else: # offset becomes closer to 0 - self._io_cursor_offset + overwrite_len + self._io_cursor_offset += overwrite_len elif self._io_cursor_offset > 0: # insert spaces before actual data @@ -679,14 +680,14 @@ def _show_squeezed_text(self, button): def _change_io_cursor_offset_csi(self, marker): ints = re.findall(INT_REGEX, marker) if len(ints) != 1: - logging.warn("bad CSI cursor positioning: %s", marker) + logging.warning("bad CSI cursor positioning: %s", marker) # do nothing return try: delta = int(ints[0]) except ValueError: - logging.warn("bad CSI cursor positioning: %s", marker) + logging.warning("bad CSI cursor positioning: %s", marker) return if marker.endswith("D"): @@ -1889,8 +1890,8 @@ def update_range(self, segments_by_color, clean): if not segments_by_color: return - range_start = 9999999999 - range_end = -9999999999 + range_start = 9_999_999_999 + range_end = -9_999_999_999 # if new block is using 3/4 of the width, # then don't consider old block's values anymore diff --git a/thonny/tktextext.py b/thonny/tktextext.py index b1e1666c9..f7e13c5d7 100644 --- a/thonny/tktextext.py +++ b/thonny/tktextext.py @@ -922,7 +922,7 @@ def __init__( self._gutter.configure(height=text_options["height"]) self._gutter_is_gridded = False - self._gutter.bind("", self.on_gutter_double_click, True), + self._gutter.bind("", self.on_gutter_double_click, True) self._gutter.bind("", self.on_gutter_click, True) self._gutter.bind("", self.on_gutter_click, True) self._gutter.bind("", self.on_gutter_motion, True) diff --git a/thonny/ui_utils.py b/thonny/ui_utils.py index 2bb4580b4..783cb62d2 100644 --- a/thonny/ui_utils.py +++ b/thonny/ui_utils.py @@ -13,10 +13,11 @@ import tkinter as tk import tkinter.font import traceback -from _tkinter import TclError from tkinter import filedialog, messagebox, ttk from typing import Callable, List, Optional, Tuple, Union # @UnusedImport +from _tkinter import TclError + from thonny import get_workbench, misc_utils, tktextext from thonny.common import TextRange from thonny.languages import get_button_padding @@ -1253,25 +1254,63 @@ def get_widget_offset_from_toplevel(widget): return x, y -def create_string_var(value, modification_listener=None): +class EnhancedVar(tk.Variable): + def __init__(self, master=None, value=None, name=None, modification_listener=None): + if master is not None and not isinstance(master, (tk.Widget, tk.Wm)): + raise TypeError("First positional argument 'master' must be None, Widget or Wm") + + super().__init__(master=master, value=value, name=name) + self.modified = False + self.modification_listener = modification_listener + self.trace_add("write", self._on_write) + + def _on_write(self, *args): + self.modified = True + if self.modification_listener: + try: + self.modification_listener() + except Exception: + # Otherwise whole process will be brought down + # because for some reason Tk tries to call non-existing method + # on variable + get_workbench().report_exception() + + +class EnhancedStringVar(EnhancedVar, tk.StringVar): + pass + + +class EnhancedIntVar(EnhancedVar, tk.IntVar): + pass + + +class EnhancedBooleanVar(EnhancedVar, tk.BooleanVar): + pass + + +class EnhancedDoubleVar(EnhancedVar, tk.DoubleVar): + pass + + +def create_string_var(value, modification_listener=None) -> EnhancedStringVar: """Creates a tk.StringVar with "modified" attribute showing whether the variable has been modified after creation""" - return _create_var(tk.StringVar, value, modification_listener) + return EnhancedStringVar(tk.StringVar, value, modification_listener) -def create_int_var(value, modification_listener=None): +def create_int_var(value, modification_listener=None) -> EnhancedIntVar: """See create_string_var""" - return _create_var(tk.IntVar, value, modification_listener) + return EnhancedIntVar(tk.IntVar, value, modification_listener) -def create_double_var(value, modification_listener=None): +def create_double_var(value, modification_listener=None) -> EnhancedDoubleVar: """See create_string_var""" - return _create_var(tk.DoubleVar, value, modification_listener) + return EnhancedDoubleVar(tk.DoubleVar, value, modification_listener) -def create_boolean_var(value, modification_listener=None): +def create_boolean_var(value, modification_listener=None) -> EnhancedBooleanVar: """See create_string_var""" - return _create_var(tk.BooleanVar, value, modification_listener) + return EnhancedBooleanVar(tk.BooleanVar, value, modification_listener) def _create_var(class_, value, modification_listener): @@ -1811,7 +1850,9 @@ def _close(self, event=None): # try gently first try: if running_on_windows(): - os.kill(self._proc.pid, signal.CTRL_BREAK_EVENT) # @UndefinedVariable + os.kill( + self._proc.pid, signal.CTRL_BREAK_EVENT # pylint: disable=no-member + ) else: os.kill(self._proc.pid, signal.SIGINT) @@ -2108,7 +2149,7 @@ def show_dialog(dlg, master=None, geometry=True, min_left=0, min_top=0): try: dlg.grab_set() - except: + except TclError: pass dlg.lift() @@ -2190,13 +2231,13 @@ def update_item_availability(self): else: self.entryconfigure(i, state=tk.NORMAL) - def add(self, kind, cnf={}, **kw): + def add(self, itemType, cnf={}, **kw): cnf = cnf or kw tester = cnf.get("tester") if "tester" in cnf: del cnf["tester"] - super().add(kind, cnf) + super().add(itemType, cnf) itemdata = self.entryconfigure(self.index("end")) labeldata = itemdata.get("label") @@ -2311,8 +2352,8 @@ def tr_btn(s): if __name__ == "__main__": root = tk.Tk() - closa = ClosableNotebook(root) - closa.add(ttk.Button(closa, text="B1"), text="B1") - closa.add(ttk.Button(closa, text="B2"), text="B2") - closa.grid() + var = EnhancedIntVar(value=2) + print(var.get()) + var.set(3) + print(repr(var.get())) root.mainloop() diff --git a/thonny/workbench.py b/thonny/workbench.py index 2a62735a1..bf48ac51b 100644 --- a/thonny/workbench.py +++ b/thonny/workbench.py @@ -17,9 +17,7 @@ import traceback from threading import Thread from tkinter import messagebox, ttk -from typing import Set # pylint: disable=unused-import -from typing import Tuple # pylint: disable=unused-import -from typing import Any, Callable, Dict, List, Sequence, Type, Union, cast +from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast from warnings import warn import thonny @@ -33,10 +31,10 @@ running, ui_utils, ) -from thonny.code import EditorNotebook from thonny.common import Record, UserError, normpath_with_actual_case from thonny.config import try_load_configuration from thonny.config_ui import ConfigurationDialog +from thonny.editors import EditorNotebook from thonny.misc_utils import ( copy_to_clipboard, running_on_linux, @@ -57,9 +55,6 @@ sequence_to_accelerator, ) -from typing import Optional # pylint: disable=unused-import; pylint: disable=unused-import - - SERVER_SUCCESS = "OK" SIMPLE_MODE_VIEWS = ["ShellView"] @@ -1568,7 +1563,7 @@ def hide_view(self, view_id: str) -> Union[bool, None]: # TODO: handle the case, when view is maximized view = self._view_records[view_id]["instance"] if view.hidden: - return + return True if hasattr(view, "before_hide") and view.before_hide() == False: return False @@ -1579,7 +1574,7 @@ def hide_view(self, view_id: str) -> Union[bool, None]: self.event_generate("HideView", view=view, view_id=view_id) view.hidden = True - return None + return True def event_generate(self, sequence: str, event: Optional[Record] = None, **kwargs) -> None: """Uses custom event handling when sequence doesn't start with <. @@ -1629,6 +1624,7 @@ def bind(self, sequence: str, func: Callable, add: bool = None) -> None: # type self._event_handlers[sequence].add(func) def unbind(self, sequence: str, func=None) -> None: + # pylint: disable=arguments-differ if sequence.startswith("<"): tk.Tk.unbind(self, sequence, funcid=func) else: