Repository: napari/napari

Issue:
NAPARI_CATCH_ERRORS=0 does not disable catching errors by Notification manager
## 🐛 Bug

<!-- A clear and concise description of what the bug is. -->
NAPARI_CATCH_ERRORS=0 does not disable catching errors by Notification manager which does not allow to use of IDE integration during debugging.  



<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

Use NAPARI_CATCH_ERRORS=0 should disable all caching methods (book installing own excepthooks) 

<!-- A clear and concise description of what you expected to happen. -->

## Environment

napari: 0.4.10
Platform: Linux-5.11.0-25-generic-x86_64-with-glibc2.2.5
System: Ubuntu 21.04
Python: 3.8.3 (default, Dec 6 2020, 00:47:20) [GCC 10.2.0]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.21.1
SciPy: 1.7.0
Dask: 2021.07.0
VisPy: 0.7.3

OpenGL:
- GL version: 4.6.0 NVIDIA 470.57.02
- MAX_TEXTURE_SIZE: 32768

Screens:
- screen 1: resolution 2560x1440, scale 1.0
- screen 2: resolution 1920x1080, scale 1.0
- screen 3: resolution 1920x1080, scale 1.0

Plugins:
- PartSeg Analysis project: 0.13.7.dev9+ge4260d9.d20210727
- PartSeg Image: 0.13.7.dev9+ge4260d9.d20210727
- PartSeg Mask project: 0.13.7.dev9+ge4260d9.d20210727
- PartSeg Masked Tiff: 0.13.7.dev9+ge4260d9.d20210727
- PartSeg measurements: 0.13.7.dev9+ge4260d9.d20210727
- console: 0.0.3
- svg: 0.1.5
 - Please copy and paste the information at napari info option in help menubar here:

 - Any other relevant information:

## Additional context

If You agree I could prepare PR. 



Discussion:
> does not allow to use of IDE integration during debugging.

yep, agreed.  PR would be great.
> yep, agreed. PR would be great.

After check of code. Did you prefer to disable notification manager or only disable registration of `excepthook`? 
I don't have a very strong opinion.  Whatever allows IDE debugging is most important.  but I guess the name itself `NAPARI_CATCH_ERRORS=0` implies that the excepthook itself should not be registered... what do you think?
> implies that the excepthook itself should not be registered

actually, I guess one tricky thing here is how to deal with the Qt abort.  i think the original intention of that env var was to toggle whether the exceptions appear in the gui or in the console... but if we disable the except hook altogether, a Qt exception will cause a full-on program exit.  is that what we want for IDE debugging?
It is possible to do this without halting the program. I have access to such code. 

Resolving patch:
diff --git a/napari/_qt/dialogs/qt_notification.py b/napari/_qt/dialogs/qt_notification.py
--- a/napari/_qt/dialogs/qt_notification.py
+++ b/napari/_qt/dialogs/qt_notification.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import os
 from typing import Callable, Optional, Sequence, Tuple, Union
 
 from qtpy.QtCore import (
@@ -372,21 +371,19 @@ def show_notification(cls, notification: Notification):
         # after https://github.com/napari/napari/issues/2370,
         # the os.getenv can be removed (and NAPARI_CATCH_ERRORS retired)
         if (
-            os.getenv("NAPARI_CATCH_ERRORS") not in ('0', 'False')
-            and notification.severity
+            notification.severity
             >= settings.application.gui_notification_level
         ):
             application_instance = QApplication.instance()
-            if application_instance:
-                # Check if this is running from a thread
-                if application_instance.thread() != QThread.currentThread():
-                    dispatcher = getattr(
-                        application_instance, "_dispatcher", None
-                    )
-                    if dispatcher:
-                        dispatcher.sig_notified.emit(notification)
-
-                    return
+            if (
+                application_instance
+                and application_instance.thread() != QThread.currentThread()
+            ):
+                dispatcher = getattr(application_instance, "_dispatcher", None)
+                if dispatcher:
+                    dispatcher.sig_notified.emit(notification)
+
+                return
 
             cls.from_notification(notification).show()
 
diff --git a/napari/_qt/qt_event_loop.py b/napari/_qt/qt_event_loop.py
--- a/napari/_qt/qt_event_loop.py
+++ b/napari/_qt/qt_event_loop.py
@@ -387,6 +387,5 @@ def run(
             )
         )
         return
-
     with notification_manager:
         app.exec_()
diff --git a/napari/utils/notifications.py b/napari/utils/notifications.py
--- a/napari/utils/notifications.py
+++ b/napari/utils/notifications.py
@@ -189,6 +189,10 @@ class NotificationManager:
     def __init__(self) -> None:
         self.records: List[Notification] = []
         self.exit_on_error = os.getenv('NAPARI_EXIT_ON_ERROR') in ('1', 'True')
+        self.catch_error = os.getenv("NAPARI_CATCH_ERRORS") not in (
+            '0',
+            'False',
+        )
         self.notification_ready = self.changed = EventEmitter(
             source=self, event_class=Notification
         )
@@ -253,7 +257,9 @@ def receive_error(
         if self.exit_on_error:
             sys.__excepthook__(exctype, value, traceback)
             sys.exit("Exit on error")
-
+        if not self.catch_error:
+            sys.__excepthook__(exctype, value, traceback)
+            return
         try:
             self.dispatch(Notification.from_exception(value))
         except Exception:


Test patch: