Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add page on magicgui type registration #399

Merged
merged 14 commits into from
Apr 16, 2024
1 change: 1 addition & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ subtrees:
- file: developers/architecture/dir_organization
- file: developers/architecture/napari_models
- file: developers/architecture/app_model
- file: developers/architecture/magicgui_type_reg
- file: naps/index
subtrees:
- maxdepth: 1
Expand Down
2 changes: 2 additions & 0 deletions docs/developers/architecture/app_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ at runtime (see [issue 6600](https://github.com/napari/napari/issues/6600)
for more on this). Note that it is likely that this pull request will be split into
smaller pull requests for ease of review and better git history.

(app_model_dep_inj_result)=

## Dependency injection and result processing

Dependency injection allows to write functions using parameter type annotations,
Expand Down
3 changes: 3 additions & 0 deletions docs/developers/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ code base. For advanced napari usage documentation, see [](explanations).
connected to Qt classes and Vispy classes.
- [](app-model): Explains the napari application model, a declarative schema for
keeping track of commands, menus and keybindings of the napari GUI.
- [](magicgui_type_registration): Explains how `magicgui` widgets declared by users
or plugins are automatically created, inputs updated and outputs added to the
`Viewer` for registered `napari` types.
71 changes: 71 additions & 0 deletions docs/developers/architecture/magicgui_type_reg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
(magicgui_type_registration)=

# `magicgui` type registration

[`magicgui`](https://pyapp-kit.github.io/magicgui/) uses
[type hints](https://peps.python.org/pep-0484/) to infer the appropriate widget type
for a given function parameter. It allows third party packages
(like `napari`) to [register](https://pyapp-kit.github.io/magicgui/type_map/#registering-support-for-custom-types) support for their types using
{func}`~magicgui.type_map.register_type`. `napari` registers
a number of types, additionally specifying, where appropriate:

* widget type for the parameter type
* function for updating inputs for the widget
* return callback, for return types

This enables `magicgui` widgets to be easily created via type annotations.

```{note}
This page provides implementation details on `napari`-specific type registration
in `magicgui` and is aimed at `napari` developers.

For information about using `magicgui` (for users and plugin developers) see
[](creating-widgets).
```

## Registration details
DragaDoncila marked this conversation as resolved.
Show resolved Hide resolved

`napari` types are either registered via the
{func}`@register_type <magicgui.type_map.register_type>` decorator when they are
defined or in
[`napari/types.py`](https://github.com/napari/napari/blob/main/napari/types.py).
For the full list of types registered, see [](magicgui-parameter-annotations).

All 'layer' types provide a `choices` callable when they are registered with
`magicgui`. This means that annotating with these types creates an
lucyleeow marked this conversation as resolved.
Show resolved Hide resolved
{class}`~magicgui.widgets.bases.CategoricalWidget`, which will have a dropdown
selection will get updated via the
lucyleeow marked this conversation as resolved.
Show resolved Hide resolved
`choices` callable. This callable is either `get_layers_data` or `get_layers`.
lucyleeow marked this conversation as resolved.
Show resolved Hide resolved
These functions retrieve the closest parent `Viewer` of the native
{class}`~magicgui.widgets.bases.CategoricalWidget` widget and returns a list of
{class}`~napari.layers.Layer` or tuple of format ('layer name', `<LayerType>Data`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{class}`~napari.layers.Layer` or tuple of format ('layer name', `<LayerType>Data`).
{class}`~napari.layers.Layer` or tuple of format `(layer name, <LayerType>Data)`.

This callable is set to the `choices` attribute of the
{class}`~magicgui.widgets.bases.CategoricalWidget` in its
{meth}`~magicgui.widgets.bases.CategoricalWidget.reset_choices` method and thus
gets called via {class}`~magicgui.widgets.bases.CategoricalWidget`'s `choices` setter.
`napari` {meth}`~napari.qt.Window.add_dock_widget` checks if the dock widget has
a `reset_choices` attribute and if so, connects it to layer events.
Note that `magicgui` {class}`~magicgui.widgets.bases.ContainerWidget`'s will call
lucyleeow marked this conversation as resolved.
Show resolved Hide resolved
`reset_choices` on all subwidgets. This means that when the dock widget is a
{class}`~magicgui.widgets.bases.ContainerWidget`, any subwidgets (e.g.,
{class}`~magicgui.widgets.bases.CategoricalWidget`s) are updated
whenever layers change.
**Note that {meth}`~napari.qt.Window.add_dock_widget` will
connect any existing `reset_choices` widget attribute to layer events for all widgets,
not just `magicgui` widgets.**

The 'layer' types also specify a `return_callback` function that adds the layer
to the closest parent `Viewer` of the native widget when a 'layer' type is a return
annotation.

{class}`~napari.viewer.Viewer` differs from the Layer types. `napari` simply specifies
lucyleeow marked this conversation as resolved.
Show resolved Hide resolved
that the closest parent {class}`~napari.viewer.Viewer` (technically a public proxy of
the {class}`~napari.viewer.Viewer` that prevents private attribute access) be bound to
the widget (technically it's bound to a hidden child
{class}`~magicgui.widgets.EmptyWidget`). This allows the {class}`~napari.viewer.Viewer`
to be used in the `magicgui` widget.

```{note}
When widget is not a `magicgui` widget, the {class}`~napari.viewer.Viewer` is provided
via a wrapper `napari` adds around widget contributions.
```
Loading