Repository: napari/napari

Issue:
Napari does not allow widgets returning List[LayerDataTuple] to return an empty list
## 🐛 Bug

napari promotes magicgui widgets with a return type hint `List[LayerDataTuple]` for situations where a widget returns an arbitrary number of layers.

That arbitrary number of layers cannot currently be zero.

## To Reproduce

Running the following widget in napari will show the error:

```python3
@magic_factory
def example_magic_widget(img_layer: Image) -> List[LayerDataTuple]:
    return []
```
yielding the error message:

```
File ~/miniconda3/envs/napari-imagej-dev/lib/python3.10/site-packages/napari/utils/_magicgui.py:124, in add_layer_data_tuples_to_viewer(gui=<FunctionGui example_magic_widget(img_layer: nap...3b237e6e0>) -> List[napari.types.LayerDataTuple]>, result=[], return_type=typing.List[napari.types.LayerDataTuple])
    122     result = ensure_list_of_layer_data_tuple(result)
    123 except TypeError:
--> 124     raise TypeError(
        trans = <napari.utils.translations.TranslationBundle object at 0x7f83d790e2c0>
        gui = <FunctionGui example_magic_widget(img_layer: napari.layers.image.image.Image = <Image layer 'brick' at 0x7f83b237e6e0>) -> List[napari.types.LayerDataTuple]>
    125         trans._(
    126             'magicgui function {gui} annotated with a return type of napari.types.LayerDataTuple did not return LayerData tuple(s)',
    127             deferred=True,
    128             gui=gui,
    129         )
    130     )
    132 with layer_source(widget=gui):
    133     for layer_datum in result:
    134         # if the layer data has a meta dict with a 'name' key in it...

TypeError: magicgui function <FunctionGui example_magic_widget(img_layer: napari.layers.image.image.Image = <Image layer 'brick' at 0x7f83b237e6e0>) -> List[napari.types.LayerDataTuple]> annotated with a return type of napari.types.LayerDataTuple did not return LayerData tuple(s)
```
The actual check on the size of the returned list is [here](https://github.com/napari/napari/blob/8b4de10b2683427f9335c9c4ab9e79bae1b09e78/napari/utils/misc.py#L453)

## Expected behavior

I would expect that if the `List` was empty, we would simply not see any layers added. I do not see any code in [`_magicgui.add_layer_data_tuples_to_viewer`](https://github.com/napari/napari/blob/8b4de10b2683427f9335c9c4ab9e79bae1b09e78/napari/utils/_magicgui.py#L78) that relies on a non-empty list.

## Environment

 - Please copy and paste the information at napari info option in help menubar here:

 ```
napari: 0.4.15
Platform: Linux-5.14.0-1036-oem-x86_64-with-glibc2.31
System: Ubuntu 20.04.4 LTS
Python: 3.10.4 | packaged by conda-forge | (main, Mar 24 2022, 17:38:57) [GCC 10.3.0]
Qt: 5.12.9
PyQt5: 5.12.3
NumPy: 1.22.3
SciPy: 1.8.0
Dask: 2022.05.0
VisPy: 0.9.6

OpenGL:
- GL version: 4.6 (Compatibility Profile) Mesa 21.2.6
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 2560x1440, scale 1.0
- screen 2: resolution 1920x1080, scale 1.0

Plugins:
- console: 0.0.4
- scikit-image: 0.4.15
- svg: 0.1.5
```

## Additional context

See [this zulip thread](https://napari.zulipchat.com/#narrow/stream/212875-general/topic/Magicgui.20widgets.20returning.20empty.20.60List.5BLayerDataTuple.5D.60s/near/283890736)



Discussion:

Resolving patch:
diff --git a/napari/utils/misc.py b/napari/utils/misc.py
--- a/napari/utils/misc.py
+++ b/napari/utils/misc.py
@@ -457,7 +457,8 @@ def ensure_layer_data_tuple(val):
 
 
 def ensure_list_of_layer_data_tuple(val) -> List[tuple]:
-    if isinstance(val, list) and len(val):
+    # allow empty list to be returned but do nothing in that case
+    if isinstance(val, list):
         with contextlib.suppress(TypeError):
             return [ensure_layer_data_tuple(v) for v in val]
     raise TypeError(


Test patch:
diff --git a/napari/utils/_tests/test_misc.py b/napari/utils/_tests/test_misc.py
--- a/napari/utils/_tests/test_misc.py
+++ b/napari/utils/_tests/test_misc.py
@@ -10,6 +10,7 @@
     _quiet_array_equal,
     abspath_or_url,
     ensure_iterable,
+    ensure_list_of_layer_data_tuple,
     ensure_sequence_of_iterables,
     pick_equality_operator,
 )
@@ -211,3 +212,24 @@ def test_is_array_type_with_xarray():
     )
     assert not _is_array_type([], 'xarray.DataArray')
     assert not _is_array_type(np.array([]), 'xarray.DataArray')
+
+
+@pytest.mark.parametrize(
+    'input, expected',
+    [
+        ([([1, 10],)], [([1, 10],)]),
+        ([([1, 10], {'name': 'hi'})], [([1, 10], {'name': 'hi'})]),
+        (
+            [([1, 10], {'name': 'hi'}, "image")],
+            [([1, 10], {'name': 'hi'}, "image")],
+        ),
+        ([], []),
+    ],
+)
+def test_ensure_list_of_layer_data_tuple(input, expected):
+    """Ensure that when given layer data that a tuple can be generated.
+
+    When data with a name is supplied a layer should be created and named.
+    When an empty dataset is supplied no layer is created and no errors are produced.
+    """
+    assert ensure_list_of_layer_data_tuple(input) == expected