Repository: napari/napari

Issue:
npe1 plugin readers broken by npe2 0.7.0
## 🐛 Bug

Hi, thanks for making napari!

I just upgraded to using npe2 v0.7.0 and noticed that if any installed plugin readers fail to return data, an error is raised rather than silently ignoring it (as was the case before npe2 0.7.0).

I'm fairly new to napari but, from what I can tell, every installed reader is used to read the data. If a readers fail to return data, [a `ValueError` is raised by npe2](https://github.com/napari/npe2/blob/main/src/npe2/io_utils.py#L169-L172) and napari will [catch and ignore it](https://github.com/napari/napari/blob/main/napari/plugins/_npe2.py#L57-L59). I guess the message in the `ValueError` was changed in npe2, meaning the errors are no longer being ignored by napari. And the result that no data is loaded into the gui.

I'll submit a pr shortly to update the expected error message from npe2

## Environment
```
napari: 0.5.0a2.dev84+g11c7d485
Platform: macOS-12.6.3-x86_64-i386-64bit
System: MacOS 12.6.3
Python: 3.10.10 | packaged by conda-forge | (main, Mar 24 2023, 20:17:34) [Clang 14.0.6 ]
Qt: 5.15.2
PyQt5: 5.15.9
NumPy: 1.24.2
SciPy: 1.10.1
Dask: 2023.4.0
VisPy: 0.12.2
magicgui: 0.7.2
superqt: unknown
in-n-out: 0.1.7
app-model: 0.1.4
npe2: 0.7.0
```




Discussion:

Resolving patch:
diff --git a/napari/plugins/_npe2.py b/napari/plugins/_npe2.py
--- a/napari/plugins/_npe2.py
+++ b/napari/plugins/_npe2.py
@@ -41,6 +41,11 @@ def read(
     paths: Sequence[str], plugin: Optional[str] = None, *, stack: bool
 ) -> Optional[Tuple[List[LayerData], _FakeHookimpl]]:
     """Try to return data for `path`, from reader plugins using a manifest."""
+
+    # do nothing if `plugin` is not an npe2 reader
+    if plugin and plugin not in get_readers():
+        return None
+
     assert stack is not None
     # the goal here would be to make read_get_reader of npe2 aware of "stack",
     # and not have this conditional here.
@@ -55,6 +60,7 @@ def read(
             npe1_path, plugin_name=plugin
         )
     except ValueError as e:
+        # plugin wasn't passed and no reader was found
         if 'No readers returned data' not in str(e):
             raise
     else:


Test patch:
diff --git a/napari/plugins/_tests/test_npe2.py b/napari/plugins/_tests/test_npe2.py
--- a/napari/plugins/_tests/test_npe2.py
+++ b/napari/plugins/_tests/test_npe2.py
@@ -3,6 +3,7 @@
 from typing import TYPE_CHECKING
 from unittest.mock import MagicMock
 
+import npe2
 import numpy as np
 import pytest
 from npe2 import PluginManifest
@@ -42,6 +43,23 @@ def test_read(mock_pm: 'TestPluginManager'):
     assert _npe2.read(["some.randomext"], stack=True) is None
     mock_pm.commands.get.assert_not_called()
 
+    mock_pm.commands.get.reset_mock()
+    assert (
+        _npe2.read(["some.randomext"], stack=True, plugin='not-npe2-plugin')
+        is None
+    )
+    mock_pm.commands.get.assert_not_called()
+
+
+@pytest.mark.skipif(
+    npe2.__version__ < '0.7.0',
+    reason='Older versions of npe2 do not throw specific error.',
+)
+def test_read_with_plugin_failure(mock_pm: 'TestPluginManager'):
+    match = f"Plugin '{PLUGIN_NAME}' was selected"
+    with pytest.raises(ValueError, match=match):
+        _npe2.read(["some.randomext"], stack=True, plugin=PLUGIN_NAME)
+
 
 def test_write(mock_pm: 'TestPluginManager'):
     # saving an image without a writer goes straight to npe2.write