We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Setting active layer from a plugin on PublicOnlyProxy works partially, but not in GUI
## 🐛 Bug

We’re writing a [classifier plugin](https://github.com/fractal-napari-plugins-collection/napari-feature-classifier/tree/classifier_refactor) that allows user to annotate a label layer and train classifiers based on those annotations. The way we show user annotations is by adding a label layer and coloring the objects based on user annotation.

When we add an annotation layer from our plugin, we don’t want to make it the selected layer (because we use layer selection to decide which label layer is currently being annotated).

Changing the active layer by setting `viewer.layers.selection.active` to the layer I want selected does not seem to work from a plugin (for layers that where not created by the plugin). I suspect it has something to do with the plugin not getting the actual viewer object, but a napari.utils._proxies.PublicOnlyProxy ⇒ only setting active state on that proxy.

It’s confusing though, because checking the active layer (via viewer.layers.selection.active) returns what I’d expect, but it’s not shown in the GUI.

## To Reproduce

Here's some sample code to reproduce this behavior:
```python
from pathlib import Path

import imageio
import napari
import napari.layers
import napari.viewer
import numpy as np
import pandas as pd
from magicgui.widgets import Container, Label
from napari.utils.notifications import show_info

def main():
    lbls = imageio.v2.imread(Path("sample_data/test_labels.tif"))
    lbls2 = np.zeros_like(lbls)
    lbls2[:, 3:, 2:] = lbls[:, :-3, :-2]
    lbls2 = lbls2 * 20

    labels = np.unique(lbls)[1:]
    labels_2 = np.unique(lbls2)[1:]

    viewer = napari.Viewer()
    lbls_layer = viewer.add_labels(lbls)
    lbls_layer2 = viewer.add_labels(lbls2)

    # Add the widget directly via code:
    label_selector_widget = LabelSelector(viewer)             # Comment out to reproduce issue
    viewer.window.add_dock_widget(label_selector_widget)      # Comment out to reproduce issue

    viewer.show(block=True)


class LabelSelector(Container):
    def __init__(
        self,
        viewer: napari.viewer.Viewer,
    ):
        self._viewer = viewer
        self.label = Label(label='Test')
        super().__init__(
            widgets=[
                self.label
            ]
        )
        self._last_selected_label_layer = self._viewer.layers[1]
        annotation_layer = self._viewer.add_labels(
            self._last_selected_label_layer.data,
            scale=self._last_selected_label_layer.scale,
            name="Annotations",
        )
        self._viewer.layers.selection.active = self._viewer.layers[0]
        print(f'Selected Layer at the end: {self._viewer.layers.selection.active}')
        print(f"Type of annotation layer: {type(annotation_layer)}")
        print(f"Type of first label layer: {type(self._viewer.layers[0])}")


if __name__ == "__main__":
    main()
```

If I run it as above (i.e. adding the dockwidget from Python), I get the expected behavior and the correct layer (the first one) is selected after the new Annotations layer was added:
<img width="1198" alt="Screenshot 2023-04-25 at 09 16 44" src="https://user-images.githubusercontent.com/18033446/234206865-8bd2fe29-a7c9-4a0b-aa73-659c51acdcbe.png">

The printing output is:
```
Selected Layer at the end: lbls
Type of annotation layer: <class 'napari.layers.labels.labels.Labels'>
Type of first label layer: <class 'napari.layers.labels.labels.Labels'>
```

If the two lines that are adding the widget manually are commented out:
```python
    # label_selector_widget = LabelSelector(viewer)
    # viewer.window.add_dock_widget(label_selector_widget)
```
and instead the dockwidget is added as a plugin, which is started from the GUI, we get this behavior:
![layer_selection_issue](https://user-images.githubusercontent.com/18033446/234207326-7e82a2c0-3285-4822-be2d-79f272276922.gif)
According to viewer.layers.selection.active, the first layer was selected. But the GUI does not show any layer selected.
The GUI still reacts to changes in the layer controls (i.e. changing opacity) and applies it to the layer that is selected behind the scenes. The user just isn't shown that it applies to that layer.

This is the print output:
```
Selected Layer at the end: lbls
Type of annotation layer: <class 'napari.layers.labels.labels.Labels'>
Type of first label layer: <class 'napari.utils._proxies.PublicOnlyProxy'>
```

## Expected behavior

I would expect the plugin flow to behave the same as when I manually add a widget: The GUI shows the selected layer.
Especially given that some parts of the GUI react to the layer selection (e.g. the layer controls), the actively selected layer should be shown.

## Environment

 - Please copy and paste the information at napari info option in help menubar here:
```
napari: 0.5.0a2.dev42+g9e911040
Platform: macOS-13.2.1-arm64-arm-64bit
System: MacOS 13.2.1
Python: 3.10.9 | packaged by conda-forge | (main, Feb 2 2023, 20:26:08) [Clang 14.0.6 ]
Qt: 5.15.6
PyQt5: 5.15.7
NumPy: 1.24.2
SciPy: 1.10.1
Dask: 2023.3.0
VisPy: 0.12.1
magicgui: 0.7.2
superqt: unknown
in-n-out: 0.1.7
app-model: 0.1.2
npe2: 0.6.2

OpenGL:
- GL version: 2.1 Metal - 83
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 2560x1440, scale 2.0
- screen 2: resolution 1512x982, scale 2.0

Settings path:
- /Users/joel/Library/Application Support/napari/classifier-dev-napari-main_2751af53b3d3e49c82e2e47937e51f1f537130c2/settings.yaml
```

 - Any other relevant information:
 I also tested it in napari 0.4.17 and in more recent napari nightly builds (0.5.0a2.dev71+g66df74d5) and always get the same behavior



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `napari/utils/_proxies.py`
Content:
```
1 import os
2 import re
3 import sys
4 import warnings
5 from typing import Any, Callable, Generic, TypeVar, Union
6 
7 import wrapt
8 
9 from napari.utils import misc
10 from napari.utils.translations import trans
11 
12 _T = TypeVar("_T")
13 
14 
15 class ReadOnlyWrapper(wrapt.ObjectProxy):
16     """
17     Disable item and attribute setting with the exception of  ``__wrapped__``.
18     """
19 
20     def __init__(self, wrapped, exceptions=()):
21         super().__init__(wrapped)
22         self._self_exceptions = exceptions
23 
24     def __setattr__(self, name, val):
25         if (
26             name not in ('__wrapped__', '_self_exceptions')
27             and name not in self._self_exceptions
28         ):
29             raise TypeError(
30                 trans._(
31                     'cannot set attribute {name}',
32                     deferred=True,
33                     name=name,
34                 )
35             )
36 
37         super().__setattr__(name, val)
38 
39     def __setitem__(self, name, val):
40         if name not in self._self_exceptions:
41             raise TypeError(
42                 trans._('cannot set item {name}', deferred=True, name=name)
43             )
44         super().__setitem__(name, val)
45 
46 
47 _SUNDER = re.compile('^_[^_]')
48 
49 
50 class PublicOnlyProxy(wrapt.ObjectProxy, Generic[_T]):
51     """Proxy to prevent private attribute and item access, recursively."""
52 
53     __wrapped__: _T
54 
55     @staticmethod
56     def _is_private_attr(name: str) -> bool:
57         return name.startswith("_") and not (
58             name.startswith('__') and name.endswith('__')
59         )
60 
61     @staticmethod
62     def _private_attr_warning(name: str, typ: str):
63         warnings.warn(
64             trans._(
65                 "Private attribute access ('{typ}.{name}') in this context (e.g. inside a plugin widget or dock widget) is deprecated and will be unavailable in version 0.5.0",
66                 deferred=True,
67                 name=name,
68                 typ=typ,
69             ),
70             category=FutureWarning,
71             stacklevel=3,
72         )
73 
74         # This is code prepared for a moment where we want to block access to private attributes
75         # raise AttributeError(
76         #     trans._(
77         #         "Private attribute set/access ('{typ}.{name}') not allowed in this context.",
78         #         deferred=True,
79         #         name=name,
80         #         typ=typ,
81         #     )
82         # )
83 
84     @staticmethod
85     def _is_called_from_napari():
86         """
87         Check if the getter or setter is called from inner napari.
88         """
89         if hasattr(sys, "_getframe"):
90             frame = sys._getframe(2)
91             return frame.f_code.co_filename.startswith(misc.ROOT_DIR)
92         return False
93 
94     def __getattr__(self, name: str):
95         if self._is_private_attr(name):
96             # allow napari to access private attributes and get an non-proxy
97             if self._is_called_from_napari():
98                 return super().__getattr__(name)
99 
100             typ = type(self.__wrapped__).__name__
101 
102             self._private_attr_warning(name, typ)
103 
104         return self.create(super().__getattr__(name))
105 
106     def __setattr__(self, name: str, value: Any):
107         if (
108             os.environ.get("NAPARI_ENSURE_PLUGIN_MAIN_THREAD", "0")
109             not in ("0", "False")
110         ) and not in_main_thread():
111             raise RuntimeError(
112                 "Setting attributes on a napari object is only allowed from the main Qt thread."
113             )
114 
115         if self._is_private_attr(name):
116             if self._is_called_from_napari():
117                 return super().__setattr__(name, value)
118 
119             typ = type(self.__wrapped__).__name__
120             self._private_attr_warning(name, typ)
121 
122         setattr(self.__wrapped__, name, value)
123         return None
124 
125     def __getitem__(self, key):
126         return self.create(super().__getitem__(key))
127 
128     def __repr__(self):
129         return repr(self.__wrapped__)
130 
131     def __dir__(self):
132         return [x for x in dir(self.__wrapped__) if not _SUNDER.match(x)]
133 
134     @classmethod
135     def create(cls, obj: Any) -> Union['PublicOnlyProxy', Any]:
136         # restrict the scope of this proxy to napari objects
137         mod = getattr(type(obj), '__module__', None) or ''
138         if not mod.startswith('napari'):
139             return obj
140         if isinstance(obj, PublicOnlyProxy):
141             return obj  # don't double-wrap
142         if callable(obj):
143             return CallablePublicOnlyProxy(obj)
144         return PublicOnlyProxy(obj)
145 
146 
147 class CallablePublicOnlyProxy(PublicOnlyProxy[Callable]):
148     def __call__(self, *args, **kwargs):
149         return self.__wrapped__(*args, **kwargs)
150 
151 
152 def in_main_thread_py() -> bool:
153     """
154     Check if caller is in main python thread.
155 
156     Returns
157     -------
158     thread_flag : bool
159         True if we are in the main thread, False otherwise.
160     """
161     import threading
162 
163     return threading.current_thread() == threading.main_thread()
164 
165 
166 def _in_main_thread() -> bool:
167     """
168     General implementation of checking if we are in a proper thread.
169     If Qt is available and Application is created then assign :py:func:`in_qt_main_thread` to `in_main_thread`.
170     If Qt liba are not available then assign :py:func:`in_main_thread_py` to in_main_thread.
171     IF Qt libs are available but there is no Application ti wil emmit warning and return result of in_main_thread_py.
172 
173     Returns
174     -------
175     thread_flag : bool
176         True if we are in the main thread, False otherwise.
177     """
178 
179     global in_main_thread
180     try:
181         from napari._qt.utils import in_qt_main_thread
182 
183         res = in_qt_main_thread()
184         in_main_thread = in_qt_main_thread
185     except ImportError:
186         in_main_thread = in_main_thread_py
187         return in_main_thread_py()
188     except AttributeError:
189         warnings.warn(
190             "Qt libs are available but no QtApplication instance is created"
191         )
192         return in_main_thread_py()
193     return res
194 
195 
196 in_main_thread = _in_main_thread
197
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/napari/utils/_proxies.py b/napari/utils/_proxies.py
--- a/napari/utils/_proxies.py
+++ b/napari/utils/_proxies.py
@@ -119,6 +119,24 @@
             typ = type(self.__wrapped__).__name__
             self._private_attr_warning(name, typ)
 
+        if isinstance(value, PublicOnlyProxy):
+            # if we want to set an attribute on a PublicOnlyProxy *and* the
+            # value that we want to set is itself a PublicOnlyProxy, we unwrap
+            # the value. This has two benefits:
+            #
+            # 1. Checking the attribute later will incur a significant
+            # performance cost, because _is_called_from_napari() will be
+            # checked on each attribute access and it involves inspecting the
+            # calling frame, which is expensive.
+            # 2. Certain equality checks fail when objects are
+            # PublicOnlyProxies. Notably, equality checks fail when such
+            # objects are included in a Qt data model. For example, plugins can
+            # grab a layer from the viewer; this layer will be wrapped by the
+            # PublicOnlyProxy, and then using this object to set the current
+            # layer selection will not propagate the selection to the Viewer.
+            # See https://github.com/napari/napari/issues/5767
+            value = value.__wrapped__
+
         setattr(self.__wrapped__, name, value)
         return None
 
@@ -134,7 +152,15 @@
     @classmethod
     def create(cls, obj: Any) -> Union['PublicOnlyProxy', Any]:
         # restrict the scope of this proxy to napari objects
-        mod = getattr(type(obj), '__module__', None) or ''
+        if type(obj).__name__ == 'method':
+            # If the given object is a method, we check the module *of the
+            # object to which that method is bound*. Otherwise, the module of a
+            # method is just builtins!
+            mod = getattr(type(obj.__self__), '__module__', None) or ''
+        else:
+            # Otherwise, the module is of an object just given by the
+            # __module__ attribute.
+            mod = getattr(type(obj), '__module__', None) or ''
         if not mod.startswith('napari'):
             return obj
         if isinstance(obj, PublicOnlyProxy):
@@ -146,7 +172,20 @@
 
 class CallablePublicOnlyProxy(PublicOnlyProxy[Callable]):
     def __call__(self, *args, **kwargs):
-        return self.__wrapped__(*args, **kwargs)
+        # if a PublicOnlyProxy is callable, then when we call it we:
+        # - unwrap the arguments, to avoid performance issues detailed in
+        #   PublicOnlyProxy.__setattr__,
+        # - call the unwrapped callable on the unwrapped arguments
+        # - wrap the result in a PublicOnlyProxy
+        args = [
+            arg.__wrapped__ if isinstance(arg, PublicOnlyProxy) else arg
+            for arg in args
+        ]
+        kwargs = {
+            k: v.__wrapped__ if isinstance(v, PublicOnlyProxy) else v
+            for k, v in kwargs.items()
+        }
+        return self.create(self.__wrapped__(*args, **kwargs))
 
 
 def in_main_thread_py() -> bool:

{"golden_diff": "diff --git a/napari/utils/_proxies.py b/napari/utils/_proxies.py\n--- a/napari/utils/_proxies.py\n+++ b/napari/utils/_proxies.py\n@@ -119,6 +119,24 @@\n             typ = type(self.__wrapped__).__name__\n             self._private_attr_warning(name, typ)\n \n+        if isinstance(value, PublicOnlyProxy):\n+            # if we want to set an attribute on a PublicOnlyProxy *and* the\n+            # value that we want to set is itself a PublicOnlyProxy, we unwrap\n+            # the value. This has two benefits:\n+            #\n+            # 1. Checking the attribute later will incur a significant\n+            # performance cost, because _is_called_from_napari() will be\n+            # checked on each attribute access and it involves inspecting the\n+            # calling frame, which is expensive.\n+            # 2. Certain equality checks fail when objects are\n+            # PublicOnlyProxies. Notably, equality checks fail when such\n+            # objects are included in a Qt data model. For example, plugins can\n+            # grab a layer from the viewer; this layer will be wrapped by the\n+            # PublicOnlyProxy, and then using this object to set the current\n+            # layer selection will not propagate the selection to the Viewer.\n+            # See https://github.com/napari/napari/issues/5767\n+            value = value.__wrapped__\n+\n         setattr(self.__wrapped__, name, value)\n         return None\n \n@@ -134,7 +152,15 @@\n     @classmethod\n     def create(cls, obj: Any) -> Union['PublicOnlyProxy', Any]:\n         # restrict the scope of this proxy to napari objects\n-        mod = getattr(type(obj), '__module__', None) or ''\n+        if type(obj).__name__ == 'method':\n+            # If the given object is a method, we check the module *of the\n+            # object to which that method is bound*. Otherwise, the module of a\n+            # method is just builtins!\n+            mod = getattr(type(obj.__self__), '__module__', None) or ''\n+        else:\n+            # Otherwise, the module is of an object just given by the\n+            # __module__ attribute.\n+            mod = getattr(type(obj), '__module__', None) or ''\n         if not mod.startswith('napari'):\n             return obj\n         if isinstance(obj, PublicOnlyProxy):\n@@ -146,7 +172,20 @@\n \n class CallablePublicOnlyProxy(PublicOnlyProxy[Callable]):\n     def __call__(self, *args, **kwargs):\n-        return self.__wrapped__(*args, **kwargs)\n+        # if a PublicOnlyProxy is callable, then when we call it we:\n+        # - unwrap the arguments, to avoid performance issues detailed in\n+        #   PublicOnlyProxy.__setattr__,\n+        # - call the unwrapped callable on the unwrapped arguments\n+        # - wrap the result in a PublicOnlyProxy\n+        args = [\n+            arg.__wrapped__ if isinstance(arg, PublicOnlyProxy) else arg\n+            for arg in args\n+        ]\n+        kwargs = {\n+            k: v.__wrapped__ if isinstance(v, PublicOnlyProxy) else v\n+            for k, v in kwargs.items()\n+        }\n+        return self.create(self.__wrapped__(*args, **kwargs))\n \n \n def in_main_thread_py() -> bool:\n", "issue": "Setting active layer from a plugin on PublicOnlyProxy works partially, but not in GUI\n## \ud83d\udc1b Bug\r\n\r\nWe\u2019re writing a [classifier plugin](https://github.com/fractal-napari-plugins-collection/napari-feature-classifier/tree/classifier_refactor) that allows user to annotate a label layer and train classifiers based on those annotations. The way we show user annotations is by adding a label layer and coloring the objects based on user annotation.\r\n\r\nWhen we add an annotation layer from our plugin, we don\u2019t want to make it the selected layer (because we use layer selection to decide which label layer is currently being annotated).\r\n\r\nChanging the active layer by setting `viewer.layers.selection.active` to the layer I want selected does not seem to work from a plugin (for layers that where not created by the plugin). I suspect it has something to do with the plugin not getting the actual viewer object, but a napari.utils._proxies.PublicOnlyProxy \u21d2 only setting active state on that proxy.\r\n\r\nIt\u2019s confusing though, because checking the active layer (via viewer.layers.selection.active) returns what I\u2019d expect, but it\u2019s not shown in the GUI.\r\n\r\n## To Reproduce\r\n\r\nHere's some sample code to reproduce this behavior:\r\n```python\r\nfrom pathlib import Path\r\n\r\nimport imageio\r\nimport napari\r\nimport napari.layers\r\nimport napari.viewer\r\nimport numpy as np\r\nimport pandas as pd\r\nfrom magicgui.widgets import Container, Label\r\nfrom napari.utils.notifications import show_info\r\n\r\ndef main():\r\n    lbls = imageio.v2.imread(Path(\"sample_data/test_labels.tif\"))\r\n    lbls2 = np.zeros_like(lbls)\r\n    lbls2[:, 3:, 2:] = lbls[:, :-3, :-2]\r\n    lbls2 = lbls2 * 20\r\n\r\n    labels = np.unique(lbls)[1:]\r\n    labels_2 = np.unique(lbls2)[1:]\r\n\r\n    viewer = napari.Viewer()\r\n    lbls_layer = viewer.add_labels(lbls)\r\n    lbls_layer2 = viewer.add_labels(lbls2)\r\n\r\n    # Add the widget directly via code:\r\n    label_selector_widget = LabelSelector(viewer)             # Comment out to reproduce issue\r\n    viewer.window.add_dock_widget(label_selector_widget)      # Comment out to reproduce issue\r\n\r\n    viewer.show(block=True)\r\n\r\n\r\nclass LabelSelector(Container):\r\n    def __init__(\r\n        self,\r\n        viewer: napari.viewer.Viewer,\r\n    ):\r\n        self._viewer = viewer\r\n        self.label = Label(label='Test')\r\n        super().__init__(\r\n            widgets=[\r\n                self.label\r\n            ]\r\n        )\r\n        self._last_selected_label_layer = self._viewer.layers[1]\r\n        annotation_layer = self._viewer.add_labels(\r\n            self._last_selected_label_layer.data,\r\n            scale=self._last_selected_label_layer.scale,\r\n            name=\"Annotations\",\r\n        )\r\n        self._viewer.layers.selection.active = self._viewer.layers[0]\r\n        print(f'Selected Layer at the end: {self._viewer.layers.selection.active}')\r\n        print(f\"Type of annotation layer: {type(annotation_layer)}\")\r\n        print(f\"Type of first label layer: {type(self._viewer.layers[0])}\")\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    main()\r\n```\r\n\r\nIf I run it as above (i.e. adding the dockwidget from Python), I get the expected behavior and the correct layer (the first one) is selected after the new Annotations layer was added:\r\n<img width=\"1198\" alt=\"Screenshot 2023-04-25 at 09 16 44\" src=\"https://user-images.githubusercontent.com/18033446/234206865-8bd2fe29-a7c9-4a0b-aa73-659c51acdcbe.png\">\r\n\r\nThe printing output is:\r\n```\r\nSelected Layer at the end: lbls\r\nType of annotation layer: <class 'napari.layers.labels.labels.Labels'>\r\nType of first label layer: <class 'napari.layers.labels.labels.Labels'>\r\n```\r\n\r\nIf the two lines that are adding the widget manually are commented out:\r\n```python\r\n    # label_selector_widget = LabelSelector(viewer)\r\n    # viewer.window.add_dock_widget(label_selector_widget)\r\n```\r\nand instead the dockwidget is added as a plugin, which is started from the GUI, we get this behavior:\r\n![layer_selection_issue](https://user-images.githubusercontent.com/18033446/234207326-7e82a2c0-3285-4822-be2d-79f272276922.gif)\r\nAccording to viewer.layers.selection.active, the first layer was selected. But the GUI does not show any layer selected.\r\nThe GUI still reacts to changes in the layer controls (i.e. changing opacity) and applies it to the layer that is selected behind the scenes. The user just isn't shown that it applies to that layer.\r\n\r\nThis is the print output:\r\n```\r\nSelected Layer at the end: lbls\r\nType of annotation layer: <class 'napari.layers.labels.labels.Labels'>\r\nType of first label layer: <class 'napari.utils._proxies.PublicOnlyProxy'>\r\n```\r\n\r\n## Expected behavior\r\n\r\nI would expect the plugin flow to behave the same as when I manually add a widget: The GUI shows the selected layer.\r\nEspecially given that some parts of the GUI react to the layer selection (e.g. the layer controls), the actively selected layer should be shown.\r\n\r\n## Environment\r\n\r\n - Please copy and paste the information at napari info option in help menubar here:\r\n```\r\nnapari: 0.5.0a2.dev42+g9e911040\r\nPlatform: macOS-13.2.1-arm64-arm-64bit\r\nSystem: MacOS 13.2.1\r\nPython: 3.10.9 | packaged by conda-forge | (main, Feb 2 2023, 20:26:08) [Clang 14.0.6 ]\r\nQt: 5.15.6\r\nPyQt5: 5.15.7\r\nNumPy: 1.24.2\r\nSciPy: 1.10.1\r\nDask: 2023.3.0\r\nVisPy: 0.12.1\r\nmagicgui: 0.7.2\r\nsuperqt: unknown\r\nin-n-out: 0.1.7\r\napp-model: 0.1.2\r\nnpe2: 0.6.2\r\n\r\nOpenGL:\r\n- GL version: 2.1 Metal - 83\r\n- MAX_TEXTURE_SIZE: 16384\r\n\r\nScreens:\r\n- screen 1: resolution 2560x1440, scale 2.0\r\n- screen 2: resolution 1512x982, scale 2.0\r\n\r\nSettings path:\r\n- /Users/joel/Library/Application Support/napari/classifier-dev-napari-main_2751af53b3d3e49c82e2e47937e51f1f537130c2/settings.yaml\r\n```\r\n\r\n - Any other relevant information:\r\n I also tested it in napari 0.4.17 and in more recent napari nightly builds (0.5.0a2.dev71+g66df74d5) and always get the same behavior\r\n\r\n\n", "before_files": [{"content": "import os\nimport re\nimport sys\nimport warnings\nfrom typing import Any, Callable, Generic, TypeVar, Union\n\nimport wrapt\n\nfrom napari.utils import misc\nfrom napari.utils.translations import trans\n\n_T = TypeVar(\"_T\")\n\n\nclass ReadOnlyWrapper(wrapt.ObjectProxy):\n    \"\"\"\n    Disable item and attribute setting with the exception of  ``__wrapped__``.\n    \"\"\"\n\n    def __init__(self, wrapped, exceptions=()):\n        super().__init__(wrapped)\n        self._self_exceptions = exceptions\n\n    def __setattr__(self, name, val):\n        if (\n            name not in ('__wrapped__', '_self_exceptions')\n            and name not in self._self_exceptions\n        ):\n            raise TypeError(\n                trans._(\n                    'cannot set attribute {name}',\n                    deferred=True,\n                    name=name,\n                )\n            )\n\n        super().__setattr__(name, val)\n\n    def __setitem__(self, name, val):\n        if name not in self._self_exceptions:\n            raise TypeError(\n                trans._('cannot set item {name}', deferred=True, name=name)\n            )\n        super().__setitem__(name, val)\n\n\n_SUNDER = re.compile('^_[^_]')\n\n\nclass PublicOnlyProxy(wrapt.ObjectProxy, Generic[_T]):\n    \"\"\"Proxy to prevent private attribute and item access, recursively.\"\"\"\n\n    __wrapped__: _T\n\n    @staticmethod\n    def _is_private_attr(name: str) -> bool:\n        return name.startswith(\"_\") and not (\n            name.startswith('__') and name.endswith('__')\n        )\n\n    @staticmethod\n    def _private_attr_warning(name: str, typ: str):\n        warnings.warn(\n            trans._(\n                \"Private attribute access ('{typ}.{name}') in this context (e.g. inside a plugin widget or dock widget) is deprecated and will be unavailable in version 0.5.0\",\n                deferred=True,\n                name=name,\n                typ=typ,\n            ),\n            category=FutureWarning,\n            stacklevel=3,\n        )\n\n        # This is code prepared for a moment where we want to block access to private attributes\n        # raise AttributeError(\n        #     trans._(\n        #         \"Private attribute set/access ('{typ}.{name}') not allowed in this context.\",\n        #         deferred=True,\n        #         name=name,\n        #         typ=typ,\n        #     )\n        # )\n\n    @staticmethod\n    def _is_called_from_napari():\n        \"\"\"\n        Check if the getter or setter is called from inner napari.\n        \"\"\"\n        if hasattr(sys, \"_getframe\"):\n            frame = sys._getframe(2)\n            return frame.f_code.co_filename.startswith(misc.ROOT_DIR)\n        return False\n\n    def __getattr__(self, name: str):\n        if self._is_private_attr(name):\n            # allow napari to access private attributes and get an non-proxy\n            if self._is_called_from_napari():\n                return super().__getattr__(name)\n\n            typ = type(self.__wrapped__).__name__\n\n            self._private_attr_warning(name, typ)\n\n        return self.create(super().__getattr__(name))\n\n    def __setattr__(self, name: str, value: Any):\n        if (\n            os.environ.get(\"NAPARI_ENSURE_PLUGIN_MAIN_THREAD\", \"0\")\n            not in (\"0\", \"False\")\n        ) and not in_main_thread():\n            raise RuntimeError(\n                \"Setting attributes on a napari object is only allowed from the main Qt thread.\"\n            )\n\n        if self._is_private_attr(name):\n            if self._is_called_from_napari():\n                return super().__setattr__(name, value)\n\n            typ = type(self.__wrapped__).__name__\n            self._private_attr_warning(name, typ)\n\n        setattr(self.__wrapped__, name, value)\n        return None\n\n    def __getitem__(self, key):\n        return self.create(super().__getitem__(key))\n\n    def __repr__(self):\n        return repr(self.__wrapped__)\n\n    def __dir__(self):\n        return [x for x in dir(self.__wrapped__) if not _SUNDER.match(x)]\n\n    @classmethod\n    def create(cls, obj: Any) -> Union['PublicOnlyProxy', Any]:\n        # restrict the scope of this proxy to napari objects\n        mod = getattr(type(obj), '__module__', None) or ''\n        if not mod.startswith('napari'):\n            return obj\n        if isinstance(obj, PublicOnlyProxy):\n            return obj  # don't double-wrap\n        if callable(obj):\n            return CallablePublicOnlyProxy(obj)\n        return PublicOnlyProxy(obj)\n\n\nclass CallablePublicOnlyProxy(PublicOnlyProxy[Callable]):\n    def __call__(self, *args, **kwargs):\n        return self.__wrapped__(*args, **kwargs)\n\n\ndef in_main_thread_py() -> bool:\n    \"\"\"\n    Check if caller is in main python thread.\n\n    Returns\n    -------\n    thread_flag : bool\n        True if we are in the main thread, False otherwise.\n    \"\"\"\n    import threading\n\n    return threading.current_thread() == threading.main_thread()\n\n\ndef _in_main_thread() -> bool:\n    \"\"\"\n    General implementation of checking if we are in a proper thread.\n    If Qt is available and Application is created then assign :py:func:`in_qt_main_thread` to `in_main_thread`.\n    If Qt liba are not available then assign :py:func:`in_main_thread_py` to in_main_thread.\n    IF Qt libs are available but there is no Application ti wil emmit warning and return result of in_main_thread_py.\n\n    Returns\n    -------\n    thread_flag : bool\n        True if we are in the main thread, False otherwise.\n    \"\"\"\n\n    global in_main_thread\n    try:\n        from napari._qt.utils import in_qt_main_thread\n\n        res = in_qt_main_thread()\n        in_main_thread = in_qt_main_thread\n    except ImportError:\n        in_main_thread = in_main_thread_py\n        return in_main_thread_py()\n    except AttributeError:\n        warnings.warn(\n            \"Qt libs are available but no QtApplication instance is created\"\n        )\n        return in_main_thread_py()\n    return res\n\n\nin_main_thread = _in_main_thread\n", "path": "napari/utils/_proxies.py"}], "after_files": [{"content": "import os\nimport re\nimport sys\nimport warnings\nfrom typing import Any, Callable, Generic, TypeVar, Union\n\nimport wrapt\n\nfrom napari.utils import misc\nfrom napari.utils.translations import trans\n\n_T = TypeVar(\"_T\")\n\n\nclass ReadOnlyWrapper(wrapt.ObjectProxy):\n    \"\"\"\n    Disable item and attribute setting with the exception of  ``__wrapped__``.\n    \"\"\"\n\n    def __init__(self, wrapped, exceptions=()):\n        super().__init__(wrapped)\n        self._self_exceptions = exceptions\n\n    def __setattr__(self, name, val):\n        if (\n            name not in ('__wrapped__', '_self_exceptions')\n            and name not in self._self_exceptions\n        ):\n            raise TypeError(\n                trans._(\n                    'cannot set attribute {name}',\n                    deferred=True,\n                    name=name,\n                )\n            )\n\n        super().__setattr__(name, val)\n\n    def __setitem__(self, name, val):\n        if name not in self._self_exceptions:\n            raise TypeError(\n                trans._('cannot set item {name}', deferred=True, name=name)\n            )\n        super().__setitem__(name, val)\n\n\n_SUNDER = re.compile('^_[^_]')\n\n\nclass PublicOnlyProxy(wrapt.ObjectProxy, Generic[_T]):\n    \"\"\"Proxy to prevent private attribute and item access, recursively.\"\"\"\n\n    __wrapped__: _T\n\n    @staticmethod\n    def _is_private_attr(name: str) -> bool:\n        return name.startswith(\"_\") and not (\n            name.startswith('__') and name.endswith('__')\n        )\n\n    @staticmethod\n    def _private_attr_warning(name: str, typ: str):\n        warnings.warn(\n            trans._(\n                \"Private attribute access ('{typ}.{name}') in this context (e.g. inside a plugin widget or dock widget) is deprecated and will be unavailable in version 0.5.0\",\n                deferred=True,\n                name=name,\n                typ=typ,\n            ),\n            category=FutureWarning,\n            stacklevel=3,\n        )\n\n        # This is code prepared for a moment where we want to block access to private attributes\n        # raise AttributeError(\n        #     trans._(\n        #         \"Private attribute set/access ('{typ}.{name}') not allowed in this context.\",\n        #         deferred=True,\n        #         name=name,\n        #         typ=typ,\n        #     )\n        # )\n\n    @staticmethod\n    def _is_called_from_napari():\n        \"\"\"\n        Check if the getter or setter is called from inner napari.\n        \"\"\"\n        if hasattr(sys, \"_getframe\"):\n            frame = sys._getframe(2)\n            return frame.f_code.co_filename.startswith(misc.ROOT_DIR)\n        return False\n\n    def __getattr__(self, name: str):\n        if self._is_private_attr(name):\n            # allow napari to access private attributes and get an non-proxy\n            if self._is_called_from_napari():\n                return super().__getattr__(name)\n\n            typ = type(self.__wrapped__).__name__\n\n            self._private_attr_warning(name, typ)\n\n        return self.create(super().__getattr__(name))\n\n    def __setattr__(self, name: str, value: Any):\n        if (\n            os.environ.get(\"NAPARI_ENSURE_PLUGIN_MAIN_THREAD\", \"0\")\n            not in (\"0\", \"False\")\n        ) and not in_main_thread():\n            raise RuntimeError(\n                \"Setting attributes on a napari object is only allowed from the main Qt thread.\"\n            )\n\n        if self._is_private_attr(name):\n            if self._is_called_from_napari():\n                return super().__setattr__(name, value)\n\n            typ = type(self.__wrapped__).__name__\n            self._private_attr_warning(name, typ)\n\n        if isinstance(value, PublicOnlyProxy):\n            # if we want to set an attribute on a PublicOnlyProxy *and* the\n            # value that we want to set is itself a PublicOnlyProxy, we unwrap\n            # the value. This has two benefits:\n            #\n            # 1. Checking the attribute later will incur a significant\n            # performance cost, because _is_called_from_napari() will be\n            # checked on each attribute access and it involves inspecting the\n            # calling frame, which is expensive.\n            # 2. Certain equality checks fail when objects are\n            # PublicOnlyProxies. Notably, equality checks fail when such\n            # objects are included in a Qt data model. For example, plugins can\n            # grab a layer from the viewer; this layer will be wrapped by the\n            # PublicOnlyProxy, and then using this object to set the current\n            # layer selection will not propagate the selection to the Viewer.\n            # See https://github.com/napari/napari/issues/5767\n            value = value.__wrapped__\n\n        setattr(self.__wrapped__, name, value)\n        return None\n\n    def __getitem__(self, key):\n        return self.create(super().__getitem__(key))\n\n    def __repr__(self):\n        return repr(self.__wrapped__)\n\n    def __dir__(self):\n        return [x for x in dir(self.__wrapped__) if not _SUNDER.match(x)]\n\n    @classmethod\n    def create(cls, obj: Any) -> Union['PublicOnlyProxy', Any]:\n        # restrict the scope of this proxy to napari objects\n        if type(obj).__name__ == 'method':\n            # If the given object is a method, we check the module *of the\n            # object to which that method is bound*. Otherwise, the module of a\n            # method is just builtins!\n            mod = getattr(type(obj.__self__), '__module__', None) or ''\n        else:\n            # Otherwise, the module is of an object just given by the\n            # __module__ attribute.\n            mod = getattr(type(obj), '__module__', None) or ''\n        if not mod.startswith('napari'):\n            return obj\n        if isinstance(obj, PublicOnlyProxy):\n            return obj  # don't double-wrap\n        if callable(obj):\n            return CallablePublicOnlyProxy(obj)\n        return PublicOnlyProxy(obj)\n\n\nclass CallablePublicOnlyProxy(PublicOnlyProxy[Callable]):\n    def __call__(self, *args, **kwargs):\n        # if a PublicOnlyProxy is callable, then when we call it we:\n        # - unwrap the arguments, to avoid performance issues detailed in\n        #   PublicOnlyProxy.__setattr__,\n        # - call the unwrapped callable on the unwrapped arguments\n        # - wrap the result in a PublicOnlyProxy\n        args = [\n            arg.__wrapped__ if isinstance(arg, PublicOnlyProxy) else arg\n            for arg in args\n        ]\n        kwargs = {\n            k: v.__wrapped__ if isinstance(v, PublicOnlyProxy) else v\n            for k, v in kwargs.items()\n        }\n        return self.create(self.__wrapped__(*args, **kwargs))\n\n\ndef in_main_thread_py() -> bool:\n    \"\"\"\n    Check if caller is in main python thread.\n\n    Returns\n    -------\n    thread_flag : bool\n        True if we are in the main thread, False otherwise.\n    \"\"\"\n    import threading\n\n    return threading.current_thread() == threading.main_thread()\n\n\ndef _in_main_thread() -> bool:\n    \"\"\"\n    General implementation of checking if we are in a proper thread.\n    If Qt is available and Application is created then assign :py:func:`in_qt_main_thread` to `in_main_thread`.\n    If Qt liba are not available then assign :py:func:`in_main_thread_py` to in_main_thread.\n    IF Qt libs are available but there is no Application ti wil emmit warning and return result of in_main_thread_py.\n\n    Returns\n    -------\n    thread_flag : bool\n        True if we are in the main thread, False otherwise.\n    \"\"\"\n\n    global in_main_thread\n    try:\n        from napari._qt.utils import in_qt_main_thread\n\n        res = in_qt_main_thread()\n        in_main_thread = in_qt_main_thread\n    except ImportError:\n        in_main_thread = in_main_thread_py\n        return in_main_thread_py()\n    except AttributeError:\n        warnings.warn(\n            \"Qt libs are available but no QtApplication instance is created\"\n        )\n        return in_main_thread_py()\n    return res\n\n\nin_main_thread = _in_main_thread\n", "path": "napari/utils/_proxies.py"}]}