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 <dae@users.noreply.github.com>

* chore: add myself to contributors
This commit is contained in:
David Culley 2024-08-04 15:51:13 +02:00 committed by GitHub
parent a5a39c9302
commit c0349ea9da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 39 additions and 38 deletions

View file

@ -35,7 +35,7 @@ def runHook(hook: str, *args: Any) -> None:
for func in hookFuncs: for func in hookFuncs:
try: try:
func(*args) func(*args)
except: except Exception:
hookFuncs.remove(func) hookFuncs.remove(func)
raise raise
@ -46,7 +46,7 @@ def runFilter(hook: str, arg: Any, *args: Any) -> Any:
for func in hookFuncs: for func in hookFuncs:
try: try:
arg = func(arg, *args) arg = func(arg, *args)
except: except Exception:
hookFuncs.remove(func) hookFuncs.remove(func)
raise raise
return arg return arg

View file

@ -99,15 +99,15 @@ class TextImporter(NoteImporter):
if not self.delimiter: if not self.delimiter:
try: try:
self.dialect = sniffer.sniff("\n".join(self.data[:10]), self.patterns) self.dialect = sniffer.sniff("\n".join(self.data[:10]), self.patterns)
except: except Exception:
try: try:
self.dialect = sniffer.sniff(self.data[0], self.patterns) self.dialect = sniffer.sniff(self.data[0], self.patterns)
except: except Exception:
pass pass
if self.dialect: if self.dialect:
try: try:
reader = csv.reader(self.data, self.dialect, doublequote=True) reader = csv.reader(self.data, self.dialect, doublequote=True)
except: except Exception:
err() err()
else: else:
if not self.delimiter: if not self.delimiter:
@ -126,7 +126,7 @@ class TextImporter(NoteImporter):
if row: if row:
self.numFields = len(row) self.numFields = len(row)
break break
except: except Exception:
err() err()
self.initMapping() self.initMapping()

View file

@ -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 # this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US
# further below # further below
(sys_lang, enc) = locale.getlocale() (sys_lang, enc) = locale.getlocale()
except: except Exception:
# fails on osx # fails on osx
sys_lang = "en_US" sys_lang = "en_US"
if user_lang in compatMap: if user_lang in compatMap:

View file

@ -170,7 +170,7 @@ def _err_msg(col: anki.collection.Collection, type: str, texpath: str) -> str:
if not log: if not log:
raise Exception() raise Exception()
msg += f"<small><pre>{html.escape(log)}</pre></small>" msg += f"<small><pre>{html.escape(log)}</pre></small>"
except: except Exception:
msg += col.tr.media_have_you_installed_latex_and_dvipngdvisvgm() msg += col.tr.media_have_you_installed_latex_and_dvipngdvisvgm()
return msg return msg

View file

