From c0349ea9daddd4058c52a931d60fc8a0756dfffb Mon Sep 17 00:00:00 2001 From: David Culley <6276049+davidculley@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:51:13 +0200 Subject: [PATCH] Improve exception handling (#3290) * fix: except only non-system-exiting exceptions see https://youtu.be/zrVfY9SuO64 * chore: add myself to CONTRIBUTORS file * refactor: explicitly specify possible exceptions If an exception is not an Exception, there are only three options left. see https://docs.python.org/3/library/exceptions.html#exception-hierarchy * refactor: use BaseException for fallback Co-authored-by: Damien Elmes * chore: add myself to contributors --- pylib/anki/hooks.py | 4 ++-- pylib/anki/importing/csvfile.py | 8 ++++---- pylib/anki/lang.py | 2 +- pylib/anki/latex.py | 2 +- pylib/anki/stats.py | 2 +- pylib/anki/utils.py | 6 +++--- pylib/tests/test_importing.py | 2 +- pylib/tools/hookslib.py | 4 ++-- qt/aqt/__init__.py | 6 +++--- qt/aqt/about.py | 1 + qt/aqt/addons.py | 6 +++--- qt/aqt/browser/table/model.py | 2 +- qt/aqt/debug_console.py | 2 +- qt/aqt/deckconf.py | 2 +- qt/aqt/main.py | 8 ++++---- qt/aqt/package.py | 2 +- qt/aqt/preferences.py | 2 +- qt/aqt/profiles.py | 10 +++++----- qt/aqt/qt/__init__.py | 2 +- qt/aqt/tts.py | 4 ++-- 20 files changed, 39 insertions(+), 38 deletions(-) diff --git a/pylib/anki/hooks.py b/pylib/anki/hooks.py index de2b0d2ea..fcc3758f4 100644 --- a/pylib/anki/hooks.py +++ b/pylib/anki/hooks.py @@ -35,7 +35,7 @@ def runHook(hook: str, *args: Any) -> None: for func in hookFuncs: try: func(*args) - except: + except Exception: hookFuncs.remove(func) raise @@ -46,7 +46,7 @@ def runFilter(hook: str, arg: Any, *args: Any) -> Any: for func in hookFuncs: try: arg = func(arg, *args) - except: + except Exception: hookFuncs.remove(func) raise return arg diff --git a/pylib/anki/importing/csvfile.py b/pylib/anki/importing/csvfile.py index 6826ddecf..6a2ed347c 100644 --- a/pylib/anki/importing/csvfile.py +++ b/pylib/anki/importing/csvfile.py @@ -99,15 +99,15 @@ class TextImporter(NoteImporter): if not self.delimiter: try: self.dialect = sniffer.sniff("\n".join(self.data[:10]), self.patterns) - except: + except Exception: try: self.dialect = sniffer.sniff(self.data[0], self.patterns) - except: + except Exception: pass if self.dialect: try: reader = csv.reader(self.data, self.dialect, doublequote=True) - except: + except Exception: err() else: if not self.delimiter: @@ -126,7 +126,7 @@ class TextImporter(NoteImporter): if row: self.numFields = len(row) break - except: + except Exception: err() self.initMapping() diff --git a/pylib/anki/lang.py b/pylib/anki/lang.py index 55987814e..188b884a1 100644 --- a/pylib/anki/lang.py +++ b/pylib/anki/lang.py @@ -199,7 +199,7 @@ def get_def_lang(user_lang: str | None = None) -> tuple[int, str]: # this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US # further below (sys_lang, enc) = locale.getlocale() - except: + except Exception: # fails on osx sys_lang = "en_US" if user_lang in compatMap: diff --git a/pylib/anki/latex.py b/pylib/anki/latex.py index 75c7d9e9f..ed8c9cce6 100644 --- a/pylib/anki/latex.py +++ b/pylib/anki/latex.py @@ -170,7 +170,7 @@ def _err_msg(col: anki.collection.Collection, type: str, texpath: str) -> str: if not log: raise Exception() msg += f"
{html.escape(log)}
" - except: + except Exception: msg += col.tr.media_have_you_installed_latex_and_dvipngdvisvgm() return msg diff --git a/pylib/anki/stats.py b/pylib/anki/stats.py index 4fbc47e07..14183aa84 100644 --- a/pylib/anki/stats.py +++ b/pylib/anki/stats.py @@ -722,7 +722,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE tot = bad + good try: pct = good / float(tot) * 100 - except: + except Exception: pct = 0 i.append( "Correct: %(pct)0.2f%%
(%(good)d of %(tot)d)" diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py index f8e32816f..b5382e6df 100644 --- a/pylib/anki/utils.py +++ b/pylib/anki/utils.py @@ -29,7 +29,7 @@ try: to_json_bytes: Callable[[Any], bytes] = orjson.dumps from_json_bytes = orjson.loads -except: +except Exception: print("orjson is missing; DB operations will be slower") def to_json_bytes(obj: Any) -> bytes: @@ -215,7 +215,7 @@ def call(argv: list[str], wait: bool = True, **kwargs: Any) -> int: info = subprocess.STARTUPINFO() # type: ignore try: info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore - except: + except Exception: # pylint: disable=no-member info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore else: @@ -286,7 +286,7 @@ def plat_desc() -> str: else: theos = system break - except: + except Exception: continue return theos diff --git a/pylib/tests/test_importing.py b/pylib/tests/test_importing.py index ec2460bf7..191de51f4 100644 --- a/pylib/tests/test_importing.py +++ b/pylib/tests/test_importing.py @@ -26,7 +26,7 @@ def clear_tempfile(tf): try: tf.close() os.unlink(tf.name) - except: + except Exception: pass diff --git a/pylib/tools/hookslib.py b/pylib/tools/hookslib.py index 8325124ad..8920cdcfc 100644 --- a/pylib/tools/hookslib.py +++ b/pylib/tools/hookslib.py @@ -127,7 +127,7 @@ class {self.classname()}: for hook in self._hooks: try: hook({", ".join(arg_names)}) - except: + except Exception: # if the hook fails, remove it self._hooks.remove(hook) raise @@ -163,7 +163,7 @@ class {self.classname()}: for filter in self._hooks: try: {arg_names[0]} = filter({", ".join(arg_names)}) - except: + except Exception: # if the hook fails, remove it self._hooks.remove(filter) raise diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 0591b95ff..e89848b3b 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -232,7 +232,7 @@ def setupLangAndBackend( global _qtrans try: locale.setlocale(locale.LC_ALL, "") - except: + except Exception: pass # add _ and ngettext globals used by legacy code @@ -630,7 +630,7 @@ def _run(argv: list[str] | None = None, exec: bool = True) -> AnkiApp | None: pmLoadResult = pm.setupMeta() Collection.initialize_backend_logging() - except: + except Exception: # will handle below traceback.print_exc() pm = None @@ -720,7 +720,7 @@ def _run(argv: list[str] | None = None, exec: bool = True) -> AnkiApp | None: # we must have a usable temp dir try: tempfile.gettempdir() - except: + except Exception: QMessageBox.critical( None, tr.qt_misc_error(), diff --git a/qt/aqt/about.py b/qt/aqt/about.py index ec2a71f99..fa20a4da1 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -87,6 +87,7 @@ def show(mw: aqt.AnkiQt) -> QDialog: "Christian Krause", "Christian Rusche", "Dave Druelinger", + "David Culley", "David Smith", "Dmitry Mikheev", "Dotan Cohen", diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index cb7a242cf..a5d1d81c7 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -248,7 +248,7 @@ class AddonManager: __import__(addon.dir_name) except AbortAddonImport: pass - except: + except Exception: name = html.escape(addon.human_name()) page = addon.page() if page: @@ -341,7 +341,7 @@ class AddonManager: except json.JSONDecodeError as e: print(f"json error in add-on {module}:\n{e}") return dict() - except: + except Exception: # missing meta file, etc return dict() @@ -644,7 +644,7 @@ class AddonManager: try: with open(path, encoding="utf8") as f: return json.load(f) - except: + except Exception: return None def set_config_help_action(self, module: str, action: Callable[[], str]) -> None: diff --git a/qt/aqt/browser/table/model.py b/qt/aqt/browser/table/model.py index de8253510..55f467f53 100644 --- a/qt/aqt/browser/table/model.py +++ b/qt/aqt/browser/table/model.py @@ -243,7 +243,7 @@ class DataModel(QAbstractTableModel): self._state = self._state.toggle_state() try: self._search_inner(context) - except: + except Exception: # rollback to prevent inconsistent state self._state = self._state.toggle_state() raise diff --git a/qt/aqt/debug_console.py b/qt/aqt/debug_console.py index 3ad96fcbf..6c8874f38 100644 --- a/qt/aqt/debug_console.py +++ b/qt/aqt/debug_console.py @@ -293,7 +293,7 @@ class DebugConsole(QDialog): try: # pylint: disable=exec-used exec(text, vars) - except: + except Exception: self._output += traceback.format_exc() self._captureOutput(False) buf = "" diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py index 8626f0a6a..11dddd3ca 100644 --- a/qt/aqt/deckconf.py +++ b/qt/aqt/deckconf.py @@ -267,7 +267,7 @@ class DeckConf(QDialog): if i == int(i): i = int(i) ret.append(i) - except: + except Exception: # invalid, don't update showWarning(tr.scheduling_steps_must_be_numbers()) return diff --git a/qt/aqt/main.py b/qt/aqt/main.py index fad9c5829..2d915b26a 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -200,7 +200,7 @@ class AnkiQt(QMainWindow): self.setupUI() self.setupAddons(args) self.finish_ui_setup() - except: + except Exception: showInfo(tr.qt_misc_error_during_startup(val=traceback.format_exc())) sys.exit(1) # must call this after ui set up @@ -351,7 +351,7 @@ class AnkiQt(QMainWindow): f.profiles.addItems(profs) try: idx = profs.index(self.pm.name) - except: + except Exception: idx = 0 f.profiles.setCurrentRow(idx) @@ -681,7 +681,7 @@ class AnkiQt(QMainWindow): self.maybeOptimize() if not dev_mode: corrupt = self.col.db.scalar("pragma quick_check") != "ok" - except: + except Exception: corrupt = True try: @@ -693,7 +693,7 @@ class AnkiQt(QMainWindow): force=False, wait_for_completion=False, ) - except: + except Exception: print("backup on close failed") self.col.close(downgrade=False) except Exception as e: diff --git a/qt/aqt/package.py b/qt/aqt/package.py index ace75ca23..3f89366a2 100644 --- a/qt/aqt/package.py +++ b/qt/aqt/package.py @@ -44,7 +44,7 @@ def _patch_pkgutil() -> None: reader = module.__loader__.get_resource_reader(package) # type: ignore[attr-defined] with reader.open_resource(resource) as f: return f.read() - except: + except Exception: return None pkgutil.get_data = get_data_custom diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 777c32cd4..82ea2ff74 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -384,7 +384,7 @@ class Preferences(QDialog): lang = lang.replace("-", "_") try: return codes.index(lang) - except: + except Exception: return codes.index("en_US") def on_language_index_changed(self, idx: int) -> None: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 7da170408..396577260 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -216,7 +216,7 @@ class ProfileManager: self.name = name try: self.profile = self._unpickle(data) - except: + except Exception: print(traceback.format_exc()) QMessageBox.warning( None, @@ -285,7 +285,7 @@ class ProfileManager: showWarning(tr.profiles_anki_could_not_rename_your_profile()) else: raise - except: + except BaseException: self.db.rollback() raise else: @@ -386,7 +386,7 @@ class ProfileManager: if self.db: try: self.db.close() - except: + except Exception: pass for suffix in ("", "-journal"): fpath = path + suffix @@ -406,7 +406,7 @@ create table if not exists profiles data = self.db.scalar( "select cast(data as blob) from profiles where name = '_global'" ) - except: + except Exception: traceback.print_stack() if result.loadError: # already failed, prevent infinite loop @@ -420,7 +420,7 @@ create table if not exists profiles try: self.meta = self._unpickle(data) return result - except: + except Exception: traceback.print_stack() print("resetting corrupt _global") result.loadError = True diff --git a/qt/aqt/qt/__init__.py b/qt/aqt/qt/__init__.py index ffbdb2dda..ea1b4bd46 100644 --- a/qt/aqt/qt/__init__.py +++ b/qt/aqt/qt/__init__.py @@ -13,7 +13,7 @@ from typing import TypeVar, Union try: import PyQt6 -except: +except Exception: from .qt5 import * # type: ignore else: if os.getenv("ENABLE_QT5_COMPAT"): diff --git a/qt/aqt/tts.py b/qt/aqt/tts.py index f80bcf6bd..cd2884795 100644 --- a/qt/aqt/tts.py +++ b/qt/aqt/tts.py @@ -504,13 +504,13 @@ if is_win: def _voice_to_objects(self, voice: Any) -> list[WindowsVoice]: try: langs = voice.GetAttribute("language") - except: + except Exception: # no associated language; ignore return [] langs = lcid_hex_str_to_lang_codes(langs) try: name = voice.GetAttribute("name") - except: + except Exception: # some voices may not have a name name = "unknown" name = self._tidy_name(name)