@ -722,7 +722,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE
tot = bad + good tot = bad + good
try: try:
pct = good / float(tot) * 100 pct = good / float(tot) * 100
except: except Exception:
pct = 0 pct = 0
i.append( i.append(
"Correct: <b>%(pct)0.2f%%</b><br>(%(good)d of %(tot)d)" "Correct: <b>%(pct)0.2f%%</b><br>(%(good)d of %(tot)d)"

View file

@ -29,7 +29,7 @@ try:
to_json_bytes: Callable[[Any], bytes] = orjson.dumps to_json_bytes: Callable[[Any], bytes] = orjson.dumps
from_json_bytes = orjson.loads from_json_bytes = orjson.loads
except: except Exception:
print("orjson is missing; DB operations will be slower") print("orjson is missing; DB operations will be slower")
def to_json_bytes(obj: Any) -> bytes: 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 info = subprocess.STARTUPINFO() # type: ignore
try: try:
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
except: except Exception:
# pylint: disable=no-member # pylint: disable=no-member
info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore
else: else:
@ -286,7 +286,7 @@ def plat_desc() -> str:
else: else:
theos = system theos = system
break break
except: except Exception:
continue continue
return theos return theos

View file

@ -26,7 +26,7 @@ def clear_tempfile(tf):
try: try:
tf.close() tf.close()
os.unlink(tf.name) os.unlink(tf.name)
except: except Exception:
pass pass

View file

@ -127,7 +127,7 @@ class {self.classname()}:
for hook in self._hooks: for hook in self._hooks:
try: try:
hook({", ".join(arg_names)}) hook({", ".join(arg_names)})
except: except Exception:
# if the hook fails, remove it # if the hook fails, remove it
self._hooks.remove(hook) self._hooks.remove(hook)
raise raise
@ -163,7 +163,7 @@ class {self.classname()}:
for filter in self._hooks: for filter in self._hooks:
try: try:
{arg_names[0]} = filter({", ".join(arg_names)}) {arg_names[0]} = filter({", ".join(arg_names)})
except: except Exception:
# if the hook fails, remove it # if the hook fails, remove it
self._hooks.remove(filter) self._hooks.remove(filter)
raise raise

View file

@ -232,7 +232,7 @@ def setupLangAndBackend(
global _qtrans global _qtrans
try: try:
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
except: except Exception:
pass pass
# add _ and ngettext globals used by legacy code # 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() pmLoadResult = pm.setupMeta()
Collection.initialize_backend_logging() Collection.initialize_backend_logging()
except: except Exception:
# will handle below # will handle below
traceback.print_exc() traceback.print_exc()
pm = None 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 # we must have a usable temp dir
try: try:
tempfile.gettempdir() tempfile.gettempdir()
except: except Exception:
QMessageBox.critical( QMessageBox.critical(
None, None,
tr.qt_misc_error(), tr.qt_misc_error(),

View file

@ -87,6 +87,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"Christian Krause", "Christian Krause",
"Christian Rusche", "Christian Rusche",
"Dave Druelinger", "Dave Druelinger",
"David Culley",
"David Smith", "David Smith",
"Dmitry Mikheev", "Dmitry Mikheev",
"Dotan Cohen", "Dotan Cohen",

View file

@ -248,7 +248,7 @@ class AddonManager:
__import__(addon.dir_name) __import__(addon.dir_name)
except AbortAddonImport: except AbortAddonImport:
pass pass
except: except Exception:
name = html.escape(addon.human_name()) name = html.escape(addon.human_name())
page = addon.page() page = addon.page()
if page: if page:
@ -341,7 +341,7 @@ class AddonManager:
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"json error in add-on {module}:\n{e}") print(f"json error in add-on {module}:\n{e}")
return dict() return dict()
except: except Exception:
# missing meta file, etc # missing meta file, etc
return dict() return dict()
@ -644,7 +644,7 @@ class AddonManager:
try: try:
with open(path, encoding="utf8") as f: with open(path, encoding="utf8") as f:
return json.load(f) return json.load(f)
except: except Exception:
return None return None
def set_config_help_action(self, module: str, action: Callable[[], str]) -> None: def set_config_help_action(self, module: str, action: Callable[[], str]) -> None:

View file

@ -243,7 +243,7 @@ class DataModel(QAbstractTableModel):
self._state = self._state.toggle_state() self._state = self._state.toggle_state()
try: try:
self._search_inner(context) self._search_inner(context)
except: except Exception:
# rollback to prevent inconsistent state # rollback to prevent inconsistent state
self._state = self._state.toggle_state() self._state = self._state.toggle_state()
raise raise

View file

@ -293,7 +293,7 @@ class DebugConsole(QDialog):
try: try:
# pylint: disable=exec-used # pylint: disable=exec-used
exec(text, vars) exec(text, vars)
except: except Exception:
self._output += traceback.format_exc() self._output += traceback.format_exc()
self._captureOutput(False) self._captureOutput(False)
buf = "" buf = ""

View file

@ -267,7 +267,7 @@ class DeckConf(QDialog):
if i == int(i): if i == int(i):
i = int(i) i = int(i)
ret.append(i) ret.append(i)
except: except Exception:
# invalid, don't update # invalid, don't update
showWarning(tr.scheduling_steps_must_be_numbers()) showWarning(tr.scheduling_steps_must_be_numbers())
return return

View file

@ -200,7 +200,7 @@ class AnkiQt(QMainWindow):
self.setupUI() self.setupUI()
self.setupAddons(args) self.setupAddons(args)
self.finish_ui_setup() self.finish_ui_setup()
except: except Exception:
showInfo(tr.qt_misc_error_during_startup(val=traceback.format_exc())) showInfo(tr.qt_misc_error_during_startup(val=traceback.format_exc()))
sys.exit(1) sys.exit(1)
# must call this after ui set up # must call this after ui set up
@ -351,7 +351,7 @@ class AnkiQt(QMainWindow):
f.profiles.addItems(profs) f.profiles.addItems(profs)
try: try:
idx = profs.index(self.pm.name) idx = profs.index(self.pm.name)
except: except Exception:
idx = 0 idx = 0
f.profiles.setCurrentRow(idx) f.profiles.setCurrentRow(idx)
@ -681,7 +681,7 @@ class AnkiQt(QMainWindow):
self.maybeOptimize() self.maybeOptimize()
if not dev_mode: if not dev_mode:
corrupt = self.col.db.scalar("pragma quick_check") != "ok" corrupt = self.col.db.scalar("pragma quick_check") != "ok"
except: except Exception:
corrupt = True corrupt = True
try: try:
@ -693,7 +693,7 @@ class AnkiQt(QMainWindow):
force=False, force=False,
wait_for_completion=False, wait_for_completion=False,
) )
except: except Exception:
print("backup on close failed") print("backup on close failed")
self.col.close(downgrade=False) self.col.close(downgrade=False)
except Exception as e: except Exception as e:

View file

@ -44,7 +44,7 @@ def _patch_pkgutil() -> None:
reader = module.__loader__.get_resource_reader(package) # type: ignore[attr-defined] reader = module.__loader__.get_resource_reader(package) # type: ignore[attr-defined]
with reader.open_resource(resource) as f: with reader.open_resource(resource) as f:
return f.read() return f.read()
except: except Exception:
return None return None
pkgutil.get_data = get_data_custom pkgutil.get_data = get_data_custom

View file

@ -384,7 +384,7 @@ class Preferences(QDialog):
lang = lang.replace("-", "_") lang = lang.replace("-", "_")
try: try:
return codes.index(lang) return codes.index(lang)
except: except Exception:
return codes.index("en_US") return codes.index("en_US")
def on_language_index_changed(self, idx: int) -> None: def on_language_index_changed(self, idx: int) -> None:

View file

@ -216,7 +216,7 @@ class ProfileManager:
self.name = name self.name = name
try: try:
self.profile = self._unpickle(data) self.profile = self._unpickle(data)
except: except Exception:
print(traceback.format_exc()) print(traceback.format_exc())
QMessageBox.warning( QMessageBox.warning(
None, None,
@ -285,7 +285,7 @@ class ProfileManager:
showWarning(tr.profiles_anki_could_not_rename_your_profile()) showWarning(tr.profiles_anki_could_not_rename_your_profile())
else: else:
raise raise
except: except BaseException:
self.db.rollback() self.db.rollback()
raise raise
else: else:
@ -386,7 +386,7 @@ class ProfileManager:
if self.db: if self.db:
try: try:
self.db.close() self.db.close()
except: except Exception:
pass pass
for suffix in ("", "-journal"): for suffix in ("", "-journal"):
fpath = path + suffix fpath = path + suffix
@ -406,7 +406,7 @@ create table if not exists profiles
data = self.db.scalar( data = self.db.scalar(
"select cast(data as blob) from profiles where name = '_global'" "select cast(data as blob) from profiles where name = '_global'"
) )
except: except Exception:
traceback.print_stack() traceback.print_stack()
if result.loadError: if result.loadError:
# already failed, prevent infinite loop # already failed, prevent infinite loop
@ -420,7 +420,7 @@ create table if not exists profiles
try: try:
self.meta = self._unpickle(data) self.meta = self._unpickle(data)
return result return result
except: except Exception:
traceback.print_stack() traceback.print_stack()
print("resetting corrupt _global") print("resetting corrupt _global")
result.loadError = True result.loadError = True

View file

@ -13,7 +13,7 @@ from typing import TypeVar, Union
try: try:
import PyQt6 import PyQt6
except: except Exception:
from .qt5 import * # type: ignore from .qt5 import * # type: ignore
else: else:
if os.getenv("ENABLE_QT5_COMPAT"): if os.getenv("ENABLE_QT5_COMPAT"):

View file

@ -504,13 +504,13 @@ if is_win:
def _voice_to_objects(self, voice: Any) -> list[WindowsVoice]: def _voice_to_objects(self, voice: Any) -> list[WindowsVoice]:
try: try:
langs = voice.GetAttribute("language") langs = voice.GetAttribute("language")
except: except Exception:
# no associated language; ignore # no associated language; ignore
return [] return []
langs = lcid_hex_str_to_lang_codes(langs) langs = lcid_hex_str_to_lang_codes(langs)
try: try:
name = voice.GetAttribute("name") name = voice.GetAttribute("name")
except: except Exception:
# some voices may not have a name # some voices may not have a name
name = "unknown" name = "unknown"
name = self._tidy_name(name) name = self._tidy_name(name)